cognite-toolkit 0.7.40__py3-none-any.whl → 0.7.41__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,6 +15,7 @@ from cognite_toolkit._cdf_tk.client.api.legacy.robotics import RoboticsAPI
15
15
  from cognite_toolkit._cdf_tk.client.http_client import HTTPClient
16
16
 
17
17
  from .api.assets import AssetsAPI
18
+ from .api.events import EventsAPI
18
19
  from .api.infield import InfieldAPI
19
20
  from .api.lookup import LookUpGroup
20
21
  from .api.migration import MigrationAPI
@@ -22,6 +23,7 @@ from .api.project import ProjectAPI
22
23
  from .api.search import SearchAPI
23
24
  from .api.streams import StreamsAPI
24
25
  from .api.three_d import ThreeDAPI
26
+ from .api.timeseries import TimeSeriesAPI
25
27
  from .api.token import TokenAPI
26
28
  from .api.verify import VerifyAPI
27
29
  from .config import ToolkitClientConfig
@@ -34,6 +36,8 @@ class ToolAPI:
34
36
  self.http_client = http_client
35
37
  self.three_d = ThreeDAPI(http_client, console)
36
38
  self.assets = AssetsAPI(http_client)
39
+ self.time_series = TimeSeriesAPI(http_client)
40
+ self.events = EventsAPI(http_client)
37
41
 
38
42
 
39
43
  class ToolkitClient(CogniteClient):
@@ -1,4 +1,5 @@
1
1
  from collections.abc import Sequence
2
+ from typing import Literal
2
3
 
3
4
  from cognite_toolkit._cdf_tk.client.cdf_client import CDFResourceAPI, PagedResponse, ResponseItems
4
5
  from cognite_toolkit._cdf_tk.client.cdf_client.api import Endpoint
@@ -14,8 +15,9 @@ class EventsAPI(CDFResourceAPI[InternalOrExternalId, EventRequest, EventResponse
14
15
  method_endpoint_map={
15
16
  "create": Endpoint(method="POST", path="/events", item_limit=1000, concurrency_max_workers=1),
16
17
  "retrieve": Endpoint(method="POST", path="/events/byids", item_limit=1000, concurrency_max_workers=1),
18
+ "update": Endpoint(method="POST", path="/events/update", item_limit=1000, concurrency_max_workers=1),
17
19
  "delete": Endpoint(method="POST", path="/events/delete", item_limit=1000, concurrency_max_workers=1),
18
- "list": Endpoint(method="GET", path="/events", item_limit=1000),
20
+ "list": Endpoint(method="POST", path="/events/list", item_limit=1000),
19
21
  },
20
22
  )
21
23
 
@@ -25,7 +27,7 @@ class EventsAPI(CDFResourceAPI[InternalOrExternalId, EventRequest, EventResponse
25
27
  def _reference_response(self, response: SuccessResponse2) -> ResponseItems[InternalOrExternalId]:
26
28
  return ResponseItems[InternalOrExternalId].model_validate_json(response.body)
27
29
 
28
- def create(self, items: list[EventRequest]) -> list[EventResponse]:
30
+ def create(self, items: Sequence[EventRequest]) -> list[EventResponse]:
29
31
  """Create events in CDF.
30
32
 
31
33
  Args:
@@ -44,16 +46,32 @@ class EventsAPI(CDFResourceAPI[InternalOrExternalId, EventRequest, EventResponse
44
46
  Returns:
45
47
  List of retrieved EventResponse objects.
46
48
  """
47
- return self._request_item_response(items, method="retrieve", params={"ignoreUnknownIds": ignore_unknown_ids})
49
+ return self._request_item_response(
50
+ items, method="retrieve", extra_body={"ignoreUnknownIds": ignore_unknown_ids}
51
+ )
52
+
53
+ def update(
54
+ self, items: Sequence[EventRequest], mode: Literal["patch", "replace"] = "replace"
55
+ ) -> list[EventResponse]:
56
+ """Update events in CDF.
57
+
58
+ Args:
59
+ items: List of EventRequest objects to update.
60
+ mode: Update mode, either "patch" or "replace".
61
+
62
+ Returns:
63
+ List of updated EventResponse objects.
64
+ """
65
+ return self._update(items, mode=mode)
48
66
 
49
- def delete(self, items: list[InternalOrExternalId], ignore_unknown_ids: bool = False) -> None:
67
+ def delete(self, items: Sequence[InternalOrExternalId], ignore_unknown_ids: bool = False) -> None:
50
68
  """Delete events from CDF.
51
69
 
52
70
  Args:
53
71
  items: List of InternalOrExternalId objects to delete.
54
72
  ignore_unknown_ids: Whether to ignore unknown IDs.
55
73
  """
56
- self._request_no_response(items, "delete", params={"ignoreUnknownIds": ignore_unknown_ids})
74
+ self._request_no_response(items, "delete", extra_body={"ignoreUnknownIds": ignore_unknown_ids})
57
75
 
58
76
  def iterate(
59
77
  self,
@@ -1,4 +1,5 @@
1
1
  from collections.abc import Sequence
2
+ from typing import Literal
2
3
 
3
4
  from cognite_toolkit._cdf_tk.client.cdf_client import CDFResourceAPI, PagedResponse, ResponseItems
4
5
  from cognite_toolkit._cdf_tk.client.cdf_client.api import Endpoint
@@ -16,10 +17,13 @@ class TimeSeriesAPI(CDFResourceAPI[InternalOrExternalId, TimeSeriesRequest, Time
16
17
  "retrieve": Endpoint(
17
18
  method="POST", path="/timeseries/byids", item_limit=1000, concurrency_max_workers=1
18
19
  ),
20
+ "update": Endpoint(
21
+ method="POST", path="/timeseries/update", item_limit=1000, concurrency_max_workers=1
22
+ ),
19
23
  "delete": Endpoint(
20
24
  method="POST", path="/timeseries/delete", item_limit=1000, concurrency_max_workers=1
21
25
  ),
22
- "list": Endpoint(method="GET", path="/timeseries", item_limit=1000),
26
+ "list": Endpoint(method="POST", path="/timeseries/list", item_limit=1000),
23
27
  },
24
28
  )
25
29
 
@@ -29,7 +33,7 @@ class TimeSeriesAPI(CDFResourceAPI[InternalOrExternalId, TimeSeriesRequest, Time
29
33
  def _reference_response(self, response: SuccessResponse2) -> ResponseItems[InternalOrExternalId]:
30
34
  return ResponseItems[InternalOrExternalId].model_validate_json(response.body)
31
35
 
32
- def create(self, items: list[TimeSeriesRequest]) -> list[TimeSeriesResponse]:
36
+ def create(self, items: Sequence[TimeSeriesRequest]) -> list[TimeSeriesResponse]:
33
37
  """Create time series in CDF.
34
38
 
35
39
  Args:
@@ -50,16 +54,32 @@ class TimeSeriesAPI(CDFResourceAPI[InternalOrExternalId, TimeSeriesRequest, Time
50
54
  Returns:
51
55
  List of retrieved TimeSeriesResponse objects.
52
56
  """
53
- return self._request_item_response(items, method="retrieve", params={"ignoreUnknownIds": ignore_unknown_ids})
57
+ return self._request_item_response(
58
+ items, method="retrieve", extra_body={"ignoreUnknownIds": ignore_unknown_ids}
59
+ )
60
+
61
+ def update(
62
+ self, items: Sequence[TimeSeriesRequest], mode: Literal["patch", "replace"] = "replace"
63
+ ) -> list[TimeSeriesResponse]:
64
+ """Update time series in CDF.
65
+
66
+ Args:
67
+ items: List of TimeSeriesRequest objects to update.
68
+ mode: Update mode, either "patch" or "replace".
69
+
70
+ Returns:
71
+ List of updated TimeSeriesResponse objects.
72
+ """
73
+ return self._update(items, mode=mode)
54
74
 
55
- def delete(self, items: list[InternalOrExternalId], ignore_unknown_ids: bool = False) -> None:
75
+ def delete(self, items: Sequence[InternalOrExternalId], ignore_unknown_ids: bool = False) -> None:
56
76
  """Delete time series from CDF.
57
77
 
58
78
  Args:
59
79
  items: List of InternalOrExternalId objects to delete.
60
80
  ignore_unknown_ids: Whether to ignore unknown IDs.
61
81
  """
62
- self._request_no_response(items, "delete", params={"ignoreUnknownIds": ignore_unknown_ids})
82
+ self._request_no_response(items, "delete", extra_body={"ignoreUnknownIds": ignore_unknown_ids})
63
83
 
64
84
  def iterate(
65
85
  self,
@@ -0,0 +1,127 @@
1
+ from typing import Annotated, Any, Literal
2
+
3
+ from pydantic import BeforeValidator, Field
4
+
5
+ from cognite_toolkit._cdf_tk.client.data_classes.base import BaseModelObject, RequestResource, ResponseResource
6
+ from tests.test_unit.test_cdf_tk.test_tk_warnings.test_warnings_metatest import get_all_subclasses
7
+
8
+ from .identifiers import ExternalId
9
+
10
+
11
+ class AgentToolDefinition(BaseModelObject):
12
+ type: str
13
+ name: str
14
+ description: str
15
+
16
+
17
+ class AskDocument(AgentToolDefinition):
18
+ type: Literal["askDocument"] = "askDocument"
19
+
20
+
21
+ class ExamineDataSemantically(AgentToolDefinition):
22
+ type: Literal["examineDataSemantically"] = "examineDataSemantically"
23
+
24
+
25
+ class AgentDataModel(BaseModelObject):
26
+ space: str
27
+ external_id: str
28
+ version: str
29
+ view_external_ids: list[str] | None = None
30
+
31
+
32
+ class AgentInstanceSpacesDefinition(BaseModelObject):
33
+ type: str
34
+
35
+
36
+ class AllInstanceSpaces(AgentInstanceSpacesDefinition):
37
+ type: Literal["all"] = "all"
38
+
39
+
40
+ class ManualInstanceSpaces(AgentInstanceSpacesDefinition):
41
+ type: Literal["manual"] = "manual"
42
+ spaces: list[str]
43
+
44
+
45
+ AgentInstanceSpaces = Annotated[
46
+ AllInstanceSpaces | ManualInstanceSpaces,
47
+ Field(discriminator="type"),
48
+ ]
49
+
50
+
51
+ class QueryKnowledgeGraphConfig(BaseModelObject):
52
+ data_models: list[AgentDataModel]
53
+ instance_spaces: AgentInstanceSpaces | None = None
54
+ # This is deviating from the API documentation, but the Atlas team has confirmed that "v2" is the default
55
+ version: Literal["v1", "v2"] = "v2"
56
+
57
+
58
+ class QueryKnowledgeGraph(AgentToolDefinition):
59
+ type: Literal["queryKnowledgeGraph"] = "queryKnowledgeGraph"
60
+ configuration: QueryKnowledgeGraphConfig
61
+
62
+
63
+ class QueryTimeSeriesDatapoints(AgentToolDefinition):
64
+ type: Literal["queryTimeSeriesDatapoints"] = "queryTimeSeriesDatapoints"
65
+
66
+
67
+ class SummarizeDocument(AgentToolDefinition):
68
+ type: Literal["summarizeDocument"] = "summarizeDocument"
69
+
70
+
71
+ class UnknownAgentTool(AgentToolDefinition):
72
+ """Fallback for unknown tool types."""
73
+
74
+ ...
75
+
76
+
77
+ KNOWN_TOOLS = {tool.type: tool for tool in get_all_subclasses(AgentToolDefinition) if hasattr(tool, "type")}
78
+
79
+
80
+ def _handle_unknown_tool(value: Any) -> Any:
81
+ if isinstance(value, dict):
82
+ tool_type = value.get("type")
83
+ if tool_type not in KNOWN_TOOLS:
84
+ return UnknownAgentTool(**value)
85
+ else:
86
+ return KNOWN_TOOLS[tool_type].model_validate(value)
87
+ return value
88
+
89
+
90
+ AgentTool = Annotated[
91
+ AskDocument
92
+ | QueryKnowledgeGraph
93
+ | QueryTimeSeriesDatapoints
94
+ | SummarizeDocument
95
+ | ExamineDataSemantically
96
+ | UnknownAgentTool,
97
+ BeforeValidator(_handle_unknown_tool),
98
+ ]
99
+
100
+
101
+ class AgentRequest(RequestResource):
102
+ external_id: str
103
+ name: str
104
+ description: str | None = None
105
+ instructions: str | None = None
106
+ model: str = "azure/gpt-4o-mini"
107
+ tools: list[AgentTool] | None = None
108
+ runtime_version: str | None = None
109
+
110
+ def as_id(self) -> ExternalId:
111
+ return ExternalId(external_id=self.external_id)
112
+
113
+
114
+ class AgentResponse(ResponseResource[AgentRequest]):
115
+ created_time: int
116
+ last_updated_time: int
117
+ owner_id: str
118
+ external_id: str
119
+ name: str
120
+ description: str | None = None
121
+ instructions: str | None = None
122
+ model: str = "azure/gpt-4o-mini"
123
+ tools: list[AgentTool] | None = None
124
+ runtime_version: str
125
+
126
+ def as_request_resource(self) -> AgentRequest:
127
+ return AgentRequest.model_validate(self.dump(), extra="ignore")
@@ -1,4 +1,4 @@
1
- from typing import Literal
1
+ from typing import ClassVar, Literal
2
2
 
3
3
  from pydantic import JsonValue
4
4
 
@@ -8,8 +8,8 @@ from .identifiers import ExternalId
8
8
 
9
9
 
10
10
  class AssetRequest(RequestUpdateable):
11
- container_fields = frozenset({"metadata", "labels"})
12
- non_nullable_fields = frozenset({"parent_id", "parent_external_id"})
11
+ container_fields: ClassVar[frozenset[str]] = frozenset({"metadata", "labels"})
12
+ non_nullable_fields: ClassVar[frozenset[str]] = frozenset({"parent_id", "parent_external_id"})
13
13
  external_id: str | None = None
14
14
  name: str
15
15
  parent_id: int | None = None
@@ -1,9 +1,12 @@
1
- from cognite_toolkit._cdf_tk.client.data_classes.base import RequestResource, ResponseResource
1
+ from typing import ClassVar
2
+
3
+ from cognite_toolkit._cdf_tk.client.data_classes.base import RequestUpdateable, ResponseResource
2
4
 
3
5
  from .identifiers import ExternalId, InternalOrExternalId
4
6
 
5
7
 
6
- class EventRequest(RequestResource):
8
+ class EventRequest(RequestUpdateable):
9
+ container_fields: ClassVar[frozenset[str]] = frozenset({"metadata", "asset_ids"})
7
10
  external_id: str | None = None
8
11
  data_set_id: int | None = None
9
12
  start_time: int | None = None
@@ -0,0 +1,56 @@
1
+ from typing import ClassVar, Literal
2
+
3
+ from pydantic import JsonValue
4
+
5
+ from cognite_toolkit._cdf_tk.client.data_classes.base import RequestUpdateable, ResponseResource
6
+
7
+ from .identifiers import ExternalId
8
+ from .instance_api import NodeReference
9
+
10
+
11
+ class FileMetadataRequest(RequestUpdateable):
12
+ container_fields: ClassVar[frozenset[str]] = frozenset({"metadata", "labels", "asset_ids", "security_categories"})
13
+ non_nullable_fields: ClassVar[frozenset[str]] = frozenset({"asset_ids", "security_categories"})
14
+ external_id: str | None = None
15
+ name: str
16
+ directory: str | None = None
17
+ source: str | None = None
18
+ mime_type: str | None = None
19
+ metadata: dict[str, str] | None = None
20
+ asset_ids: list[int] | None = None
21
+ data_set_id: int | None = None
22
+ labels: list[dict[Literal["externalId"], str]] | None = None
23
+ geo_location: JsonValue | None = None
24
+ source_created_time: int | None = None
25
+ source_modified_time: int | None = None
26
+ security_categories: list[int] | None = None
27
+
28
+ def as_id(self) -> ExternalId:
29
+ if self.external_id is None:
30
+ raise ValueError("Cannot convert FileMetadataRequest to ExternalId when external_id is None")
31
+ return ExternalId(external_id=self.external_id)
32
+
33
+
34
+ class FileMetadataResponse(ResponseResource[FileMetadataRequest]):
35
+ created_time: int
36
+ last_updated_time: int
37
+ uploaded_time: int | None = None
38
+ uploaded: bool
39
+ id: int
40
+ external_id: str | None = None
41
+ name: str
42
+ directory: str | None = None
43
+ instance_id: NodeReference | None = None
44
+ source: str | None = None
45
+ mime_type: str | None = None
46
+ metadata: dict[str, str] | None = None
47
+ asset_ids: list[int] | None = None
48
+ data_set_id: int | None = None
49
+ labels: list[dict[Literal["externalId"], str]] | None = None
50
+ geo_location: JsonValue | None = None
51
+ source_created_time: int | None = None
52
+ source_modified_time: int | None = None
53
+ security_categories: list[int] | None = None
54
+
55
+ def as_request_resource(self) -> FileMetadataRequest:
56
+ return FileMetadataRequest.model_validate(self.dump(), extra="ignore")
@@ -0,0 +1,43 @@
1
+ import sys
2
+
3
+ from pydantic import Field
4
+
5
+ from cognite_toolkit._cdf_tk.client.data_classes.base import (
6
+ Identifier,
7
+ RequestResource,
8
+ ResponseResource,
9
+ )
10
+
11
+ if sys.version_info >= (3, 11):
12
+ from typing import Self
13
+ else:
14
+ from typing_extensions import Self
15
+
16
+
17
+ class RAWDatabase(RequestResource, Identifier, ResponseResource["RAWDatabase"]):
18
+ name: str
19
+
20
+ def as_id(self) -> Self:
21
+ return self
22
+
23
+ def __str__(self) -> str:
24
+ return f"name='{self.name}'"
25
+
26
+ def as_request_resource(self) -> "RAWDatabase":
27
+ return type(self).model_validate(self.dump(), extra="ignore")
28
+
29
+
30
+ class RAWTable(RequestResource, Identifier, ResponseResource["RAWTable"]):
31
+ # This is a query parameter, so we exclude it from serialization
32
+ db_name: str = Field(exclude=True)
33
+ name: str
34
+
35
+ def as_id(self) -> Self:
36
+ return self
37
+
38
+ def __str__(self) -> str:
39
+ return f"dbName='{self.db_name}', tableName='{self.name}'"
40
+
41
+ def as_request_resource(self) -> "RAWTable":
42
+ dumped = {**self.dump(), "dbName": self.db_name}
43
+ return type(self).model_validate(dumped, extra="ignore")
@@ -1,13 +1,17 @@
1
- from cognite_toolkit._cdf_tk.client.data_classes.base import RequestResource, ResponseResource
1
+ from typing import Any, ClassVar, Literal
2
+
3
+ from cognite_toolkit._cdf_tk.client.data_classes.base import RequestUpdateable, ResponseResource
2
4
 
3
5
  from .identifiers import ExternalId, InternalOrExternalId
4
6
  from .instance_api import NodeReference
5
7
 
6
8
 
7
- class TimeSeriesRequest(RequestResource):
9
+ class TimeSeriesRequest(RequestUpdateable):
10
+ container_fields: ClassVar[frozenset[str]] = frozenset({"metadata", "security_categories"})
11
+ non_nullable_fields: ClassVar[frozenset[str]] = frozenset({"is_step"})
8
12
  external_id: str | None = None
9
13
  name: str | None = None
10
- is_sting: bool = False
14
+ is_string: bool = False
11
15
  metadata: dict[str, str] | None = None
12
16
  unit: str | None = None
13
17
  unit_external_id: str | None = None
@@ -22,13 +26,19 @@ class TimeSeriesRequest(RequestResource):
22
26
  raise ValueError("Cannot convert TimeSeriesRequest to ExternalId when external_id is None")
23
27
  return ExternalId(external_id=self.external_id)
24
28
 
29
+ def as_update(self, mode: Literal["patch", "replace"]) -> dict[str, Any]:
30
+ dumped = super().as_update(mode)
31
+ # isString is immutable in CDF, so we remove it from update payloads
32
+ dumped.pop("isString", None)
33
+ return dumped
34
+
25
35
 
26
36
  class TimeSeriesResponse(ResponseResource[TimeSeriesRequest]):
27
37
  id: int
28
38
  instance_id: NodeReference | None = None
29
39
  external_id: str | None = None
30
40
  name: str | None = None
31
- is_sting: bool
41
+ is_string: bool
32
42
  metadata: dict[str, str] | None = None
33
43
  unit: str | None = None
34
44
  type: str
@@ -33,6 +33,7 @@ from cognite_toolkit._cdf_tk.client.api.legacy.robotics import LocationsAPI as R
33
33
  from cognite_toolkit._cdf_tk.client.api.legacy.search_config import SearchConfigurationsAPI
34
34
 
35
35
  from ._toolkit_client import ToolAPI
36
+ from .api.events import EventsAPI
36
37
  from .api.infield import InfieldAPI, InFieldCDMConfigAPI, InfieldConfigAPI
37
38
  from .api.lookup import (
38
39
  AssetLookUpAPI,
@@ -58,6 +59,7 @@ from .api.project import ProjectAPI
58
59
  from .api.search import SearchAPI
59
60
  from .api.streams import StreamsAPI
60
61
  from .api.three_d import ThreeDAPI, ThreeDModelAPI
62
+ from .api.timeseries import TimeSeriesAPI
61
63
  from .api.token import TokenAPI
62
64
  from .api.verify import VerifyAPI
63
65
 
@@ -140,6 +142,8 @@ class ToolkitClientMock(CogniteClientMock):
140
142
  self.tool.three_d = MagicMock(spec=ThreeDAPI)
141
143
  self.tool.three_d.models = MagicMock(spec_set=ThreeDModelAPI)
142
144
  self.tool.assets = MagicMock(spec_set=AssetsAPI)
145
+ self.tool.time_series = MagicMock(spec_set=TimeSeriesAPI)
146
+ self.tool.events = MagicMock(spec_set=EventsAPI)
143
147
 
144
148
  self.streams = MagicMock(spec=StreamsAPI)
145
149
 
@@ -1,5 +1,6 @@
1
1
  import dataclasses
2
2
  import itertools
3
+ import json
3
4
  import re
4
5
  import sys
5
6
  import tempfile
@@ -28,6 +29,7 @@ from cognite_toolkit._cdf_tk.cruds import (
28
29
  HostedExtractorSourceCRUD,
29
30
  ResourceCRUD,
30
31
  StreamlitCRUD,
32
+ ViewCRUD,
31
33
  )
32
34
  from cognite_toolkit._cdf_tk.data_classes import (
33
35
  BuildEnvironment,
@@ -643,6 +645,44 @@ class PullCommand(ToolkitCommand):
643
645
  loader: ResourceCRUD[T_ID, T_ResourceRequest, T_ResourceResponse],
644
646
  source_file: Path,
645
647
  ) -> tuple[str, dict[Path, str]]:
648
+ """Convert resource data from CDF into YAML file content ready to be written to disk.
649
+
650
+ This method takes the raw CDF resource data and transforms it back into a properly
651
+ formatted YAML file that preserves:
652
+ - Template variables (e.g., {{ variable_name }}) instead of their resolved values
653
+ - YAML comments from the original source file
654
+ - The original key ordering in dictionaries
655
+
656
+ The transformation process:
657
+ 1. Replace all template variables with unique placeholders
658
+ 2. Load source YAML content while preserving comments
659
+ 3. Update the resource data with placeholder values where variables were used
660
+ 4. Dump the updated data back to YAML format
661
+ 5. Replace placeholders with the original template variable syntax
662
+ 6. Restore the YAML comments
663
+
664
+ Args:
665
+ source: The original YAML file content as a string.
666
+ to_write: A mapping from resource identifiers to their updated data dictionaries
667
+ pulled from CDF.
668
+ resources: The list of built resources containing build variables and metadata.
669
+ environment_variables: A mapping of environment variable names to their values,
670
+ used to resolve variables like ${VAR_NAME} in template values.
671
+ loader: The ResourceCRUD loader instance for this resource type.
672
+ source_file: The path to the source file being processed.
673
+
674
+ Returns:
675
+ A tuple containing:
676
+ - The final YAML content string ready to be written to disk.
677
+ - A dictionary mapping extra file paths to their content (for resources
678
+ that have additional files, like SQL queries for transformations).
679
+
680
+ Raises:
681
+ ValueError: If the loaded YAML structure doesn't match between the original
682
+ and placeholder versions.
683
+ ToolkitMissingResourceError: If a resource identifier is not found in the
684
+ to_write or resources mappings.
685
+ """
646
686
  # 1. Replace all variables with placeholders
647
687
  # 2. Load source and keep the comments
648
688
  # 3. Update the to_write dict with the placeholders
@@ -762,9 +802,22 @@ class PullCommand(ToolkitCommand):
762
802
 
763
803
 
764
804
  class ResourceReplacer:
765
- """Replaces values in a local resource directory with the updated values from CDF.
805
+ """Replaces values in a local resource dictionary with the updated values from CDF.
766
806
 
767
807
  The local resource dict order is maintained. In addition, placeholders are used for variables.
808
+
809
+ This class is responsible for merging CDF resource values back into local configuration files
810
+ while preserving:
811
+ - The original key ordering in dictionaries
812
+ - Template variable placeholders (e.g., {{ variable_name }})
813
+ - Comments and formatting where possible
814
+
815
+ Args:
816
+ value_by_placeholder: A mapping from placeholder strings to their corresponding
817
+ BuildVariable objects. Placeholders are temporary substitutes for template
818
+ variables during processing.
819
+ loader: The ResourceCRUD loader instance used to determine how to diff lists
820
+ and handle resource-specific logic.
768
821
  """
769
822
 
770
823
  def __init__(self, value_by_placeholder: dict[str, BuildVariable], loader: ResourceCRUD) -> None:
@@ -777,7 +830,49 @@ class ResourceReplacer:
777
830
  placeholder: dict[str, Any],
778
831
  to_write: dict[str, Any],
779
832
  ) -> dict[str, Any]:
780
- return self._replace_dict(current, placeholder, to_write, tuple())
833
+ """Replace values in a local resource dict with updated values from CDF.
834
+
835
+ Merges the CDF resource values into the local configuration while maintaining
836
+ the original dictionary key ordering and preserving template variable placeholders.
837
+
838
+ Args:
839
+ current: The current local resource dictionary with resolved variable values.
840
+ This represents the source file content after template variables have
841
+ been substituted with their actual values.
842
+ placeholder: The local resource dictionary with placeholder strings instead
843
+ of resolved values. Used to identify which values contain template
844
+ variables that should be preserved.
845
+ to_write: The resource dictionary from CDF containing the updated values
846
+ to merge into the local configuration.
847
+
848
+ Returns:
849
+ A new dictionary with CDF values merged in, maintaining the original key
850
+ order from `current`. Template variables are preserved as placeholders
851
+ (to be converted back to {{ variable }} syntax by the caller). New keys
852
+ from CDF are appended at the end, and removed keys are omitted.
853
+
854
+ Raises:
855
+ ToolkitValueError: If a list variable has changed and cannot be updated,
856
+ or if there's a type mismatch between local and CDF values.
857
+ """
858
+ has_stringified_view_filter = False
859
+ if isinstance(self._loader, ViewCRUD):
860
+ # view.filter are recursive nested dicts that are complex. To avoid issues with comparing
861
+ # lists inside the filters, we stringify them before processing such that they are compared
862
+ # as strings.
863
+ processed = []
864
+ for d in (current, placeholder, to_write):
865
+ if isinstance(d.get("filter"), dict):
866
+ d = d.copy()
867
+ d["filter"] = json.dumps(d["filter"])
868
+ has_stringified_view_filter = True
869
+ processed.append(d)
870
+ current, placeholder, to_write = processed
871
+ output = self._replace_dict(current, placeholder, to_write, tuple())
872
+ if has_stringified_view_filter and "filter" in output:
873
+ # Special case for ViewCRUD where the filter is stringified in CDF
874
+ output["filter"] = json.loads(output["filter"])
875
+ return output
781
876
 
782
877
  def _replace_dict(
783
878
  self,
@@ -12,7 +12,7 @@ jobs:
12
12
  environment: dev
13
13
  name: Deploy
14
14
  container:
15
- image: cognite/toolkit:0.7.40
15
+ image: cognite/toolkit:0.7.41
16
16
  env:
17
17
  CDF_CLUSTER: ${{ vars.CDF_CLUSTER }}
18
18
  CDF_PROJECT: ${{ vars.CDF_PROJECT }}
@@ -10,7 +10,7 @@ jobs:
10
10
  environment: dev
11
11
  name: Deploy Dry Run
12
12
  container:
13
- image: cognite/toolkit:0.7.40
13
+ image: cognite/toolkit:0.7.41
14
14
  env:
15
15
  CDF_CLUSTER: ${{ vars.CDF_CLUSTER }}
16
16
  CDF_PROJECT: ${{ vars.CDF_PROJECT }}
@@ -4,7 +4,7 @@ default_env = "<DEFAULT_ENV_PLACEHOLDER>"
4
4
  [modules]
5
5
  # This is the version of the modules. It should not be changed manually.
6
6
  # It will be updated by the 'cdf modules upgrade' command.
7
- version = "0.7.40"
7
+ version = "0.7.41"
8
8
 
9
9
 
10
10
  [plugins]
@@ -1 +1 @@
1
- __version__ = "0.7.40"
1
+ __version__ = "0.7.41"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cognite_toolkit
3
- Version: 0.7.40
3
+ Version: 0.7.41
4
4
  Summary: Official Cognite Data Fusion tool for project templates and configuration deployment
5
5
  Author: Cognite AS
6
6
  Author-email: Cognite AS <support@cognite.com>
@@ -29,10 +29,10 @@ cognite_toolkit/_cdf_tk/builders/_transformation.py,sha256=STB42zhzOW5M_-b8cKOQ_
29
29
  cognite_toolkit/_cdf_tk/cdf_toml.py,sha256=VSWV9h44HusWIaKpWgjrOMrc3hDoPTTXBXlp6-NOrIM,9079
30
30
  cognite_toolkit/_cdf_tk/client/__init__.py,sha256=a6rQXDGfW2g7K5WwrOW5oakh1TdFlBjUVjf9wusOox8,135
31
31
  cognite_toolkit/_cdf_tk/client/_constants.py,sha256=COUGcea37mDF2sf6MGqJXWmecTY_6aCImslxXrYW1I0,73
32
- cognite_toolkit/_cdf_tk/client/_toolkit_client.py,sha256=nX_Q2-VX1nD8b7FoSNfQ8uq96ioFtgOfDHSEmgw3Qrw,3830
32
+ cognite_toolkit/_cdf_tk/client/_toolkit_client.py,sha256=AxGfAlqwY3xJK5fQOFVu0SlMrC9gSKkwNN_hBdrULZ0,4005
33
33
  cognite_toolkit/_cdf_tk/client/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
34
  cognite_toolkit/_cdf_tk/client/api/assets.py,sha256=Mphk5zuZC7sVnLXjx27Q-8z8q2p4T38S8tka5HABTVQ,4781
35
- cognite_toolkit/_cdf_tk/client/api/events.py,sha256=gvllwHEctxSALiu9KJcv9rTMRlw8pyjnqSijEYf5op4,3290
35
+ cognite_toolkit/_cdf_tk/client/api/events.py,sha256=JysQP-pr3DFTJ82jyZRG2wzvcQAUb7gIDvl5lJMA1GU,3911
36
36
  cognite_toolkit/_cdf_tk/client/api/infield.py,sha256=QkZ-KVDtSwq0X1ztM4xbqO2IL6ql5gJSjSuLIpqOEhA,11036
37
37
  cognite_toolkit/_cdf_tk/client/api/legacy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
38
  cognite_toolkit/_cdf_tk/client/api/legacy/canvas.py,sha256=t5TNNtoCyOHwQbbYetT27Xh5eSwNIZVnBBCIh7y8KC4,9299
@@ -61,7 +61,7 @@ cognite_toolkit/_cdf_tk/client/api/project.py,sha256=Zkm--qIZgRqCyAxV4dSijrC7nc9
61
61
  cognite_toolkit/_cdf_tk/client/api/search.py,sha256=wl6MjmkELCWmkXf9yYM03LElLC5l0_DwwifsZc_tXSg,694
62
62
  cognite_toolkit/_cdf_tk/client/api/streams.py,sha256=5Ex8gRJhvtuPuv0aQVg_DKG7DxfXZfyp2y8tRmt93gQ,3045
63
63
  cognite_toolkit/_cdf_tk/client/api/three_d.py,sha256=UQfquguKunNX3qD65255N8CFtA-SBFBHzOZwxFFOcg4,15984
64
- cognite_toolkit/_cdf_tk/client/api/timeseries.py,sha256=80YTUo75jmdmSssvcBblok0xsDHI7JXGJq4YmiAFyMo,3510
64
+ cognite_toolkit/_cdf_tk/client/api/timeseries.py,sha256=jAZWR0OeiKHRz8EFC9v-8ucYUaN7xcW4PInLGZX-KDo,4198
65
65
  cognite_toolkit/_cdf_tk/client/api/token.py,sha256=8SiA44Dwsx0j_X8lgIxl2rdNCQSdEiSfoD_4ybxMtFA,5131
66
66
  cognite_toolkit/_cdf_tk/client/api/verify.py,sha256=-x6z6lMaOZG91adi0m9NtJ4wIQgoZURbzluPALXM-ps,3730
67
67
  cognite_toolkit/_cdf_tk/client/api_client.py,sha256=CQdD_gfDqQkz5OYHrTnKvBvEvzHPdHDB1BkZPWRoahg,440
@@ -70,11 +70,13 @@ cognite_toolkit/_cdf_tk/client/cdf_client/api.py,sha256=gebhyi7wAOIaNxrnS26pkj-X
70
70
  cognite_toolkit/_cdf_tk/client/cdf_client/responses.py,sha256=Rhv3ekirfdz-Y1Por8fu1yrC1_2feyi92dAJ8xMEGUU,689
71
71
  cognite_toolkit/_cdf_tk/client/config.py,sha256=weMR43z-gqHMn-Jqvfmh_nJ0HbgEdyeCGtISuEf3OuY,4269
72
72
  cognite_toolkit/_cdf_tk/client/data_classes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
73
- cognite_toolkit/_cdf_tk/client/data_classes/asset.py,sha256=rn0NS9_96uxRAfuSlf6Rroxu-kVd1WKTgHBpSk9Etsg,1808
73
+ cognite_toolkit/_cdf_tk/client/data_classes/agent.py,sha256=4MtebdmOKlaqy1Duwwkoe_I2Q3Y7A0VbQucoYVbtxw4,3426
74
+ cognite_toolkit/_cdf_tk/client/data_classes/asset.py,sha256=POZyWG9E4CpMd3azZLL14s6XLGPKLWRA_JxBMHn8To0,1870
74
75
  cognite_toolkit/_cdf_tk/client/data_classes/base.py,sha256=Gzc2co19NGJRJwCVbVCqxeeTdEOI73yinARwmqgnQvk,6169
75
76
  cognite_toolkit/_cdf_tk/client/data_classes/capabilities.py,sha256=muqpAC2JLCFcEpRPzuh_3sS3o_q42WFyfsGzl-LfB_U,8773
76
77
  cognite_toolkit/_cdf_tk/client/data_classes/charts_data.py,sha256=-dFfY53cos5DwASLU18aBfYF1VC6bfaUshC2HiGJ2uI,5571
77
- cognite_toolkit/_cdf_tk/client/data_classes/event.py,sha256=i88NFxa2O7x_3Q303ZT6_-daqs8dVt2aoihJgVJPg4E,1339
78
+ cognite_toolkit/_cdf_tk/client/data_classes/event.py,sha256=HGDXmLz_NibWGKcfDtejFmeZKL24lP6xQd4WbqUHu1U,1458
79
+ cognite_toolkit/_cdf_tk/client/data_classes/filemetadata.py,sha256=Dmab73qyAXV1lmhnqRhqDCSNlCsMHyd65IhSpb6LlN0,2125
78
80
  cognite_toolkit/_cdf_tk/client/data_classes/identifiers.py,sha256=VZ6mYDR715rjOlHMaspWgCSa5VSKnZUyFq8Lnsjz3nA,1133
79
81
  cognite_toolkit/_cdf_tk/client/data_classes/infield.py,sha256=xZDpHw190FgX2vK6zk_r8dUJA7J6UzdS8227VOu74ms,4298
80
82
  cognite_toolkit/_cdf_tk/client/data_classes/instance_api.py,sha256=bAGdLl5pNmIKoLx8CFe5TjFViAmIoz9aIV1geN1tN1A,6276
@@ -98,16 +100,17 @@ cognite_toolkit/_cdf_tk/client/data_classes/legacy/robotics.py,sha256=1hlcuFANJW
98
100
  cognite_toolkit/_cdf_tk/client/data_classes/legacy/search_config.py,sha256=Reo_rcFrwk_sWEtIGd87UNgD0k3Zy8f52XIjkiPxGG8,8155
99
101
  cognite_toolkit/_cdf_tk/client/data_classes/legacy/sequences.py,sha256=02d34fPcJ1H7U5ZnCCfOi36z5WJ4WnRfCWwkp99mW2E,6234
100
102
  cognite_toolkit/_cdf_tk/client/data_classes/legacy/streamlit_.py,sha256=nEk00FH3i-px2r6ql4kk1VVL4sytjUn0_sTkEdDSHVc,6746
103
+ cognite_toolkit/_cdf_tk/client/data_classes/raw.py,sha256=_6woOd-9VTP96SZTq5O31up6nzfN_xlPEkUgnS88uUY,1127
101
104
  cognite_toolkit/_cdf_tk/client/data_classes/streams.py,sha256=CKvb8oxQ-4BuN_Axp3SJj5RQ2KF3j4SqFRhrhx8jLLE,2526
102
105
  cognite_toolkit/_cdf_tk/client/data_classes/three_d.py,sha256=pJqsviRPau5V5t0We1Nnhq9RSBv2DqWmRLl9eK1ZWQk,3438
103
- cognite_toolkit/_cdf_tk/client/data_classes/timeseries.py,sha256=UWUCPPZxE-06Jn7DbrraMHfBfZpSNsGEjUIBVqiWiLk,1524
106
+ cognite_toolkit/_cdf_tk/client/data_classes/timeseries.py,sha256=xA7Qzg2QVcTig_Qvh1xhSCjW2tIq3AzPyik7VmloSm4,2000
104
107
  cognite_toolkit/_cdf_tk/client/http_client/__init__.py,sha256=JhxTcEfagUA54AZY6v7bS8SBMeEJhciR-4gbRBmGPSU,1473
105
108
  cognite_toolkit/_cdf_tk/client/http_client/_client.py,sha256=HxIsSqBpp35k990u1s_SXDjv22OjE7b8kdUvOS98yYU,22477
106
109
  cognite_toolkit/_cdf_tk/client/http_client/_data_classes.py,sha256=PMVmI_-a-Skq_u9LPJ5rkXNuW6haelLrzLnEmD-Rgng,14966
107
110
  cognite_toolkit/_cdf_tk/client/http_client/_data_classes2.py,sha256=jZ4qNh5iEg1E7uvANipm3xArxVldAYh4n63atcf-IQw,7905
108
111
  cognite_toolkit/_cdf_tk/client/http_client/_exception.py,sha256=fC9oW6BN0HbUe2AkYABMP7Kj0-9dNYXVFBY5RQztq2c,126
109
112
  cognite_toolkit/_cdf_tk/client/http_client/_tracker.py,sha256=pu6oA-XpOeaOLdoeD_mGfJXC3BFGWfh5oGcRDpb6maw,1407
110
- cognite_toolkit/_cdf_tk/client/testing.py,sha256=Q0bIJn8j786I9Op352fPcNqBA-UHXmKQJR6qCBeo_Wc,7507
113
+ cognite_toolkit/_cdf_tk/client/testing.py,sha256=Lzqk_pFPUJqAzRXaBbUNeLBd_DBu9oyC24F2WEg_RaA,7706
111
114
  cognite_toolkit/_cdf_tk/client/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
112
115
  cognite_toolkit/_cdf_tk/client/utils/_concurrency.py,sha256=3GtQbKDaosyKHEt-KzxKK9Yie4TvZPdoou2vUk6dUa8,2298
113
116
  cognite_toolkit/_cdf_tk/client/utils/_http_client.py,sha256=oXNKrIaizG4WiSAhL_kSCHAuL4aaaEhCU4pOJGxh6Xs,483
@@ -148,7 +151,7 @@ cognite_toolkit/_cdf_tk/commands/deploy.py,sha256=DpA6q0f17qCpsrYUywavvaRjJ-qaIm
148
151
  cognite_toolkit/_cdf_tk/commands/dump_resource.py,sha256=mNuxRagFxkSRUzRQX5rNpzysxUV8OQfNxh8G_85JghI,39814
149
152
  cognite_toolkit/_cdf_tk/commands/init.py,sha256=pcxFhZheXm3FPU1pkeh10M0WXPg7EcLFUgJlrE817tE,9257
150
153
  cognite_toolkit/_cdf_tk/commands/modules.py,sha256=91Fov16fEIVk-3ZmBd3MlibbbzRuYLjUY9CUnudV4w0,40976
151
- cognite_toolkit/_cdf_tk/commands/pull.py,sha256=3dU-jQj40laL1eURukMwvr1clJz5MZ1ppz0aatGBgso,38944
154
+ cognite_toolkit/_cdf_tk/commands/pull.py,sha256=hQopF9PY4p-CnuuEk4GFLj27gtb6QJ3At4z1ucYia48,43980
152
155
  cognite_toolkit/_cdf_tk/commands/repo.py,sha256=MNy8MWphTklIZHvQOROCweq8_SYxGv6BaqnLpkFFnuk,3845
153
156
  cognite_toolkit/_cdf_tk/commands/resources.py,sha256=NeHVA1b1TMsP-2wgd5u1vif_N6nln7ePxZ0BXypwt-k,7377
154
157
  cognite_toolkit/_cdf_tk/commands/run.py,sha256=0NFMFAgFS8m8fVIhfz28FqKDI20AIbIWEOJMp9MmFsc,37338
@@ -319,14 +322,14 @@ cognite_toolkit/_repo_files/.gitignore,sha256=ip9kf9tcC5OguF4YF4JFEApnKYw0nG0vPi
319
322
  cognite_toolkit/_repo_files/AzureDevOps/.devops/README.md,sha256=OLA0D7yCX2tACpzvkA0IfkgQ4_swSd-OlJ1tYcTBpsA,240
320
323
  cognite_toolkit/_repo_files/AzureDevOps/.devops/deploy-pipeline.yml,sha256=brULcs8joAeBC_w_aoWjDDUHs3JheLMIR9ajPUK96nc,693
321
324
  cognite_toolkit/_repo_files/AzureDevOps/.devops/dry-run-pipeline.yml,sha256=OBFDhFWK1mlT4Dc6mDUE2Es834l8sAlYG50-5RxRtHk,723
322
- cognite_toolkit/_repo_files/GitHub/.github/workflows/deploy.yaml,sha256=pq5a3TfSg6puNx9p8xP0geaRdujHFKh06JZtUcyu0uA,667
323
- cognite_toolkit/_repo_files/GitHub/.github/workflows/dry-run.yaml,sha256=MGsJGlDQOeETLirCkpY92xlDsR9y4TE0jIzB2RFH2tc,2430
324
- cognite_toolkit/_resources/cdf.toml,sha256=oAtH8UQsDIvhVjpptwe_SawsBxKHUlW3cEudDbmkbeM,475
325
- cognite_toolkit/_version.py,sha256=p30XwBqe3JFRswv-rl6fmiBag8rT6uL2uAxa804ZA6Y,23
325
+ cognite_toolkit/_repo_files/GitHub/.github/workflows/deploy.yaml,sha256=vU5axvxU23UHv1jlWp2G30HAL1u2TcJ1j9OC3Wi836U,667
326
+ cognite_toolkit/_repo_files/GitHub/.github/workflows/dry-run.yaml,sha256=7mTyCl1ijNBsQdHzdPwURK5PWlZF-BguxmiBEcGWsto,2430
327
+ cognite_toolkit/_resources/cdf.toml,sha256=F0suS-sTyzcPCGKxQZOaORNrSqGAFbfVtehpbiUU78k,475
328
+ cognite_toolkit/_version.py,sha256=h4pIxiUzLA3N0vLHe7RWZdbQ0eyLS_C3FlNbahA3Dxc,23
326
329
  cognite_toolkit/config.dev.yaml,sha256=M33FiIKdS3XKif-9vXniQ444GTZ-bLXV8aFH86u9iUQ,332
327
330
  cognite_toolkit/demo/__init__.py,sha256=-m1JoUiwRhNCL18eJ6t7fZOL7RPfowhCuqhYFtLgrss,72
328
331
  cognite_toolkit/demo/_base.py,sha256=6xKBUQpXZXGQ3fJ5f7nj7oT0s2n7OTAGIa17ZlKHZ5U,8052
329
- cognite_toolkit-0.7.40.dist-info/WHEEL,sha256=KSLUh82mDPEPk0Bx0ScXlWL64bc8KmzIPNcpQZFV-6E,79
330
- cognite_toolkit-0.7.40.dist-info/entry_points.txt,sha256=EtZ17K2mUjh-AY0QNU1CPIB_aDSSOdmtNI_4Fj967mA,84
331
- cognite_toolkit-0.7.40.dist-info/METADATA,sha256=kE3eNSKqU5uHxyGKuEpQe_5aHdw0wmuPg4i60vv6j5w,4507
332
- cognite_toolkit-0.7.40.dist-info/RECORD,,
332
+ cognite_toolkit-0.7.41.dist-info/WHEEL,sha256=KSLUh82mDPEPk0Bx0ScXlWL64bc8KmzIPNcpQZFV-6E,79
333
+ cognite_toolkit-0.7.41.dist-info/entry_points.txt,sha256=EtZ17K2mUjh-AY0QNU1CPIB_aDSSOdmtNI_4Fj967mA,84
334
+ cognite_toolkit-0.7.41.dist-info/METADATA,sha256=ooWYd_X2bh0cxW5IlYxFhOwWCE2aAtNRHMRWaTWYPB8,4507
335
+ cognite_toolkit-0.7.41.dist-info/RECORD,,