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
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ weight: 100
api_ref: "CatalogOrganizationService.create_or_update_jwk"
---

``create_or_update_jwk( jwk: CatalogJwk ) -> None``
``create_or_update_jwk( jwk: CatalogJwk ) -> UpsertOutcome``

Create a new jwk or overwrite an existing jwk with the same id.

Expand All @@ -18,4 +18,6 @@ Create a new jwk or overwrite an existing jwk with the same id.

## Returns

_None_
| type | description |
| -- | -- |
| UpsertOutcome | CREATED if the jwk did not exist yet, UPDATED if it did. |
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ User group entity object.
{{< /parameter >}}
{{% /parameters-block %}}

{{% parameters-block title="Returns" None="yes" %}}
{{% parameters-block title="Returns" %}}
{{< parameter p_type="UpsertOutcome" >}}
CREATED if the user group did not exist yet, UPDATED if it did.
{{< /parameter >}}
{{% /parameters-block %}}

## Example
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ User entity object.
{{< /parameter >}}
{{% /parameters-block %}}

{{% parameters-block title="Returns" None="yes" %}}
{{% parameters-block title="Returns" %}}
{{< parameter p_type="UpsertOutcome" >}}
CREATED if the user did not exist yet, UPDATED if it did.
{{< /parameter >}}
{{% /parameters-block %}}

## Example
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ Catalog data source object

{{% /parameters-block %}}

{{% parameters-block title="Returns" None="yes"%}}
{{% parameters-block title="Returns" %}}
{{< parameter p_type="UpsertOutcome" >}}
CREATED if the data source did not exist yet, UPDATED if it did.
{{< /parameter >}}
{{% /parameters-block %}}

### Example
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ Creates a new workspace or overwrite an existing workspace with the same id.
Data source Object, including physical data model.
{{< /parameter >}}
{{% /parameters-block %}}
{{% parameters-block title="Returns" None="yes" %}}
{{% parameters-block title="Returns" %}}
{{< parameter p_type="UpsertOutcome" >}}
CREATED if the workspace did not exist yet, UPDATED if it did.
{{< /parameter >}}
{{% /parameters-block %}}
{{% parameters-block title="Raises" %}}
{{< parameter p_type="Value Error" >}}
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 @@ -177,6 +177,7 @@
CatalogOrganizationPermissionAssignment,
)
from gooddata_sdk.catalog.rule import CatalogAssigneeRule
from gooddata_sdk.catalog.types import UpsertOutcome
from gooddata_sdk.catalog.user.declarative_model.user import (
CatalogDeclarativeUser,
CatalogDeclarativeUserPermission,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
)
from gooddata_sdk.catalog.data_source.entity_model.data_source import CatalogDataSource
from gooddata_sdk.catalog.entity import ClientSecretCredentialsFromFile, TokenCredentialsFromFile
from gooddata_sdk.catalog.types import UpsertOutcome
from gooddata_sdk.catalog.workspace.declarative_model.workspace.logical_model.ldm import CatalogDeclarativeModel
from gooddata_sdk.client import GoodDataApiClient
from gooddata_sdk.utils import get_ds_credentials, load_all_entities_dict, read_layout_from_file
Expand All @@ -57,7 +58,7 @@ def __init__(self, api_client: GoodDataApiClient) -> None:
def create_or_update_data_source(
self,
data_source: CatalogDataSource,
) -> None:
) -> UpsertOutcome:
"""Pushes the Data Source to the GoodData environment.

Automatically decides, whether to create or update.
Expand All @@ -67,7 +68,8 @@ def create_or_update_data_source(
Catalog Data Source object

Returns:
None
UpsertOutcome:
CREATED if the data source did not exist yet, UPDATED if it did.
"""
try:
self._entities_api.get_entity_data_sources(data_source.id)
Expand All @@ -77,6 +79,8 @@ def create_or_update_data_source(
)
except NotFoundException:
self._entities_api.create_entity_data_sources(data_source.to_api())
return UpsertOutcome.CREATED
return UpsertOutcome.UPDATED

def get_data_source(self, data_source_id: str) -> CatalogDataSource:
"""Retrieve Data Source entity using data source id.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
from gooddata_sdk.catalog.organization.entity_model.setting import CatalogOrganizationSetting
from gooddata_sdk.catalog.organization.layout.identity_provider import CatalogDeclarativeIdentityProvider
from gooddata_sdk.catalog.organization.layout.notification_channel import CatalogDeclarativeNotificationChannel
from gooddata_sdk.catalog.types import UpsertOutcome
from gooddata_sdk.client import GoodDataApiClient
from gooddata_sdk.utils import load_all_entities, load_all_entities_dict

Expand Down Expand Up @@ -107,15 +108,16 @@ def update_allowed_origins(self, allowed_origins: list[str]) -> None:
patch_document = JsonApiOrganizationPatchDocument(data=patch_data)
self._entities_api.patch_entity_organizations(organization.id, patch_document)

def create_or_update_jwk(self, jwk: CatalogJwk) -> None:
def create_or_update_jwk(self, jwk: CatalogJwk) -> UpsertOutcome:
"""Create a new jwk or overwrite an existing jwk with the same id.

Args:
jwk (CatalogJwk):
Catalog Jwk object to be created or updated.

Returns:
None
UpsertOutcome:
CREATED if the jwk did not exist yet, UPDATED if it did.

Raises:
ValueError: Jwk can not be updated.
Expand All @@ -126,6 +128,8 @@ def create_or_update_jwk(self, jwk: CatalogJwk) -> None:
self._entities_api.update_entity_jwks(id=jwk.id, json_api_jwk_in_document=jwk_document.to_api())
except NotFoundException:
self._entities_api.create_entity_jwks(json_api_jwk_in_document=jwk_document.to_api())
return UpsertOutcome.CREATED
return UpsertOutcome.UPDATED

def get_jwk(self, jwk_id: str) -> CatalogJwk:
"""Get an individual jwk.
Expand Down Expand Up @@ -473,15 +477,16 @@ def patch_identity_provider_attributes(self, identity_provider_id: str, attribut
identity_provider_id, CatalogIdentityProvider.to_api_patch(identity_provider_id, attributes)
)

def create_or_update_export_template(self, export_template: CatalogExportTemplate) -> None:
def create_or_update_export_template(self, export_template: CatalogExportTemplate) -> UpsertOutcome:
"""Create a new export template or overwrite an existing export template with the same id.

Args:
export_template (CatalogExportTemplate):
Catalog export template object to be created or updated.

Returns:
None
UpsertOutcome:
CREATED if the export template did not exist yet, UPDATED if it did.

Raises:
ValueError: Export template cannot be updated.
Expand All @@ -500,6 +505,8 @@ def create_or_update_export_template(self, export_template: CatalogExportTemplat
data=export_template.to_api()
)
)
return UpsertOutcome.CREATED
return UpsertOutcome.UPDATED

def get_export_template(self, export_template_id: str) -> CatalogExportTemplate:
"""Get an individual export template.
Expand Down
19 changes: 19 additions & 0 deletions packages/gooddata-sdk/src/gooddata_sdk/catalog/types.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
# (C) 2022 GoodData Corporation
from __future__ import annotations

from enum import Enum

# Use typing collection types to support python < py3.9
ValidObjects = dict[str, set[str]]


class UpsertOutcome(str, Enum):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: we would like to eventually switch this to StrEnum – we cannot right now because we have a support for Python 3.10 which does not support StrEnum.

Consider adding the following:

__str__ = str.__str__

It should make switch to StrEnum a noop.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in c618ee2 — added __str__ = str.__str__ on UpsertOutcome.

Verified it makes the eventual StrEnum swap a no-op: str(UpsertOutcome.CREATED) / f"{...}" / "%s" % / json.dumps all yield "created" instead of "UpsertOutcome.CREATED", while repr(), == "created" and UpsertOutcome("created") are unchanged. Pinned it in test_upsert_outcome.py::test_outcome_is_a_plain_string (str(outcome) == outcome.value + f-string), so a future base-class change that regresses this trips the test.

"""Which branch a ``create_or_update*`` method took.

The outcome is best-effort: it reports the branch the SDK chose after its
existence check, and that check is not atomic with the write that follows.
A concurrent actor can create or delete the entity in between, so treat the
value as informational rather than as an authoritative audit record.
"""

CREATED = "created"
UPDATED = "updated"

# Match StrEnum's str() (the value, not "UpsertOutcome.CREATED") so moving
# to StrEnum once py3.10 support is dropped is a no-op for callers.
__str__ = str.__str__
15 changes: 11 additions & 4 deletions packages/gooddata-sdk/src/gooddata_sdk/catalog/user/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from gooddata_api_client.model.json_api_api_token_in_document import JsonApiApiTokenInDocument

from gooddata_sdk.catalog.catalog_service_base import CatalogServiceBase
from gooddata_sdk.catalog.types import UpsertOutcome
from gooddata_sdk.catalog.user.declarative_model.user import CatalogDeclarativeUsers
from gooddata_sdk.catalog.user.declarative_model.user_and_user_groups import CatalogDeclarativeUsersUserGroups
from gooddata_sdk.catalog.user.declarative_model.user_group import CatalogDeclarativeUserGroups
Expand All @@ -25,7 +26,7 @@
class CatalogUserService(CatalogServiceBase):
# Entity methods for users

def create_or_update_user(self, user: CatalogUser) -> None:
def create_or_update_user(self, user: CatalogUser) -> UpsertOutcome:
"""Creates a new user or overwrites an existing user.


Expand All @@ -34,7 +35,8 @@ def create_or_update_user(self, user: CatalogUser) -> None:
User entity object.

Returns:
None
UpsertOutcome:
CREATED if the user did not exist yet, UPDATED if it did.
Comment on lines 37 to +39

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document that UpsertOutcome is best-effort.

The existence check and write are not atomic. Another actor can change the resource between these operations. State that the outcome reports the branch selected by the SDK. Do not describe it as an authoritative existence result.

  • packages/gooddata-sdk/src/gooddata_sdk/catalog/user/service.py#L37-L39: Add the best-effort qualification to create_or_update_user.
  • packages/gooddata-sdk/src/gooddata_sdk/catalog/user/service.py#L103-L105: Add the best-effort qualification to create_or_update_user_group.
  • packages/gooddata-sdk/src/gooddata_sdk/catalog/workspace/service.py#L71-L73: Add the best-effort qualification to create_or_update.
  • packages/gooddata-sdk/src/gooddata_sdk/catalog/workspace/service.py#L164-L170: Add the best-effort qualification to create_or_update_workspace_setting.
  • packages/gooddata-sdk/src/gooddata_sdk/catalog/workspace/service.py#L1234-L1240: Add the best-effort qualification to create_or_update_user_data_filter.
  • packages/gooddata-sdk/src/gooddata_sdk/catalog/workspace/service.py#L1444-L1450: Add the best-effort qualification to create_or_update_filter_view.
  • docs/content/en/latest/administration/organization/create_or_update_jwk.md#L21-L23: State that the JWK result is best-effort.
  • docs/content/en/latest/data/data-source/create_or_update_data_source.md#L23-L26: State that the data-source result is best-effort.
  • docs/content/en/latest/administration/user-groups/create_or_update_user_group.md#L22-L25: State that the user-group result is best-effort.
  • docs/content/en/latest/administration/users/create_or_update_user.md#L22-L25: State that the user result is best-effort.
  • docs/content/en/latest/workspace/workspaces/create_or_update.md#L18-L21: State that the workspace result is best-effort.
📍 Affects 7 files
  • packages/gooddata-sdk/src/gooddata_sdk/catalog/user/service.py#L37-L39 (this comment)
  • packages/gooddata-sdk/src/gooddata_sdk/catalog/user/service.py#L103-L105
  • packages/gooddata-sdk/src/gooddata_sdk/catalog/workspace/service.py#L71-L73
  • packages/gooddata-sdk/src/gooddata_sdk/catalog/workspace/service.py#L164-L170
  • packages/gooddata-sdk/src/gooddata_sdk/catalog/workspace/service.py#L1234-L1240
  • packages/gooddata-sdk/src/gooddata_sdk/catalog/workspace/service.py#L1444-L1450
  • docs/content/en/latest/administration/organization/create_or_update_jwk.md#L21-L23
  • docs/content/en/latest/data/data-source/create_or_update_data_source.md#L23-L26
  • docs/content/en/latest/administration/user-groups/create_or_update_user_group.md#L22-L25
  • docs/content/en/latest/administration/users/create_or_update_user.md#L22-L25
  • docs/content/en/latest/workspace/workspaces/create_or_update.md#L18-L21
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/gooddata-sdk/src/gooddata_sdk/catalog/user/service.py` around lines
37 - 39, Document that UpsertOutcome returned by create_or_update_user,
create_or_update_user_group, create_or_update,
create_or_update_workspace_setting, create_or_update_user_data_filter, and
create_or_update_filter_view is best-effort and reports the SDK-selected branch
rather than authoritative resource existence. Apply the same qualification in
packages/gooddata-sdk/src/gooddata_sdk/catalog/user/service.py lines 37-39 and
103-105; packages/gooddata-sdk/src/gooddata_sdk/catalog/workspace/service.py
lines 71-73, 164-170, 1234-1240, and 1444-1450. Update the result documentation
in docs/content/en/latest/administration/organization/create_or_update_jwk.md
lines 21-23,
docs/content/en/latest/data/data-source/create_or_update_data_source.md lines
23-26,
docs/content/en/latest/administration/user-groups/create_or_update_user_group.md
lines 22-25,
docs/content/en/latest/administration/users/create_or_update_user.md lines
22-25, and docs/content/en/latest/workspace/workspaces/create_or_update.md lines
18-21 to state that each result is best-effort.

"""
try:
self.get_user(user_id=user.id)
Expand All @@ -43,6 +45,8 @@ def create_or_update_user(self, user: CatalogUser) -> None:
except NotFoundException:
user_document = CatalogUserDocument(data=user)
self._entities_api.create_entity_users(json_api_user_in_document=user_document.to_api())
return UpsertOutcome.CREATED
return UpsertOutcome.UPDATED

def get_user(self, user_id: str) -> CatalogUser:
"""Get an individual user using User id.
Expand Down Expand Up @@ -89,15 +93,16 @@ def list_users(self) -> list[CatalogUser]:

# Entity methods for user groups

def create_or_update_user_group(self, user_group: CatalogUserGroup) -> None:
def create_or_update_user_group(self, user_group: CatalogUserGroup) -> UpsertOutcome:
"""Create a new user group or overwrite an existing user group.

Args:
user_group (CatalogUserGroup):
UserGroup entity object.

Returns:
None
UpsertOutcome:
CREATED if the user group did not exist yet, UPDATED if it did.
"""
try:
self.get_user_group(user_group_id=user_group.id)
Expand All @@ -108,6 +113,8 @@ def create_or_update_user_group(self, user_group: CatalogUserGroup) -> None:
except NotFoundException:
user_group_document = CatalogUserGroupDocument(data=user_group)
self._entities_api.create_entity_user_groups(user_group_document.to_api())
return UpsertOutcome.CREATED
return UpsertOutcome.UPDATED

def get_user_group(self, user_group_id: str) -> CatalogUserGroup:
"""Get an individual user group using user group id.
Expand Down
Loading
Loading