diff --git a/packages/gooddata-sdk/pyproject.toml b/packages/gooddata-sdk/pyproject.toml index 7a218dbe6..aa3666384 100644 --- a/packages/gooddata-sdk/pyproject.toml +++ b/packages/gooddata-sdk/pyproject.toml @@ -76,7 +76,7 @@ test = [ ] [tool.ty.analysis] -allowed-unresolved-imports = ["gooddata_api_client.**"] +allowed-unresolved-imports = ["gooddata_api_client.**", "pyarrow"] [tool.hatch.build.targets.wheel] packages = ["src/gooddata_sdk"] diff --git a/packages/gooddata-sdk/src/gooddata_sdk/__init__.py b/packages/gooddata-sdk/src/gooddata_sdk/__init__.py index 91f87c918..32c254da8 100644 --- a/packages/gooddata-sdk/src/gooddata_sdk/__init__.py +++ b/packages/gooddata-sdk/src/gooddata_sdk/__init__.py @@ -11,6 +11,7 @@ CatalogAILakeOperation, CatalogAILakeOperationError, CatalogAILakeService, + CatalogObjectStorageInfo, ) from gooddata_sdk.catalog.appearance.entity_model.color_palette import ( CatalogColorPalette, 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 index b593e09e8..f788ea5cd 100644 --- a/packages/gooddata-sdk/src/gooddata_sdk/catalog/ai_lake/service.py +++ b/packages/gooddata-sdk/src/gooddata_sdk/catalog/ai_lake/service.py @@ -58,6 +58,25 @@ def is_failed(self) -> bool: return self.status == "failed" +@define(kw_only=True) +class CatalogObjectStorageInfo(Base): + """Information about a registered AI Lake object storage.""" + + name: str + storage_config: dict[str, str] + storage_id: str + storage_type: str + + @classmethod + def from_api(cls, entity: dict[str, Any]) -> CatalogObjectStorageInfo: + return cls( + name=entity["name"], + storage_config=entity.get("storageConfig") or {}, + storage_id=entity["storageId"], + storage_type=entity["storageType"], + ) + + class CatalogAILakeOperationError(RuntimeError): """Raised when an AI Lake long-running operation finishes in `failed` state.""" @@ -76,6 +95,23 @@ def __init__(self, api_client: GoodDataApiClient) -> None: self._client = api_client self._ai_lake_api: AILakeApi = AILakeApi(api_client._api_client) + def list_object_storages(self) -> list[CatalogObjectStorageInfo]: + """List all object storages registered for the organization. + + Returns: + List of `CatalogObjectStorageInfo` objects, ordered by name. + """ + response = self._ai_lake_api.list_ai_lake_object_storages() + return [ + CatalogObjectStorageInfo( + name=s.name, + storage_config=s.storage_config or {}, + storage_id=s.storage_id, + storage_type=s.storage_type, + ) + for s in response.storages + ] + def analyze_statistics( self, instance_id: str, diff --git a/packages/gooddata-sdk/src/gooddata_sdk/client.py b/packages/gooddata-sdk/src/gooddata_sdk/client.py index 0fd65a276..02c626e78 100644 --- a/packages/gooddata-sdk/src/gooddata_sdk/client.py +++ b/packages/gooddata-sdk/src/gooddata_sdk/client.py @@ -91,6 +91,30 @@ def __init__( self._ai_lake_api = apis.AILakeApi(self._api_client) self._executions_cancellable = executions_cancellable + def _do_get_request( + self, + endpoint: str, + ) -> requests.Response: + """Perform a GET request to a specified endpoint. + + Args: + endpoint (str): The endpoint URL to which the request is made. + + Returns: + requests.Response: The response from the HTTP request. + """ + if not self._hostname.endswith("/"): + endpoint = f"/{endpoint}" + + response = requests.get( + url=f"{self._hostname}{endpoint}", + headers={ + "Authorization": f"Bearer {self._token}", + }, + ) + + return response + def _do_post_request( self, data: bytes, diff --git a/packages/gooddata-sdk/src/gooddata_sdk/compute/model/execution.py b/packages/gooddata-sdk/src/gooddata_sdk/compute/model/execution.py index df5284ec6..424a6d7ea 100644 --- a/packages/gooddata-sdk/src/gooddata_sdk/compute/model/execution.py +++ b/packages/gooddata-sdk/src/gooddata_sdk/compute/model/execution.py @@ -19,8 +19,8 @@ import pyarrow as _pyarrow from pyarrow import ipc as _ipc except ImportError: - _pyarrow = None # type: ignore - _ipc = None # type: ignore + _pyarrow = None + _ipc = None from gooddata_sdk.client import GoodDataApiClient from gooddata_sdk.compute.model.attribute import Attribute diff --git a/packages/gooddata-sdk/src/gooddata_sdk/compute/model/filter.py b/packages/gooddata-sdk/src/gooddata_sdk/compute/model/filter.py index 94171f156..bbac98e7b 100644 --- a/packages/gooddata-sdk/src/gooddata_sdk/compute/model/filter.py +++ b/packages/gooddata-sdk/src/gooddata_sdk/compute/model/filter.py @@ -326,7 +326,7 @@ def __init__( self._from_shift = from_shift self._to_shift = to_shift self._bounded_filter = bounded_filter - self._empty_value_handling = empty_value_handling + self._empty_value_handling: EmptyValueHandling | None = empty_value_handling @property def dataset(self) -> ObjId: @@ -435,7 +435,7 @@ def __init__( self._dataset = dataset self._granularity = granularity - self._empty_value_handling = empty_value_handling + self._empty_value_handling: EmptyValueHandling | None = empty_value_handling @property def dataset(self) -> ObjId: @@ -490,7 +490,7 @@ def __init__( self._dataset = dataset self._from_date = from_date self._to_date = to_date - self._empty_value_handling = empty_value_handling + self._empty_value_handling: EmptyValueHandling | None = empty_value_handling @property def dataset(self) -> ObjId: diff --git a/packages/gooddata-sdk/tests/catalog/fixtures/ai_lake/test_list_object_storages.yaml b/packages/gooddata-sdk/tests/catalog/fixtures/ai_lake/test_list_object_storages.yaml new file mode 100644 index 000000000..163896fc9 --- /dev/null +++ b/packages/gooddata-sdk/tests/catalog/fixtures/ai_lake/test_list_object_storages.yaml @@ -0,0 +1,67 @@ +interactions: + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Connection: + - keep-alive + method: GET + uri: http://localhost:3000/api/v1/ailake/objectStorages + response: + body: + string: + detail: Server-side problem. Contact support. + status: 500 + title: Internal Server Error + traceId: NORMALIZED_TRACE_ID_000000000000 + headers: + Content-Type: + - application/problem+json + DATE: &id001 + - PLACEHOLDER + Expires: + - '0' + Pragma: + - no-cache + X-Content-Type-Options: + - nosniff + X-GDC-TRACE-ID: *id001 + status: + code: 500 + message: Internal Server Error + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Connection: + - keep-alive + method: GET + uri: http://localhost:3000/api/v1/ailake/objectStorages + response: + body: + string: + detail: Server-side problem. Contact support. + status: 500 + title: Internal Server Error + traceId: NORMALIZED_TRACE_ID_000000000000 + headers: + Content-Type: + - application/problem+json + DATE: *id001 + Expires: + - '0' + Pragma: + - no-cache + X-Content-Type-Options: + - nosniff + X-GDC-TRACE-ID: *id001 + status: + code: 500 + message: Internal Server Error +version: 1 diff --git a/packages/gooddata-sdk/tests/catalog/test_ai_lake_service.py b/packages/gooddata-sdk/tests/catalog/test_ai_lake_service.py new file mode 100644 index 000000000..339ccdcff --- /dev/null +++ b/packages/gooddata-sdk/tests/catalog/test_ai_lake_service.py @@ -0,0 +1,26 @@ +# (C) 2026 GoodData Corporation +"""Integration tests for `CatalogAILakeService`.""" + +from __future__ import annotations + +from pathlib import Path + +from gooddata_sdk import CatalogObjectStorageInfo, GoodDataSdk +from tests_support.vcrpy_utils import get_vcr + +gd_vcr = get_vcr() + +_current_dir = Path(__file__).parent.absolute() +_fixtures_dir = _current_dir / "fixtures" / "ai_lake" + + +@gd_vcr.use_cassette(str(_fixtures_dir / "test_list_object_storages.yaml")) +def test_list_object_storages(test_config): + sdk = GoodDataSdk.create(host_=test_config["host"], token_=test_config["token"]) + storages = sdk.catalog_ai_lake.list_object_storages() + assert isinstance(storages, list) + for s in storages: + assert isinstance(s, CatalogObjectStorageInfo) + assert s.name + assert s.storage_id + assert s.storage_type