diff --git a/cognite_toolkit/_cdf_tk/client/_toolkit_client.py b/cognite_toolkit/_cdf_tk/client/_toolkit_client.py index 3a48781186..ce2736ed1e 100644 --- a/cognite_toolkit/_cdf_tk/client/_toolkit_client.py +++ b/cognite_toolkit/_cdf_tk/client/_toolkit_client.py @@ -23,6 +23,7 @@ from .api.extraction_pipelines import ExtractionPipelinesAPI from .api.filemetadata import FileMetadataAPI from .api.functions import FunctionsAPI +from .api.groups import GroupsAPI from .api.hosted_extractors import HostedExtractorsAPI from .api.infield import InfieldAPI from .api.instances import InstancesAPI @@ -61,6 +62,7 @@ def __init__(self, http_client: HTTPClient, console: Console) -> None: self.events = EventsAPI(http_client) self.extraction_pipelines = ExtractionPipelinesAPI(http_client) self.functions = FunctionsAPI(http_client) + self.groups = GroupsAPI(http_client) self.hosted_extractors = HostedExtractorsAPI(http_client) self.instances = InstancesAPI(http_client) self.spaces = SpacesAPI(http_client) diff --git a/cognite_toolkit/_cdf_tk/client/api/groups.py b/cognite_toolkit/_cdf_tk/client/api/groups.py index fb3a36797e..ab446d1ef8 100644 --- a/cognite_toolkit/_cdf_tk/client/api/groups.py +++ b/cognite_toolkit/_cdf_tk/client/api/groups.py @@ -4,7 +4,7 @@ https://api-docs.cognite.com/20230101/tag/Groups/operation/createGroups """ -from collections.abc import Iterable, Sequence +from collections.abc import Sequence from cognite_toolkit._cdf_tk.client.cdf_client import CDFResourceAPI, Endpoint, PagedResponse from cognite_toolkit._cdf_tk.client.http_client import ( @@ -65,55 +65,21 @@ def delete(self, items: Sequence[InternalId]) -> None: response = self._http_client.request_single_retries(request) response.get_success_or_raise() - def paginate( - self, - all_groups: bool = False, - limit: int = 100, - cursor: str | None = None, - ) -> PagedResponse[GroupResponse]: - """Get a page of groups from CDF. - - Args: - all_groups: Whether to return all groups (requires admin permissions). - limit: Maximum number of groups to return. - cursor: Cursor for pagination. - - Returns: - PagedResponse of GroupResponse objects. - """ - return self._paginate( - cursor=cursor, - limit=limit, - params={"all": all_groups} if all_groups else None, - ) - - def iterate( - self, - all_groups: bool = False, - limit: int | None = None, - ) -> Iterable[list[GroupResponse]]: - """Iterate over all groups in CDF. - - Args: - all_groups: Whether to return all groups (requires admin permissions). - limit: Maximum total number of groups to return. - - Returns: - Iterable of lists of GroupResponse objects. - """ - return self._iterate( - limit=limit, - params={"all": all_groups} if all_groups else None, - ) - - def list(self, all_groups: bool = False, limit: int | None = None) -> list[GroupResponse]: + def list(self, all_groups: bool = False) -> list[GroupResponse]: """List all groups in CDF. Args: all_groups: Whether to return all groups (requires admin permissions). - limit: Maximum total number of groups to return. Returns: List of GroupResponse objects. """ - return self._list(limit=limit, params={"all": all_groups} if all_groups else None) + endpoint = self._method_endpoint_map["list"] + response = self._http_client.request_single_retries( + RequestMessage( + endpoint_url=self._make_url(endpoint.path), + method=endpoint.method, + parameters={"all": all_groups}, + ) + ).get_success_or_raise() + return self._validate_page_response(response).items diff --git a/cognite_toolkit/_cdf_tk/client/resource_classes/group/acls.py b/cognite_toolkit/_cdf_tk/client/resource_classes/group/acls.py index 1f954b5d7f..c20e827cf1 100644 --- a/cognite_toolkit/_cdf_tk/client/resource_classes/group/acls.py +++ b/cognite_toolkit/_cdf_tk/client/resource_classes/group/acls.py @@ -7,7 +7,7 @@ from collections.abc import Sequence from typing import Annotated, Any, Literal, TypeAlias -from pydantic import BeforeValidator, Field, TypeAdapter, model_serializer, model_validator +from pydantic import BeforeValidator, Field, JsonValue, TypeAdapter, model_serializer, model_validator from pydantic_core.core_schema import FieldSerializationInfo from cognite_toolkit._cdf_tk.client._resource_base import BaseModelObject @@ -550,16 +550,16 @@ class SimulatorsAcl(Acl): """ACL for Simulators resources.""" acl_name: Literal["simulatorsAcl"] = Field("simulatorsAcl", exclude=True) - actions: Sequence[Literal["READ", "WRITE"]] + actions: Sequence[Literal["READ", "WRITE", "DELETE", "RUN", "MANAGE"]] scope: AllScope | DataSetScope -class UnknownAcl(Acl): +class UnknownAcl(BaseModelObject): """Fallback for unknown ACL types.""" - acl_name: Literal["unknownAcl"] = Field("unknownAcl", exclude=True) + acl_name: str = Field("unknownAcl", exclude=True) actions: Sequence[str] - scope: AllScope + scope: dict[str, JsonValue] def _get_acl_name(cls: type[Acl]) -> str | None: @@ -578,7 +578,9 @@ def _get_acl_name(cls: type[Acl]) -> str | None: def _handle_unknown_acl(value: Any) -> Any: - if isinstance(value, dict) and isinstance(acl_name := value[ACL_NAME], str): + if isinstance(value, Acl | UnknownAcl): + return value + if isinstance(value, dict) and isinstance(acl_name := value.get(ACL_NAME), str): acl_class = _KNOWN_ACLS.get(acl_name) if acl_class: return TypeAdapter(acl_class).validate_python(value) diff --git a/cognite_toolkit/_cdf_tk/client/resource_classes/group/group.py b/cognite_toolkit/_cdf_tk/client/resource_classes/group/group.py index bc1604f051..fa3dd8da18 100644 --- a/cognite_toolkit/_cdf_tk/client/resource_classes/group/group.py +++ b/cognite_toolkit/_cdf_tk/client/resource_classes/group/group.py @@ -37,7 +37,7 @@ class Group(BaseModelObject): capabilities: list[GroupCapability] | None = None metadata: dict[str, str] | None = None attributes: GroupAttributes | None = None - source_id: str | None = None + source_id: str | None = Field(None, coerce_numbers_to_str=True) members: list[str] | Literal["allUserAccounts"] | None = None diff --git a/cognite_toolkit/_cdf_tk/client/resource_classes/group/scopes.py b/cognite_toolkit/_cdf_tk/client/resource_classes/group/scopes.py index 7ebadd4d7a..4378e24c84 100644 --- a/cognite_toolkit/_cdf_tk/client/resource_classes/group/scopes.py +++ b/cognite_toolkit/_cdf_tk/client/resource_classes/group/scopes.py @@ -6,7 +6,7 @@ from typing import Annotated, Any, Literal, TypeAlias -from pydantic import BeforeValidator, Field, TypeAdapter +from pydantic import BeforeValidator, Field, TypeAdapter, field_validator from cognite_toolkit._cdf_tk.client._resource_base import BaseModelObject from cognite_toolkit._cdf_tk.client.resource_classes.group._constants import SCOPE_NAME @@ -72,6 +72,28 @@ class TableScope(ScopeDefinition): scope_name: Literal["tableScope"] = Field("tableScope", exclude=True) dbs_to_tables: dict[str, list[str]] + @field_validator("dbs_to_tables", mode="before") + @classmethod + def standardize_format(cls, value: Any) -> dict[str, list[str]]: + """The API returns the dbsToTables field in a different format + than the documentation specifies. This validator standardizes the format to match the documentation.""" + if not isinstance(value, dict): + # Let pydantic handle the type error + return value + standardized: dict[str, list[str]] = {} + for db, tables in value.items(): + if isinstance(tables, list): + standardized[db] = tables + elif isinstance(tables, dict) and "tables" in tables and isinstance(tables["tables"], list): + standardized[db] = tables["tables"] + elif tables == {}: + standardized[db] = [] + else: + raise ValueError( + f"Invalid format for dbsToTables: expected dict[str, list[str]] or dict[str, dict[tables: list[str]]], got {type(tables).__name__} for db '{db}'" + ) + return standardized + class ExtractionPipelineScope(ScopeDefinition): """Scope limited to specific extraction pipelines.""" diff --git a/cognite_toolkit/_cdf_tk/client/testing.py b/cognite_toolkit/_cdf_tk/client/testing.py index eca956240b..70eed65997 100644 --- a/cognite_toolkit/_cdf_tk/client/testing.py +++ b/cognite_toolkit/_cdf_tk/client/testing.py @@ -48,6 +48,7 @@ from .api.filemetadata import FileMetadataAPI from .api.function_schedules import FunctionSchedulesAPI from .api.functions import FunctionsAPI +from .api.groups import GroupsAPI from .api.hosted_extractor_destinations import HostedExtractorDestinationsAPI from .api.hosted_extractor_jobs import HostedExtractorJobsAPI from .api.hosted_extractor_mappings import HostedExtractorMappingsAPI @@ -182,6 +183,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: self.tool.events = MagicMock(spec_set=EventsAPI) self.tool.functions = MagicMock(spec=FunctionsAPI) self.tool.functions.schedules = MagicMock(spec_set=FunctionSchedulesAPI) + self.tool.groups = MagicMock(spec_set=GroupsAPI) self.tool.search_configurations = MagicMock(spec_set=SearchConfigurationsAPI) self.tool.simulators = MagicMock(spec=SimulatorsAPI) self.tool.simulators.models = MagicMock(spec_set=SimulatorModelsAPI) diff --git a/cognite_toolkit/_cdf_tk/commands/dump_resource.py b/cognite_toolkit/_cdf_tk/commands/dump_resource.py index 3a256ba74f..c8ad8a7ae5 100644 --- a/cognite_toolkit/_cdf_tk/commands/dump_resource.py +++ b/cognite_toolkit/_cdf_tk/commands/dump_resource.py @@ -12,8 +12,6 @@ import typer from cognite.client import data_modeling as dm from cognite.client.data_classes import ( - Group, - GroupList, filters, ) from cognite.client.data_classes.data_modeling import ViewId @@ -46,8 +44,10 @@ from cognite_toolkit._cdf_tk.client.resource_classes.dataset import DataSetResponse from cognite_toolkit._cdf_tk.client.resource_classes.extraction_pipeline import ExtractionPipelineResponse from cognite_toolkit._cdf_tk.client.resource_classes.function import FunctionResponse +from cognite_toolkit._cdf_tk.client.resource_classes.group import GroupResponse from cognite_toolkit._cdf_tk.client.resource_classes.identifiers import ( ExternalId, + NameId, WorkflowVersionId, ) from cognite_toolkit._cdf_tk.client.resource_classes.instance_api import TypedViewReference @@ -366,16 +366,16 @@ def __iter__( class GroupFinder(ResourceFinder[tuple[str, ...]]): def __init__(self, client: ToolkitClient, identifier: tuple[str, ...] | None = None): super().__init__(client, identifier) - self.groups: list[Group] | None = None + self.groups: list[GroupResponse] | None = None def _interactive_select(self) -> tuple[str, ...]: - groups = self.client.iam.groups.list(all=True) + groups = self.client.tool.groups.list(all_groups=True) if not groups: raise ToolkitMissingResourceError("No groups found") - groups_by_name: dict[str, list[Group]] = defaultdict(list) + groups_by_name: dict[str, list[GroupResponse]] = defaultdict(list) for group in groups: groups_by_name[group.name].append(group) - selected_groups: list[list[Group]] | None = questionary.checkbox( + selected_groups: list[list[GroupResponse]] | None = questionary.checkbox( "Which group(s) would you like to dump?", choices=[ Choice(f"{group_name} ({len(group_list)} group{'s' if len(group_list) > 1 else ''})", value=group_list) @@ -393,9 +393,14 @@ def __iter__( ) -> Iterator[tuple[Sequence[Hashable], Sequence[ResourceResponseProtocol] | None, ResourceCRUD, None | str]]: self.identifier = self._selected() if self.groups: - yield [], GroupList(self.groups), GroupCRUD.create_loader(self.client), None + yield ( + [], + [group for group in self.groups if group.name in self.identifier], + GroupCRUD.create_loader(self.client), + None, + ) else: - yield list(self.identifier), None, GroupCRUD.create_loader(self.client), None + yield [NameId(name=name) for name in self.identifier], None, GroupCRUD.create_loader(self.client), None class AgentFinder(ResourceFinder[tuple[str, ...]]): diff --git a/cognite_toolkit/_cdf_tk/cruds/_resource_cruds/auth.py b/cognite_toolkit/_cdf_tk/cruds/_resource_cruds/auth.py index 67b834731b..962c3e7e9d 100644 --- a/cognite_toolkit/_cdf_tk/cruds/_resource_cruds/auth.py +++ b/cognite_toolkit/_cdf_tk/cruds/_resource_cruds/auth.py @@ -13,7 +13,6 @@ # limitations under the License. -import difflib from collections import defaultdict from collections.abc import Callable, Hashable, Iterable, Sequence from dataclasses import dataclass @@ -26,21 +25,21 @@ GroupsAcl, SecurityCategoriesAcl, ) -from cognite.client.data_classes.iam import ( - Group, - GroupList, - GroupWrite, -) -from cognite.client.exceptions import CogniteAPIError, CogniteNotFoundError from cognite.client.utils.useful_types import SequenceNotStr from rich import print from rich.console import Console from rich.markup import escape from cognite_toolkit._cdf_tk.client import ToolkitClient +from cognite_toolkit._cdf_tk.client.http_client import ToolkitAPIError from cognite_toolkit._cdf_tk.client.resource_classes.data_modeling import SpaceReference +from cognite_toolkit._cdf_tk.client.resource_classes.group import ( + GroupRequest, + GroupResponse, +) from cognite_toolkit._cdf_tk.client.resource_classes.identifiers import ( ExternalId, + InternalId, InternalIdUnwrapped, NameId, RawDatabaseId, @@ -59,6 +58,7 @@ ) from cognite_toolkit._cdf_tk.utils import humanize_collection from cognite_toolkit._cdf_tk.utils.diff_list import diff_list_hashable, diff_list_identifiable, hash_dict +from cognite_toolkit._cdf_tk.utils.file import sanitize_filename @dataclass @@ -71,11 +71,11 @@ class _ReplaceMethod: id_name: str -class GroupCRUD(ResourceCRUD[str, GroupWrite, Group]): +class GroupCRUD(ResourceCRUD[NameId, GroupRequest, GroupResponse]): folder_name = "auth" kind = "Group" - resource_cls = Group - resource_write_cls = GroupWrite + resource_cls = GroupResponse + resource_write_cls = GroupRequest yaml_cls = GroupYAML resource_scopes = frozenset( { @@ -110,7 +110,7 @@ def display_name(self) -> str: @classmethod def get_required_capability( - cls, items: Sequence[GroupWrite] | None, read_only: bool + cls, items: Sequence[GroupRequest] | None, read_only: bool ) -> Capability | list[Capability]: if not items and items is not None: return [] @@ -136,14 +136,18 @@ def get_required_capability( ) @classmethod - def get_id(cls, item: GroupWrite | Group | dict) -> str: + def get_id(cls, item: GroupRequest | GroupResponse | dict) -> NameId: if isinstance(item, dict): - return item["name"] - return item.name + return NameId(name=item["name"]) + return NameId(name=item.name) @classmethod - def dump_id(cls, id: str) -> dict[str, Any]: - return {"name": id} + def dump_id(cls, id: NameId) -> dict[str, Any]: + return id.dump() + + @classmethod + def as_str(cls, id: NameId) -> str: + return sanitize_filename(id.name) @classmethod def get_dependent_items(cls, item: dict) -> Iterable[tuple[type[ResourceCRUD], Hashable]]: @@ -287,7 +291,7 @@ def _create_replace_method_by_acl_and_scope(self) -> dict[tuple[str, str] | str, for key, method in source.items() } - def load_resource(self, resource: dict[str, Any], is_dry_run: bool = False) -> GroupWrite: + def load_resource(self, resource: dict[str, Any], is_dry_run: bool = False) -> GroupRequest: is_resource_scoped = any( any(scope_name in capability.get(acl, {}).get("scope", {}) for scope_name in self.resource_scope_names) for capability in resource.get("capabilities", []) @@ -301,26 +305,10 @@ def load_resource(self, resource: dict[str, Any], is_dry_run: bool = False) -> G raise ToolkitWrongResourceError() substituted = self._substitute_scope_ids(resource, is_dry_run) - try: - loaded = GroupWrite.load(substituted) - except ValueError: - # The GroupWrite class in the SDK will raise a ValueError if the ACI or scope is not valid or unknown. - loaded = GroupWrite._load(substituted, allow_unknown=True) - for capability in loaded.capabilities or []: - if isinstance(capability, cap.UnknownAcl): - msg = ( - f"In group {loaded.name!r}, unknown capability found: {capability.capability_name!r}.\n" - "Will proceed with group creation and let the API validate the capability." - ) - if matches := difflib.get_close_matches(capability.capability_name, cap.ALL_CAPABILITIES): - msg += f"\nIf the API rejects the capability, could it be that you meant on of: {matches}?" - prefix, warning_msg = MediumSeverityWarning(msg).print_prepare() - print(prefix, warning_msg) - - return loaded + return GroupRequest._load(substituted) - def dump_resource(self, resource: Group, local: dict[str, Any] | None = None) -> dict[str, Any]: - dumped = resource.as_write().dump() + def dump_resource(self, resource: GroupResponse, local: dict[str, Any] | None = None) -> dict[str, Any]: + dumped = resource.as_request_resource().dump() local = local or {} if not dumped.get("metadata") and "metadata" not in local: dumped.pop("metadata", None) @@ -355,56 +343,57 @@ def dump_resource(self, resource: Group, local: dict[str, Any] | None = None) -> # When you dump a CDF Group, all the referenced resources should be available in CDF. return self._substitute_scope_ids(dumped, is_dry_run=False, reverse=True) - def create(self, items: Sequence[GroupWrite]) -> GroupList: + def create(self, items: Sequence[GroupRequest]) -> list[GroupResponse]: if len(items) == 0: - return GroupList([]) + return [] return self._create_with_fallback(items, action="create") - def update(self, items: Sequence[GroupWrite]) -> GroupList: + def update(self, items: Sequence[GroupRequest]) -> list[GroupResponse]: # We MUST retrieve all the old groups BEFORE we add the new, if not the new will be deleted - old_groups = self.client.iam.groups.list(all=True) + old_groups = self.client.tool.groups.list(all_groups=True) created = self._create_with_fallback(items, action="update") created_names = {g.name for g in created} - to_delete = GroupList([group for group in old_groups if group.name in created_names]) + to_delete = [group for group in old_groups if group.name in created_names] if to_delete: self._delete(to_delete, check_own_principal=False) return created - def _create_with_fallback(self, items: Sequence[GroupWrite], action: Literal["create", "update"]) -> GroupList: + def _create_with_fallback( + self, items: Sequence[GroupRequest], action: Literal["create", "update"] + ) -> list[GroupResponse]: try: - return self.client.iam.groups.create(items) - except CogniteAPIError as e: + return self.client.tool.groups.create(items) + except ToolkitAPIError as e: if not (e.code == 400 and "buffer" in e.message.lower() and len(items) > 1): raise e # Fallback to create one by one - created_list = GroupList([]) + created_list: list[GroupResponse] = [] for item in items: try: - created = self.client.iam.groups.create(item) - except CogniteAPIError as e: + created = self.client.tool.groups.create([item]) + except ToolkitAPIError as e: HighSeverityWarning(f"Failed to {action} group {item.name}. Error: {escape(str(e))}").print_warning( include_timestamp=True, console=self.console ) else: - created_list.append(created) + created_list.extend(created) return created_list - def retrieve(self, ids: SequenceNotStr[str]) -> GroupList: - id_set = set(ids) - remote = self.client.iam.groups.list(all=True) - found = [g for g in remote if g.name in id_set] - return GroupList(found) + def retrieve(self, ids: SequenceNotStr[NameId]) -> list[GroupResponse]: + names = {id.name for id in ids} + remote = self.client.tool.groups.list(all_groups=True) + return [g for g in remote if g.name in names] - def delete(self, ids: SequenceNotStr[str]) -> int: + def delete(self, ids: SequenceNotStr[NameId]) -> int: return self._delete(self.retrieve(ids), check_own_principal=True) - def _delete(self, delete_candidates: GroupList, check_own_principal: bool = True) -> int: + def _delete(self, delete_candidates: list[GroupResponse], check_own_principal: bool = True) -> int: if check_own_principal: print_fun = self.console.print if self.console else print try: # Let's prevent that we delete groups we belong to - my_groups = self.client.iam.groups.list() - except CogniteAPIError as e: + my_groups = self.client.tool.groups.list() + except ToolkitAPIError as e: print_fun( f"[bold red]ERROR:[/] Failed to retrieve the current service principal's groups. Aborting group deletion.\n{e}" ) @@ -432,21 +421,22 @@ def _delete(self, delete_candidates: GroupList, check_own_principal: bool = True failed_deletes = [] error_str = "" try: - self.client.iam.groups.delete(to_delete) - except CogniteNotFoundError: - # Fallback to delete one by one - for delete_item_id in to_delete: - try: - self.client.iam.groups.delete(delete_item_id) - except CogniteNotFoundError: - # If the group is already deleted, we can ignore the error - ... - except CogniteAPIError as e: - error_str = str(e) - failed_deletes.append(delete_item_id) - except CogniteAPIError as e: - error_str = str(e) - failed_deletes.extend(to_delete) + self.client.tool.groups.delete([InternalId(id=id_) for id_ in to_delete]) + except ToolkitAPIError as e: + if e.missing: + # Fallback to delete one by one + for delete_item_id in to_delete: + try: + self.client.tool.groups.delete([InternalId(id=delete_item_id)]) + except ToolkitAPIError as inner_e: + if inner_e.missing: + # If the error is that the group is already deleted, we can ignore it. + continue + error_str = str(inner_e) + failed_deletes.append(delete_item_id) + else: + error_str = str(e) + failed_deletes.extend(to_delete) if failed_deletes: MediumSeverityWarning( f"Failed to delete groups: {humanize_collection(to_delete)}. " @@ -460,8 +450,8 @@ def _iterate( data_set_external_id: str | None = None, space: str | None = None, parent_ids: Sequence[Hashable] | None = None, - ) -> Iterable[Group]: - return self.client.iam.groups.list(all=True) + ) -> Iterable[GroupResponse]: + yield from self.client.tool.groups.list(all_groups=True) def diff_list( self, local: list[Any], cdf: list[Any], json_path: tuple[str | int, ...] diff --git a/cognite_toolkit/_cdf_tk/cruds/_resource_cruds/fieldops.py b/cognite_toolkit/_cdf_tk/cruds/_resource_cruds/fieldops.py index 67cf51cfed..aa807683f9 100644 --- a/cognite_toolkit/_cdf_tk/cruds/_resource_cruds/fieldops.py +++ b/cognite_toolkit/_cdf_tk/cruds/_resource_cruds/fieldops.py @@ -13,7 +13,7 @@ ) from cognite_toolkit._cdf_tk.client.resource_classes.data_modeling import SpaceReference from cognite_toolkit._cdf_tk.client.resource_classes.data_modeling._instance import InstanceSlimDefinition -from cognite_toolkit._cdf_tk.client.resource_classes.identifiers import ExternalId +from cognite_toolkit._cdf_tk.client.resource_classes.identifiers import ExternalId, NameId from cognite_toolkit._cdf_tk.client.resource_classes.infield import ( InFieldCDMLocationConfigRequest, InFieldCDMLocationConfigResponse, @@ -134,7 +134,7 @@ def get_dependent_items(cls, item: dict) -> Iterable[tuple[type[ResourceCRUD], H for key in cls._group_keys: for group in config.get(key, []): if isinstance(group, str): - yield GroupResourceScopedCRUD, group + yield GroupResourceScopedCRUD, NameId(name=group) data_filters = config.get("dataFilters") if not isinstance(data_filters, dict): continue diff --git a/tests/test_integration/test_cruds/test_resource_cruds.py b/tests/test_integration/test_cruds/test_resource_cruds.py index 992eff12b4..e71e3497ff 100644 --- a/tests/test_integration/test_cruds/test_resource_cruds.py +++ b/tests/test_integration/test_cruds/test_resource_cruds.py @@ -19,13 +19,11 @@ FunctionSchedulesList, FunctionScheduleWrite, FunctionScheduleWriteList, - GroupWrite, TimeSeriesList, TimeSeriesWrite, TimeSeriesWriteList, filters, ) -from cognite.client.data_classes.capabilities import IDScopeLowerCase, TimeSeriesAcl from cognite.client.data_classes.data_modeling import NodeApplyList, NodeList, Space from cognite.client.data_classes.data_modeling.cdm.v1 import CogniteFileApply, CogniteTimeSeries, CogniteTimeSeriesApply from cognite.client.data_classes.datapoints_subscriptions import ( @@ -45,6 +43,12 @@ ViewRequest, ) from cognite_toolkit._cdf_tk.client.resource_classes.function import FunctionRequest, FunctionResponse +from cognite_toolkit._cdf_tk.client.resource_classes.group import ( + GroupCapability, + GroupRequest, + IDScopeLowerCase, + TimeSeriesAcl, +) from cognite_toolkit._cdf_tk.client.resource_classes.identifiers import ExternalId from cognite_toolkit._cdf_tk.client.resource_classes.instance_api import TypedViewReference from cognite_toolkit._cdf_tk.client.resource_classes.label import LabelRequest @@ -57,6 +61,7 @@ RobotCapabilityResponse, RobotDataPostProcessingRequest, ) +from cognite_toolkit._cdf_tk.client.resource_classes.timeseries import TimeSeriesRequest from cognite_toolkit._cdf_tk.client.resource_classes.transformation import TransformationRequest from cognite_toolkit._cdf_tk.client.resource_classes.workflow_version import ( FunctionTaskParameters, @@ -775,7 +780,7 @@ def test_create_update_retrieve_delete_extension( class TestGroupLoader: def test_dump_cdf_group_with_invalid_reference(self, toolkit_client: ToolkitClient) -> None: - to_delete = TimeSeriesWrite( + to_delete = TimeSeriesRequest( external_id="test_dump_cdf_group_with_invalid_reference", name="Test dump CDF group with invalid reference", is_step=False, @@ -783,17 +788,19 @@ def test_dump_cdf_group_with_invalid_reference(self, toolkit_client: ToolkitClie ) group_id: int | None = None try: - created_ts = toolkit_client.time_series.create(to_delete) - group = GroupWrite( + created_ts = toolkit_client.tool.timeseries.create([to_delete])[0] + group = GroupRequest( name="test_dump_cdf_group_with_invalid_reference", source_id="1234-dummy", capabilities=[ - TimeSeriesAcl(actions=[TimeSeriesAcl.Action.Read], scope=IDScopeLowerCase([created_ts.id])) + GroupCapability(acl=TimeSeriesAcl(actions=["READ"], scope=IDScopeLowerCase(ids=[created_ts.id]))) ], ) - created_group = toolkit_client.iam.groups.create(group) - group_id = created_group.id - toolkit_client.time_series.delete(id=created_ts.id) + created_group_list = toolkit_client.tool.groups.create([group]) + assert len(created_group_list) == 1 + created_group = created_group_list[0] + group_id = created_group.as_request_resource().as_id() + toolkit_client.tool.timeseries.delete([to_delete.as_id()]) loader = GroupCRUD.create_loader(toolkit_client) @@ -804,10 +811,10 @@ def test_dump_cdf_group_with_invalid_reference(self, toolkit_client: ToolkitClie assert len(capabilities) == 1 assert capabilities[0] == {"timeSeriesAcl": {"actions": ["READ"], "scope": {"idscope": {"ids": []}}}} finally: - toolkit_client.time_series.delete(external_id=to_delete.external_id, ignore_unknown_ids=True) + toolkit_client.tool.timeseries.delete([to_delete.as_id()], ignore_unknown_ids=True) if group_id: with suppress(CogniteAPIError): - toolkit_client.iam.groups.delete(id=group_id) + toolkit_client.tool.groups.delete([group_id]) class TestWorkflowVersionLoader: diff --git a/tests/test_unit/approval_client/config.py b/tests/test_unit/approval_client/config.py index cd4f349e42..a3a9303b8c 100644 --- a/tests/test_unit/approval_client/config.py +++ b/tests/test_unit/approval_client/config.py @@ -155,6 +155,7 @@ FunctionScheduleRequest, FunctionScheduleResponse, ) +from cognite_toolkit._cdf_tk.client.resource_classes.group import GroupRequest, GroupResponse from cognite_toolkit._cdf_tk.client.resource_classes.hosted_extractor_destination import ( HostedExtractorDestinationRequest, HostedExtractorDestinationResponse, @@ -849,6 +850,18 @@ ], }, ), + APIResource( + api_name="tool.groups", + resource_cls=GroupResponse, + _write_cls=GroupRequest, + methods={ + "create": [Method(api_class_method="create", mock_class_method="create")], + "retrieve": [ + Method(api_class_method="list", mock_class_method="list"), + ], + "delete": [Method(api_class_method="delete", mock_class_method="delete_id_external_id")], + }, + ), APIResource( api_name="tool.labels", resource_cls=LabelResponse, diff --git a/tests/test_unit/test_cdf_tk/test_client/test_api_data_classes.py b/tests/test_unit/test_cdf_tk/test_client/test_api_data_classes.py index a1efdf3933..7237628102 100644 --- a/tests/test_unit/test_cdf_tk/test_client/test_api_data_classes.py +++ b/tests/test_unit/test_cdf_tk/test_client/test_api_data_classes.py @@ -5,6 +5,7 @@ from cognite_toolkit._cdf_tk.client._resource_base import UpdatableRequestResource from cognite_toolkit._cdf_tk.client.resource_classes.agent import AgentRequest from cognite_toolkit._cdf_tk.client.resource_classes.asset import AssetRequest +from cognite_toolkit._cdf_tk.client.resource_classes.group import GroupResponse from tests.test_unit.test_cdf_tk.test_client.data import CDFResource, iterate_cdf_resources @@ -152,3 +153,21 @@ def test_allow_unknown_tool(self) -> None: } agent_request = AgentRequest.model_validate(data) assert agent_request.dump() == data + + +class TestGroupResponse: + def test_load_table_scope(self) -> None: + data = { + "name": "Group 1", + "capabilities": [ + { + "rawAcl": { + "actions": ["READ"], + "scope": {"tableScope": {"dbsToTables": {"contextualizationState": {}, "ingestion": {}}}}, + } + } + ], + "id": 37, + } + + GroupResponse.model_validate(data) diff --git a/tests/test_unit/test_cdf_tk/test_client/test_cdf_apis.py b/tests/test_unit/test_cdf_tk/test_client/test_cdf_apis.py index d2426ed90e..da642de8b6 100644 --- a/tests/test_unit/test_cdf_tk/test_client/test_cdf_apis.py +++ b/tests/test_unit/test_cdf_tk/test_client/test_cdf_apis.py @@ -107,7 +107,10 @@ def test_create_retrieve_delete_iterate_list( if hasattr(api, "list"): self._mock_endpoint(api, "list", {"items": [resource.example_data]}, respx_mock) - listed = api.list(limit=10) + try: + listed = api.list(limit=10) + except TypeError: + listed = api.list() # Some APIs do not support limit parameter assert len(listed) >= 1 assert listed[0].dump() == resource.example_data if hasattr(api, "paginate"): diff --git a/tests/test_unit/test_cdf_tk/test_commands/test_dump_resource.py b/tests/test_unit/test_cdf_tk/test_commands/test_dump_resource.py index 1c2001312e..e1a153a1d8 100644 --- a/tests/test_unit/test_cdf_tk/test_commands/test_dump_resource.py +++ b/tests/test_unit/test_cdf_tk/test_commands/test_dump_resource.py @@ -8,13 +8,8 @@ from cognite.client import data_modeling as dm from cognite.client.data_classes import ( FileMetadataList, - Group, - GroupList, ) from cognite.client.data_classes.aggregations import UniqueResult, UniqueResultList -from cognite.client.data_classes.capabilities import ( - TimeSeriesAcl, -) from cognite.client.data_classes.data_modeling.statistics import SpaceStatistics from cognite.client.data_classes.functions import FunctionsStatus from cognite.client.exceptions import CogniteAPIError @@ -31,6 +26,7 @@ from cognite_toolkit._cdf_tk.client.resource_classes.dataset import DataSetResponse from cognite_toolkit._cdf_tk.client.resource_classes.extraction_pipeline import ExtractionPipelineResponse from cognite_toolkit._cdf_tk.client.resource_classes.function import FunctionResponse +from cognite_toolkit._cdf_tk.client.resource_classes.group import GroupResponse from cognite_toolkit._cdf_tk.client.resource_classes.legacy.streamlit_ import Streamlit, StreamlitList from cognite_toolkit._cdf_tk.client.resource_classes.location_filter import LocationFilterResponse from cognite_toolkit._cdf_tk.client.resource_classes.resource_view_mapping import ResourceViewMappingResponse @@ -448,33 +444,32 @@ def test_dump_extraction_pipelines(self, tmp_path: Path) -> None: @pytest.fixture() -def three_groups() -> GroupList: - return GroupList( - [ - Group( - "Group A", - source_id="123", - capabilities=[TimeSeriesAcl([TimeSeriesAcl.Action.Read], TimeSeriesAcl.Scope.All())], - ), - Group( - "Group B", - source_id="456", - capabilities=[TimeSeriesAcl([TimeSeriesAcl.Action.Write], TimeSeriesAcl.Scope.All())], - ), - Group( - "Group C", - source_id="789", - capabilities=[ - TimeSeriesAcl([TimeSeriesAcl.Action.Read, TimeSeriesAcl.Action.Write], TimeSeriesAcl.Scope.All()) - ], - ), - ] - ) +def three_groups() -> list[GroupResponse]: + return [ + GroupResponse( + id=1, + name="Group A", + source_id="123", + capabilities=[{"timeSeriesAcl": {"actions": ["READ"], "scope": {"all": {}}}}], + ), + GroupResponse( + id=2, + name="Group B", + source_id="456", + capabilities=[{"timeSeriesAcl": {"actions": ["WRITE"], "scope": {"all": {}}}}], + ), + GroupResponse( + id=3, + name="Group C", + source_id="789", + capabilities=[{"timeSeriesAcl": {"actions": ["READ", "WRITE"], "scope": {"all": {}}}}], + ), + ] class TestGroupFinder: - def test_select_groups(self, three_groups: GroupList, monkeypatch: MonkeyPatch) -> None: - def select_groups(choices: list[Choice]) -> list[list[Group]]: + def test_select_groups(self, three_groups: list[GroupResponse], monkeypatch: MonkeyPatch) -> None: + def select_groups(choices: list[Choice]) -> list[list[GroupResponse]]: assert len(choices) == len(three_groups) return [choices[1].value, choices[2].value] @@ -484,7 +479,7 @@ def select_groups(choices: list[Choice]) -> list[list[Group]]: monkeypatch_toolkit_client() as client, MockQuestionary(GroupFinder.__module__, monkeypatch, answers), ): - client.iam.groups.list.return_value = three_groups + client.tool.groups.list.return_value = three_groups finder = GroupFinder(client, None) selected = finder._interactive_select() @@ -492,9 +487,9 @@ def select_groups(choices: list[Choice]) -> list[list[Group]]: class TestDumpGroups: - def test_dump_groups(self, three_groups: GroupList, tmp_path: Path) -> None: + def test_dump_groups(self, three_groups: list[GroupResponse], tmp_path: Path) -> None: with monkeypatch_toolkit_client() as client: - client.iam.groups.list.return_value = three_groups + client.tool.groups.list.return_value = three_groups cmd = DumpResourceCommand(silent=True) cmd.dump_to_yamls( diff --git a/tests/test_unit/test_cdf_tk/test_cruds/test_fieldops.py b/tests/test_unit/test_cdf_tk/test_cruds/test_fieldops.py index 002b082b5c..301035e846 100644 --- a/tests/test_unit/test_cdf_tk/test_cruds/test_fieldops.py +++ b/tests/test_unit/test_cdf_tk/test_cruds/test_fieldops.py @@ -8,7 +8,7 @@ RootLocationDataFilters, ) from cognite_toolkit._cdf_tk.client.resource_classes.data_modeling import SpaceReference -from cognite_toolkit._cdf_tk.client.resource_classes.identifiers import ExternalId +from cognite_toolkit._cdf_tk.client.resource_classes.identifiers import ExternalId, NameId from cognite_toolkit._cdf_tk.cruds import ( AssetCRUD, DataSetsCRUD, @@ -58,9 +58,9 @@ def test_dependent_items(self) -> None: (SpaceCRUD.__name__, SpaceReference(space="my_app_data_space")), (SpaceCRUD.__name__, SpaceReference(space="my_customer_data_space")), (SpaceCRUD.__name__, SpaceReference(space="my_source_data_space")), - (GroupResourceScopedCRUD.__name__, "my_admin_group1"), - (GroupResourceScopedCRUD.__name__, "my_admin_group2"), - (GroupResourceScopedCRUD.__name__, "my_admin_group3"), + (GroupResourceScopedCRUD.__name__, NameId(name="my_admin_group1")), + (GroupResourceScopedCRUD.__name__, NameId(name="my_admin_group2")), + (GroupResourceScopedCRUD.__name__, NameId(name="my_admin_group3")), (DataSetsCRUD.__name__, ExternalId(external_id="my_other_dataset")), (AssetCRUD.__name__, ExternalId(external_id="my_asset_subtree")), (SpaceCRUD.__name__, SpaceReference(space="my_source_data_space")), diff --git a/tests/test_unit/test_cdf_tk/test_cruds/test_group.py b/tests/test_unit/test_cdf_tk/test_cruds/test_group.py index 49b807e829..9136832c88 100644 --- a/tests/test_unit/test_cdf_tk/test_cruds/test_group.py +++ b/tests/test_unit/test_cdf_tk/test_cruds/test_group.py @@ -4,9 +4,15 @@ import pytest from _pytest.monkeypatch import MonkeyPatch -from cognite.client.data_classes import Group, GroupWrite from cognite_toolkit._cdf_tk.client.resource_classes.data_modeling import SpaceReference +from cognite_toolkit._cdf_tk.client.resource_classes.group import ( + AllScope, + AssetsAcl, + GroupCapability, + GroupRequest, + GroupResponse, +) from cognite_toolkit._cdf_tk.client.resource_classes.identifiers import ExternalId, RawDatabaseId, RawTableId from cognite_toolkit._cdf_tk.cruds import ( DataSetsCRUD, @@ -52,12 +58,12 @@ def test_load_resource_scoped_only(self, env_vars_with_client: EnvironmentVariab assert loaded.name == "scoped_group_name" assert len(loaded.capabilities) == 4 - caps = {str(type(element).__name__): element for element in loaded.capabilities} + caps = {type(cap.acl).__name__: cap.acl for cap in loaded.capabilities} assert all(isinstance(item, int) for item in caps["DataSetsAcl"].scope.ids) assert all(isinstance(item, int) for item in caps["AssetsAcl"].scope.ids) assert all(isinstance(item, int) for item in caps["ExtractionConfigsAcl"].scope.ids) - assert caps["SessionsAcl"].scope._scope_name == "all" + assert caps["SessionsAcl"].scope.scope_name == "all" def test_load_group_list_resource_scoped_only( self, env_vars_with_client: EnvironmentVariables, monkeypatch: MonkeyPatch @@ -68,7 +74,7 @@ def test_load_group_list_resource_scoped_only( ) loaded = loader.load_resource(raw_list[0], is_dry_run=False) - assert isinstance(loaded, GroupWrite) + assert isinstance(loaded, GroupRequest) assert loaded.name == "scoped_group_name" def test_load_group_list_all_scoped_only( @@ -80,7 +86,7 @@ def test_load_group_list_all_scoped_only( ) loaded = loader.load_resource(raw_list[1], is_dry_run=False) - assert isinstance(loaded, GroupWrite) + assert isinstance(loaded, GroupRequest) assert loaded.name == "unscoped_group_name" def test_unchanged_new_group( @@ -93,25 +99,20 @@ def test_unchanged_new_group( raw_list = loader.load_resource_file(LOAD_DATA / "auth" / "1.my_group_scoped.yaml", env_vars_with_client.dump()) loaded = loader.load_resource(raw_list[0], is_dry_run=False) - # Simulate that one group is is already in CDF - toolkit_client_approval.append( - Group, - [ - Group( - id=123, - name=loaded.name, - source_id=loaded.source_id, - capabilities=loaded.capabilities, - metadata=loaded.metadata, - is_deleted=False, - ) - ], + # Simulate that one group is already in CDF + cdf_group = GroupResponse( + id=123, + name=loaded.name, + source_id=loaded.source_id, + capabilities=loaded.capabilities, + metadata=loaded.metadata, + is_deleted=False, ) + toolkit_client_approval.append(GroupResponse, [cdf_group]) + new_group = GroupRequest(name="new_group", source_id="123", capabilities=loaded.capabilities) new_file = MagicMock(spec=Path) - new_file.read_text.return_value = GroupWrite( - name="new_group", source_id="123", capabilities=loaded.capabilities - ).dump_yaml() + new_file.read_text.return_value = new_group.dump_yaml() worker = ResourceWorker(loader, "deploy") resources = worker.prepare_resources( [ @@ -136,21 +137,16 @@ def test_upsert_group( raw_list = loader.load_resource_file(LOAD_DATA / "auth" / "1.my_group_scoped.yaml", env_vars_with_client.dump()) loaded = loader.load_resource(raw_list[0], is_dry_run=False) - # Simulate that the group is is already in CDF, but with fewer capabilities - # Simulate that one group is is already in CDF - toolkit_client_approval.append( - Group, - [ - Group( - id=123, - name=loaded.name, - source_id=loaded.source_id, - capabilities=loaded.capabilities[0:1], - metadata=loaded.metadata, - is_deleted=False, - ) - ], + # Simulate that the group is already in CDF, but with fewer capabilities + cdf_group = GroupResponse( + id=123, + name=loaded.name, + source_id=loaded.source_id, + capabilities=loaded.capabilities[0:1], + metadata=loaded.metadata, + is_deleted=False, ) + toolkit_client_approval.append(GroupResponse, [cdf_group]) # group exists, no changes worker = ResourceWorker(loader, "deploy") @@ -226,7 +222,7 @@ def test_unchanged_new_group_without_metadata( ) -> None: loader = GroupAllScopedCRUD.create_loader(env_vars_with_client.get_client()) local_group = """name: gp_no_metadata -sourceId: 123 +sourceId: '123' capabilities: - assetsAcl: actions: @@ -234,25 +230,17 @@ def test_unchanged_new_group_without_metadata( scope: all: {} """ - cdf_group = Group.load("""name: gp_no_metadata -sourceId: 123 -capabilities: -- assetsAcl: - actions: - - READ - scope: - all: {} -metadata: {} -id: 3760258445038144 -isDeleted: false -deletedTime: -1 -""") - - # Simulate that one group is is already in CDF - toolkit_client_approval.append( - Group, - [cdf_group], + cdf_group = GroupResponse( + name="gp_no_metadata", + source_id="123", + capabilities=[GroupCapability(acl=AssetsAcl(actions=["READ"], scope=AllScope()))], + metadata={}, + id=3760258445038144, + is_deleted=False, ) + + # Simulate that one group is already in CDF + toolkit_client_approval.append(GroupResponse, [cdf_group]) filepath = MagicMock(spec=Path) filepath.read_text.return_value = local_group @@ -284,29 +272,30 @@ def test_unchanged_group_raw_acl_table_scoped( 'db_name': - labels """ - cdf_group = Group.load("""name: gp_raw_acl_table_scoped -sourceId: '123' -capabilities: -- rawAcl: - actions: - - READ - scope: - tableScope: - dbsToTables: - 'db_name': - tables: - - labels -metadata: {} -id: 3760258445038144 -isDeleted: false -deletedTime: -1 - """) - - # Simulate that one group is is already in CDF - toolkit_client_approval.append( - Group, - [cdf_group], + cdf_group = GroupResponse( + name="gp_raw_acl_table_scoped", + source_id="123", + capabilities=[ + { + "rawAcl": { + "actions": ["READ"], + "scope": { + "tableScope": { + "dbsToTables": { + "db_name": ["labels"], + } + } + }, + } + } + ], + metadata={}, + id=3760258445038144, + is_deleted=False, ) + + # Simulate that one group is already in CDF + toolkit_client_approval.append(GroupResponse, [cdf_group]) filepath = MagicMock(spec=Path) filepath.read_text.return_value = local_group diff --git a/tests/test_unit/test_cli/test_behavior.py b/tests/test_unit/test_cli/test_behavior.py index 8e604f07fd..f8fdad385d 100644 --- a/tests/test_unit/test_cli/test_behavior.py +++ b/tests/test_unit/test_cli/test_behavior.py @@ -8,10 +8,7 @@ import yaml from cognite.client.data_classes import ( DataSet, - Group, - GroupWrite, ) -from cognite.client.data_classes.capabilities import AssetsAcl, EventsAcl, TimeSeriesAcl from pytest import MonkeyPatch from cognite_toolkit._cdf_tk import cdf_toml @@ -32,7 +29,8 @@ ) from cognite_toolkit._cdf_tk.client.resource_classes.data_modeling._view_property import ConstraintOrIndexState from cognite_toolkit._cdf_tk.client.resource_classes.dataset import DataSetResponse -from cognite_toolkit._cdf_tk.client.resource_classes.identifiers import WorkflowVersionId +from cognite_toolkit._cdf_tk.client.resource_classes.group import GroupRequest, GroupResponse +from cognite_toolkit._cdf_tk.client.resource_classes.identifiers import NameId, WorkflowVersionId from cognite_toolkit._cdf_tk.client.resource_classes.location_filter import ( LocationFilterResponse, ) @@ -335,17 +333,17 @@ def test_pull_group( local_path.write_text(local_file) (org_dir / "config.dev.yaml").write_text(default_config_dev_yaml, encoding="utf-8") - cdf_group = Group( + cdf_group = GroupResponse( name="my_group", source_id="123-456", capabilities=[ - AssetsAcl(scope=AssetsAcl.Scope.All(), actions=[AssetsAcl.Action.Read]), - TimeSeriesAcl(scope=TimeSeriesAcl.Scope.All(), actions=[TimeSeriesAcl.Action.Read]), - EventsAcl(scope=EventsAcl.Scope.All(), actions=[EventsAcl.Action.Read]), + {"assetsAcl": {"actions": ["READ"], "scope": {"all": {}}}}, + {"timeSeriesAcl": {"actions": ["READ"], "scope": {"all": {}}}}, + {"eventsAcl": {"actions": ["READ"], "scope": {"all": {}}}}, ], id=123, ) - toolkit_client_approval.append(Group, cdf_group) + toolkit_client_approval.append(GroupResponse, cdf_group) cmd = PullCommand(skip_tracking=True, silent=True) cmd.pull_module( @@ -357,9 +355,9 @@ def test_pull_group( env_vars=env_vars_with_client, ) - reloaded = GroupWrite.load(local_path.read_text()) + reloaded = GroupRequest._load(yaml.safe_load(local_path.read_text())) - assert reloaded.dump() == cdf_group.as_write().dump() + assert reloaded.dump() == cdf_group.as_request_resource().dump() def test_dump_datamodel( @@ -638,9 +636,9 @@ def test_deploy_group_with_unknown_acl( force_update=False, ) - groups = toolkit_client_approval.created_resources["Group"] + groups = toolkit_client_approval.created_resources["GroupResponse"] assert len(groups) == 1 - group = cast(GroupWrite, groups[0]) + group = cast(GroupRequest, groups[0]) assert group.name == "my_group_with_unknown_acl" assert len(group.capabilities) == 1 assert group.capabilities[0].dump() == { @@ -1013,4 +1011,5 @@ def test_warning_missing_dependency( warning = cmd.warning_list[0] assert isinstance(warning, MissingDependencyWarning) assert warning.identifier == SpaceReference(space="my_non_existent_space") - assert warning.required_by == {("scoped_group", yaml_filepath.relative_to(my_org))} + + assert warning.required_by == {(NameId(name="scoped_group"), yaml_filepath.relative_to(my_org))} diff --git a/tests/test_unit/test_cli/test_build_deploy_snapshots/cdf_ingestion.yaml b/tests/test_unit/test_cli/test_build_deploy_snapshots/cdf_ingestion.yaml index 949988d3a8..3cc5aa3750 100644 --- a/tests/test_unit/test_cli/test_build_deploy_snapshots/cdf_ingestion.yaml +++ b/tests/test_unit/test_cli/test_build_deploy_snapshots/cdf_ingestion.yaml @@ -1,4 +1,4 @@ -Group: +GroupResponse: - capabilities: - projectsAcl: actions: @@ -36,10 +36,8 @@ Group: scope: tableScope: dbsToTables: - contextualizationState: - tables: [] - ingestion: - tables: [] + contextualizationState: [] + ingestion: [] - dataModelInstancesAcl: actions: - READ @@ -150,10 +148,8 @@ Group: scope: tableScope: dbsToTables: - contextualizationState: - tables: [] - ingestion: - tables: [] + contextualizationState: [] + ingestion: [] - dataModelInstancesAcl: actions: - READ diff --git a/tests/test_unit/test_cli/test_build_deploy_snapshots/cdf_pi.yaml b/tests/test_unit/test_cli/test_build_deploy_snapshots/cdf_pi.yaml index c1d06615f6..32283f8814 100644 --- a/tests/test_unit/test_cli/test_build_deploy_snapshots/cdf_pi.yaml +++ b/tests/test_unit/test_cli/test_build_deploy_snapshots/cdf_pi.yaml @@ -15,7 +15,7 @@ ExtractionPipelineResponse: - dbName: ingestion tableName: timeseries_metadata source: Springfield AVEVA PI -Group: +GroupResponse: - capabilities: - projectsAcl: actions: @@ -38,8 +38,7 @@ Group: scope: tableScope: dbsToTables: - ingestion: - tables: [] + ingestion: [] - dataModelInstancesAcl: actions: - READ diff --git a/tests/test_unit/test_cli/test_build_deploy_snapshots/complete_org.yaml b/tests/test_unit/test_cli/test_build_deploy_snapshots/complete_org.yaml index d00b4dfb33..15d2232094 100644 --- a/tests/test_unit/test_cli/test_build_deploy_snapshots/complete_org.yaml +++ b/tests/test_unit/test_cli/test_build_deploy_snapshots/complete_org.yaml @@ -385,7 +385,7 @@ FunctionScheduleResponse: functionId: 16394514322001950830 name: daily-8am-utc nonce: dummy-nonce -Group: +GroupResponse: - capabilities: - projectsAcl: actions: diff --git a/tests/test_unit/utils.py b/tests/test_unit/utils.py index 4b978a8600..76203ae399 100644 --- a/tests/test_unit/utils.py +++ b/tests/test_unit/utils.py @@ -62,6 +62,7 @@ from pydantic import BaseModel, JsonValue from questionary import Choice +from cognite_toolkit._cdf_tk.client.resource_classes.group.capability import GroupCapability from cognite_toolkit._cdf_tk.client.resource_classes.legacy.sequences import ToolkitSequenceRows from cognite_toolkit._cdf_tk.client.resource_classes.location_filter import LocationFilterResponse from cognite_toolkit._cdf_tk.constants import MODULES @@ -346,6 +347,9 @@ def create_instance(self, resource_cls: type[T_Object], skip_defaulted_args: boo return resource_cls(*positional_arguments, **keyword_arguments) def create_pydantic_instance(self, model_cls: type[BaseModel], skip_defaulted_args: bool = False) -> BaseModel: + if model_cls is GroupCapability: + return GroupCapability.model_validate({"assetsAcl": {"actions": ["READ"], "scope": {"all": {}}}}) + keyword_arguments: dict[str, Any] = {} for field_id, field in model_cls.model_fields.items(): if skip_defaulted_args and field.default is not None: diff --git a/tests_smoke/test_client/test_cdf_resource_api.py b/tests_smoke/test_client/test_cdf_resource_api.py index 0c94b959f6..a51137366a 100644 --- a/tests_smoke/test_client/test_cdf_resource_api.py +++ b/tests_smoke/test_client/test_cdf_resource_api.py @@ -9,6 +9,7 @@ from cognite_toolkit._cdf_tk.client import ToolkitClient from cognite_toolkit._cdf_tk.client._resource_base import RequestResource, T_ResponseResource from cognite_toolkit._cdf_tk.client.api.cognite_files import CogniteFilesAPI +from cognite_toolkit._cdf_tk.client.api.data_products import DataProductsAPI from cognite_toolkit._cdf_tk.client.api.datasets import DataSetsAPI from cognite_toolkit._cdf_tk.client.api.extraction_pipeline_config import ExtractionPipelineConfigsAPI from cognite_toolkit._cdf_tk.client.api.function_schedules import FunctionSchedulesAPI @@ -69,6 +70,7 @@ from cognite_toolkit._cdf_tk.client.resource_classes.filemetadata import FileMetadataRequest, FileMetadataResponse from cognite_toolkit._cdf_tk.client.resource_classes.function import FunctionRequest from cognite_toolkit._cdf_tk.client.resource_classes.function_schedule import FunctionScheduleRequest +from cognite_toolkit._cdf_tk.client.resource_classes.group import GroupRequest from cognite_toolkit._cdf_tk.client.resource_classes.hosted_extractor_destination import ( HostedExtractorDestinationRequest, ) @@ -193,6 +195,8 @@ TransformationNotificationsAPI, # List method requires an argument, SequenceRowsAPI, + # The dataproduct API is not yet supported in CDF. + DataProductsAPI, } ) @@ -322,6 +326,7 @@ def get_examples_minimum_requests(request_cls: type[RequestResource]) -> list[di "externalId": "smoke-test-infield-cdm-location-config", } ], + GroupRequest: [{"name": "smoke-test-group"}], NodeRequest: [{"externalId": "smoke-test-node", "space": SMOKE_SPACE, "instanceType": "node"}], EdgeRequest: [ { @@ -678,7 +683,10 @@ def test_crudl( self.assert_endpoint_method(lambda: api.update([request]), "update", updated_endpoint, id) if hasattr(api, "list"): list_endpoint = methods["list"] - listed_items = list(api.list(limit=1)) + try: + listed_items = api.list(limit=1) + except TypeError: + listed_items = api.list() if len(listed_items) == 0: raise EndpointAssertionError(list_endpoint.path, "Expected at least 1 listed item, got 0") finally: