From ca8d878c34b504234219e39ca0db7315314a56f4 Mon Sep 17 00:00:00 2001 From: anders-albert Date: Wed, 4 Dec 2024 13:26:36 +0100 Subject: [PATCH 1/9] tests: added failing tests --- .../test_api/test_sequences.py | 62 ++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/tests/tests_integration/test_api/test_sequences.py b/tests/tests_integration/test_api/test_sequences.py index 37c55a82cf..ce20825285 100644 --- a/tests/tests_integration/test_api/test_sequences.py +++ b/tests/tests_integration/test_api/test_sequences.py @@ -1,3 +1,4 @@ +import unittest from unittest import mock import pytest @@ -14,7 +15,12 @@ SequenceUpdate, filters, ) -from cognite.client.data_classes.sequences import SequenceProperty, SortableSequenceProperty +from cognite.client.data_classes.sequences import ( + SequenceColumnWrite, + SequenceProperty, + SequenceWrite, + SortableSequenceProperty, +) from cognite.client.exceptions import CogniteNotFoundError from cognite.client.utils._text import random_string from tests.utils import set_request_limit @@ -329,3 +335,57 @@ def test_aggregate_unique_metadata_keys(self, cognite_client: CogniteClient, seq assert {tuple(item.value["property"]) for item in result} >= { ("metadata", key.casefold()) for a in sequence_list for key in a.metadata or [] } + + def test_upsert_sequence(self, cognite_client: CogniteClient) -> None: + original_sequence = SequenceWrite( + external_id=f"upsert_sequence_{random_string(5)}", + columns=[ + SequenceColumnWrite( + description="KW Description", name="KW Name", value_type="Double", external_id="kw_seq_01" + ), + ], + description="Description of the Test Sequence", + name="Test Sequence Name", + ) + upsert = SequenceWrite( + external_id=original_sequence.external_id, + columns=[ + SequenceColumnWrite( + description="KW Description", + name="KW Name", + value_type="Double", + external_id="kw_seq_01", + metadata={}, + ), + SequenceColumnWrite( + description="PW Description", + name="PW Name", + value_type="Double", + external_id="pw_seq_01", + metadata={}, + ), + SequenceColumnWrite( + description="LW Description", + name="LW Name", + value_type="Double", + external_id="lw_seq_01", + metadata={}, + ), + ], + description="Description of the Test Sequence", + name="Test Sequence Name", + ) + + created: Sequence | None = None + try: + created = cognite_client.sequences.create(original_sequence) + + upserted = cognite_client.sequences.upsert(upsert) + + retrieved = cognite_client.sequences.retrieve(external_id=upserted.external_id) + + assert retrieved is not None + unittest.TestCase().assertCountEqual(retrieved.as_write().columns.dump(), upsert.columns.dump()) + finally: + if created: + cognite_client.sequences.delete(external_id=created.external_id, ignore_unknown_ids=True) From f707b9031a125af1936a028e08c65208858dbf3d Mon Sep 17 00:00:00 2001 From: anders-albert Date: Wed, 4 Dec 2024 13:54:23 +0100 Subject: [PATCH 2/9] refactor: stipulate solution --- cognite/client/_api/sequences.py | 30 ++++++++++++++++++++++++ cognite/client/data_classes/sequences.py | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/cognite/client/_api/sequences.py b/cognite/client/_api/sequences.py index 8c638a0f02..21d8b6d03f 100644 --- a/cognite/client/_api/sequences.py +++ b/cognite/client/_api/sequences.py @@ -692,6 +692,22 @@ def upsert( >>> new_sequence = Sequence(external_id="new_sequence", description="New sequence") >>> res = client.sequences.upsert([existing_sequence, new_sequence], mode="replace") """ + + if isinstance(item, SequenceWrite): + if item.external_id is None: + raise ValueError("External ID must be set when upserting a SequenceWrite object.") + cdf_item = self.retrieve(external_id=item.external_id) + item = self._write_to_update(item, mode, {cdf_item.external_id: cdf_item}) + elif isinstance(item, Sequence): + external_ids = [i.external_id for i in item if isinstance(i, SequenceWrite)] + if None in external_ids: + raise ValueError("External ID must be set when upserting a SequenceWrite object.") + cdf_items = self.retrieve_multiple(external_ids=external_ids) + sequence_by_id = {cdf_item.external_id: cdf_item for cdf_item in cdf_items} + item = [ + self._write_to_update(item, mode, sequence_by_id) if isinstance(item, SequenceWrite) else item + for item in item + ] return self._upsert_multiple( item, list_cls=SequenceList, @@ -701,6 +717,20 @@ def upsert( mode=mode, ) + def _write_to_update( + self, + item: SequenceWrite, + mode: Literal["patch", "replace"], + cdf_item_by_ext_id: dict[str, Sequence], + ) -> SequenceUpdate: + update_obj = self._convert_resource_to_patch_object( + item, + SequenceUpdate._get_update_properties(item), + mode, + ) + # Todo Custom handling of columns + raise NotImplementedError + def search( self, name: str | None = None, diff --git a/cognite/client/data_classes/sequences.py b/cognite/client/data_classes/sequences.py index e958854649..777d54cd58 100644 --- a/cognite/client/data_classes/sequences.py +++ b/cognite/client/data_classes/sequences.py @@ -589,7 +589,7 @@ def _get_update_properties(cls, item: CogniteResource | None = None) -> list[Pro # Sequences do not support setting metadata to an empty array. PropertySpec("metadata", is_object=True, is_nullable=False), PropertySpec("data_set_id"), - # PropertySpec("columns", is_list=True), + # Columns are handled separately in the .upsert() method ] From 7fe85b39342e76d6d803bcfac35f0d026b8b6440 Mon Sep 17 00:00:00 2001 From: anders-albert Date: Wed, 4 Dec 2024 15:14:32 +0100 Subject: [PATCH 3/9] fix: implemented upsert of sequences --- cognite/client/_api/annotations.py | 5 +- .../client/_api/hosted_extractors/sources.py | 5 +- cognite/client/_api/sequences.py | 73 +++++++++++++------ cognite/client/_api_client.py | 16 +++- .../test_api/test_sequences.py | 10 +-- 5 files changed, 77 insertions(+), 32 deletions(-) diff --git a/cognite/client/_api/annotations.py b/cognite/client/_api/annotations.py index b16c2ca8cc..fa6be3acaf 100644 --- a/cognite/client/_api/annotations.py +++ b/cognite/client/_api/annotations.py @@ -1,13 +1,13 @@ from __future__ import annotations -from collections.abc import Sequence +from collections.abc import Hashable, Sequence from copy import deepcopy from typing import TYPE_CHECKING, Any, Literal, cast, overload from cognite.client._api_client import APIClient from cognite.client._constants import DEFAULT_LIMIT_READ from cognite.client.data_classes import Annotation, AnnotationFilter, AnnotationList, AnnotationUpdate -from cognite.client.data_classes._base import CogniteResource, PropertySpec +from cognite.client.data_classes._base import CogniteResource, PropertySpec, T_WritableCogniteResource from cognite.client.data_classes.annotations import AnnotationCore, AnnotationReverseLookupFilter, AnnotationWrite from cognite.client.data_classes.contextualization import ResourceReference, ResourceReferenceList from cognite.client.utils._auxiliary import is_unlimited, split_into_chunks @@ -102,6 +102,7 @@ def _convert_resource_to_patch_object( resource: CogniteResource, update_attributes: list[PropertySpec], mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", + cdf_item_by_id: dict[Hashable, T_WritableCogniteResource] | None = None, ) -> dict[str, dict[str, dict]]: if not isinstance(resource, Annotation): return APIClient._convert_resource_to_patch_object(resource, update_attributes) diff --git a/cognite/client/_api/hosted_extractors/sources.py b/cognite/client/_api/hosted_extractors/sources.py index 0edc1e21d5..4491f2dffd 100644 --- a/cognite/client/_api/hosted_extractors/sources.py +++ b/cognite/client/_api/hosted_extractors/sources.py @@ -1,11 +1,11 @@ from __future__ import annotations -from collections.abc import Iterator, Sequence +from collections.abc import Hashable, Iterator, Sequence from typing import TYPE_CHECKING, Any, Literal, overload from cognite.client._api_client import APIClient from cognite.client._constants import DEFAULT_LIMIT_READ -from cognite.client.data_classes._base import CogniteResource, PropertySpec +from cognite.client.data_classes._base import CogniteResource, PropertySpec, T_WritableCogniteResource from cognite.client.data_classes.hosted_extractors.sources import Source, SourceList, SourceUpdate, SourceWrite from cognite.client.utils._experimental import FeaturePreviewWarning from cognite.client.utils._identifier import IdentifierSequence @@ -237,6 +237,7 @@ def _convert_resource_to_patch_object( resource: CogniteResource, update_attributes: list[PropertySpec], mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", + cdf_item_by_id: dict[Hashable, T_WritableCogniteResource] | None = None, ) -> dict[str, dict[str, dict]]: output = super()._convert_resource_to_patch_object(resource, update_attributes, mode) if hasattr(resource, "_type"): diff --git a/cognite/client/_api/sequences.py b/cognite/client/_api/sequences.py index 21d8b6d03f..90917af92b 100644 --- a/cognite/client/_api/sequences.py +++ b/cognite/client/_api/sequences.py @@ -1,5 +1,6 @@ from __future__ import annotations +import collections import math import typing import warnings @@ -17,9 +18,11 @@ SequenceUpdate, filters, ) +from cognite.client.data_classes._base import CogniteResource, PropertySpec from cognite.client.data_classes.aggregations import AggregationFilter, CountAggregate, UniqueResultList from cognite.client.data_classes.filters import _BASIC_FILTERS, Filter, _validate_filter from cognite.client.data_classes.sequences import ( + SequenceColumnUpdate, SequenceCore, SequenceProperty, SequenceSort, @@ -697,17 +700,16 @@ def upsert( if item.external_id is None: raise ValueError("External ID must be set when upserting a SequenceWrite object.") cdf_item = self.retrieve(external_id=item.external_id) - item = self._write_to_update(item, mode, {cdf_item.external_id: cdf_item}) - elif isinstance(item, Sequence): + if cdf_item and cdf_item.external_id: + cdf_item_by_id: dict[str, Sequence] = {cdf_item.external_id: cdf_item} + elif isinstance(item, collections.abc.Sequence): external_ids = [i.external_id for i in item if isinstance(i, SequenceWrite)] if None in external_ids: raise ValueError("External ID must be set when upserting a SequenceWrite object.") - cdf_items = self.retrieve_multiple(external_ids=external_ids) - sequence_by_id = {cdf_item.external_id: cdf_item for cdf_item in cdf_items} - item = [ - self._write_to_update(item, mode, sequence_by_id) if isinstance(item, SequenceWrite) else item - for item in item - ] + cdf_items = self.retrieve_multiple(external_ids=typing.cast(list[str], external_ids)) + cdf_item_by_id = {cdf_item.external_id: cdf_item for cdf_item in cdf_items if cdf_item.external_id} + else: + cdf_item_by_id = {} return self._upsert_multiple( item, list_cls=SequenceList, @@ -715,21 +717,50 @@ def upsert( update_cls=SequenceUpdate, input_resource_cls=Sequence, mode=mode, + # String is not recognized as hashable by mypy + cdf_item_by_id=cdf_item_by_id, # type: ignore[arg-type] ) - def _write_to_update( - self, - item: SequenceWrite, - mode: Literal["patch", "replace"], - cdf_item_by_ext_id: dict[str, Sequence], - ) -> SequenceUpdate: - update_obj = self._convert_resource_to_patch_object( - item, - SequenceUpdate._get_update_properties(item), - mode, - ) - # Todo Custom handling of columns - raise NotImplementedError + @classmethod + def _convert_resource_to_patch_object( + cls, + resource: CogniteResource, + update_attributes: list[PropertySpec], + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", + cdf_item_by_id: dict[str, Sequence] | None = None, # type: ignore[override] + ) -> dict[str, dict[str, dict]]: + update_obj = super()._convert_resource_to_patch_object(resource, update_attributes, mode) + if not isinstance(resource, SequenceWrite): + return update_obj + # Lookup columns to check what to add, remove and modify + cdf_item: Sequence | None = None + if cdf_item_by_id and resource.external_id: + cdf_item = cdf_item_by_id.get(resource.external_id) + update_obj["update"]["columns"] = {} + if cdf_item is None: + update_obj["update"]["columns"]["add"] = [column.dump() for column in resource.columns] + else: + cdf_columns_by_external_id = { + column.external_id: column for column in cdf_item.columns or [] if column.external_id + } + columns_by_external_id = {column.external_id: column for column in resource.columns if column.external_id} + if to_remove := (set(cdf_columns_by_external_id.keys()) - set(columns_by_external_id.keys())): + update_obj["update"]["columns"]["remove"] = list(to_remove) + if to_add := (set(columns_by_external_id.keys()) - set(cdf_columns_by_external_id.keys())): + update_obj["update"]["columns"]["add"] = [columns_by_external_id[ext_id].dump() for ext_id in to_add] + if to_modify := (set(columns_by_external_id.keys()) & set(cdf_columns_by_external_id.keys())): + modify_list: list[dict[str, dict[str, Any]]] = [] + for col_ext_id in to_modify: + col_write_obj = columns_by_external_id[col_ext_id] + column_update = super()._convert_resource_to_patch_object( + col_write_obj, + SequenceColumnUpdate._get_update_properties(col_write_obj), + mode, + ) + modify_list.append(column_update) + update_obj["update"]["columns"]["modify"] = modify_list + + return update_obj def search( self, diff --git a/cognite/client/_api_client.py b/cognite/client/_api_client.py index 135c2bf7f5..5a7b503c07 100644 --- a/cognite/client/_api_client.py +++ b/cognite/client/_api_client.py @@ -7,7 +7,7 @@ import re import warnings from collections import UserList -from collections.abc import Iterator, MutableMapping, Sequence +from collections.abc import Hashable, Iterator, MutableMapping, Sequence from typing import ( TYPE_CHECKING, Any, @@ -1004,6 +1004,7 @@ def _update_multiple( headers: dict[str, Any] | None = None, mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", api_subversion: str | None = None, + cdf_item_by_id: dict[Hashable, T_WritableCogniteResource] | None = None, ) -> T_CogniteResource: ... @overload @@ -1018,6 +1019,7 @@ def _update_multiple( headers: dict[str, Any] | None = None, mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", api_subversion: str | None = None, + cdf_item_by_id: dict[Hashable, T_WritableCogniteResource] | None = None, ) -> T_CogniteResourceList: ... def _update_multiple( @@ -1031,6 +1033,7 @@ def _update_multiple( headers: dict[str, Any] | None = None, mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", api_subversion: str | None = None, + cdf_item_by_id: dict[Hashable, T_WritableCogniteResource] | None = None, ) -> T_CogniteResourceList | T_CogniteResource: resource_path = resource_path or self._RESOURCE_PATH patch_objects = [] @@ -1047,6 +1050,7 @@ def _update_multiple( item, update_cls._get_update_properties(item), mode, + cdf_item_by_id, ) ) elif isinstance(item, CogniteUpdate): @@ -1085,6 +1089,7 @@ def _upsert_multiple( mode: Literal["patch", "replace"], input_resource_cls: type[CogniteResource] | None = None, api_subversion: str | None = None, + cdf_item_by_id: dict[Hashable, T_WritableCogniteResource] | None = None, ) -> T_WritableCogniteResource | T_CogniteResourceList: if mode not in ["patch", "replace"]: raise ValueError(f"mode must be either 'patch' or 'replace', got {mode!r}") @@ -1092,7 +1097,13 @@ def _upsert_multiple( items = cast(Sequence[T_WritableCogniteResource], [items] if is_single else items) try: result = self._update_multiple( - items, list_cls, resource_cls, update_cls, mode=mode, api_subversion=api_subversion + items, + list_cls, + resource_cls, + update_cls, + mode=mode, + api_subversion=api_subversion, + cdf_item_by_id=cdf_item_by_id, ) except CogniteNotFoundError as not_found_error: items_by_external_id = {item.external_id: item for item in items if item.external_id is not None} # type: ignore [attr-defined] @@ -1232,6 +1243,7 @@ def _convert_resource_to_patch_object( resource: CogniteResource, update_attributes: list[PropertySpec], mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", + cdf_item_by_id: dict[Hashable, T_WritableCogniteResource] | None = None, ) -> dict[str, dict[str, dict]]: dumped = resource.dump(camel_case=True) diff --git a/tests/tests_integration/test_api/test_sequences.py b/tests/tests_integration/test_api/test_sequences.py index ce20825285..f2bc5f3c12 100644 --- a/tests/tests_integration/test_api/test_sequences.py +++ b/tests/tests_integration/test_api/test_sequences.py @@ -1,4 +1,3 @@ -import unittest from unittest import mock import pytest @@ -353,21 +352,22 @@ def test_upsert_sequence(self, cognite_client: CogniteClient) -> None: SequenceColumnWrite( description="KW Description", name="KW Name", - value_type="Double", + # UPPER to match what the API returns + value_type="DOUBLE", external_id="kw_seq_01", metadata={}, ), SequenceColumnWrite( description="PW Description", name="PW Name", - value_type="Double", + value_type="DOUBLE", external_id="pw_seq_01", metadata={}, ), SequenceColumnWrite( description="LW Description", name="LW Name", - value_type="Double", + value_type="DOUBLE", external_id="lw_seq_01", metadata={}, ), @@ -385,7 +385,7 @@ def test_upsert_sequence(self, cognite_client: CogniteClient) -> None: retrieved = cognite_client.sequences.retrieve(external_id=upserted.external_id) assert retrieved is not None - unittest.TestCase().assertCountEqual(retrieved.as_write().columns.dump(), upsert.columns.dump()) + assert retrieved.as_write().columns.dump() == upsert.columns.dump() finally: if created: cognite_client.sequences.delete(external_id=created.external_id, ignore_unknown_ids=True) From 5c11b383e0a9f211ea5a07bfc439c3108764f5c4 Mon Sep 17 00:00:00 2001 From: anders-albert Date: Wed, 4 Dec 2024 15:16:48 +0100 Subject: [PATCH 4/9] build: changelog --- CHANGELOG.md | 4 ++++ cognite/client/_version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a9a782cac..6dee1cab89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,10 @@ Changes are grouped as follows - `Fixed` for any bug fixes. - `Security` in case of vulnerabilities. +## [7.70.3] - 2024-12-04 +### Fixed +- Upserting a Sequence with columns no longer silently skips the columns, but instead updates them as intended. + ## [7.70.2] - 2024-12-04 ### Fixed - Retrieving `ExtractionPipeline` either with `client.extraction_pipelines.retrieve` or diff --git a/cognite/client/_version.py b/cognite/client/_version.py index bd37cfe182..d71cebc18e 100644 --- a/cognite/client/_version.py +++ b/cognite/client/_version.py @@ -1,5 +1,5 @@ from __future__ import annotations -__version__ = "7.70.2" +__version__ = "7.70.3" __api_subversion__ = "20230101" diff --git a/pyproject.toml b/pyproject.toml index 73e1d07378..5a937116bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "cognite-sdk" -version = "7.70.2" +version = "7.70.3" description = "Cognite Python SDK" readme = "README.md" From 5634de6a2b23ae61057c6ddda97942bc3245a8bb Mon Sep 17 00:00:00 2001 From: anders-albert Date: Wed, 11 Dec 2024 10:46:43 +0100 Subject: [PATCH 5/9] tests: updated test --- cognite/client/_api/sequences.py | 9 ++-- .../test_api/test_sequences.py | 52 ++++++++++++++++++- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/cognite/client/_api/sequences.py b/cognite/client/_api/sequences.py index 90917af92b..d943394d17 100644 --- a/cognite/client/_api/sequences.py +++ b/cognite/client/_api/sequences.py @@ -707,7 +707,7 @@ def upsert( if None in external_ids: raise ValueError("External ID must be set when upserting a SequenceWrite object.") cdf_items = self.retrieve_multiple(external_ids=typing.cast(list[str], external_ids)) - cdf_item_by_id = {cdf_item.external_id: cdf_item for cdf_item in cdf_items if cdf_item.external_id} + cdf_item_by_id = cdf_items._external_id_to_item else: cdf_item_by_id = {} return self._upsert_multiple( @@ -730,7 +730,7 @@ def _convert_resource_to_patch_object( cdf_item_by_id: dict[str, Sequence] | None = None, # type: ignore[override] ) -> dict[str, dict[str, dict]]: update_obj = super()._convert_resource_to_patch_object(resource, update_attributes, mode) - if not isinstance(resource, SequenceWrite): + if not isinstance(resource, SequenceWrite) or not resource.columns: return update_obj # Lookup columns to check what to add, remove and modify cdf_item: Sequence | None = None @@ -744,7 +744,10 @@ def _convert_resource_to_patch_object( column.external_id: column for column in cdf_item.columns or [] if column.external_id } columns_by_external_id = {column.external_id: column for column in resource.columns if column.external_id} - if to_remove := (set(cdf_columns_by_external_id.keys()) - set(columns_by_external_id.keys())): + if mode != "patch" and ( + to_remove := (set(cdf_columns_by_external_id.keys()) - set(columns_by_external_id.keys())) + ): + # Replace or replace_ignore_null, remove all columns that are not in the new columns update_obj["update"]["columns"]["remove"] = list(to_remove) if to_add := (set(columns_by_external_id.keys()) - set(cdf_columns_by_external_id.keys())): update_obj["update"]["columns"]["add"] = [columns_by_external_id[ext_id].dump() for ext_id in to_add] diff --git a/tests/tests_integration/test_api/test_sequences.py b/tests/tests_integration/test_api/test_sequences.py index f2bc5f3c12..76271ae754 100644 --- a/tests/tests_integration/test_api/test_sequences.py +++ b/tests/tests_integration/test_api/test_sequences.py @@ -16,6 +16,7 @@ ) from cognite.client.data_classes.sequences import ( SequenceColumnWrite, + SequenceColumnWriteList, SequenceProperty, SequenceWrite, SortableSequenceProperty, @@ -335,7 +336,7 @@ def test_aggregate_unique_metadata_keys(self, cognite_client: CogniteClient, seq ("metadata", key.casefold()) for a in sequence_list for key in a.metadata or [] } - def test_upsert_sequence(self, cognite_client: CogniteClient) -> None: + def test_upsert_sequence_replace(self, cognite_client: CogniteClient) -> None: original_sequence = SequenceWrite( external_id=f"upsert_sequence_{random_string(5)}", columns=[ @@ -380,7 +381,7 @@ def test_upsert_sequence(self, cognite_client: CogniteClient) -> None: try: created = cognite_client.sequences.create(original_sequence) - upserted = cognite_client.sequences.upsert(upsert) + upserted = cognite_client.sequences.upsert(upsert, mode="replace") retrieved = cognite_client.sequences.retrieve(external_id=upserted.external_id) @@ -389,3 +390,50 @@ def test_upsert_sequence(self, cognite_client: CogniteClient) -> None: finally: if created: cognite_client.sequences.delete(external_id=created.external_id, ignore_unknown_ids=True) + + def test_upsert_sequence_patch(self, cognite_client: CogniteClient) -> None: + original_sequence = SequenceWrite( + external_id=f"upsert_sequence_{random_string(5)}", + columns=[ + SequenceColumnWrite( + description="KW Description", + name="KW Name", + value_type="DOUBLE", + external_id="kw_seq_01", + metadata={}, + ), + ], + description="Description of the Test Sequence", + name="Test Sequence Name", + ) + upsert = SequenceWrite( + external_id=original_sequence.external_id, + columns=[ + SequenceColumnWrite( + description="PW Description", + name="PW Name", + value_type="DOUBLE", + external_id="pw_seq_01", + metadata={}, + ), + ], + description="Description of the Test Sequence", + name="Test Sequence Name", + ) + + created: Sequence | None = None + try: + created = cognite_client.sequences.create(original_sequence) + + upserted = cognite_client.sequences.upsert(upsert, mode="patch") + + retrieved = cognite_client.sequences.retrieve(external_id=upserted.external_id) + + assert retrieved is not None + assert ( + retrieved.as_write().columns.dump() + == SequenceColumnWriteList([original_sequence.columns[0], upsert.columns[0]]).dump() + ) + finally: + if created: + cognite_client.sequences.delete(external_id=created.external_id, ignore_unknown_ids=True) From b0257a6990419e5a797ab6a3ce400ad11a1fdd5d Mon Sep 17 00:00:00 2001 From: anders-albert Date: Thu, 12 Dec 2024 08:52:16 +0100 Subject: [PATCH 6/9] refactor: review feedback --- cognite/client/_api/sequences.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cognite/client/_api/sequences.py b/cognite/client/_api/sequences.py index d943394d17..1f75ce079e 100644 --- a/cognite/client/_api/sequences.py +++ b/cognite/client/_api/sequences.py @@ -730,7 +730,7 @@ def _convert_resource_to_patch_object( cdf_item_by_id: dict[str, Sequence] | None = None, # type: ignore[override] ) -> dict[str, dict[str, dict]]: update_obj = super()._convert_resource_to_patch_object(resource, update_attributes, mode) - if not isinstance(resource, SequenceWrite) or not resource.columns: + if not isinstance(resource, SequenceWrite): return update_obj # Lookup columns to check what to add, remove and modify cdf_item: Sequence | None = None From d178562501af0c0bf1a39118c50f418b9fd08888 Mon Sep 17 00:00:00 2001 From: anders-albert Date: Thu, 12 Dec 2024 11:54:46 +0100 Subject: [PATCH 7/9] refactor; updated example --- cognite/client/_api/sequences.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cognite/client/_api/sequences.py b/cognite/client/_api/sequences.py index 1f75ce079e..7f81cb171e 100644 --- a/cognite/client/_api/sequences.py +++ b/cognite/client/_api/sequences.py @@ -688,11 +688,15 @@ def upsert( Upsert for sequences: >>> from cognite.client import CogniteClient - >>> from cognite.client.data_classes import Sequence + >>> from cognite.client.data_classes import SequenceWrite, SequenceColumnWrite >>> client = CogniteClient() >>> existing_sequence = client.sequences.retrieve(id=1) >>> existing_sequence.description = "New description" - >>> new_sequence = Sequence(external_id="new_sequence", description="New sequence") + >>> new_sequence = SequenceWrite( + ... external_id="new_sequence", + ... description="New sequence", + ... columns=[SequenceColumnWrite(external_id="col1", value_type="String")] + ... ) >>> res = client.sequences.upsert([existing_sequence, new_sequence], mode="replace") """ From 7cc8e0749812bd8effce1458121ff5e12e482bab Mon Sep 17 00:00:00 2001 From: anders-albert Date: Thu, 12 Dec 2024 11:56:02 +0100 Subject: [PATCH 8/9] build; changelog --- cognite/client/_version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cognite/client/_version.py b/cognite/client/_version.py index a9241b2585..b565e13989 100644 --- a/cognite/client/_version.py +++ b/cognite/client/_version.py @@ -1,5 +1,5 @@ from __future__ import annotations -__version__ = "7.70.4" +__version__ = "7.70.5" __api_subversion__ = "20230101" diff --git a/pyproject.toml b/pyproject.toml index ec0f7d5e54..d4b301d516 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "cognite-sdk" -version = "7.70.4" +version = "7.70.5" description = "Cognite Python SDK" readme = "README.md" From d15ec32c5e1afd64b3bdc53ff1b4d765b9c46ecf Mon Sep 17 00:00:00 2001 From: anders-albert Date: Thu, 12 Dec 2024 12:09:26 +0100 Subject: [PATCH 9/9] refactor; robustify test --- tests/tests_integration/test_api/test_sequences.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/tests_integration/test_api/test_sequences.py b/tests/tests_integration/test_api/test_sequences.py index 76271ae754..8bd0148f59 100644 --- a/tests/tests_integration/test_api/test_sequences.py +++ b/tests/tests_integration/test_api/test_sequences.py @@ -1,4 +1,4 @@ -from unittest import mock +from unittest import TestCase, mock import pytest @@ -386,7 +386,7 @@ def test_upsert_sequence_replace(self, cognite_client: CogniteClient) -> None: retrieved = cognite_client.sequences.retrieve(external_id=upserted.external_id) assert retrieved is not None - assert retrieved.as_write().columns.dump() == upsert.columns.dump() + TestCase().assertCountEqual(retrieved.as_write().columns.dump(), upsert.columns.dump()) finally: if created: cognite_client.sequences.delete(external_id=created.external_id, ignore_unknown_ids=True) @@ -430,9 +430,9 @@ def test_upsert_sequence_patch(self, cognite_client: CogniteClient) -> None: retrieved = cognite_client.sequences.retrieve(external_id=upserted.external_id) assert retrieved is not None - assert ( - retrieved.as_write().columns.dump() - == SequenceColumnWriteList([original_sequence.columns[0], upsert.columns[0]]).dump() + TestCase().assertCountEqual( + retrieved.as_write().columns.dump(), + SequenceColumnWriteList([original_sequence.columns[0], upsert.columns[0]]).dump(), ) finally: if created: