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