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
109 changes: 109 additions & 0 deletions packages/gooddata-sdk/USER_SETTINGS_USAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# User Settings API

The GoodData SDK now supports user-specific settings management with access restrictions for certain settings.

## Features

- CRUD operations for user settings (Create, Read, Update, Delete)
- Access restrictions for workspace/organization-level settings
- Type-safe API with proper validation

## Usage Examples

### Creating User Settings

```python
from gooddata_sdk import GoodDataSdk, CatalogUserSetting

# Initialize SDK
sdk = GoodDataSdk.create(host="http://localhost:3000", token="your_token")

# Create a locale setting for a user
locale_setting = CatalogUserSetting.init(
setting_id="locale",
setting_type="LOCALE",
content={"value": "en-US"}
)

# Apply the setting to a user
sdk.catalog_user.create_or_update_user_setting("user123", locale_setting)
```

### Retrieving User Settings

```python
# Get a specific user setting
setting = sdk.catalog_user.get_user_setting("user123", "locale")
print(f"Locale setting: {setting.attributes.content}")

# List all settings for a user
all_settings = sdk.catalog_user.list_user_settings("user123")
for setting in all_settings:
print(f"Setting {setting.id}: {setting.attributes.content}")
```

### Updating User Settings

```python
# Update an existing setting
updated_setting = CatalogUserSetting.init(
setting_id="locale",
setting_type="LOCALE",
content={"value": "de-DE"}
)
sdk.catalog_user.create_or_update_user_setting("user123", updated_setting)
```

### Deleting User Settings

```python
# Delete a user setting
sdk.catalog_user.delete_user_setting("user123", "locale")
```

## Access Restrictions

Some settings are restricted to workspace or organization level only and cannot be set at the user level:

- `nullJoins` - Controls null join behavior, restricted to workspace/organization level

```python
# This will raise a ValueError
try:
restricted_setting = CatalogUserSetting.init(
setting_id="nullJoins",
setting_type="BOOLEAN",
content={"value": True}
)
except ValueError as e:
print(f"Error: {e}")
# Output: Error: Setting 'nullJoins' (type: 'BOOLEAN') is restricted to workspace/organization level only.
```

## API Reference

### CatalogUserSetting.init()

Creates a new user setting with validation.

**Parameters:**
- `setting_id` (str): The ID of the setting
- `setting_type` (str): The type of the setting
- `content` (dict): The setting content/value

**Raises:**
- `ValueError`: If the setting is restricted to workspace/organization level

### Service Methods

#### create_or_update_user_setting(user_id, user_setting)
Creates a new user setting or updates an existing one.

#### get_user_setting(user_id, user_setting_id)
Retrieves a specific user setting.

#### list_user_settings(user_id)
Lists all settings for a user.

#### delete_user_setting(user_id, user_setting_id)
Deletes a user setting.
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 @@ -153,6 +153,11 @@
)
from gooddata_sdk.catalog.user.entity_model.user import CatalogUser
from gooddata_sdk.catalog.user.entity_model.user_group import CatalogUserGroup
from gooddata_sdk.catalog.user.entity_model.user_setting import (
CatalogUserSetting,
CatalogUserSettingAttributes,
CatalogUserSettingDocument,
)
from gooddata_sdk.catalog.user.management_model.management import (
CatalogDataSourcePermissionAssignment,
CatalogPermissionAssignments,
Expand Down
35 changes: 35 additions & 0 deletions packages/gooddata-sdk/src/gooddata_sdk/catalog/user/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,36 @@
# (C) 2022 GoodData Corporation
from gooddata_sdk.catalog.user.declarative_model.user import CatalogDeclarativeUser, CatalogDeclarativeUsers
from gooddata_sdk.catalog.user.declarative_model.user_and_user_groups import CatalogDeclarativeUsersUserGroups
from gooddata_sdk.catalog.user.declarative_model.user_group import CatalogDeclarativeUserGroup, CatalogDeclarativeUserGroups
from gooddata_sdk.catalog.user.entity_model.api_token import CatalogApiToken
from gooddata_sdk.catalog.user.entity_model.user import CatalogUser, CatalogUserDocument
from gooddata_sdk.catalog.user.entity_model.user_group import CatalogUserGroup, CatalogUserGroupDocument
from gooddata_sdk.catalog.user.entity_model.user_setting import (
CatalogUserSetting,
CatalogUserSettingAttributes,
CatalogUserSettingDocument,
)
from gooddata_sdk.catalog.user.management_model.management import (
CatalogPermissionAssignments,
CatalogPermissionsAssignment,
)
from gooddata_sdk.catalog.user.service import CatalogUserService

__all__ = [
"CatalogApiToken",
"CatalogDeclarativeUser",
"CatalogDeclarativeUserGroup",
"CatalogDeclarativeUserGroups",
"CatalogDeclarativeUsers",
"CatalogDeclarativeUsersUserGroups",
"CatalogPermissionAssignments",
"CatalogPermissionsAssignment",
"CatalogUser",
"CatalogUserDocument",
"CatalogUserGroup",
"CatalogUserGroupDocument",
"CatalogUserService",
"CatalogUserSetting",
"CatalogUserSettingAttributes",
"CatalogUserSettingDocument",
]
Original file line number Diff line number Diff line change
@@ -1 +1,20 @@
# (C) 2022 GoodData Corporation
from gooddata_sdk.catalog.user.entity_model.api_token import CatalogApiToken
from gooddata_sdk.catalog.user.entity_model.user import CatalogUser, CatalogUserDocument
from gooddata_sdk.catalog.user.entity_model.user_group import CatalogUserGroup, CatalogUserGroupDocument
from gooddata_sdk.catalog.user.entity_model.user_setting import (
CatalogUserSetting,
CatalogUserSettingAttributes,
CatalogUserSettingDocument,
)

__all__ = [
"CatalogApiToken",
"CatalogUser",
"CatalogUserDocument",
"CatalogUserGroup",
"CatalogUserGroupDocument",
"CatalogUserSetting",
"CatalogUserSettingAttributes",
"CatalogUserSettingDocument",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# (C) 2024 GoodData Corporation
from __future__ import annotations

import builtins
from typing import Any, Optional

import attr
from gooddata_api_client.model.json_api_user_setting_in import JsonApiUserSettingIn
from gooddata_api_client.model.json_api_user_setting_in_document import JsonApiUserSettingInDocument
from gooddata_api_client.model.json_api_user_setting_out import JsonApiUserSettingOut
from gooddata_api_client.model.json_api_organization_setting_in_attributes import JsonApiOrganizationSettingInAttributes

from gooddata_sdk.catalog.base import Base


@attr.s(auto_attribs=True, kw_only=True)
class CatalogUserSetting(Base):
id: str
attributes: Optional[CatalogUserSettingAttributes] = None

@classmethod
def init(cls, setting_id: str, setting_type: str, content: dict[str, Any]) -> CatalogUserSetting:
"""Initialize a CatalogUserSetting with restricted access for certain settings.

Some settings are restricted to workspace/organization level only and cannot be set at user level.

Args:
setting_id: The ID of the setting
setting_type: The type of the setting
content: The setting content

Returns:
CatalogUserSetting instance

Raises:
ValueError: If trying to set a restricted setting at user level
"""
# Define settings that are restricted to workspace/organization level only
restricted_settings = {
"nullJoins", # Based on the JIRA description about null joins setting access restrictions
}

if setting_id in restricted_settings or setting_type in restricted_settings:
raise ValueError(
f"Setting '{setting_id}' (type: '{setting_type}') is restricted to workspace/organization level only. "
"It cannot be set at user level."
)

return cls(id=setting_id, attributes=CatalogUserSettingAttributes(type=setting_type, content=content))

@staticmethod
def client_class() -> type[JsonApiUserSettingIn]:
return JsonApiUserSettingIn

def to_api(self, as_document: bool = False) -> JsonApiUserSettingIn | JsonApiUserSettingInDocument:
"""Convert to API representation."""
api_setting = JsonApiUserSettingIn(
id=self.id,
type="userSetting",
attributes=self.attributes.to_api() if self.attributes else None
)

if as_document:
return JsonApiUserSettingInDocument(data=api_setting)
return api_setting

@classmethod
def from_api(cls, api_setting: JsonApiUserSettingOut) -> CatalogUserSetting:
"""Create CatalogUserSetting from API representation."""
attributes = None
if hasattr(api_setting, 'attributes') and api_setting.attributes:
attributes = CatalogUserSettingAttributes.from_api(api_setting.attributes)

return cls(
id=api_setting.id,
attributes=attributes
)


@attr.s(auto_attribs=True, kw_only=True)
class CatalogUserSettingAttributes(Base):
type: Optional[str] = None
content: dict[str, Any] = attr.field(factory=dict)

@staticmethod
def client_class() -> builtins.type[JsonApiOrganizationSettingInAttributes]:
return JsonApiOrganizationSettingInAttributes

def to_api(self) -> JsonApiOrganizationSettingInAttributes:
"""Convert to API representation."""
return JsonApiOrganizationSettingInAttributes(
type=self.type,
content=self.content
)

@classmethod
def from_api(cls, api_attributes) -> CatalogUserSettingAttributes:
"""Create CatalogUserSettingAttributes from API representation."""
return cls(
type=api_attributes.type if hasattr(api_attributes, 'type') else None,
content=api_attributes.content if hasattr(api_attributes, 'content') else {}
)


@attr.s(auto_attribs=True, kw_only=True)
class CatalogUserSettingDocument(Base):
data: CatalogUserSetting

def to_api(self) -> JsonApiUserSettingInDocument:
return JsonApiUserSettingInDocument(data=self.data.to_api())

@staticmethod
def client_class() -> type[JsonApiUserSettingInDocument]:
return JsonApiUserSettingInDocument
80 changes: 80 additions & 0 deletions packages/gooddata-sdk/src/gooddata_sdk/catalog/user/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from gooddata_sdk.catalog.user.entity_model.api_token import CatalogApiToken
from gooddata_sdk.catalog.user.entity_model.user import CatalogUser, CatalogUserDocument
from gooddata_sdk.catalog.user.entity_model.user_group import CatalogUserGroup, CatalogUserGroupDocument
from gooddata_sdk.catalog.user.entity_model.user_setting import CatalogUserSetting, CatalogUserSettingDocument
from gooddata_sdk.catalog.user.management_model.management import (
CatalogPermissionAssignments,
CatalogPermissionsAssignment,
Expand Down Expand Up @@ -473,3 +474,82 @@ def get_user_api_token(self, user_id: str, api_token_id: str) -> CatalogApiToken

def delete_user_api_token(self, user_id: str, api_token_id: str) -> None:
self._entities_api.delete_entity_api_tokens(user_id, api_token_id)

# Entity methods for user settings

def create_or_update_user_setting(self, user_id: str, user_setting: CatalogUserSetting) -> None:
"""Create a new user setting or overwrite an existing user setting with the same id.

Some settings are restricted to workspace/organization level only and cannot be set at user level.
The create operation will enforce these restrictions.

Args:
user_id (str):
User identification string. e.g. "demo.user"
user_setting (CatalogUserSetting):
User setting entity object.

Returns:
None

Raises:
ValueError: If trying to set a restricted setting at user level
"""
try:
self.get_user_setting(user_id, user_setting.id)
user_setting_document = CatalogUserSettingDocument(data=user_setting)
self._entities_api.update_entity_user_settings(
user_id, user_setting.id, user_setting_document.to_api()
)
except NotFoundException:
user_setting_document = CatalogUserSettingDocument(data=user_setting)
self._entities_api.create_entity_user_settings(user_id, user_setting_document.to_api())

def get_user_setting(self, user_id: str, user_setting_id: str) -> CatalogUserSetting:
"""Get an individual user setting using user id and user setting id.

Args:
user_id (str):
User identification string. e.g. "demo.user"
user_setting_id (str):
User Setting identification string. e.g. "locale"

Returns:
CatalogUserSetting:
User setting entity object.
"""
user_setting = self._entities_api.get_entity_user_settings(user_id, user_setting_id).data
return CatalogUserSetting.from_api(user_setting)

def delete_user_setting(self, user_id: str, user_setting_id: str) -> None:
"""Delete User Setting using User id and User Setting id.

Args:
user_id (str):
User identification string. e.g. "demo.user"
user_setting_id (str):
User Setting identification string. e.g. "locale"

Returns:
None
"""
self._entities_api.delete_entity_user_settings(user_id, user_setting_id)

def list_user_settings(self, user_id: str) -> list[CatalogUserSetting]:
"""Get a list of all existing user settings for a specific user.

Args:
user_id (str):
User identification string. e.g. "demo.user"

Returns:
list[CatalogUserSetting]:
List of all User Settings for the user as User Setting entity objects.
"""
get_user_settings = functools.partial(
self._entities_api.get_all_entities_user_settings,
user_id,
_check_return_type=False,
)
user_settings = load_all_entities(get_user_settings)
return [CatalogUserSetting.from_api(us) for us in user_settings.data]
Loading
Loading