Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/gooddata-sdk/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
1 change: 1 addition & 0 deletions packages/gooddata-sdk/src/gooddata_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
CatalogAILakeOperation,
CatalogAILakeOperationError,
CatalogAILakeService,
CatalogObjectStorageInfo,
)
from gooddata_sdk.catalog.appearance.entity_model.color_palette import (
CatalogColorPalette,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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."""

Expand All @@ -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,
Expand Down
24 changes: 24 additions & 0 deletions packages/gooddata-sdk/src/gooddata_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
@@ -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
26 changes: 26 additions & 0 deletions packages/gooddata-sdk/tests/catalog/test_ai_lake_service.py
Original file line number Diff line number Diff line change
@@ -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
Loading