diff --git a/packages/gooddata-sdk/src/gooddata_sdk/__init__.py b/packages/gooddata-sdk/src/gooddata_sdk/__init__.py index 77397b92d..79e5fd49d 100644 --- a/packages/gooddata-sdk/src/gooddata_sdk/__init__.py +++ b/packages/gooddata-sdk/src/gooddata_sdk/__init__.py @@ -7,6 +7,17 @@ import logging from gooddata_sdk._version import __version__ +from gooddata_sdk.catalog.ai_lake.entity_model.database_instance import ( + CatalogDatabaseInstance, + CatalogProvisionDatabaseInstanceRequest, +) +from gooddata_sdk.catalog.ai_lake.entity_model.pipe_table import ( + CatalogCreatePipeTableRequest, + CatalogPipeTable, + CatalogPipeTableSummary, +) +from gooddata_sdk.catalog.ai_lake.entity_model.service_info import CatalogServiceInfo +from gooddata_sdk.catalog.ai_lake.service import CatalogAiLakeService from gooddata_sdk.catalog.appearance.entity_model.color_palette import ( CatalogColorPalette, CatalogColorPaletteAttributes, @@ -36,6 +47,7 @@ ) from gooddata_sdk.catalog.data_source.entity_model.data_source import ( CatalogDataSource, + CatalogDataSourceAiLakehouse, CatalogDataSourceBigQuery, CatalogDataSourceDatabricks, CatalogDataSourceGdStorage, diff --git a/packages/gooddata-sdk/src/gooddata_sdk/catalog/ai_lake/__init__.py b/packages/gooddata-sdk/src/gooddata_sdk/catalog/ai_lake/__init__.py new file mode 100644 index 000000000..efe7c60c8 --- /dev/null +++ b/packages/gooddata-sdk/src/gooddata_sdk/catalog/ai_lake/__init__.py @@ -0,0 +1 @@ +# (C) 2026 GoodData Corporation diff --git a/packages/gooddata-sdk/src/gooddata_sdk/catalog/ai_lake/entity_model/__init__.py b/packages/gooddata-sdk/src/gooddata_sdk/catalog/ai_lake/entity_model/__init__.py new file mode 100644 index 000000000..efe7c60c8 --- /dev/null +++ b/packages/gooddata-sdk/src/gooddata_sdk/catalog/ai_lake/entity_model/__init__.py @@ -0,0 +1 @@ +# (C) 2026 GoodData Corporation diff --git a/packages/gooddata-sdk/src/gooddata_sdk/catalog/ai_lake/entity_model/database_instance.py b/packages/gooddata-sdk/src/gooddata_sdk/catalog/ai_lake/entity_model/database_instance.py new file mode 100644 index 000000000..469a374f1 --- /dev/null +++ b/packages/gooddata-sdk/src/gooddata_sdk/catalog/ai_lake/entity_model/database_instance.py @@ -0,0 +1,39 @@ +# (C) 2026 GoodData Corporation +from __future__ import annotations + +from typing import Any + +import attrs +from gooddata_api_client.model.provision_database_instance_request import ProvisionDatabaseInstanceRequest + + +@attrs.define(kw_only=True) +class CatalogDatabaseInstance: + """Represents an AI Lake database instance.""" + + id: str + name: str + storage_ids: list[str] = attrs.field(factory=list) + + @classmethod + def from_api(cls, entity: dict[str, Any]) -> CatalogDatabaseInstance: + return cls( + id=entity["id"], + name=entity["name"], + storage_ids=entity.get("storage_ids") or [], + ) + + +@attrs.define(kw_only=True) +class CatalogProvisionDatabaseInstanceRequest: + """Request to provision a new AI Lake database instance.""" + + name: str + storage_ids: list[str] = attrs.field(factory=list) + + def as_api_model(self) -> ProvisionDatabaseInstanceRequest: + return ProvisionDatabaseInstanceRequest( + name=self.name, + storage_ids=self.storage_ids, + _check_type=False, + ) diff --git a/packages/gooddata-sdk/src/gooddata_sdk/catalog/ai_lake/entity_model/pipe_table.py b/packages/gooddata-sdk/src/gooddata_sdk/catalog/ai_lake/entity_model/pipe_table.py new file mode 100644 index 000000000..d3b3bf0f1 --- /dev/null +++ b/packages/gooddata-sdk/src/gooddata_sdk/catalog/ai_lake/entity_model/pipe_table.py @@ -0,0 +1,86 @@ +# (C) 2026 GoodData Corporation +from __future__ import annotations + +from typing import Any + +import attrs +from gooddata_api_client.model.create_pipe_table_request import CreatePipeTableRequest + + +@attrs.define(kw_only=True) +class CatalogPipeTableSummary: + """Summary of an AI Lake pipe table as returned by the list endpoint.""" + + pipe_table_id: str + table_name: str + path_prefix: str + columns: list[dict[str, Any]] = attrs.field(factory=list) + + @classmethod + def from_api(cls, entity: dict[str, Any]) -> CatalogPipeTableSummary: + return cls( + pipe_table_id=entity["pipe_table_id"], + table_name=entity["table_name"], + path_prefix=entity["path_prefix"], + columns=entity.get("columns") or [], + ) + + +@attrs.define(kw_only=True) +class CatalogPipeTable: + """Full representation of an AI Lake pipe table.""" + + pipe_table_id: str + table_name: str + source_storage_name: str + path_prefix: str + database_name: str + polling_interval_seconds: int + partition_columns: list[str] = attrs.field(factory=list) + table_properties: dict[str, str] = attrs.field(factory=dict) + columns: list[dict[str, Any]] = attrs.field(factory=list) + + @classmethod + def from_api(cls, entity: dict[str, Any]) -> CatalogPipeTable: + return cls( + pipe_table_id=entity["pipe_table_id"], + table_name=entity["table_name"], + source_storage_name=entity["source_storage_name"], + path_prefix=entity["path_prefix"], + database_name=entity["database_name"], + polling_interval_seconds=entity["polling_interval_seconds"], + partition_columns=entity.get("partition_columns") or [], + table_properties=entity.get("table_properties") or {}, + columns=entity.get("columns") or [], + ) + + +@attrs.define(kw_only=True) +class CatalogCreatePipeTableRequest: + """Request to create a new AI Lake pipe table.""" + + path_prefix: str + source_storage_name: str + table_name: str + column_overrides: dict[str, str] | None = None + max_varchar_length: int | None = None + polling_interval_seconds: int | None = None + table_properties: dict[str, str] | None = None + + def as_api_model(self) -> CreatePipeTableRequest: + kwargs: dict[str, Any] = {} + if self.column_overrides is not None: + kwargs["column_overrides"] = self.column_overrides + if self.max_varchar_length is not None: + kwargs["max_varchar_length"] = self.max_varchar_length + if self.polling_interval_seconds is not None: + kwargs["polling_interval_seconds"] = self.polling_interval_seconds + if self.table_properties is not None: + kwargs["table_properties"] = self.table_properties + return CreatePipeTableRequest( + path_prefix=self.path_prefix, + source_storage_name=self.source_storage_name, + table_name=self.table_name, + _check_type=False, + **kwargs, + ) diff --git a/packages/gooddata-sdk/src/gooddata_sdk/catalog/ai_lake/entity_model/service_info.py b/packages/gooddata-sdk/src/gooddata_sdk/catalog/ai_lake/entity_model/service_info.py new file mode 100644 index 000000000..e7a935093 --- /dev/null +++ b/packages/gooddata-sdk/src/gooddata_sdk/catalog/ai_lake/entity_model/service_info.py @@ -0,0 +1,21 @@ +# (C) 2026 GoodData Corporation +from __future__ import annotations + +from typing import Any + +import attrs + + +@attrs.define(kw_only=True) +class CatalogServiceInfo: + """Information about an AI Lake service.""" + + name: str + service_id: str + + @classmethod + def from_api(cls, entity: dict[str, Any]) -> CatalogServiceInfo: + return cls( + name=entity["name"], + service_id=entity["service_id"], + ) diff --git a/packages/gooddata-sdk/src/gooddata_sdk/catalog/ai_lake/service.py b/packages/gooddata-sdk/src/gooddata_sdk/catalog/ai_lake/service.py new file mode 100644 index 000000000..883267162 --- /dev/null +++ b/packages/gooddata-sdk/src/gooddata_sdk/catalog/ai_lake/service.py @@ -0,0 +1,183 @@ +# (C) 2026 GoodData Corporation +from __future__ import annotations + +from gooddata_sdk.catalog.ai_lake.entity_model.database_instance import ( + CatalogDatabaseInstance, + CatalogProvisionDatabaseInstanceRequest, +) +from gooddata_sdk.catalog.ai_lake.entity_model.pipe_table import ( + CatalogCreatePipeTableRequest, + CatalogPipeTable, + CatalogPipeTableSummary, +) +from gooddata_sdk.catalog.ai_lake.entity_model.service_info import CatalogServiceInfo +from gooddata_sdk.client import GoodDataApiClient + + +class CatalogAiLakeService: + """Service for managing AI Lake database instances, pipe tables, and StarRocks services.""" + + def __init__(self, api_client: GoodDataApiClient) -> None: + self._client = api_client + self._ai_lake_api = api_client.ai_lake_api + self._ai_lake_pipe_tables_api = api_client.ai_lake_pipe_tables_api + + # Database instance methods + + def provision_database_instance( + self, + request: CatalogProvisionDatabaseInstanceRequest, + ) -> None: + """Provision a new AI Lake database instance. + + Args: + request (CatalogProvisionDatabaseInstanceRequest): + The provision request containing name and storage IDs. + + Returns: + None + """ + self._ai_lake_api.provision_ai_lake_database_instance( + request.as_api_model(), + _check_return_type=False, + ) + + def get_database_instance(self, instance_id: str) -> CatalogDatabaseInstance: + """Retrieve an AI Lake database instance by ID or name. + + Args: + instance_id (str): + Database instance identifier (name preferred, or UUID). + + Returns: + CatalogDatabaseInstance: + The database instance. + """ + response = self._ai_lake_api.get_ai_lake_database_instance( + instance_id, + _check_return_type=False, + ) + return CatalogDatabaseInstance.from_api(response.to_dict(camel_case=False)) + + def list_database_instances(self) -> list[CatalogDatabaseInstance]: + """List all AI Lake database instances. + + Returns: + list[CatalogDatabaseInstance]: + All database instances in the organization. + """ + response = self._ai_lake_api.list_ai_lake_database_instances( + _check_return_type=False, + ) + data = response.to_dict(camel_case=False) + return [CatalogDatabaseInstance.from_api(db) for db in data.get("databases") or []] + + def deprovision_database_instance(self, instance_id: str) -> None: + """Delete an existing AI Lake database instance. + + Args: + instance_id (str): + Database instance identifier (name preferred, or UUID). + + Returns: + None + """ + self._ai_lake_api.deprovision_ai_lake_database_instance( + instance_id, + _check_return_type=False, + ) + + # Pipe table methods + + def create_pipe_table( + self, + instance_id: str, + request: CatalogCreatePipeTableRequest, + ) -> None: + """Create a new AI Lake pipe table in the given database instance. + + Args: + instance_id (str): + Database instance identifier. + request (CatalogCreatePipeTableRequest): + The create request with path prefix, source storage name, and table name. + + Returns: + None + """ + self._ai_lake_pipe_tables_api.create_ai_lake_pipe_table( + instance_id, + request.as_api_model(), + _check_return_type=False, + ) + + def get_pipe_table(self, instance_id: str, table_name: str) -> CatalogPipeTable: + """Retrieve a specific AI Lake pipe table. + + Args: + instance_id (str): + Database instance identifier. + table_name (str): + OLAP table name. + + Returns: + CatalogPipeTable: + The full pipe table details. + """ + response = self._ai_lake_pipe_tables_api.get_ai_lake_pipe_table( + instance_id, + table_name, + _check_return_type=False, + ) + return CatalogPipeTable.from_api(response.to_dict(camel_case=False)) + + def list_pipe_tables(self, instance_id: str) -> list[CatalogPipeTableSummary]: + """List all AI Lake pipe tables for a database instance. + + Args: + instance_id (str): + Database instance identifier. + + Returns: + list[CatalogPipeTableSummary]: + All pipe tables in the given database instance. + """ + response = self._ai_lake_pipe_tables_api.list_ai_lake_pipe_tables( + instance_id, + _check_return_type=False, + ) + data = response.to_dict(camel_case=False) + return [CatalogPipeTableSummary.from_api(pt) for pt in data.get("pipe_tables") or []] + + def delete_pipe_table(self, instance_id: str, table_name: str) -> None: + """Delete an AI Lake pipe table. + + Args: + instance_id (str): + Database instance identifier. + table_name (str): + OLAP table name. + + Returns: + None + """ + self._ai_lake_pipe_tables_api.delete_ai_lake_pipe_table( + instance_id, + table_name, + _check_return_type=False, + ) + + # Service methods + + def list_services(self) -> list[CatalogServiceInfo]: + """List all AI Lake services configured for the organization. + + Returns: + list[CatalogServiceInfo]: + All services configured in the organization's AI Lake. + """ + response = self._ai_lake_api.list_ai_lake_services( + _check_return_type=False, + ) + data = response.to_dict(camel_case=False) + return [CatalogServiceInfo.from_api(svc) for svc in data.get("services") or []] diff --git a/packages/gooddata-sdk/src/gooddata_sdk/catalog/data_source/entity_model/data_source.py b/packages/gooddata-sdk/src/gooddata_sdk/catalog/data_source/entity_model/data_source.py index 43b0163fb..cb2948417 100644 --- a/packages/gooddata-sdk/src/gooddata_sdk/catalog/data_source/entity_model/data_source.py +++ b/packages/gooddata-sdk/src/gooddata_sdk/catalog/data_source/entity_model/data_source.py @@ -318,3 +318,12 @@ class CatalogDataSourceGdStorage(CatalogDataSource): type: str = "GDSTORAGE" schema: str = "" credentials: Credentials = field(factory=_NoCredentials, repr=False) + + +@define(kw_only=True, eq=False) +class CatalogDataSourceAiLakehouse(CatalogDataSource): + """Data source backed by an AI Lake (StarRocks) database instance.""" + + type: str = "AILAKEHOUSE" + schema: str = "" + credentials: Credentials = field(factory=_NoCredentials, repr=False) diff --git a/packages/gooddata-sdk/src/gooddata_sdk/client.py b/packages/gooddata-sdk/src/gooddata_sdk/client.py index 80ff83925..46c3c6e0b 100644 --- a/packages/gooddata-sdk/src/gooddata_sdk/client.py +++ b/packages/gooddata-sdk/src/gooddata_sdk/client.py @@ -8,6 +8,7 @@ import gooddata_api_client as api_client import requests from gooddata_api_client import apis +from gooddata_api_client.api.ai_lake_pipe_tables_api import AILakePipeTablesApi from gooddata_sdk import __version__ from gooddata_sdk.utils import HttpMethod @@ -71,6 +72,8 @@ def __init__( self._actions_api = apis.ActionsApi(self._api_client) self._user_management_api = apis.UserManagementApi(self._api_client) self._appearance_api = apis.AppearanceApi(self._api_client) + self._ai_lake_api = apis.AILakeApi(self._api_client) + self._ai_lake_pipe_tables_api = AILakePipeTablesApi(self._api_client) self._executions_cancellable = executions_cancellable def _do_post_request( @@ -158,6 +161,14 @@ def user_management_api(self) -> apis.UserManagementApi: def appearance_api(self) -> apis.AppearanceApi: return self._appearance_api + @property + def ai_lake_api(self) -> apis.AILakeApi: + return self._ai_lake_api + + @property + def ai_lake_pipe_tables_api(self) -> AILakePipeTablesApi: + return self._ai_lake_pipe_tables_api + @property def executions_cancellable(self) -> bool: return self._executions_cancellable diff --git a/packages/gooddata-sdk/src/gooddata_sdk/sdk.py b/packages/gooddata-sdk/src/gooddata_sdk/sdk.py index 003840083..96d323da3 100644 --- a/packages/gooddata-sdk/src/gooddata_sdk/sdk.py +++ b/packages/gooddata-sdk/src/gooddata_sdk/sdk.py @@ -3,6 +3,7 @@ from pathlib import Path +from gooddata_sdk.catalog.ai_lake.service import CatalogAiLakeService from gooddata_sdk.catalog.appearance.service import CatalogAppearanceService from gooddata_sdk.catalog.data_source.service import CatalogDataSourceService from gooddata_sdk.catalog.export.service import ExportService @@ -77,6 +78,7 @@ def __init__(self, client: GoodDataApiClient) -> None: """ self._client = client + self._catalog_ai_lake = CatalogAiLakeService(self._client) self._catalog_appearance = CatalogAppearanceService(self._client) self._catalog_workspace = CatalogWorkspaceService(self._client) self._catalog_workspace_content = CatalogWorkspaceContentService(self._client) @@ -90,6 +92,10 @@ def __init__(self, client: GoodDataApiClient) -> None: self._catalog_permission = CatalogPermissionService(self._client) self._export = ExportService(self._client) + @property + def catalog_ai_lake(self) -> CatalogAiLakeService: + return self._catalog_ai_lake + @property def catalog_appearance(self) -> CatalogAppearanceService: return self._catalog_appearance diff --git a/packages/gooddata-sdk/tests/catalog/test_catalog_ai_lake.py b/packages/gooddata-sdk/tests/catalog/test_catalog_ai_lake.py new file mode 100644 index 000000000..52b97b383 --- /dev/null +++ b/packages/gooddata-sdk/tests/catalog/test_catalog_ai_lake.py @@ -0,0 +1,235 @@ +# (C) 2026 GoodData Corporation +from __future__ import annotations + +from pathlib import Path + +from gooddata_sdk import ( + CatalogCreatePipeTableRequest, + CatalogDatabaseInstance, + CatalogPipeTable, + CatalogPipeTableSummary, + CatalogProvisionDatabaseInstanceRequest, + CatalogServiceInfo, + GoodDataSdk, +) +from gooddata_sdk.catalog.data_source.entity_model.data_source import CatalogDataSourceAiLakehouse +from tests_support.vcrpy_utils import get_vcr + +from .conftest import safe_delete + +gd_vcr = get_vcr() + +_current_dir = Path(__file__).parent.absolute() +_fixtures_dir = _current_dir / "fixtures" / "ai_lake" + +_TEST_INSTANCE_ID = "test-instance" +_TEST_TABLE_NAME = "test_pipe_table" + + +class TestCatalogDatabaseInstance: + def test_from_api_full(self): + entity = { + "id": "my-instance", + "name": "My Instance", + "storage_ids": ["storage-1", "storage-2"], + } + instance = CatalogDatabaseInstance.from_api(entity) + assert instance.id == "my-instance" + assert instance.name == "My Instance" + assert instance.storage_ids == ["storage-1", "storage-2"] + + def test_from_api_no_storage_ids(self): + entity = { + "id": "inst-1", + "name": "Instance 1", + } + instance = CatalogDatabaseInstance.from_api(entity) + assert instance.id == "inst-1" + assert instance.storage_ids == [] + + +class TestCatalogProvisionDatabaseInstanceRequest: + def test_as_api_model(self): + request = CatalogProvisionDatabaseInstanceRequest( + name="new-instance", + storage_ids=["s1", "s2"], + ) + api_model = request.as_api_model() + assert api_model.name == "new-instance" + assert api_model.storage_ids == ["s1", "s2"] + + def test_as_api_model_empty_storage(self): + request = CatalogProvisionDatabaseInstanceRequest(name="minimal") + api_model = request.as_api_model() + assert api_model.name == "minimal" + assert api_model.storage_ids == [] + + +class TestCatalogCreatePipeTableRequest: + def test_as_api_model_required_only(self): + request = CatalogCreatePipeTableRequest( + path_prefix="my-dataset/", + source_storage_name="my-storage", + table_name="my_table", + ) + api_model = request.as_api_model() + assert api_model.path_prefix == "my-dataset/" + assert api_model.source_storage_name == "my-storage" + assert api_model.table_name == "my_table" + + def test_as_api_model_with_optional_fields(self): + request = CatalogCreatePipeTableRequest( + path_prefix="data/", + source_storage_name="s3-storage", + table_name="sales_data", + column_overrides={"amount": "DECIMAL(10,2)"}, + max_varchar_length=255, + polling_interval_seconds=60, + table_properties={"replication_num": "3"}, + ) + api_model = request.as_api_model() + assert api_model.column_overrides == {"amount": "DECIMAL(10,2)"} + assert api_model.max_varchar_length == 255 + assert api_model.polling_interval_seconds == 60 + assert api_model.table_properties == {"replication_num": "3"} + + +class TestCatalogPipeTableSummary: + def test_from_api(self): + entity = { + "pipe_table_id": "uuid-1", + "table_name": "orders", + "path_prefix": "data/orders/", + "columns": [{"name": "id", "data_type": "INT"}], + } + summary = CatalogPipeTableSummary.from_api(entity) + assert summary.pipe_table_id == "uuid-1" + assert summary.table_name == "orders" + assert summary.path_prefix == "data/orders/" + assert len(summary.columns) == 1 + + +class TestCatalogPipeTable: + def test_from_api(self): + entity = { + "pipe_table_id": "uuid-2", + "table_name": "events", + "source_storage_name": "s3-src", + "path_prefix": "data/events/", + "database_name": "mydb", + "polling_interval_seconds": 30, + "partition_columns": ["year", "month"], + "table_properties": {"replication_num": "1"}, + "columns": [], + } + table = CatalogPipeTable.from_api(entity) + assert table.pipe_table_id == "uuid-2" + assert table.table_name == "events" + assert table.source_storage_name == "s3-src" + assert table.database_name == "mydb" + assert table.polling_interval_seconds == 30 + assert table.partition_columns == ["year", "month"] + assert table.table_properties == {"replication_num": "1"} + + +class TestCatalogServiceInfo: + def test_from_api(self): + entity = { + "name": "StarRocks Service", + "service_id": "svc-uuid-123", + } + svc = CatalogServiceInfo.from_api(entity) + assert svc.name == "StarRocks Service" + assert svc.service_id == "svc-uuid-123" + + +class TestCatalogDataSourceAiLakehouse: + def test_ailakehouse_type(self): + from gooddata_sdk.catalog.entity import BasicCredentials + + ds = CatalogDataSourceAiLakehouse( + id="ailake-ds", + name="AI Lake DS", + type="AILAKEHOUSE", + schema="", + credentials=BasicCredentials(username="u", password="p"), + ) + assert ds.type == "AILAKEHOUSE" + assert ds.schema == "" + + +# --------------------------------------------------------------------------- +# Integration tests — cassettes recorded against a live GoodData backend. +# Run with OVERWRITE=1 to record; subsequent runs replay from cassette. +# --------------------------------------------------------------------------- + + +@gd_vcr.use_cassette(str(_fixtures_dir / "list_database_instances.yaml")) +def test_list_database_instances(test_config): + sdk = GoodDataSdk.create(host_=test_config["host"], token_=test_config["token"]) + instances = sdk.catalog_ai_lake.list_database_instances() + assert isinstance(instances, list) + for instance in instances: + assert isinstance(instance, CatalogDatabaseInstance) + assert instance.id + assert instance.name + + +@gd_vcr.use_cassette(str(_fixtures_dir / "provision_get_deprovision_database_instance.yaml")) +def test_provision_get_deprovision_database_instance(test_config): + sdk = GoodDataSdk.create(host_=test_config["host"], token_=test_config["token"]) + request = CatalogProvisionDatabaseInstanceRequest(name=_TEST_INSTANCE_ID) + try: + sdk.catalog_ai_lake.provision_database_instance(request) + instance = sdk.catalog_ai_lake.get_database_instance(_TEST_INSTANCE_ID) + assert isinstance(instance, CatalogDatabaseInstance) + assert instance.name == _TEST_INSTANCE_ID + finally: + safe_delete(sdk.catalog_ai_lake.deprovision_database_instance, _TEST_INSTANCE_ID) + + +@gd_vcr.use_cassette(str(_fixtures_dir / "list_pipe_tables.yaml")) +def test_list_pipe_tables(test_config): + sdk = GoodDataSdk.create(host_=test_config["host"], token_=test_config["token"]) + instances = sdk.catalog_ai_lake.list_database_instances() + if not instances: + return + instance_id = instances[0].id + pipe_tables = sdk.catalog_ai_lake.list_pipe_tables(instance_id) + assert isinstance(pipe_tables, list) + for pt in pipe_tables: + assert isinstance(pt, CatalogPipeTableSummary) + assert pt.table_name + + +@gd_vcr.use_cassette(str(_fixtures_dir / "create_get_delete_pipe_table.yaml")) +def test_create_get_delete_pipe_table(test_config): + sdk = GoodDataSdk.create(host_=test_config["host"], token_=test_config["token"]) + instances = sdk.catalog_ai_lake.list_database_instances() + assert instances, "Need at least one database instance to test pipe table operations" + instance_id = instances[0].id + request = CatalogCreatePipeTableRequest( + path_prefix="test/data/", + source_storage_name="test-storage", + table_name=_TEST_TABLE_NAME, + ) + try: + sdk.catalog_ai_lake.create_pipe_table(instance_id, request) + table = sdk.catalog_ai_lake.get_pipe_table(instance_id, _TEST_TABLE_NAME) + assert isinstance(table, CatalogPipeTable) + assert table.table_name == _TEST_TABLE_NAME + pipe_tables = sdk.catalog_ai_lake.list_pipe_tables(instance_id) + table_names = [pt.table_name for pt in pipe_tables] + assert _TEST_TABLE_NAME in table_names + finally: + safe_delete(sdk.catalog_ai_lake.delete_pipe_table, instance_id, _TEST_TABLE_NAME) + + +@gd_vcr.use_cassette(str(_fixtures_dir / "list_services.yaml")) +def test_list_services(test_config): + sdk = GoodDataSdk.create(host_=test_config["host"], token_=test_config["token"]) + services = sdk.catalog_ai_lake.list_services() + assert isinstance(services, list) + for svc in services: + assert isinstance(svc, CatalogServiceInfo) + assert svc.name