From 18fca4026e0b1ed340ba7e4526081740fd285c54 Mon Sep 17 00:00:00 2001 From: Anders Albert <60234212+doctrino@users.noreply.github.com> Date: Thu, 19 Sep 2024 12:26:17 +0200 Subject: [PATCH] [CDF-22563] Update Resources mode (#1935) --- CHANGELOG.md | 5 + cognite/client/_api/annotations.py | 21 ++- cognite/client/_api/assets.py | 27 ++- cognite/client/_api/data_sets.py | 28 ++- .../client/_api/datapoints_subscriptions.py | 15 +- cognite/client/_api/entity_matching.py | 10 +- cognite/client/_api/events.py | 26 ++- cognite/client/_api/extractionpipelines.py | 8 + cognite/client/_api/files.py | 17 +- .../_api/hosted_extractors/destinations.py | 25 ++- cognite/client/_api/hosted_extractors/jobs.py | 27 ++- .../client/_api/hosted_extractors/sources.py | 25 ++- cognite/client/_api/relationships.py | 9 +- cognite/client/_api/sequences.py | 21 ++- cognite/client/_api/three_d.py | 35 +++- cognite/client/_api/time_series.py | 20 +- .../client/_api/transformations/__init__.py | 20 +- .../client/_api/transformations/schedules.py | 13 +- cognite/client/_version.py | 2 +- docs/source/appendix.rst | 175 +++++++++++++++++- docs/source/conftest.py | 62 ++++++- pyproject.toml | 2 +- 22 files changed, 529 insertions(+), 64 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a12b708a3..c28ac6ec4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,11 @@ Changes are grouped as follows - `Fixed` for any bug fixes. - `Security` in case of vulnerabilities. +## [7.62.0] - 2024-09-19 +### Added +- All `update` methods now accept a new parameter `mode` that controls how non-update objects should be + interpreted. For example, should we do a partial update or a full replacement. + ## [7.61.1] - 2024-09-19 ### Added - [Feature Preview - alpha] Support for `client.hosted_extractors.jobs`. diff --git a/cognite/client/_api/annotations.py b/cognite/client/_api/annotations.py index 953f03ced..9ca86b0c4 100644 --- a/cognite/client/_api/annotations.py +++ b/cognite/client/_api/annotations.py @@ -113,10 +113,18 @@ def _convert_resource_to_patch_object( return annotation_update.dump() @overload - def update(self, item: Annotation | AnnotationWrite | AnnotationUpdate) -> Annotation: ... + def update( + self, + item: Annotation | AnnotationWrite | AnnotationUpdate, + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", + ) -> Annotation: ... @overload - def update(self, item: Sequence[Annotation | AnnotationWrite | AnnotationUpdate]) -> AnnotationList: ... + def update( + self, + item: Sequence[Annotation | AnnotationWrite | AnnotationUpdate], + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", + ) -> AnnotationList: ... def update( self, @@ -124,16 +132,23 @@ def update( | AnnotationWrite | AnnotationUpdate | Sequence[Annotation | AnnotationWrite | AnnotationUpdate], + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", ) -> Annotation | AnnotationList: """`Update annotations `_ Args: item (Annotation | AnnotationWrite | AnnotationUpdate | Sequence[Annotation | AnnotationWrite | AnnotationUpdate]): Annotation or list of annotations to update (or patch or list of patches to apply) + mode (Literal["replace_ignore_null", "patch", "replace"]): How to update data when a non-update + object is given (Annotation or -Write). If you use 'replace_ignore_null', only the fields + you have set will be used to replace existing (default). Using 'replace' will additionally + clear all the fields that are not specified by you. Last option, 'patch', will update only + the fields you have set and for container-like fields such as metadata or labels, add the + values to the existing. For more details, see :ref:`appendix-update`. Returns: Annotation | AnnotationList: No description.""" return self._update_multiple( - list_cls=AnnotationList, resource_cls=Annotation, update_cls=AnnotationUpdate, items=item + list_cls=AnnotationList, resource_cls=Annotation, update_cls=AnnotationUpdate, items=item, mode=mode ) def delete(self, id: int | Sequence[int]) -> None: diff --git a/cognite/client/_api/assets.py b/cognite/client/_api/assets.py index 13f60b733..9f04c8b8f 100644 --- a/cognite/client/_api/assets.py +++ b/cognite/client/_api/assets.py @@ -755,20 +755,35 @@ def delete( ) @overload - def update(self, item: Sequence[Asset | AssetWrite | AssetUpdate]) -> AssetList: ... + def update( + self, + item: Sequence[Asset | AssetWrite | AssetUpdate], + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", + ) -> AssetList: ... @overload - def update(self, item: Asset | AssetWrite | AssetUpdate) -> Asset: ... + def update( + self, + item: Asset | AssetWrite | AssetUpdate, + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", + ) -> Asset: ... def update( - self, item: Asset | AssetWrite | AssetUpdate | Sequence[Asset | AssetWrite | AssetUpdate] + self, + item: Asset | AssetWrite | AssetUpdate | Sequence[Asset | AssetWrite | AssetUpdate], + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", ) -> Asset | AssetList: """`Update one or more assets `_ Labels can be added, removed or replaced (set). Note that set operation deletes all the existing labels and adds the new specified labels. Args: item (Asset | AssetWrite | AssetUpdate | Sequence[Asset | AssetWrite | AssetUpdate]): Asset(s) to update - + mode (Literal["replace_ignore_null", "patch", "replace"]): How to update data when a non-update + object is given (Asset or -Write). If you use 'replace_ignore_null', only the fields + you have set will be used to replace existing (default). Using 'replace' will additionally + clear all the fields that are not specified by you. Last option, 'patch', will update only + the fields you have set and for container-like fields such as metadata or labels, add the + values to the existing. For more details, see :ref:`appendix-update`. Returns: Asset | AssetList: Updated asset(s) @@ -820,7 +835,9 @@ def update( >>> my_update = AssetUpdate(id=1).labels.set("PUMP") >>> res = client.assets.update(my_update) """ - return self._update_multiple(list_cls=AssetList, resource_cls=Asset, update_cls=AssetUpdate, items=item) + return self._update_multiple( + list_cls=AssetList, resource_cls=Asset, update_cls=AssetUpdate, items=item, mode=mode + ) @overload def upsert(self, item: Sequence[Asset | AssetWrite], mode: Literal["patch", "replace"] = "patch") -> AssetList: ... diff --git a/cognite/client/_api/data_sets.py b/cognite/client/_api/data_sets.py index 471a6183c..b054a4448 100644 --- a/cognite/client/_api/data_sets.py +++ b/cognite/client/_api/data_sets.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Iterator, Sequence, overload +from typing import TYPE_CHECKING, Any, Iterator, Literal, Sequence, overload from cognite.client._api_client import APIClient from cognite.client._constants import DEFAULT_LIMIT_READ @@ -213,18 +213,34 @@ def aggregate(self, filter: DataSetFilter | dict[str, Any] | None = None) -> lis return self._aggregate(filter=filter, cls=CountAggregate) @overload - def update(self, item: DataSet | DataSetWrite | DataSetUpdate) -> DataSet: ... + def update( + self, + item: DataSet | DataSetWrite | DataSetUpdate, + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", + ) -> DataSet: ... @overload - def update(self, item: Sequence[DataSet | DataSetWrite | DataSetUpdate]) -> DataSetList: ... + def update( + self, + item: Sequence[DataSet | DataSetWrite | DataSetUpdate], + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", + ) -> DataSetList: ... def update( - self, item: DataSet | DataSetWrite | DataSetUpdate | Sequence[DataSet | DataSetWrite | DataSetUpdate] + self, + item: DataSet | DataSetWrite | DataSetUpdate | Sequence[DataSet | DataSetWrite | DataSetUpdate], + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", ) -> DataSet | DataSetList: """`Update one or more data sets `_ Args: item (DataSet | DataSetWrite | DataSetUpdate | Sequence[DataSet | DataSetWrite | DataSetUpdate]): Data set(s) to update + mode (Literal["replace_ignore_null", "patch", "replace"]): How to update data when a non-update + object is given (DataSet or -Write). If you use 'replace_ignore_null', only the fields + you have set will be used to replace existing (default). Using 'replace' will additionally + clear all the fields that are not specified by you. Last option, 'patch', will update only + the fields you have set and for container-like fields such as metadata or labels, add the + values to the existing. For more details, see :ref:`appendix-update`. Returns: DataSet | DataSetList: Updated data set(s) @@ -247,7 +263,9 @@ def update( >>> my_update = DataSetUpdate(id=1).description.set("New description").metadata.remove(["key"]) >>> res = client.data_sets.update(my_update) """ - return self._update_multiple(list_cls=DataSetList, resource_cls=DataSet, update_cls=DataSetUpdate, items=item) + return self._update_multiple( + list_cls=DataSetList, resource_cls=DataSet, update_cls=DataSetUpdate, items=item, mode=mode + ) def list( self, diff --git a/cognite/client/_api/datapoints_subscriptions.py b/cognite/client/_api/datapoints_subscriptions.py index ef4b81957..d676d3af5 100644 --- a/cognite/client/_api/datapoints_subscriptions.py +++ b/cognite/client/_api/datapoints_subscriptions.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterator, cast, overload +from typing import TYPE_CHECKING, Iterator, Literal, cast, overload from cognite.client._api_client import APIClient from cognite.client._constants import DEFAULT_LIMIT_READ @@ -192,7 +192,11 @@ def list_member_time_series(self, external_id: str, limit: int | None = DEFAULT_ other_params={"externalId": external_id}, ) - def update(self, update: DataPointSubscriptionUpdate | DataPointSubscriptionWrite) -> DatapointSubscription: + def update( + self, + update: DataPointSubscriptionUpdate | DataPointSubscriptionWrite, + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", + ) -> DatapointSubscription: """`Update a subscriptions `_ Update a subscription. Note that Fields that are not included in the request are not changed. @@ -200,6 +204,12 @@ def update(self, update: DataPointSubscriptionUpdate | DataPointSubscriptionWrit Args: update (DataPointSubscriptionUpdate | DataPointSubscriptionWrite): The subscription update. + mode (Literal["replace_ignore_null", "patch", "replace"]): How to update data when a non-update + object is given (DataPointSubscriptionWrite). If you use 'replace_ignore_null', only the fields + you have set will be used to replace existing (default). Using 'replace' will additionally + clear all the fields that are not specified by you. Last option, 'patch', will update only + the fields you have set and for container-like fields such as metadata or labels, add the + values to the existing. Returns: DatapointSubscription: Updated subscription. @@ -229,6 +239,7 @@ def update(self, update: DataPointSubscriptionUpdate | DataPointSubscriptionWrit list_cls=DatapointSubscriptionList, resource_cls=DatapointSubscription, update_cls=DataPointSubscriptionUpdate, + mode=mode, ) def iterate_data( diff --git a/cognite/client/_api/entity_matching.py b/cognite/client/_api/entity_matching.py index 758eecede..5bed0e5bf 100644 --- a/cognite/client/_api/entity_matching.py +++ b/cognite/client/_api/entity_matching.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any, Sequence, TypeVar +from typing import Any, Literal, Sequence, TypeVar from cognite.client._api_client import APIClient from cognite.client._constants import DEFAULT_LIMIT_READ @@ -89,11 +89,18 @@ def update( item: EntityMatchingModel | EntityMatchingModelUpdate | Sequence[EntityMatchingModel | EntityMatchingModelUpdate], + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", ) -> EntityMatchingModelList | EntityMatchingModel: """`Update model `_ Args: item (EntityMatchingModel | EntityMatchingModelUpdate | Sequence[EntityMatchingModel | EntityMatchingModelUpdate]): Model(s) to update + mode (Literal["replace_ignore_null", "patch", "replace"]): How to update data when a non-update + object is given (EntityMatchingModel). If you use 'replace_ignore_null', only the fields + you have set will be used to replace existing (default). Using 'replace' will additionally + clear all the fields that are not specified by you. Last option, 'patch', will update only + the fields you have set and for container-like fields such as metadata or labels, add the + values to the existing. For more details, see :ref:`appendix-update`. Returns: EntityMatchingModelList | EntityMatchingModel: No description. @@ -109,6 +116,7 @@ def update( resource_cls=EntityMatchingModel, update_cls=EntityMatchingModelUpdate, items=item, + mode=mode, ) def list( diff --git a/cognite/client/_api/events.py b/cognite/client/_api/events.py index db9455884..9c56e3f37 100644 --- a/cognite/client/_api/events.py +++ b/cognite/client/_api/events.py @@ -555,18 +555,34 @@ def delete( ) @overload - def update(self, item: Sequence[Event | EventWrite | EventUpdate]) -> EventList: ... + def update( + self, + item: Sequence[Event | EventWrite | EventUpdate], + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", + ) -> EventList: ... @overload - def update(self, item: Event | EventWrite | EventUpdate) -> Event: ... + def update( + self, + item: Event | EventWrite | EventUpdate, + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", + ) -> Event: ... def update( - self, item: Event | EventWrite | EventUpdate | Sequence[Event | EventWrite | EventUpdate] + self, + item: Event | EventWrite | EventUpdate | Sequence[Event | EventWrite | EventUpdate], + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", ) -> Event | EventList: """`Update one or more events `_ Args: item (Event | EventWrite | EventUpdate | Sequence[Event | EventWrite | EventUpdate]): Event(s) to update + mode (Literal["replace_ignore_null", "patch", "replace"]): How to update data when a non-update + object is given (Event or -Write). If you use 'replace_ignore_null', only the fields + you have set will be used to replace existing (default). Using 'replace' will additionally + clear all the fields that are not specified by you. Last option, 'patch', will update only + the fields you have set and for container-like fields such as metadata or labels, add the + values to the existing. For more details, see :ref:`appendix-update`. Returns: Event | EventList: Updated event(s) @@ -589,7 +605,9 @@ def update( >>> my_update = EventUpdate(id=1).description.set("New description").metadata.add({"key": "value"}) >>> res = client.events.update(my_update) """ - return self._update_multiple(list_cls=EventList, resource_cls=Event, update_cls=EventUpdate, items=item) + return self._update_multiple( + list_cls=EventList, resource_cls=Event, update_cls=EventUpdate, items=item, mode=mode + ) def search( self, diff --git a/cognite/client/_api/extractionpipelines.py b/cognite/client/_api/extractionpipelines.py index 26a4e8a77..65650f2c1 100644 --- a/cognite/client/_api/extractionpipelines.py +++ b/cognite/client/_api/extractionpipelines.py @@ -245,11 +245,18 @@ def update( | ExtractionPipelineWrite | ExtractionPipelineUpdate | Sequence[ExtractionPipeline | ExtractionPipelineWrite | ExtractionPipelineUpdate], + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", ) -> ExtractionPipeline | ExtractionPipelineList: """`Update one or more extraction pipelines `_ Args: item (ExtractionPipeline | ExtractionPipelineWrite | ExtractionPipelineUpdate | Sequence[ExtractionPipeline | ExtractionPipelineWrite | ExtractionPipelineUpdate]): Extraction pipeline(s) to update + mode (Literal["replace_ignore_null", "patch", "replace"]): How to update data when a non-update + object is given (ExtractionPipeline or -Write). If you use 'replace_ignore_null', only the fields + you have set will be used to replace existing (default). Using 'replace' will additionally + clear all the fields that are not specified by you. Last option, 'patch', will update only + the fields you have set and for container-like fields such as metadata or labels, add the + values to the existing. For more details, see :ref:`appendix-update`. Returns: ExtractionPipeline | ExtractionPipelineList: Updated extraction pipeline(s) @@ -270,6 +277,7 @@ def update( resource_cls=ExtractionPipeline, update_cls=ExtractionPipelineUpdate, items=item, + mode=mode, ) diff --git a/cognite/client/_api/files.py b/cognite/client/_api/files.py index 97722c656..745d9ffbd 100644 --- a/cognite/client/_api/files.py +++ b/cognite/client/_api/files.py @@ -6,7 +6,7 @@ from collections import defaultdict from io import BufferedReader from pathlib import Path -from typing import Any, BinaryIO, Iterator, Sequence, TextIO, cast, overload +from typing import Any, BinaryIO, Iterator, Literal, Sequence, TextIO, cast, overload from urllib.parse import urljoin, urlparse from cognite.client._api_client import APIClient @@ -360,10 +360,18 @@ def delete( ) @overload - def update(self, item: FileMetadata | FileMetadataWrite | FileMetadataUpdate) -> FileMetadata: ... + def update( + self, + item: FileMetadata | FileMetadataWrite | FileMetadataUpdate, + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", + ) -> FileMetadata: ... @overload - def update(self, item: Sequence[FileMetadata | FileMetadataWrite | FileMetadataUpdate]) -> FileMetadataList: ... + def update( + self, + item: Sequence[FileMetadata | FileMetadataWrite | FileMetadataUpdate], + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", + ) -> FileMetadataList: ... def update( self, @@ -371,12 +379,14 @@ def update( | FileMetadataWrite | FileMetadataUpdate | Sequence[FileMetadata | FileMetadataWrite | FileMetadataUpdate], + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", ) -> FileMetadata | FileMetadataList: """`Update files `_ Currently, a full replacement of labels on a file is not supported (only partial add/remove updates). See the example below on how to perform partial labels update. Args: item (FileMetadata | FileMetadataWrite | FileMetadataUpdate | Sequence[FileMetadata | FileMetadataWrite | FileMetadataUpdate]): file(s) to update. + mode (Literal["replace_ignore_null", "patch", "replace"]): How to update data when a non-update object is given (FilesMetadata or -Write). If you use 'replace_ignore_null', only the fields you have set will be used to replace existing (default). Using 'replace' will additionally clear all the fields that are not specified by you. Last option, 'patch', will update only the fields you have set and for container-like fields such as metadata or labels, add the values to the existing. For more details, see :ref:`appendix-update`. Returns: FileMetadata | FileMetadataList: The updated files. @@ -429,6 +439,7 @@ def update( resource_path=self._RESOURCE_PATH, items=item, headers=headers, + mode=mode, ) def search( diff --git a/cognite/client/_api/hosted_extractors/destinations.py b/cognite/client/_api/hosted_extractors/destinations.py index f41b0ec9b..75703fe27 100644 --- a/cognite/client/_api/hosted_extractors/destinations.py +++ b/cognite/client/_api/hosted_extractors/destinations.py @@ -1,7 +1,7 @@ from __future__ import annotations from collections.abc import Iterator -from typing import TYPE_CHECKING, Any, Sequence, overload +from typing import TYPE_CHECKING, Any, Literal, Sequence, overload from cognite.client._api_client import APIClient from cognite.client._constants import DEFAULT_LIMIT_READ @@ -193,18 +193,34 @@ def create(self, items: DestinationWrite | Sequence[DestinationWrite]) -> Destin ) @overload - def update(self, items: DestinationWrite | DestinationUpdate) -> Destination: ... + def update( + self, + items: DestinationWrite | DestinationUpdate, + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", + ) -> Destination: ... @overload - def update(self, items: Sequence[DestinationWrite | DestinationUpdate]) -> DestinationList: ... + def update( + self, + items: Sequence[DestinationWrite | DestinationUpdate], + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", + ) -> DestinationList: ... def update( - self, items: DestinationWrite | DestinationUpdate | Sequence[DestinationWrite | DestinationUpdate] + self, + items: DestinationWrite | DestinationUpdate | Sequence[DestinationWrite | DestinationUpdate], + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", ) -> Destination | DestinationList: """`Update one or more destinations. `_ Args: items (DestinationWrite | DestinationUpdate | Sequence[DestinationWrite | DestinationUpdate]): Destination(s) to update. + mode (Literal["replace_ignore_null", "patch", "replace"]): How to update data when a non-update + object is given (DestinationWrite). If you use 'replace_ignore_null', only the fields + you have set will be used to replace existing (default). Using 'replace' will additionally + clear all the fields that are not specified by you. Last option, 'patch', will update only + the fields you have set and for container-like fields such as metadata or labels, add the + values to the existing. For more details, see :ref:`appendix-update`. Returns: Destination | DestinationList: Updated destination(s) @@ -225,6 +241,7 @@ def update( list_cls=DestinationList, resource_cls=Destination, update_cls=DestinationUpdate, + mode=mode, headers={"cdf-version": "beta"}, ) diff --git a/cognite/client/_api/hosted_extractors/jobs.py b/cognite/client/_api/hosted_extractors/jobs.py index 22b1683ce..3488d33ae 100644 --- a/cognite/client/_api/hosted_extractors/jobs.py +++ b/cognite/client/_api/hosted_extractors/jobs.py @@ -1,7 +1,7 @@ from __future__ import annotations from collections.abc import Iterator -from typing import TYPE_CHECKING, Any, Sequence, overload +from typing import TYPE_CHECKING, Any, Literal, Sequence, overload from cognite.client._api_client import APIClient from cognite.client._constants import DEFAULT_LIMIT_READ @@ -194,16 +194,34 @@ def create(self, items: JobWrite | Sequence[JobWrite]) -> Job | JobList: ) @overload - def update(self, items: JobWrite | JobUpdate) -> Job: ... + def update( + self, + items: JobWrite | JobUpdate, + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", + ) -> Job: ... @overload - def update(self, items: Sequence[JobWrite | JobUpdate]) -> JobList: ... + def update( + self, + items: Sequence[JobWrite | JobUpdate], + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", + ) -> JobList: ... - def update(self, items: JobWrite | JobUpdate | Sequence[JobWrite | JobUpdate]) -> Job | JobList: + def update( + self, + items: JobWrite | JobUpdate | Sequence[JobWrite | JobUpdate], + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", + ) -> Job | JobList: """`Update one or more jobs. `_ Args: items (JobWrite | JobUpdate | Sequence[JobWrite | JobUpdate]): Job(s) to update. + mode (Literal["replace_ignore_null", "patch", "replace"]): How to update data when a non-update + object is given (JobWrite). If you use 'replace_ignore_null', only the fields + you have set will be used to replace existing (default). Using 'replace' will additionally + clear all the fields that are not specified by you. Last option, 'patch', will update only + the fields you have set and for container-like fields such as metadata or labels, add the + values to the existing. For more details, see :ref:`appendix-update`. Returns: Job | JobList: Updated job(s) @@ -224,6 +242,7 @@ def update(self, items: JobWrite | JobUpdate | Sequence[JobWrite | JobUpdate]) - list_cls=JobList, resource_cls=Job, update_cls=JobUpdate, + mode=mode, headers={"cdf-version": "beta"}, ) diff --git a/cognite/client/_api/hosted_extractors/sources.py b/cognite/client/_api/hosted_extractors/sources.py index 066930127..6e57deb35 100644 --- a/cognite/client/_api/hosted_extractors/sources.py +++ b/cognite/client/_api/hosted_extractors/sources.py @@ -186,16 +186,34 @@ def create(self, items: SourceWrite | Sequence[SourceWrite]) -> Source | SourceL ) @overload - def update(self, items: SourceWrite | SourceUpdate) -> Source: ... + def update( + self, + items: SourceWrite | SourceUpdate, + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", + ) -> Source: ... @overload - def update(self, items: Sequence[SourceWrite | SourceUpdate]) -> SourceList: ... + def update( + self, + items: Sequence[SourceWrite | SourceUpdate], + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", + ) -> SourceList: ... - def update(self, items: SourceWrite | SourceUpdate | Sequence[SourceWrite | SourceUpdate]) -> Source | SourceList: + def update( + self, + items: SourceWrite | SourceUpdate | Sequence[SourceWrite | SourceUpdate], + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", + ) -> Source | SourceList: """`Update one or more sources. `_ Args: items (SourceWrite | SourceUpdate | Sequence[SourceWrite | SourceUpdate]): Source(s) to update. + mode (Literal["replace_ignore_null", "patch", "replace"]): How to update data when a non-update + object is given (SourceWrite). If you use 'replace_ignore_null', only the fields + you have set will be used to replace existing (default). Using 'replace' will additionally + clear all the fields that are not specified by you. Last option, 'patch', will update only + the fields you have set and for container-like fields such as metadata or labels, add the + values to the existing. For more details, see :ref:`appendix-update`. Returns: Source | SourceList: Updated source(s) @@ -216,6 +234,7 @@ def update(self, items: SourceWrite | SourceUpdate | Sequence[SourceWrite | Sour list_cls=SourceList, resource_cls=Source, # type: ignore[type-abstract] update_cls=SourceUpdate, + mode=mode, headers={"cdf-version": "beta"}, ) diff --git a/cognite/client/_api/relationships.py b/cognite/client/_api/relationships.py index 361f2272f..aaec16622 100644 --- a/cognite/client/_api/relationships.py +++ b/cognite/client/_api/relationships.py @@ -421,12 +421,19 @@ def update( | RelationshipWrite | RelationshipUpdate | Sequence[Relationship | RelationshipWrite | RelationshipUpdate], + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", ) -> Relationship | RelationshipList: """`Update one or more relationships `_ Currently, a full replacement of labels on a relationship is not supported (only partial add/remove updates). See the example below on how to perform partial labels update. Args: item (Relationship | RelationshipWrite | RelationshipUpdate | Sequence[Relationship | RelationshipWrite | RelationshipUpdate]): Relationship(s) to update + mode (Literal["replace_ignore_null", "patch", "replace"]): How to update data when a non-update + object is given (Relationship or -Write). If you use 'replace_ignore_null', only the fields + you have set will be used to replace existing (default). Using 'replace' will additionally + clear all the fields that are not specified by you. Last option, 'patch', will update only + the fields you have set and for container-like fields such as metadata or labels, add the + values to the existing. For more details, see :ref:`appendix-update`. Returns: Relationship | RelationshipList: Updated relationship(s) @@ -468,7 +475,7 @@ def update( >>> res = client.relationships.update(my_update) """ return self._update_multiple( - list_cls=RelationshipList, resource_cls=Relationship, update_cls=RelationshipUpdate, items=item + list_cls=RelationshipList, resource_cls=Relationship, update_cls=RelationshipUpdate, items=item, mode=mode ) @overload diff --git a/cognite/client/_api/sequences.py b/cognite/client/_api/sequences.py index bc954a80c..e114b8d49 100644 --- a/cognite/client/_api/sequences.py +++ b/cognite/client/_api/sequences.py @@ -574,19 +574,34 @@ def delete( ) @overload - def update(self, item: Sequence | SequenceWrite | SequenceUpdate) -> Sequence: ... + def update( + self, + item: Sequence | SequenceWrite | SequenceUpdate, + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", + ) -> Sequence: ... @overload - def update(self, item: typing.Sequence[Sequence | SequenceWrite | SequenceUpdate]) -> SequenceList: ... + def update( + self, + item: typing.Sequence[Sequence | SequenceWrite | SequenceUpdate], + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", + ) -> SequenceList: ... def update( self, item: Sequence | SequenceWrite | SequenceUpdate | typing.Sequence[Sequence | SequenceWrite | SequenceUpdate], + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", ) -> Sequence | SequenceList: """`Update one or more sequences. `_ Args: item (Sequence | SequenceWrite | SequenceUpdate | typing.Sequence[Sequence | SequenceWrite | SequenceUpdate]): Sequences to update + mode (Literal["replace_ignore_null", "patch", "replace"]): How to update data when a non-update + object is given (Sequence or -Write). If you use 'replace_ignore_null', only the fields + you have set will be used to replace existing (default). Using 'replace' will additionally + clear all the fields that are not specified by you. Last option, 'patch', will update only + the fields you have set and for container-like fields such as metadata or labels, add the + values to the existing. For more details, see :ref:`appendix-update`. Returns: Sequence | SequenceList: Updated sequences. @@ -666,7 +681,7 @@ def update( >>> res = client.sequences.update(my_update) """ return self._update_multiple( - list_cls=SequenceList, resource_cls=Sequence, update_cls=SequenceUpdate, items=item + list_cls=SequenceList, resource_cls=Sequence, update_cls=SequenceUpdate, items=item, mode=mode ) @overload diff --git a/cognite/client/_api/three_d.py b/cognite/client/_api/three_d.py index a97688578..b437ccf8f 100644 --- a/cognite/client/_api/three_d.py +++ b/cognite/client/_api/three_d.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterator, Sequence, overload +from typing import TYPE_CHECKING, Iterator, Literal, Sequence, overload from cognite.client._api_client import APIClient from cognite.client._constants import DEFAULT_LIMIT_READ @@ -193,19 +193,34 @@ def create( return self._create_multiple(list_cls=ThreeDModelList, resource_cls=ThreeDModel, items=items) @overload - def update(self, item: ThreeDModel | ThreeDModelUpdate) -> ThreeDModel: ... + def update( + self, + item: ThreeDModel | ThreeDModelUpdate, + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", + ) -> ThreeDModel: ... @overload - def update(self, item: Sequence[ThreeDModel | ThreeDModelUpdate]) -> ThreeDModelList: ... + def update( + self, + item: Sequence[ThreeDModel | ThreeDModelUpdate], + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", + ) -> ThreeDModelList: ... def update( self, item: ThreeDModel | ThreeDModelUpdate | Sequence[ThreeDModel | ThreeDModelUpdate], + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", ) -> ThreeDModel | ThreeDModelList: """`Update 3d models. `_ Args: item (ThreeDModel | ThreeDModelUpdate | Sequence[ThreeDModel | ThreeDModelUpdate]): ThreeDModel(s) to update + mode (Literal["replace_ignore_null", "patch", "replace"]): How to update data when a non-update + object is given (ThreeDModel or -Write). If you use 'replace_ignore_null', only the fields + you have set will be used to replace existing (default). Using 'replace' will additionally + clear all the fields that are not specified by you. Last option, 'patch', will update only + the fields you have set and for container-like fields such as metadata or labels, add the + values to the existing. For more details, see :ref:`appendix-update`. Returns: ThreeDModel | ThreeDModelList: Updated ThreeDModel(s) @@ -232,7 +247,11 @@ def update( # Note that we cannot use the ThreeDModelWrite to update as the write format of a 3D model # does not have ID or External ID, thus no identifier to know which model to update. return self._update_multiple( - list_cls=ThreeDModelList, resource_cls=ThreeDModel, update_cls=ThreeDModelUpdate, items=item + list_cls=ThreeDModelList, + resource_cls=ThreeDModel, + update_cls=ThreeDModelUpdate, + items=item, + mode=mode, ) def delete(self, id: int | Sequence[int]) -> None: @@ -395,12 +414,19 @@ def update( item: ThreeDModelRevision | ThreeDModelRevisionUpdate | Sequence[ThreeDModelRevision | ThreeDModelRevisionUpdate], + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", ) -> ThreeDModelRevision | ThreeDModelRevisionList: """`Update 3d model revisions. `_ Args: model_id (int): Update the revision under the model with this id. item (ThreeDModelRevision | ThreeDModelRevisionUpdate | Sequence[ThreeDModelRevision | ThreeDModelRevisionUpdate]): ThreeDModelRevision(s) to update + mode (Literal["replace_ignore_null", "patch", "replace"]): How to update data when a non-update + object is given (ThreeDModelRevision or -Write). If you use 'replace_ignore_null', only the fields + you have set will be used to replace existing (default). Using 'replace' will additionally + clear all the fields that are not specified by you. Last option, 'patch', will update only + the fields you have set and for container-like fields such as metadata or labels, add the + values to the existing. For more details, see :ref:`appendix-update`. Returns: ThreeDModelRevision | ThreeDModelRevisionList: Updated ThreeDModelRevision(s) @@ -429,6 +455,7 @@ def update( update_cls=ThreeDModelRevisionUpdate, resource_path=interpolate_and_url_encode(self._RESOURCE_PATH, model_id), items=item, + mode=mode, ) def delete(self, model_id: int, id: int | Sequence[int]) -> None: diff --git a/cognite/client/_api/time_series.py b/cognite/client/_api/time_series.py index 35977c03f..77bbc65fe 100644 --- a/cognite/client/_api/time_series.py +++ b/cognite/client/_api/time_series.py @@ -579,10 +579,18 @@ def delete( ) @overload - def update(self, item: Sequence[TimeSeries | TimeSeriesWrite | TimeSeriesUpdate]) -> TimeSeriesList: ... + def update( + self, + item: Sequence[TimeSeries | TimeSeriesWrite | TimeSeriesUpdate], + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", + ) -> TimeSeriesList: ... @overload - def update(self, item: TimeSeries | TimeSeriesWrite | TimeSeriesUpdate) -> TimeSeries: ... + def update( + self, + item: TimeSeries | TimeSeriesWrite | TimeSeriesUpdate, + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", + ) -> TimeSeries: ... def update( self, @@ -590,11 +598,18 @@ def update( | TimeSeriesWrite | TimeSeriesUpdate | Sequence[TimeSeries | TimeSeriesWrite | TimeSeriesUpdate], + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", ) -> TimeSeries | TimeSeriesList: """`Update one or more time series. `_ Args: item (TimeSeries | TimeSeriesWrite | TimeSeriesUpdate | Sequence[TimeSeries | TimeSeriesWrite | TimeSeriesUpdate]): Time series to update + mode (Literal["replace_ignore_null", "patch", "replace"]): How to update data when a non-update + object is given (TimeSeries or -Write). If you use 'replace_ignore_null', only the fields + you have set will be used to replace existing (default). Using 'replace' will additionally + clear all the fields that are not specified by you. Last option, 'patch', will update only + the fields you have set and for container-like fields such as metadata or labels, add the + values to the existing. For more details, see :ref:`appendix-update`. Returns: TimeSeries | TimeSeriesList: Updated time series. @@ -622,6 +637,7 @@ def update( resource_cls=TimeSeries, update_cls=TimeSeriesUpdate, items=item, + mode=mode, ) @overload diff --git a/cognite/client/_api/transformations/__init__.py b/cognite/client/_api/transformations/__init__.py index bc8ad5e3e..f4f9f82a4 100644 --- a/cognite/client/_api/transformations/__init__.py +++ b/cognite/client/_api/transformations/__init__.py @@ -1,7 +1,7 @@ from __future__ import annotations from collections.abc import Iterator -from typing import TYPE_CHECKING, Any, Sequence, overload +from typing import TYPE_CHECKING, Any, Literal, Sequence, overload from cognite.client._api.transformations.jobs import TransformationJobsAPI from cognite.client._api.transformations.notifications import TransformationNotificationsAPI @@ -415,11 +415,17 @@ def retrieve_multiple( ) @overload - def update(self, item: Transformation | TransformationWrite | TransformationUpdate) -> Transformation: ... + def update( + self, + item: Transformation | TransformationWrite | TransformationUpdate, + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", + ) -> Transformation: ... @overload def update( - self, item: Sequence[Transformation | TransformationWrite | TransformationUpdate] + self, + item: Sequence[Transformation | TransformationWrite | TransformationUpdate], + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", ) -> TransformationList: ... def update( @@ -428,11 +434,13 @@ def update( | TransformationWrite | TransformationUpdate | Sequence[Transformation | TransformationWrite | TransformationUpdate], + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", ) -> Transformation | TransformationList: """`Update one or more transformations `_ Args: item (Transformation | TransformationWrite | TransformationUpdate | Sequence[Transformation | TransformationWrite | TransformationUpdate]): Transformation(s) to update + mode (Literal["replace_ignore_null", "patch", "replace"]): How to update data when a non-update object is given (Transformation or -Write). If you use 'replace_ignore_null', only the fields you have set will be used to replace existing (default). Using 'replace' will additionally clear all the fields that are not specified by you. Last option, 'patch', will update only the fields you have set and for container-like fields such as metadata or labels, add the values to the existing. For more details, see :ref:`appendix-update`. Returns: Transformation | TransformationList: Updated transformation(s) @@ -488,7 +496,11 @@ def update( ) return self._update_multiple( - list_cls=TransformationList, resource_cls=Transformation, update_cls=TransformationUpdate, items=item + list_cls=TransformationList, + resource_cls=Transformation, + update_cls=TransformationUpdate, + items=item, + mode=mode, ) def run( diff --git a/cognite/client/_api/transformations/schedules.py b/cognite/client/_api/transformations/schedules.py index bf78e8ecc..2f43f89bd 100644 --- a/cognite/client/_api/transformations/schedules.py +++ b/cognite/client/_api/transformations/schedules.py @@ -1,7 +1,7 @@ from __future__ import annotations from collections.abc import Iterator -from typing import TYPE_CHECKING, Sequence, overload +from typing import TYPE_CHECKING, Literal, Sequence, overload from cognite.client._api_client import APIClient from cognite.client._constants import DEFAULT_LIMIT_READ @@ -234,12 +234,16 @@ def delete( @overload def update( - self, item: TransformationSchedule | TransformationScheduleWrite | TransformationScheduleUpdate + self, + item: TransformationSchedule | TransformationScheduleWrite | TransformationScheduleUpdate, + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", ) -> TransformationSchedule: ... @overload def update( - self, item: Sequence[TransformationSchedule | TransformationScheduleWrite | TransformationScheduleUpdate] + self, + item: Sequence[TransformationSchedule | TransformationScheduleWrite | TransformationScheduleUpdate], + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", ) -> TransformationScheduleList: ... def update( @@ -248,11 +252,13 @@ def update( | TransformationScheduleWrite | TransformationScheduleUpdate | Sequence[TransformationSchedule | TransformationScheduleWrite | TransformationScheduleUpdate], + mode: Literal["replace_ignore_null", "patch", "replace"] = "replace_ignore_null", ) -> TransformationSchedule | TransformationScheduleList: """`Update one or more transformation schedules `_ Args: item (TransformationSchedule | TransformationScheduleWrite | TransformationScheduleUpdate | Sequence[TransformationSchedule | TransformationScheduleWrite | TransformationScheduleUpdate]): Transformation schedule(s) to update + mode (Literal["replace_ignore_null", "patch", "replace"]): How to update data when a non-update object is given (TransformationSchedule or -Write). If you use 'replace_ignore_null', only the fields you have set will be used to replace existing (default). Using 'replace' will additionally clear all the fields that are not specified by you. Last option, 'patch', will update only the fields you have set and for container-like fields such as metadata or labels, add the values to the existing. For more details, see :ref:`appendix-update`. Returns: TransformationSchedule | TransformationScheduleList: Updated transformation schedule(s) @@ -280,4 +286,5 @@ def update( resource_cls=TransformationSchedule, update_cls=TransformationScheduleUpdate, items=item, + mode=mode, ) diff --git a/cognite/client/_version.py b/cognite/client/_version.py index a39249548..31a9fd931 100644 --- a/cognite/client/_version.py +++ b/cognite/client/_version.py @@ -1,4 +1,4 @@ from __future__ import annotations -__version__ = "7.61.1" +__version__ = "7.62.0" __api_subversion__ = "20230101" diff --git a/docs/source/appendix.rst b/docs/source/appendix.rst index 046948e00..541d5694e 100644 --- a/docs/source/appendix.rst +++ b/docs/source/appendix.rst @@ -1,5 +1,3 @@ -Appendix ---------- .. _appendix-upsert: @@ -14,12 +12,173 @@ notes apply: on whether the items exist from before or not. This means that if one of the calls fail, it is possible that some of the items have been updated/created while others have not been created/updated. -.. note:: - The mode parameter controls how the update is performed. If you set 'patch', the call will only update - the fields in the Item object that are not None. This means that if the items exists from before, the - fields that are not specified will not be changed. If you set 'replace', all the fields that are not - specified, i.e., set to None and support being set to null, will be nulled out. See the API - documentation for the update endpoint for more information. +.. _appendix-update: + +Update and Upsert Mode Parameter +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The mode parameter controls how the update is performed. If you set 'patch', the call will only update +the fields in the Item object that are not None. This means that if the items exists from before, the +fields that are not specified will not be changed. The 'replace_ignore_null' works similarly, to +'patch', but instead of updating it will replace the fields with new values. There is not difference +between 'patch' and 'replace_ignore_null' for fields that only supports set. For example, `name` and +`description` on TimeSeries. However, for fields that supports `set` and `add/remove`, like `metadata`, +'patch` will add to the metadata, while 'replace_ignore_null' will replace the metadata with the new +metadata. If you set 'replace', all the fields that are not specified, i.e., set to None and +support being set to null, will be nulled out. + +Example **patch**: + +.. testsetup:: patch_update + + >>> getfixture("appendix_update_patch") # Fixture defined in conftest.py + +.. doctest:: patch_update + +.. code:: python + + >>> from cognite.client import CogniteClient + >>> from cognite.client.data_classes import TimeSeriesWrite + >>> from pprint import pprint + >>> client = CogniteClient() + >>> + >>> new_ts = client.time_series.create( + ... TimeSeriesWrite( + ... external_id="new_ts", + ... name="New TS", + ... metadata={"key": "value", "another": "one"} + ... ) + ... ) + >>> + >>> updated = client.time_series.update( + ... TimeSeriesWrite( + ... external_id="new_ts", + ... description="Updated description", + ... metadata={"key": "new value", "brand": "new"} + ... ), + ... mode="patch" + ... ) + >>> pprint(updated.as_write().dump()) + {'description': 'Updated description', + 'externalId': 'new_ts', + 'metadata': {'another': 'one', 'brand': 'new', 'key': 'new value'}, + 'name': 'New TS'} + +Example **replace**: + +.. testsetup:: patch_replace + + >>> getfixture("appendix_update_replace") # Fixture defined in conftest.py + +.. doctest:: patch_replace + +.. code:: python + + >>> from cognite.client import CogniteClient + >>> from cognite.client.data_classes import TimeSeriesWrite + >>> from pprint import pprint + >>> client = CogniteClient() + >>> + >>> new_ts = client.time_series.create( + ... TimeSeriesWrite( + ... external_id="new_ts", + ... name="New TS", + ... metadata={"key": "value"} + ... ) + ... ) + >>> + >>> updated = client.time_series.update( + ... TimeSeriesWrite( + ... external_id="new_ts", + ... description="Updated description", + ... metadata={"new": "entry"} + ... ), + ... mode="replace" + ... ) + >>> pprint(updated.as_write().dump()) + {'description': 'Updated description', + 'externalId': 'new_ts', + 'metadata': {'new': 'entry'}} + +**Note** that the `name` parameter was not specified in the update, and was therefore nulled out. + +Example **replace_ignore_null**: + +.. testsetup:: patch_replace_ignore_null + + >>> getfixture("appendix_update_replace_ignore_null") # Fixture defined in conftest.py + +.. doctest:: patch_replace_ignore_null + +.. code:: python + + >>> from cognite.client import CogniteClient + >>> from cognite.client.data_classes import TimeSeriesWrite + >>> from pprint import pprint + >>> client = CogniteClient() + >>> + >>> new_ts = client.time_series.create( + ... TimeSeriesWrite( + ... external_id="new_ts", + ... name="New TS", + ... metadata={"key": "value"} + ... ) + ... ) + >>> + >>> updated = client.time_series.update( + ... TimeSeriesWrite( + ... external_id="new_ts", + ... description="Updated description", + ... metadata={"new": "entry"} + ... ), + ... mode="replace_ignore_null" + ... ) + >>> pprint(updated.as_write().dump()) + {'description': 'Updated description', + 'externalId': 'new_ts', + 'metadata': {'new': 'entry'}, + 'name': 'New TS'} + +**Note** that the `name` parameter was not specified in the update, and was therefore not changed, +same as in `patch` + +Example **replace_ignore_null** without `metadata`: + +.. testsetup:: patch_replace_ignore_null2 + + >>> getfixture("appendix_update_replace_ignore_null2") # Fixture defined in conftest.py + +.. doctest:: patch_replace_ignore_null2 + +.. code:: python + + >>> from cognite.client import CogniteClient + >>> from cognite.client.data_classes import TimeSeriesWrite + >>> from pprint import pprint + >>> client = CogniteClient() + >>> + >>> new_ts = client.time_series.create( + ... TimeSeriesWrite( + ... external_id="new_ts", + ... name="New TS", + ... metadata={"key": "value"} + ... ) + ... ) + >>> + >>> updated = client.time_series.update( + ... TimeSeriesWrite( + ... external_id="new_ts", + ... description="Updated description", + ... ), + ... mode="replace_ignore_null" + ... ) + >>> pprint(updated.as_write().dump()) + {'description': 'Updated description', + 'externalId': 'new_ts', + 'metadata': {'key': 'value'}, + 'name': 'New TS'} + +**Note** Since `metadata` was not specified in the update, it was not changed. .. _appendix-alpha-beta-features: diff --git a/docs/source/conftest.py b/docs/source/conftest.py index 8c3839c62..4bbdbada1 100644 --- a/docs/source/conftest.py +++ b/docs/source/conftest.py @@ -1,9 +1,15 @@ +from __future__ import annotations + import os from pathlib import Path +from typing import Any import pytest import yaml +from cognite.client.data_classes import TimeSeries +from cognite.client.testing import monkeypatch_cognite_client + # Files to exclude test directories or modules collect_ignore = ["conf.py"] @@ -21,8 +27,8 @@ def set_envs(monkeypatch): @pytest.fixture -def quickstart_client_config_file(monkeypatch): - data = { +def client_data() -> dict[str, Any]: + return { "client": { "project": "my-project", "client_name": "my-special-client", @@ -42,7 +48,57 @@ def quickstart_client_config_file(monkeypatch): }, } + +@pytest.fixture +def quickstart_client_config_file(monkeypatch, client_data): def read_text(*args, **kwargs): - return yaml.dump(data) + return yaml.dump(client_data) monkeypatch.setattr(Path, "read_text", read_text) + + +@pytest.fixture() +def appendix_update_patch() -> None: + with monkeypatch_cognite_client() as client: + client.time_series.update.return_value = TimeSeries( + external_id="new_ts", + name="New TS", + description="Updated description", + metadata={"another": "one", "brand": "new", "key": "new value"}, + ) + yield None + + +@pytest.fixture() +def appendix_update_replace() -> None: + with monkeypatch_cognite_client() as client: + client.time_series.update.return_value = TimeSeries( + external_id="new_ts", + description="Updated description", + metadata={"new": "entry"}, + ) + yield None + + +@pytest.fixture() +def appendix_update_replace_ignore_null() -> None: + with monkeypatch_cognite_client() as client: + client.time_series.update.return_value = TimeSeries( + external_id="new_ts", + name="New TS", + description="Updated description", + metadata={"new": "entry"}, + ) + yield None + + +@pytest.fixture() +def appendix_update_replace_ignore_null2() -> None: + with monkeypatch_cognite_client() as client: + client.time_series.update.return_value = TimeSeries( + external_id="new_ts", + name="New TS", + description="Updated description", + metadata={"key": "value"}, + ) + yield None diff --git a/pyproject.toml b/pyproject.toml index 4ac205034..6e43cfb26 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "cognite-sdk" -version = "7.61.1" +version = "7.62.0" description = "Cognite Python SDK" readme = "README.md" documentation = "https://cognite-sdk-python.readthedocs-hosted.com"