From c63d047438717dcff7aa8caccca7709b3b1c1e83 Mon Sep 17 00:00:00 2001 From: yenkins-admin <5391010+yenkins-admin@users.noreply.github.com> Date: Mon, 20 Apr 2026 12:52:51 +0000 Subject: [PATCH] feat(gooddata-sdk): [AUTO] Add typed JsonApiParameter entity with NumberParameterDefinition --- .../gooddata-sdk/src/gooddata_sdk/__init__.py | 5 + .../catalog/catalog_service_base.py | 2 + .../workspace/entity_model/parameter.py | 192 +++++++++++ .../gooddata_sdk/catalog/workspace/service.py | 92 ++++++ .../gooddata-sdk/src/gooddata_sdk/client.py | 6 + .../tests/catalog/test_workspace_parameter.py | 304 ++++++++++++++++++ validate_import.py | 7 + 7 files changed, 608 insertions(+) create mode 100644 packages/gooddata-sdk/src/gooddata_sdk/catalog/workspace/entity_model/parameter.py create mode 100644 packages/gooddata-sdk/tests/catalog/test_workspace_parameter.py create mode 100644 validate_import.py diff --git a/packages/gooddata-sdk/src/gooddata_sdk/__init__.py b/packages/gooddata-sdk/src/gooddata_sdk/__init__.py index 77397b92d..ed777bc0d 100644 --- a/packages/gooddata-sdk/src/gooddata_sdk/__init__.py +++ b/packages/gooddata-sdk/src/gooddata_sdk/__init__.py @@ -264,6 +264,11 @@ CatalogDependentEntitiesResponse, CatalogEntityIdentifier, ) +from gooddata_sdk.catalog.workspace.entity_model.parameter import ( + CatalogNumberParameterDefinition, + CatalogWorkspaceParameter, + CatalogWorkspaceParameterConstraints, +) from gooddata_sdk.catalog.workspace.entity_model.user_data_filter import ( CatalogUserDataFilter, CatalogUserDataFilterAttributes, diff --git a/packages/gooddata-sdk/src/gooddata_sdk/catalog/catalog_service_base.py b/packages/gooddata-sdk/src/gooddata_sdk/catalog/catalog_service_base.py index 3da72a6b4..4057741f1 100644 --- a/packages/gooddata-sdk/src/gooddata_sdk/catalog/catalog_service_base.py +++ b/packages/gooddata-sdk/src/gooddata_sdk/catalog/catalog_service_base.py @@ -4,6 +4,7 @@ from pathlib import Path from gooddata_api_client import apis +from gooddata_api_client.api.parameters_api import ParametersApi from gooddata_api_client.model.json_api_organization_out_document import JsonApiOrganizationOutDocument from gooddata_sdk.catalog.organization.entity_model.organization import CatalogOrganization @@ -19,6 +20,7 @@ def __init__(self, api_client: GoodDataApiClient) -> None: self._layout_api: apis.LayoutApi = api_client.layout_api self._actions_api: apis.ActionsApi = api_client.actions_api self._user_management_api: apis.UserManagementApi = api_client.user_management_api + self._parameters_api: ParametersApi = api_client.parameters_api def get_organization(self) -> CatalogOrganization: # The generated client does work properly with redirecting APIs diff --git a/packages/gooddata-sdk/src/gooddata_sdk/catalog/workspace/entity_model/parameter.py b/packages/gooddata-sdk/src/gooddata_sdk/catalog/workspace/entity_model/parameter.py new file mode 100644 index 000000000..5a9176ca0 --- /dev/null +++ b/packages/gooddata-sdk/src/gooddata_sdk/catalog/workspace/entity_model/parameter.py @@ -0,0 +1,192 @@ +# (C) 2024 GoodData Corporation +from __future__ import annotations + +from typing import Any + +import attrs +from gooddata_api_client.model.json_api_parameter_in import JsonApiParameterIn +from gooddata_api_client.model.json_api_parameter_in_document import JsonApiParameterInDocument +from gooddata_api_client.model.json_api_parameter_patch import JsonApiParameterPatch +from gooddata_api_client.model.json_api_parameter_patch_document import JsonApiParameterPatchDocument +from gooddata_api_client.model.json_api_parameter_post_optional_id import JsonApiParameterPostOptionalId +from gooddata_api_client.model.json_api_parameter_post_optional_id_document import ( + JsonApiParameterPostOptionalIdDocument, +) +from gooddata_api_client.model.number_constraints import NumberConstraints +from gooddata_api_client.model.number_parameter_definition import NumberParameterDefinition + +from gooddata_sdk.catalog.base import Base + + +@attrs.define(kw_only=True) +class CatalogWorkspaceParameterConstraints(Base): + """Constraints for a number parameter.""" + + min: float | None = None + max: float | None = None + + @staticmethod + def client_class() -> type[NumberConstraints]: + return NumberConstraints + + def as_api_model(self) -> NumberConstraints: + kwargs: dict[str, Any] = {} + if self.min is not None: + kwargs["min"] = self.min + if self.max is not None: + kwargs["max"] = self.max + return NumberConstraints(_check_type=False, **kwargs) + + +@attrs.define(kw_only=True) +class CatalogNumberParameterDefinition(Base): + """Definition of a number-typed parameter.""" + + default_value: float + type: str = "NUMBER" + constraints: CatalogWorkspaceParameterConstraints | None = None + + @staticmethod + def client_class() -> type[NumberParameterDefinition]: + return NumberParameterDefinition + + def as_api_model(self) -> NumberParameterDefinition: + kwargs: dict[str, Any] = {} + if self.constraints is not None: + kwargs["constraints"] = self.constraints.as_api_model() + return NumberParameterDefinition( + default_value=self.default_value, + _check_type=False, + **kwargs, + ) + + +@attrs.define(kw_only=True) +class CatalogWorkspaceParameter(Base): + """A workspace-scoped parameter entity.""" + + id: str | None = None + definition: CatalogNumberParameterDefinition | None = None + title: str | None = None + description: str | None = None + tags: list[str] = attrs.field(factory=list) + + @staticmethod + def client_class() -> type[JsonApiParameterIn]: + return JsonApiParameterIn + + @classmethod + def init( + cls, + *, + parameter_id: str, + default_value: float, + title: str | None = None, + description: str | None = None, + tags: list[str] | None = None, + constraints: CatalogWorkspaceParameterConstraints | None = None, + ) -> CatalogWorkspaceParameter: + definition = CatalogNumberParameterDefinition( + default_value=default_value, + constraints=constraints, + ) + return cls( + id=parameter_id, + definition=definition, + title=title, + description=description, + tags=tags or [], + ) + + @classmethod + def from_api(cls, entity: Any) -> CatalogWorkspaceParameter: + """Deserialize from an API model object or a snake_case dict. + + When entity is an API model object (ModelNormal), its internal + _data_store uses snake_case keys. When it is a plain dict produced + by ``model.to_dict()``, the keys are also snake_case (the default). + """ + attrs_data = entity.get("attributes") or {} + raw_definition = attrs_data.get("definition") + definition: CatalogNumberParameterDefinition | None = None + if raw_definition is not None: + raw_constraints = raw_definition.get("constraints") + constraints: CatalogWorkspaceParameterConstraints | None = None + if raw_constraints is not None: + constraints = CatalogWorkspaceParameterConstraints( + min=raw_constraints.get("min"), + max=raw_constraints.get("max"), + ) + definition = CatalogNumberParameterDefinition( + default_value=raw_definition.get("default_value", 0.0), + type=raw_definition.get("type", "NUMBER"), + constraints=constraints, + ) + raw_tags = attrs_data.get("tags") + return cls( + id=entity.get("id"), + definition=definition, + title=attrs_data.get("title"), + description=attrs_data.get("description"), + tags=raw_tags if raw_tags is not None else [], + ) + + def as_post_document(self) -> JsonApiParameterPostOptionalIdDocument: + """Serialize to document for POST (create).""" + attributes = self._build_attributes() + kwargs: dict[str, Any] = {} + if self.id is not None: + kwargs["id"] = self.id + data = JsonApiParameterPostOptionalId( + type="parameter", + attributes=attributes, + _check_type=False, + **kwargs, + ) + return JsonApiParameterPostOptionalIdDocument(data=data, _check_type=False) + + def as_put_document(self) -> JsonApiParameterInDocument: + """Serialize to document for PUT (full update).""" + attributes = self._build_attributes() + data = JsonApiParameterIn( + id=self.id, + type="parameter", + attributes=attributes, + _check_type=False, + ) + return JsonApiParameterInDocument(data=data, _check_type=False) + + def as_patch_document(self) -> JsonApiParameterPatchDocument: + """Serialize to document for PATCH.""" + attributes = self._build_attributes() + data = JsonApiParameterPatch( + id=self.id, + type="parameter", + attributes=attributes, + _check_type=False, + ) + return JsonApiParameterPatchDocument(data=data, _check_type=False) + + def _build_attributes(self) -> dict[str, Any]: + attributes: dict[str, Any] = {} + if self.definition is not None: + definition_dict: dict[str, Any] = { + "defaultValue": self.definition.default_value, + "type": self.definition.type, + } + if self.definition.constraints is not None: + constraints_dict: dict[str, Any] = {} + if self.definition.constraints.min is not None: + constraints_dict["min"] = self.definition.constraints.min + if self.definition.constraints.max is not None: + constraints_dict["max"] = self.definition.constraints.max + if constraints_dict: + definition_dict["constraints"] = constraints_dict + attributes["definition"] = definition_dict + if self.title is not None: + attributes["title"] = self.title + if self.description is not None: + attributes["description"] = self.description + if self.tags: + attributes["tags"] = self.tags + return attributes diff --git a/packages/gooddata-sdk/src/gooddata_sdk/catalog/workspace/service.py b/packages/gooddata-sdk/src/gooddata_sdk/catalog/workspace/service.py index 606331e07..5c61fd8a3 100644 --- a/packages/gooddata-sdk/src/gooddata_sdk/catalog/workspace/service.py +++ b/packages/gooddata-sdk/src/gooddata_sdk/catalog/workspace/service.py @@ -34,6 +34,7 @@ CatalogFilterView, CatalogFilterViewDocument, ) +from gooddata_sdk.catalog.workspace.entity_model.parameter import CatalogWorkspaceParameter from gooddata_sdk.catalog.workspace.entity_model.user_data_filter import ( CatalogUserDataFilter, CatalogUserDataFilterDocument, @@ -676,6 +677,97 @@ def provision_workspace_with_locales( self.put_declarative_workspace_data_filters(new_filters) self.put_declarative_workspace(new_workspace.id, new_workspace_content) + + # Parameters methods + + def list_workspace_parameters(self, workspace_id: str) -> list[CatalogWorkspaceParameter]: + """Returns a list of all parameters defined in a workspace. + + Args: + workspace_id (str): + Workspace identification string e.g. "demo" + + Returns: + list[CatalogWorkspaceParameter]: + List of workspace parameters. + """ + get_parameters = functools.partial( + self._parameters_api.get_all_entities_parameters, + workspace_id, + _check_return_type=False, + ) + parameters = load_all_entities(get_parameters) + return [CatalogWorkspaceParameter.from_api(p) for p in parameters.data] + + def get_workspace_parameter(self, workspace_id: str, parameter_id: str) -> CatalogWorkspaceParameter: + """Get an individual workspace parameter. + + Args: + workspace_id (str): + Workspace identification string e.g. "demo" + parameter_id (str): + Parameter identification string e.g. "my-param" + + Returns: + CatalogWorkspaceParameter: + Catalog workspace parameter object. + """ + response = self._parameters_api.get_entity_parameters(workspace_id, parameter_id, _check_return_type=False) + return CatalogWorkspaceParameter.from_api(response.data) + + def create_or_update_workspace_parameter( + self, workspace_id: str, parameter: CatalogWorkspaceParameter + ) -> CatalogWorkspaceParameter: + """Create a new workspace parameter or overwrite an existing one with the same id. + + Args: + workspace_id (str): + Workspace identification string e.g. "demo" + parameter (CatalogWorkspaceParameter): + Catalog workspace parameter object to be created or updated. + + Returns: + CatalogWorkspaceParameter: + The created or updated catalog workspace parameter. + """ + try: + self.get_workspace_parameter(workspace_id, parameter.id) + response = self._parameters_api.update_entity_parameters( + workspace_id, + parameter.id, + parameter.as_put_document(), + _check_return_type=False, + ) + except NotFoundException: + response = self._parameters_api.create_entity_parameters( + workspace_id, + parameter.as_post_document(), + _check_return_type=False, + ) + return CatalogWorkspaceParameter.from_api(response.data) + + def delete_workspace_parameter(self, workspace_id: str, parameter_id: str) -> None: + """Delete a workspace parameter. + + Args: + workspace_id (str): + Workspace identification string e.g. "demo" + parameter_id (str): + Parameter identification string e.g. "my-param" + + Returns: + None + + Raises: + ValueError: + Parameter does not exist. + """ + try: + self._parameters_api.delete_entity_parameters(workspace_id, parameter_id, _check_return_type=False) + except NotFoundException: + raise ValueError( + f"Cannot delete parameter {parameter_id} from workspace {workspace_id}. This parameter does not exist." + ) # TODO - uncomment the copy after the fix is fully released # - list_workspace_settings is failing with 500 error too :-( # Copy settings from source workspace diff --git a/packages/gooddata-sdk/src/gooddata_sdk/client.py b/packages/gooddata-sdk/src/gooddata_sdk/client.py index 80ff83925..d3a6441cb 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.parameters_api import ParametersApi from gooddata_sdk import __version__ from gooddata_sdk.utils import HttpMethod @@ -71,6 +72,7 @@ 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._parameters_api = ParametersApi(self._api_client) self._executions_cancellable = executions_cancellable def _do_post_request( @@ -158,6 +160,10 @@ def user_management_api(self) -> apis.UserManagementApi: def appearance_api(self) -> apis.AppearanceApi: return self._appearance_api + @property + def parameters_api(self) -> ParametersApi: + return self._parameters_api + @property def executions_cancellable(self) -> bool: return self._executions_cancellable diff --git a/packages/gooddata-sdk/tests/catalog/test_workspace_parameter.py b/packages/gooddata-sdk/tests/catalog/test_workspace_parameter.py new file mode 100644 index 000000000..c7c2eb7ab --- /dev/null +++ b/packages/gooddata-sdk/tests/catalog/test_workspace_parameter.py @@ -0,0 +1,304 @@ +# (C) 2024 GoodData Corporation +"""Unit tests for CatalogWorkspaceParameter entity model. + +These tests are pure-Python (no live server, no VCR cassettes) and exercise +the entity-model layer only: construction, serialization, and deserialization. +""" + +from __future__ import annotations + +from gooddata_sdk.catalog.workspace.entity_model.parameter import ( + CatalogNumberParameterDefinition, + CatalogWorkspaceParameter, + CatalogWorkspaceParameterConstraints, +) + +# --------------------------------------------------------------------------- +# CatalogWorkspaceParameterConstraints +# --------------------------------------------------------------------------- + + +class TestCatalogWorkspaceParameterConstraints: + def test_both_bounds(self) -> None: + c = CatalogWorkspaceParameterConstraints(min=1.0, max=100.0) + assert c.min == 1.0 + assert c.max == 100.0 + + def test_min_only(self) -> None: + c = CatalogWorkspaceParameterConstraints(min=0.0) + assert c.min == 0.0 + assert c.max is None + + def test_max_only(self) -> None: + c = CatalogWorkspaceParameterConstraints(max=50.0) + assert c.min is None + assert c.max == 50.0 + + def test_no_bounds(self) -> None: + c = CatalogWorkspaceParameterConstraints() + assert c.min is None + assert c.max is None + + def test_as_api_model_both_bounds(self) -> None: + c = CatalogWorkspaceParameterConstraints(min=1.0, max=100.0) + api = c.as_api_model() + assert api["min"] == 1.0 + assert api["max"] == 100.0 + + def test_as_api_model_no_bounds_omits_keys(self) -> None: + c = CatalogWorkspaceParameterConstraints() + api = c.as_api_model() + # Neither min nor max should appear when they are None + assert "min" not in api or api.get("min") is None + assert "max" not in api or api.get("max") is None + + +# --------------------------------------------------------------------------- +# CatalogNumberParameterDefinition +# --------------------------------------------------------------------------- + + +class TestCatalogNumberParameterDefinition: + def test_defaults(self) -> None: + d = CatalogNumberParameterDefinition(default_value=42.0) + assert d.default_value == 42.0 + assert d.type == "NUMBER" + assert d.constraints is None + + def test_with_constraints(self) -> None: + constraints = CatalogWorkspaceParameterConstraints(min=0.0, max=200.0) + d = CatalogNumberParameterDefinition(default_value=10.0, constraints=constraints) + assert d.constraints is not None + assert d.constraints.min == 0.0 + assert d.constraints.max == 200.0 + + def test_as_api_model_no_constraints(self) -> None: + d = CatalogNumberParameterDefinition(default_value=5.0) + api = d.as_api_model() + assert api["default_value"] == 5.0 + + def test_as_api_model_with_constraints(self) -> None: + constraints = CatalogWorkspaceParameterConstraints(min=1.0, max=99.0) + d = CatalogNumberParameterDefinition(default_value=50.0, constraints=constraints) + api = d.as_api_model() + assert api["default_value"] == 50.0 + assert api["constraints"]["min"] == 1.0 + assert api["constraints"]["max"] == 99.0 + + +# --------------------------------------------------------------------------- +# CatalogWorkspaceParameter.init() +# --------------------------------------------------------------------------- + + +class TestCatalogWorkspaceParameterInit: + def test_minimal(self) -> None: + p = CatalogWorkspaceParameter.init(parameter_id="p1", default_value=7.0) + assert p.id == "p1" + assert p.definition is not None + assert p.definition.default_value == 7.0 + assert p.definition.type == "NUMBER" + assert p.definition.constraints is None + assert p.title is None + assert p.description is None + assert p.tags == [] + + def test_full(self) -> None: + constraints = CatalogWorkspaceParameterConstraints(min=0.0, max=100.0) + p = CatalogWorkspaceParameter.init( + parameter_id="p2", + default_value=50.0, + title="My Param", + description="A test parameter", + tags=["tag1", "tag2"], + constraints=constraints, + ) + assert p.id == "p2" + assert p.title == "My Param" + assert p.description == "A test parameter" + assert p.tags == ["tag1", "tag2"] + assert p.definition is not None + assert p.definition.constraints is not None + assert p.definition.constraints.min == 0.0 + assert p.definition.constraints.max == 100.0 + + def test_tags_default_to_empty_list(self) -> None: + p = CatalogWorkspaceParameter.init(parameter_id="p3", default_value=1.0) + assert p.tags == [] + assert isinstance(p.tags, list) + + +# --------------------------------------------------------------------------- +# CatalogWorkspaceParameter.from_api() +# --------------------------------------------------------------------------- + + +class TestCatalogWorkspaceParameterFromApi: + """Test deserialization from snake_case dict (as returned by API model .to_dict()).""" + + def _make_entity( + self, + *, + id: str = "param-1", + default_value: float = 10.0, + param_type: str = "NUMBER", + min_val: float | None = None, + max_val: float | None = None, + title: str | None = None, + description: str | None = None, + tags: list[str] | None = None, + ) -> dict: + """Build a snake_case dict that mimics a deserialized API response.""" + constraints: dict = {} + if min_val is not None: + constraints["min"] = min_val + if max_val is not None: + constraints["max"] = max_val + + definition: dict = {"default_value": default_value, "type": param_type} + if constraints: + definition["constraints"] = constraints + + attributes: dict = {"definition": definition} + if title is not None: + attributes["title"] = title + if description is not None: + attributes["description"] = description + if tags is not None: + attributes["tags"] = tags + + return {"id": id, "attributes": attributes} + + def test_minimal_entity(self) -> None: + entity = self._make_entity() + p = CatalogWorkspaceParameter.from_api(entity) + assert p.id == "param-1" + assert p.definition is not None + assert p.definition.default_value == 10.0 + assert p.definition.type == "NUMBER" + assert p.definition.constraints is None + assert p.title is None + assert p.description is None + assert p.tags == [] + + def test_full_entity(self) -> None: + entity = self._make_entity( + id="param-full", + default_value=42.5, + min_val=0.0, + max_val=100.0, + title="Full Param", + description="Has everything", + tags=["a", "b"], + ) + p = CatalogWorkspaceParameter.from_api(entity) + assert p.id == "param-full" + assert p.definition is not None + assert p.definition.default_value == 42.5 + assert p.definition.constraints is not None + assert p.definition.constraints.min == 0.0 + assert p.definition.constraints.max == 100.0 + assert p.title == "Full Param" + assert p.description == "Has everything" + assert p.tags == ["a", "b"] + + def test_no_attributes(self) -> None: + entity: dict = {"id": "bare"} + p = CatalogWorkspaceParameter.from_api(entity) + assert p.id == "bare" + assert p.definition is None + assert p.tags == [] + + def test_tags_none_becomes_empty_list(self) -> None: + entity = self._make_entity() + entity["attributes"].pop("tags", None) + p = CatalogWorkspaceParameter.from_api(entity) + assert p.tags == [] + + def test_default_value_fallback(self) -> None: + """When default_value is missing, fall back to 0.0.""" + entity = { + "id": "p-fallback", + "attributes": {"definition": {"type": "NUMBER"}}, + } + p = CatalogWorkspaceParameter.from_api(entity) + assert p.definition is not None + assert p.definition.default_value == 0.0 + + +# --------------------------------------------------------------------------- +# CatalogWorkspaceParameter serialization round-trip +# --------------------------------------------------------------------------- + + +class TestCatalogWorkspaceParameterSerialization: + def test_as_post_document(self) -> None: + p = CatalogWorkspaceParameter.init(parameter_id="post-param", default_value=3.14) + doc = p.as_post_document() + data = doc.data + assert data.type == "parameter" + assert data.id == "post-param" + + def test_as_put_document(self) -> None: + p = CatalogWorkspaceParameter.init(parameter_id="put-param", default_value=2.71) + doc = p.as_put_document() + data = doc.data + assert data.type == "parameter" + assert data.id == "put-param" + + def test_as_patch_document(self) -> None: + p = CatalogWorkspaceParameter.init(parameter_id="patch-param", default_value=1.0) + doc = p.as_patch_document() + data = doc.data + assert data.type == "parameter" + assert data.id == "patch-param" + + def test_post_document_no_id(self) -> None: + """When id is None, POST document should omit id.""" + p = CatalogWorkspaceParameter( + id=None, + definition=CatalogNumberParameterDefinition(default_value=5.0), + ) + doc = p.as_post_document() + data = doc.data + assert data.type == "parameter" + + def test_build_attributes_with_title_and_description(self) -> None: + p = CatalogWorkspaceParameter.init( + parameter_id="p", + default_value=1.0, + title="T", + description="D", + tags=["x"], + ) + attrs = p._build_attributes() + assert "definition" in attrs + assert attrs["definition"]["defaultValue"] == 1.0 + assert attrs["definition"]["type"] == "NUMBER" + assert attrs["title"] == "T" + assert attrs["description"] == "D" + assert attrs["tags"] == ["x"] + + def test_build_attributes_with_constraints(self) -> None: + constraints = CatalogWorkspaceParameterConstraints(min=0.0, max=50.0) + p = CatalogWorkspaceParameter.init( + parameter_id="p", + default_value=25.0, + constraints=constraints, + ) + attrs = p._build_attributes() + definition = attrs["definition"] + assert definition["defaultValue"] == 25.0 + assert definition["constraints"]["min"] == 0.0 + assert definition["constraints"]["max"] == 50.0 + + def test_build_attributes_no_tags_omitted(self) -> None: + p = CatalogWorkspaceParameter.init(parameter_id="p", default_value=1.0) + attrs = p._build_attributes() + assert "tags" not in attrs + + def test_build_attributes_no_title_omitted(self) -> None: + p = CatalogWorkspaceParameter.init(parameter_id="p", default_value=1.0) + attrs = p._build_attributes() + assert "title" not in attrs + assert "description" not in attrs diff --git a/validate_import.py b/validate_import.py new file mode 100644 index 000000000..8a54f4cb6 --- /dev/null +++ b/validate_import.py @@ -0,0 +1,7 @@ +import sys +sys.path.insert(0, '/home/runner/_work/gdc-nas/gdc-nas/sdk/packages/gooddata-sdk/src') +sys.path.insert(0, '/home/runner/_work/gdc-nas/gdc-nas/sdk/packages/gooddata-api-client/src') +import gooddata_sdk +print('Import OK') +from gooddata_sdk import CatalogWorkspaceParameter, CatalogNumberParameterDefinition, CatalogWorkspaceParameterConstraints +print('Parameter exports OK')