cognite-neat 0.77.4__py3-none-any.whl → 0.77.6__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.
cognite/neat/_version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.77.4"
1
+ __version__ = "0.77.6"
@@ -116,17 +116,17 @@ class ExcelExporter(BaseExporter[Workbook]):
116
116
  }
117
117
 
118
118
  if self.dump_as == "last":
119
- dumped_last_rules = rules.model_dump(by_alias=True)
119
+ dumped_last_rules = rules.dump(by_alias=True)
120
120
  if rules.reference:
121
- dumped_reference_rules = rules.reference.model_dump(by_alias=True)
121
+ dumped_reference_rules = rules.reference.dump(by_alias=True, as_reference=True)
122
122
  elif self.dump_as == "reference":
123
- dumped_reference_rules = rules.reference_self().model_dump(by_alias=True)
123
+ dumped_reference_rules = rules.dump(by_alias=True, as_reference=True)
124
124
  else:
125
- dumped_user_rules = rules.model_dump(by_alias=True)
125
+ dumped_user_rules = rules.dump(by_alias=True)
126
126
  if rules.last:
127
- dumped_last_rules = rules.last.model_dump(by_alias=True)
127
+ dumped_last_rules = rules.last.dump(by_alias=True)
128
128
  if rules.reference:
129
- dumped_reference_rules = rules.reference.model_dump(by_alias=True)
129
+ dumped_reference_rules = rules.reference.dump(by_alias=True, as_reference=True)
130
130
 
131
131
  self._write_metadata_sheet(workbook, dumped_user_rules["Metadata"])
132
132
  self._write_sheets(workbook, dumped_user_rules, rules)
@@ -69,10 +69,10 @@ class YAMLExporter(BaseExporter[str]):
69
69
  rules = self._convert_to_output_role(rules, self.output_role)
70
70
  # model_dump_json ensures that the output is in JSON format,
71
71
  # if we don't do this, we will get Enums and other types that are not serializable to YAML
72
- json_output = rules.model_dump_json()
72
+ json_output = rules.dump(mode="json")
73
73
  if self.output == "json":
74
- return json_output
74
+ return json.dumps(json_output)
75
75
  elif self.output == "yaml":
76
- return yaml.safe_dump(json.loads(json_output))
76
+ return yaml.safe_dump(json_output)
77
77
  else:
78
78
  raise ValueError(f"Invalid output: {self.output}. Valid options are {self.format_option}")
@@ -7,10 +7,9 @@ from __future__ import annotations
7
7
  import math
8
8
  import sys
9
9
  import types
10
- from abc import abstractmethod
11
10
  from collections.abc import Callable, Iterator
12
11
  from functools import wraps
13
- from typing import Annotated, Any, ClassVar, Generic, TypeAlias, TypeVar
12
+ from typing import Annotated, Any, ClassVar, Generic, Literal, TypeAlias, TypeVar
14
13
 
15
14
  import pandas as pd
16
15
  from pydantic import (
@@ -26,13 +25,12 @@ from pydantic import (
26
25
  model_validator,
27
26
  )
28
27
  from pydantic.fields import FieldInfo
28
+ from pydantic.main import IncEx
29
29
 
30
30
  if sys.version_info >= (3, 11):
31
31
  from enum import StrEnum
32
- from typing import Self
33
32
  else:
34
33
  from backports.strenum import StrEnum
35
- from typing_extensions import Self
36
34
 
37
35
 
38
36
  METADATA_VALUE_MAX_LENGTH = 5120
@@ -259,26 +257,33 @@ class BaseRules(RuleModel):
259
257
 
260
258
  Args:
261
259
  metadata: Data model metadata
262
- classes: Classes defined in the data model
263
- properties: Class properties defined in the data model with accompanying transformation rules
264
- to transform data from source to target representation
265
- prefixes: Prefixes used in the data model. Defaults to PREFIXES
266
- instances: Instances defined in the data model. Defaults to None
267
260
  validators_to_skip: List of validators to skip. Defaults to []
268
261
  """
269
262
 
270
263
  metadata: BaseMetadata
271
264
 
272
- @abstractmethod
273
- def reference_self(self) -> Self:
265
+ def dump(
266
+ self,
267
+ mode: Literal["python", "json"] = "python",
268
+ by_alias: bool = False,
269
+ exclude: IncEx = None,
270
+ exclude_none: bool = False,
271
+ exclude_unset: bool = False,
272
+ exclude_defaults: bool = False,
273
+ as_reference: bool = False,
274
+ ) -> dict[str, Any]:
275
+ """Dump the model to a dictionary.
276
+
277
+ This is used in the Exporters to dump rules in the required format.
274
278
  """
275
- Returns a copy of the rules with reference fields set to itself
276
-
277
- For example, if the rules have a property with a reference field, then
278
- the reference field will be set to the property itself. This is used when
279
- exporting a reference model.
280
- """
281
- raise NotImplementedError
279
+ return self.model_dump(
280
+ mode=mode,
281
+ by_alias=by_alias,
282
+ exclude=exclude,
283
+ exclude_none=exclude_none,
284
+ exclude_unset=exclude_unset,
285
+ exclude_defaults=exclude_defaults,
286
+ )
282
287
 
283
288
 
284
289
  # An sheet entity is either a class or a property.
@@ -1,5 +1,6 @@
1
1
  import warnings
2
2
  from collections import defaultdict
3
+ from collections.abc import Sequence
3
4
  from typing import Any, cast
4
5
 
5
6
  from cognite.client.data_classes import data_modeling as dm
@@ -11,7 +12,7 @@ from cognite.client.data_classes.data_modeling.views import (
11
12
  )
12
13
 
13
14
  from cognite.neat.rules import issues
14
- from cognite.neat.rules.models._base import DataModelType
15
+ from cognite.neat.rules.models._base import DataModelType, ExtensionCategory, SchemaCompleteness
15
16
  from cognite.neat.rules.models.data_types import DataType
16
17
  from cognite.neat.rules.models.entities import (
17
18
  ContainerEntity,
@@ -58,19 +59,83 @@ class _DMSExporter:
58
59
  else:
59
60
  self._ref_views_by_id = {}
60
61
 
62
+ self.is_addition = (
63
+ rules.metadata.schema_ is SchemaCompleteness.extended
64
+ and rules.metadata.extension is ExtensionCategory.addition
65
+ )
66
+ self.is_reshape = (
67
+ rules.metadata.schema_ is SchemaCompleteness.extended
68
+ and rules.metadata.extension is ExtensionCategory.reshape
69
+ )
70
+ self.is_rebuild = (
71
+ rules.metadata.schema_ is SchemaCompleteness.extended
72
+ and rules.metadata.extension is ExtensionCategory.rebuild
73
+ )
74
+
61
75
  def to_schema(self) -> DMSSchema:
62
76
  rules = self.rules
63
- container_properties_by_id, view_properties_by_id = self._gather_properties()
77
+ container_properties_by_id, view_properties_by_id = self._gather_properties(list(self.rules.properties))
78
+
79
+ # If we are reshaping or rebuilding, and there are no properties in the current rules, we will
80
+ # include those properties from the last rules.
81
+ if rules.last and (self.is_reshape or self.is_rebuild):
82
+ selected_views = {view.view for view in rules.views}
83
+ selected_properties = [
84
+ prop
85
+ for prop in rules.last.properties
86
+ if prop.view in selected_views and prop.view.as_id() not in view_properties_by_id
87
+ ]
88
+ self._update_with_properties(
89
+ selected_properties, container_properties_by_id, view_properties_by_id, include_new_containers=True
90
+ )
91
+
92
+ # We need to include the properties from the last rules as well to create complete containers and view
93
+ # depending on the type of extension.
94
+ if rules.last and self.is_addition:
95
+ selected_properties = [
96
+ prop for prop in rules.last.properties if (prop.view.as_id() in view_properties_by_id)
97
+ ]
98
+ self._update_with_properties(selected_properties, container_properties_by_id, view_properties_by_id)
99
+ elif rules.last and (self.is_reshape or self.is_rebuild):
100
+ selected_properties = [
101
+ prop
102
+ for prop in rules.last.properties
103
+ if prop.container and prop.container.as_id() in container_properties_by_id
104
+ ]
105
+ self._update_with_properties(selected_properties, container_properties_by_id, None)
106
+
64
107
  containers = self._create_containers(container_properties_by_id)
65
108
 
66
109
  views, node_types = self._create_views_with_node_types(view_properties_by_id)
67
110
 
111
+ last_schema: DMSSchema | None = None
112
+ if self.rules.last:
113
+ last_schema = self.rules.last.as_schema()
114
+ # Remove the views that are in the current model, last + current should equal the full model
115
+ # without any duplicates
116
+ last_schema.views = ViewApplyDict(
117
+ {view_id: view for view_id, view in last_schema.views.items() if view_id not in views}
118
+ )
119
+ last_schema.containers = ContainerApplyDict(
120
+ {
121
+ container_id: container
122
+ for container_id, container in last_schema.containers.items()
123
+ if container_id not in containers
124
+ }
125
+ )
126
+
68
127
  views_not_in_model = {view.view.as_id() for view in rules.views if not view.in_model}
128
+ if rules.last and self.is_addition:
129
+ views_not_in_model.update({view.view.as_id() for view in rules.last.views if not view.in_model})
130
+
69
131
  data_model = rules.metadata.as_data_model()
70
- data_model.views = sorted(
71
- [view_id for view_id in views.keys() if view_id not in views_not_in_model],
72
- key=lambda v: v.as_tuple(), # type: ignore[union-attr]
73
- )
132
+
133
+ data_model_views = [view_id for view_id in views if view_id not in views_not_in_model]
134
+ if last_schema and self.is_addition:
135
+ data_model_views.extend([view_id for view_id in last_schema.views if view_id not in views_not_in_model])
136
+
137
+ # Sorting to ensure deterministic order
138
+ data_model.views = sorted(data_model_views, key=lambda v: v.as_tuple()) # type: ignore[union-attr]
74
139
 
75
140
  spaces = self._create_spaces(rules.metadata, containers, views, data_model)
76
141
 
@@ -86,8 +151,8 @@ class _DMSExporter:
86
151
 
87
152
  if self._ref_schema:
88
153
  output.reference = self._ref_schema
89
- if self.rules.last:
90
- output.last = self.rules.last.as_schema()
154
+ if last_schema:
155
+ output.last = last_schema
91
156
  return output
92
157
 
93
158
  def _create_spaces(
@@ -113,8 +178,18 @@ class _DMSExporter:
113
178
  self,
114
179
  view_properties_by_id: dict[dm.ViewId, list[DMSProperty]],
115
180
  ) -> tuple[ViewApplyDict, NodeApplyDict]:
116
- views = ViewApplyDict([dms_view.as_view() for dms_view in self.rules.views])
117
- dms_view_by_id = {dms_view.view.as_id(): dms_view for dms_view in self.rules.views}
181
+ input_views = list(self.rules.views)
182
+ if self.rules.last:
183
+ existing = {view.view.as_id() for view in input_views}
184
+ modified_views = [
185
+ v
186
+ for v in self.rules.last.views
187
+ if v.view.as_id() in view_properties_by_id and v.view.as_id() not in existing
188
+ ]
189
+ input_views.extend(modified_views)
190
+
191
+ views = ViewApplyDict([dms_view.as_view() for dms_view in input_views])
192
+ dms_view_by_id = {dms_view.view.as_id(): dms_view for dms_view in input_views}
118
193
 
119
194
  for view_id, view in views.items():
120
195
  view.properties = {}
@@ -177,9 +252,17 @@ class _DMSExporter:
177
252
  self,
178
253
  container_properties_by_id: dict[dm.ContainerId, list[DMSProperty]],
179
254
  ) -> ContainerApplyDict:
180
- containers = dm.ContainerApplyList(
181
- [dms_container.as_container() for dms_container in self.rules.containers or []]
182
- )
255
+ containers = list(self.rules.containers or [])
256
+ if self.rules.last:
257
+ existing = {container.container.as_id() for container in containers}
258
+ modified_containers = [
259
+ c
260
+ for c in self.rules.last.containers or []
261
+ if c.container.as_id() in container_properties_by_id and c.container.as_id() not in existing
262
+ ]
263
+ containers.extend(modified_containers)
264
+
265
+ containers = dm.ContainerApplyList([dms_container.as_container() for dms_container in containers])
183
266
  container_to_drop = set()
184
267
  for container in containers:
185
268
  container_id = container.as_id()
@@ -233,10 +316,13 @@ class _DMSExporter:
233
316
  }
234
317
  return ContainerApplyDict([container for container in containers if container.as_id() not in container_to_drop])
235
318
 
236
- def _gather_properties(self) -> tuple[dict[dm.ContainerId, list[DMSProperty]], dict[dm.ViewId, list[DMSProperty]]]:
319
+ @staticmethod
320
+ def _gather_properties(
321
+ properties: Sequence[DMSProperty],
322
+ ) -> tuple[dict[dm.ContainerId, list[DMSProperty]], dict[dm.ViewId, list[DMSProperty]]]:
237
323
  container_properties_by_id: dict[dm.ContainerId, list[DMSProperty]] = defaultdict(list)
238
324
  view_properties_by_id: dict[dm.ViewId, list[DMSProperty]] = defaultdict(list)
239
- for prop in self.rules.properties:
325
+ for prop in properties:
240
326
  view_id = prop.view.as_id()
241
327
  view_properties_by_id[view_id].append(prop)
242
328
 
@@ -246,6 +332,32 @@ class _DMSExporter:
246
332
 
247
333
  return container_properties_by_id, view_properties_by_id
248
334
 
335
+ @classmethod
336
+ def _update_with_properties(
337
+ cls,
338
+ selected_properties: Sequence[DMSProperty],
339
+ container_properties_by_id: dict[dm.ContainerId, list[DMSProperty]],
340
+ view_properties_by_id: dict[dm.ViewId, list[DMSProperty]] | None,
341
+ include_new_containers: bool = False,
342
+ ) -> None:
343
+ view_properties_by_id = view_properties_by_id or {}
344
+ last_container_properties_by_id, last_view_properties_by_id = cls._gather_properties(selected_properties)
345
+
346
+ for container_id, properties in last_container_properties_by_id.items():
347
+ # Only add the container properties that are not already present, and do not overwrite.
348
+ if (container_id in container_properties_by_id) or include_new_containers:
349
+ existing = {prop.container_property for prop in container_properties_by_id.get(container_id, [])}
350
+ container_properties_by_id[container_id].extend(
351
+ [prop for prop in properties if prop.container_property not in existing]
352
+ )
353
+
354
+ if view_properties_by_id:
355
+ for view_id, properties in last_view_properties_by_id.items():
356
+ existing = {prop.view_property for prop in view_properties_by_id[view_id]}
357
+ view_properties_by_id[view_id].extend(
358
+ [prop for prop in properties if prop.view_property not in existing]
359
+ )
360
+
249
361
  def _create_view_filter(
250
362
  self,
251
363
  view: dm.ViewApply,
@@ -291,8 +403,9 @@ class _DMSExporter:
291
403
  # HasData or not provided (this is the default)
292
404
  return HasDataFilter(inner=[ContainerEntity.from_id(id_) for id_ in ref_containers])
293
405
 
406
+ @classmethod
294
407
  def _create_view_property(
295
- self, prop: DMSProperty, view_properties_by_id: dict[dm.ViewId, list[DMSProperty]]
408
+ cls, prop: DMSProperty, view_properties_by_id: dict[dm.ViewId, list[DMSProperty]]
296
409
  ) -> ViewPropertyApply | None:
297
410
  if prop.container and prop.container_property:
298
411
  container_prop_identifier = prop.container_property
@@ -336,7 +449,7 @@ class _DMSExporter:
336
449
  edge_cls = SingleEdgeConnectionApply
337
450
 
338
451
  return edge_cls(
339
- type=self._create_edge_type_from_prop(prop),
452
+ type=cls._create_edge_type_from_prop(prop),
340
453
  source=source_view_id,
341
454
  direction="outwards",
342
455
  name=prop.name,
@@ -377,7 +490,7 @@ class _DMSExporter:
377
490
  dm.MultiEdgeConnectionApply if prop.is_list in [True, None] else SingleEdgeConnectionApply
378
491
  )
379
492
  return inwards_edge_cls(
380
- type=self._create_edge_type_from_prop(reverse_prop or prop),
493
+ type=cls._create_edge_type_from_prop(reverse_prop or prop),
381
494
  source=source_view_id,
382
495
  name=prop.name,
383
496
  description=prop.description,
@@ -2,13 +2,13 @@ import math
2
2
  import re
3
3
  import sys
4
4
  import warnings
5
- from collections.abc import Callable
6
5
  from datetime import datetime
7
- from typing import TYPE_CHECKING, Any, ClassVar, Literal, cast
6
+ from typing import TYPE_CHECKING, Any, ClassVar, Literal
8
7
 
9
8
  from cognite.client import data_modeling as dm
10
- from pydantic import Field, field_serializer, field_validator, model_serializer, model_validator
11
- from pydantic_core.core_schema import SerializationInfo, ValidationInfo
9
+ from pydantic import Field, field_serializer, field_validator, model_validator
10
+ from pydantic.main import IncEx
11
+ from pydantic_core.core_schema import ValidationInfo
12
12
 
13
13
  from cognite.neat.rules import issues
14
14
  from cognite.neat.rules.issues import MultiValueError
@@ -49,9 +49,9 @@ if TYPE_CHECKING:
49
49
  from cognite.neat.rules.models.information._rules import InformationRules
50
50
 
51
51
  if sys.version_info >= (3, 11):
52
- from typing import Self
52
+ pass
53
53
  else:
54
- from typing_extensions import Self
54
+ pass
55
55
 
56
56
  _DEFAULT_VERSION = "1"
57
57
 
@@ -264,13 +264,20 @@ class DMSView(SheetEntity):
264
264
 
265
265
  def as_view(self) -> dm.ViewApply:
266
266
  view_id = self.view.as_id()
267
+ implements = [parent.as_id() for parent in self.implements or []] or None
268
+ if implements is None and isinstance(self.reference, ReferenceEntity):
269
+ # Fallback to the reference if no implements are provided
270
+ parent = self.reference.as_view_id()
271
+ if (parent.space, parent.external_id) != (view_id.space, view_id.external_id):
272
+ implements = [parent]
273
+
267
274
  return dm.ViewApply(
268
275
  space=view_id.space,
269
276
  external_id=view_id.external_id,
270
277
  version=view_id.version or _DEFAULT_VERSION,
271
278
  name=self.name or None,
272
279
  description=self.description,
273
- implements=[parent.as_id() for parent in self.implements or []] or None,
280
+ implements=implements,
274
281
  properties={},
275
282
  )
276
283
 
@@ -334,17 +341,37 @@ class DMSRules(BaseRules):
334
341
  raise MultiValueError([error for error in issue_list if isinstance(error, issues.NeatValidationError)])
335
342
  return self
336
343
 
337
- @model_serializer(mode="wrap", when_used="always")
338
- def dms_rules_serialization(
344
+ def dump(
339
345
  self,
340
- handler: Callable,
341
- info: SerializationInfo,
346
+ mode: Literal["python", "json"] = "python",
347
+ by_alias: bool = False,
348
+ exclude: IncEx = None,
349
+ exclude_none: bool = False,
350
+ exclude_unset: bool = False,
351
+ exclude_defaults: bool = False,
352
+ as_reference: bool = False,
342
353
  ) -> dict[str, Any]:
343
354
  from ._serializer import _DMSRulesSerializer
344
355
 
345
- dumped = cast(dict[str, Any], handler(self, info))
356
+ dumped = self.model_dump(
357
+ mode=mode,
358
+ by_alias=by_alias,
359
+ exclude=exclude,
360
+ exclude_none=exclude_none,
361
+ exclude_unset=exclude_unset,
362
+ exclude_defaults=exclude_defaults,
363
+ )
346
364
  space, version = self.metadata.space, self.metadata.version
347
- return _DMSRulesSerializer(info, space, version).clean(dumped)
365
+ serializer = _DMSRulesSerializer(by_alias, space, version)
366
+ clean = serializer.clean(dumped, as_reference)
367
+ last = "Last" if by_alias else "last"
368
+ if last_dump := clean.get(last):
369
+ clean[last] = serializer.clean(last_dump, False)
370
+ reference = "Reference" if by_alias else "reference"
371
+ if self.reference and (ref_dump := clean.get(reference)):
372
+ space, version = self.reference.metadata.space, self.reference.metadata.version
373
+ clean[reference] = _DMSRulesSerializer(by_alias, space, version).clean(ref_dump, True)
374
+ return clean
348
375
 
349
376
  def as_schema(self, include_pipeline: bool = False, instance_space: str | None = None) -> DMSSchema:
350
377
  from ._exporter import _DMSExporter
@@ -360,19 +387,3 @@ class DMSRules(BaseRules):
360
387
  from ._converter import _DMSRulesConverter
361
388
 
362
389
  return _DMSRulesConverter(self).as_domain_rules()
363
-
364
- def reference_self(self) -> Self:
365
- new_rules = self.model_copy(deep=True)
366
- for prop in new_rules.properties:
367
- prop.reference = ReferenceEntity(
368
- prefix=prop.view.prefix, suffix=prop.view.suffix, version=prop.view.version, property=prop.property_
369
- )
370
- view: DMSView
371
- for view in new_rules.views:
372
- view.reference = ReferenceEntity(
373
- prefix=view.view.prefix, suffix=view.view.suffix, version=view.view.version
374
- )
375
- container: DMSContainer
376
- for container in new_rules.containers or []:
377
- container.reference = ReferenceEntity(prefix=container.container.prefix, suffix=container.container.suffix)
378
- return new_rules
@@ -1,9 +1,8 @@
1
1
  from typing import Any, ClassVar, cast
2
2
 
3
- from pydantic_core.core_schema import SerializationInfo
4
-
5
3
  from cognite.neat.rules.models import DMSRules
6
4
  from cognite.neat.rules.models.dms import DMSContainer, DMSProperty, DMSView
5
+ from cognite.neat.rules.models.entities import ReferenceEntity, ViewEntity
7
6
 
8
7
 
9
8
  class _DMSRulesSerializer:
@@ -12,7 +11,7 @@ class _DMSRulesSerializer:
12
11
  VIEWS_FIELDS: ClassVar[list[str]] = ["class_", "view", "implements"]
13
12
  CONTAINERS_FIELDS: ClassVar[list[str]] = ["class_", "container"]
14
13
 
15
- def __init__(self, info: SerializationInfo, default_space: str, default_version: str) -> None:
14
+ def __init__(self, by_alias: bool, default_space: str, default_version: str) -> None:
16
15
  self.default_space = f"{default_space}:"
17
16
  self.default_version = f"version={default_version}"
18
17
  self.default_version_wrapped = f"({self.default_version})"
@@ -25,14 +24,16 @@ class _DMSRulesSerializer:
25
24
  self.container_name = "containers"
26
25
  self.metadata_name = "metadata"
27
26
  self.prop_view = "view"
27
+ self.prop_container = "container"
28
28
  self.prop_view_property = "view_property"
29
29
  self.prop_value_type = "value_type"
30
30
  self.view_view = "view"
31
31
  self.view_implements = "implements"
32
32
  self.container_container = "container"
33
33
  self.container_constraint = "constraint"
34
+ self.reference = "Reference" if by_alias else "reference"
34
35
 
35
- if info.by_alias:
36
+ if by_alias:
36
37
  self.properties_fields = [
37
38
  DMSProperty.model_fields[field].alias or field for field in self.properties_fields
38
39
  ]
@@ -41,6 +42,7 @@ class _DMSRulesSerializer:
41
42
  DMSContainer.model_fields[field].alias or field for field in self.containers_fields
42
43
  ]
43
44
  self.prop_view = DMSProperty.model_fields[self.prop_view].alias or self.prop_view
45
+ self.prop_container = DMSProperty.model_fields[self.prop_container].alias or self.prop_container
44
46
  self.prop_view_property = DMSProperty.model_fields[self.prop_view_property].alias or self.prop_view_property
45
47
  self.prop_value_type = DMSProperty.model_fields[self.prop_value_type].alias or self.prop_value_type
46
48
  self.view_view = DMSView.model_fields[self.view_view].alias or self.view_view
@@ -56,22 +58,7 @@ class _DMSRulesSerializer:
56
58
  self.container_name = DMSRules.model_fields[self.container_name].alias or self.container_name
57
59
  self.metadata_name = DMSRules.model_fields[self.metadata_name].alias or self.metadata_name
58
60
 
59
- if isinstance(info.exclude, dict):
60
- # Just for happy mypy
61
- exclude = cast(dict, info.exclude)
62
- self.exclude_properties = exclude.get("properties", {}).get("__all__", set())
63
- self.exclude_views = exclude.get("views", {}).get("__all__", set()) or set()
64
- self.exclude_containers = exclude.get("containers", {}).get("__all__", set()) or set()
65
- self.metadata_exclude = exclude.get("metadata", set()) or set()
66
- self.exclude_top = {k for k, v in exclude.items() if not v}
67
- else:
68
- self.exclude_top = set(info.exclude or {})
69
- self.exclude_properties = set()
70
- self.exclude_views = set()
71
- self.exclude_containers = set()
72
- self.metadata_exclude = set()
73
-
74
- def clean(self, dumped: dict[str, Any]) -> dict[str, Any]:
61
+ def clean(self, dumped: dict[str, Any], as_reference: bool) -> dict[str, Any]:
75
62
  # Sorting to get a deterministic order
76
63
  dumped[self.prop_name] = sorted(
77
64
  dumped[self.prop_name]["data"], key=lambda p: (p[self.prop_view], p[self.prop_view_property])
@@ -83,16 +70,29 @@ class _DMSRulesSerializer:
83
70
  dumped.pop(self.container_name, None)
84
71
 
85
72
  for prop in dumped[self.prop_name]:
73
+ if as_reference:
74
+ view_entity = cast(ViewEntity, ViewEntity.load(prop[self.prop_view]))
75
+ prop[self.reference] = str(
76
+ ReferenceEntity(
77
+ prefix=view_entity.prefix,
78
+ suffix=view_entity.suffix,
79
+ version=view_entity.version,
80
+ property=prop[self.prop_view_property],
81
+ )
82
+ )
86
83
  for field_name in self.properties_fields:
84
+ if as_reference and field_name == self.prop_container:
85
+ # When dumping as reference, the container should keep the default space for easy copying
86
+ # over to user sheets.
87
+ continue
87
88
  if value := prop.get(field_name):
88
89
  prop[field_name] = value.removeprefix(self.default_space).removesuffix(self.default_version_wrapped)
89
90
  # Value type can have a property as well
90
91
  prop[self.prop_value_type] = prop[self.prop_value_type].replace(self.default_version, "")
91
- if self.exclude_properties:
92
- for field in self.exclude_properties:
93
- prop.pop(field, None)
94
92
 
95
93
  for view in dumped[self.view_name]:
94
+ if as_reference:
95
+ view[self.reference] = view[self.view_view]
96
96
  for field_name in self.views_fields:
97
97
  if value := view.get(field_name):
98
98
  view[field_name] = value.removeprefix(self.default_space).removesuffix(self.default_version_wrapped)
@@ -101,11 +101,10 @@ class _DMSRulesSerializer:
101
101
  parent.strip().removeprefix(self.default_space).removesuffix(self.default_version_wrapped)
102
102
  for parent in value.split(",")
103
103
  )
104
- if self.exclude_views:
105
- for field in self.exclude_views:
106
- view.pop(field, None)
107
104
 
108
105
  for container in dumped.get(self.container_name, []):
106
+ if as_reference:
107
+ container[self.reference] = container[self.container_container]
109
108
  for field_name in self.containers_fields:
110
109
  if value := container.get(field_name):
111
110
  container[field_name] = value.removeprefix(self.default_space)
@@ -114,13 +113,4 @@ class _DMSRulesSerializer:
114
113
  container[self.container_constraint] = ",".join(
115
114
  constraint.strip().removeprefix(self.default_space) for constraint in value.split(",")
116
115
  )
117
- if self.exclude_containers:
118
- for field in self.exclude_containers:
119
- container.pop(field, None)
120
-
121
- if self.metadata_exclude:
122
- for field in self.metadata_exclude:
123
- dumped[self.metadata_name].pop(field, None)
124
- for field in self.exclude_top:
125
- dumped.pop(field, None)
126
116
  return dumped
@@ -1,5 +1,5 @@
1
1
  from collections import defaultdict
2
- from typing import Any
2
+ from typing import Any, ClassVar
3
3
 
4
4
  from cognite.client import data_modeling as dm
5
5
 
@@ -18,6 +18,10 @@ class DMSPostValidation:
18
18
  """This class does all the validation of the DMS rules that have dependencies between
19
19
  components."""
20
20
 
21
+ # When checking for changes extension=addition, we need to check if the new view has changed.
22
+ # For example, changing the filter is allowed, but changing the properties is not.
23
+ changeable_view_attributes: ClassVar[set[str]] = {"filter"}
24
+
21
25
  def __init__(self, rules: DMSRules):
22
26
  self.rules = rules
23
27
  self.metadata = rules.metadata
@@ -212,6 +216,13 @@ class DMSPostValidation:
212
216
  changed_attributes, changed_properties = self._changed_attributes_and_properties(
213
217
  view.dump(), existing_view.dump()
214
218
  )
219
+ existing_properties = existing_view.properties or {}
220
+ changed_properties = [prop for prop in changed_properties if prop in existing_properties]
221
+ changed_attributes = [attr for attr in changed_attributes if attr not in self.changeable_view_attributes]
222
+
223
+ if not changed_attributes and not changed_properties:
224
+ # Only added new properties, no problem
225
+ continue
215
226
  self.issue_list.append(
216
227
  issues.dms.ChangingViewError(
217
228
  view_id=view_id,
@@ -9,8 +9,8 @@ from cognite.neat.rules.models.entities import ClassEntity, ParentEntityList
9
9
 
10
10
  from ._base import (
11
11
  BaseMetadata,
12
+ BaseRules,
12
13
  RoleTypes,
13
- RuleModel,
14
14
  SheetEntity,
15
15
  SheetList,
16
16
  )
@@ -51,7 +51,7 @@ class DomainClass(SheetEntity):
51
51
  parent: ParentEntityList | None = Field(alias="Parent Class")
52
52
 
53
53
 
54
- class DomainRules(RuleModel):
54
+ class DomainRules(BaseRules):
55
55
  metadata: DomainMetadata = Field(alias="Metadata")
56
56
  properties: SheetList[DomainProperty] = Field(alias="Properties")
57
57
  classes: SheetList[DomainClass] | None = Field(None, alias="Classes")
@@ -70,7 +70,3 @@ class DomainRules(RuleModel):
70
70
  cls.model_dump(**kwargs) for cls in self.classes or []
71
71
  ] or None
72
72
  return output
73
-
74
- def reference_self(self) -> "DomainRules":
75
- """DomainRules does not have reference field, so it returns a copy of itself."""
76
- return self.model_copy(deep=True)
@@ -3,6 +3,8 @@ from collections import defaultdict
3
3
  from typing import TYPE_CHECKING, Literal
4
4
 
5
5
  from cognite.neat.rules.models._base import (
6
+ ExtensionCategory,
7
+ SchemaCompleteness,
6
8
  SheetList,
7
9
  )
8
10
  from cognite.neat.rules.models.data_types import DataType
@@ -25,7 +27,20 @@ if TYPE_CHECKING:
25
27
 
26
28
  class _InformationRulesConverter:
27
29
  def __init__(self, information: InformationRules):
28
- self.information = information
30
+ self.rules = information
31
+ self.is_addition = (
32
+ self.rules.metadata.schema_ is SchemaCompleteness.extended
33
+ and self.rules.metadata.extension is ExtensionCategory.addition
34
+ )
35
+ self.is_reshape = (
36
+ self.rules.metadata.schema_ is SchemaCompleteness.extended
37
+ and self.rules.metadata.extension is ExtensionCategory.reshape
38
+ )
39
+ if self.rules.last:
40
+ self.last_classes = {class_.class_: class_ for class_ in self.rules.last.classes}
41
+ else:
42
+ self.last_classes = {}
43
+ self._created_classes_from: dict[ClassEntity, ClassEntity] = {}
29
44
 
30
45
  def as_domain_rules(self) -> DomainRules:
31
46
  raise NotImplementedError("DomainRules not implemented yet")
@@ -38,13 +53,13 @@ class _InformationRulesConverter:
38
53
  DMSView,
39
54
  )
40
55
 
41
- info_metadata = self.information.metadata
56
+ info_metadata = self.rules.metadata
42
57
  default_version = info_metadata.version
43
58
  default_space = self._to_space(info_metadata.prefix)
44
59
  metadata = self._convert_metadata_to_dms(info_metadata)
45
60
 
46
61
  properties_by_class: dict[str, list[DMSProperty]] = defaultdict(list)
47
- for prop in self.information.properties:
62
+ for prop in self.rules.properties:
48
63
  properties_by_class[prop.class_.versioned_id].append(
49
64
  self._as_dms_property(prop, default_space, default_version)
50
65
  )
@@ -58,11 +73,11 @@ class _InformationRulesConverter:
58
73
  reference=cls_.reference,
59
74
  implements=self._get_view_implements(cls_, info_metadata),
60
75
  )
61
- for cls_ in self.information.classes
76
+ for cls_ in self.rules.classes
62
77
  ]
63
78
 
64
79
  classes_without_properties: set[str] = set()
65
- for class_ in self.information.classes:
80
+ for class_ in self.rules.classes:
66
81
  properties: list[DMSProperty] = properties_by_class.get(class_.class_.versioned_id, [])
67
82
  if not properties or all(
68
83
  isinstance(prop.value_type, ViewPropertyEntity) and prop.connection != "direct" for prop in properties
@@ -70,7 +85,16 @@ class _InformationRulesConverter:
70
85
  classes_without_properties.add(class_.class_.versioned_id)
71
86
 
72
87
  containers: list[DMSContainer] = []
73
- for class_ in self.information.classes:
88
+ classes = list(self.rules.classes)
89
+ for new_class_entity, created_from in self._created_classes_from.items():
90
+ # We create new classes in case metadata is set to addition or reshape
91
+ # and the class was present in the last schema to avoid creating
92
+ # a changed container and instead create a new one.
93
+ created_class = self.last_classes[created_from].copy(deep=True)
94
+ created_class.class_ = new_class_entity
95
+ classes.append(created_class)
96
+
97
+ for class_ in classes:
74
98
  if class_.class_.versioned_id in classes_without_properties:
75
99
  continue
76
100
  containers.append(
@@ -95,8 +119,8 @@ class _InformationRulesConverter:
95
119
  ),
96
120
  views=SheetList[DMSView](data=views),
97
121
  containers=SheetList[DMSContainer](data=containers),
98
- last=self.information.last.as_dms_architect_rules() if self.information.last else None,
99
- reference=self.information.reference.as_dms_architect_rules() if self.information.reference else None,
122
+ last=self.rules.last.as_dms_architect_rules() if self.rules.last else None,
123
+ reference=self.rules.reference.as_dms_architect_rules() if self.rules.reference else None,
100
124
  )
101
125
 
102
126
  @classmethod
@@ -119,8 +143,7 @@ class _InformationRulesConverter:
119
143
  updated=metadata.updated,
120
144
  )
121
145
 
122
- @classmethod
123
- def _as_dms_property(cls, prop: InformationProperty, default_space: str, default_version: str) -> "DMSProperty":
146
+ def _as_dms_property(self, prop: InformationProperty, default_space: str, default_version: str) -> "DMSProperty":
124
147
  """This creates the first"""
125
148
 
126
149
  from cognite.neat.rules.models.dms._rules import DMSProperty
@@ -148,9 +171,9 @@ class _InformationRulesConverter:
148
171
  nullable = None
149
172
  elif relation == "direct":
150
173
  nullable = True
151
- container, container_property = cls._get_container(prop, default_space)
174
+ container, container_property = self._get_container(prop, default_space)
152
175
  else:
153
- container, container_property = cls._get_container(prop, default_space)
176
+ container, container_property = self._get_container(prop, default_space)
154
177
 
155
178
  return DMSProperty(
156
179
  class_=prop.class_,
@@ -179,13 +202,20 @@ class _InformationRulesConverter:
179
202
  prefix = f"{prefix[:-1]}1"
180
203
  return prefix
181
204
 
182
- @classmethod
183
- def _get_container(cls, prop: InformationProperty, default_space: str) -> tuple[ContainerEntity, str]:
205
+ def _get_container(self, prop: InformationProperty, default_space: str) -> tuple[ContainerEntity, str]:
184
206
  if isinstance(prop.reference, ReferenceEntity):
185
207
  return (
186
208
  prop.reference.as_container_entity(default_space),
187
209
  prop.reference.property_ or prop.property_,
188
210
  )
211
+ elif (self.is_addition or self.is_reshape) and prop.class_ in self.last_classes:
212
+ # We need to create a new container for the property, as we cannot change
213
+ # the existing container in the last schema
214
+ class_entity = prop.class_.copy()
215
+ class_entity.suffix = self._bump_suffix(class_entity.suffix)
216
+ if class_entity not in self._created_classes_from:
217
+ self._created_classes_from[class_entity] = prop.class_
218
+ return class_entity.as_container_entity(default_space), prop.property_
189
219
  else:
190
220
  return prop.class_.as_container_entity(default_space), prop.property_
191
221
 
@@ -201,3 +231,12 @@ class _InformationRulesConverter:
201
231
 
202
232
  implements.extend([parent.as_view_entity(metadata.prefix, metadata.version) for parent in cls_.parent or []])
203
233
  return implements
234
+
235
+ @staticmethod
236
+ def _bump_suffix(suffix: str) -> str:
237
+ suffix_number = re.search(r"\d+$", suffix)
238
+
239
+ if suffix_number:
240
+ return suffix[: suffix_number.start()] + str(int(suffix_number.group()) + 1)
241
+ else:
242
+ return f"{suffix}2"
@@ -1,11 +1,10 @@
1
1
  import math
2
2
  import sys
3
- from collections.abc import Callable
4
3
  from datetime import datetime
5
- from typing import TYPE_CHECKING, Any, ClassVar, cast
4
+ from typing import TYPE_CHECKING, Any, ClassVar, Literal, cast
6
5
 
7
- from pydantic import Field, field_serializer, field_validator, model_serializer, model_validator
8
- from pydantic_core.core_schema import SerializationInfo
6
+ from pydantic import Field, field_serializer, field_validator, model_validator
7
+ from pydantic.main import IncEx
9
8
  from rdflib import Namespace
10
9
 
11
10
  from cognite.neat.constants import PREFIXES
@@ -13,12 +12,12 @@ from cognite.neat.rules import exceptions, issues
13
12
  from cognite.neat.rules.issues.base import MultiValueError
14
13
  from cognite.neat.rules.models._base import (
15
14
  BaseMetadata,
15
+ BaseRules,
16
16
  DataModelType,
17
17
  ExtensionCategory,
18
18
  ExtensionCategoryType,
19
19
  MatchType,
20
20
  RoleTypes,
21
- RuleModel,
22
21
  SchemaCompleteness,
23
22
  SheetEntity,
24
23
  SheetList,
@@ -51,7 +50,6 @@ from cognite.neat.rules.models.entities import (
51
50
  Undefined,
52
51
  UnknownEntity,
53
52
  URLEntity,
54
- _UndefinedType,
55
53
  )
56
54
 
57
55
  if TYPE_CHECKING:
@@ -259,7 +257,7 @@ class InformationProperty(SheetEntity):
259
257
  return self.max_count != 1
260
258
 
261
259
 
262
- class InformationRules(RuleModel):
260
+ class InformationRules(BaseRules):
263
261
  metadata: InformationMetadata = Field(alias="Metadata")
264
262
  properties: SheetList[InformationProperty] = Field(alias="Properties")
265
263
  classes: SheetList[InformationClass] = Field(alias="Classes")
@@ -304,14 +302,37 @@ class InformationRules(RuleModel):
304
302
  raise MultiValueError([error for error in issue_list if isinstance(error, issues.NeatValidationError)])
305
303
  return self
306
304
 
307
- @model_serializer(mode="wrap", when_used="always")
308
- def information_rules_serializer(self, handler: Callable, info: SerializationInfo) -> dict[str, Any]:
305
+ def dump(
306
+ self,
307
+ mode: Literal["python", "json"] = "python",
308
+ by_alias: bool = False,
309
+ exclude: IncEx = None,
310
+ exclude_none: bool = False,
311
+ exclude_unset: bool = False,
312
+ exclude_defaults: bool = False,
313
+ as_reference: bool = False,
314
+ ) -> dict[str, Any]:
309
315
  from ._serializer import _InformationRulesSerializer
310
316
 
311
- dumped = cast(dict[str, Any], handler(self, info))
317
+ dumped = self.model_dump(
318
+ mode=mode,
319
+ by_alias=by_alias,
320
+ exclude=exclude,
321
+ exclude_none=exclude_none,
322
+ exclude_unset=exclude_unset,
323
+ exclude_defaults=exclude_defaults,
324
+ )
312
325
  prefix = self.metadata.prefix
313
-
314
- return _InformationRulesSerializer(info, prefix).clean(dumped)
326
+ serializer = _InformationRulesSerializer(by_alias, prefix)
327
+ cleaned = serializer.clean(dumped, as_reference)
328
+ last = "Last" if by_alias else "last"
329
+ if last_dump := cleaned.get(last):
330
+ cleaned[last] = serializer.clean(last_dump, False)
331
+ reference = "Reference" if by_alias else "reference"
332
+ if self.reference and (ref_dump := cleaned.get(reference)):
333
+ prefix = self.reference.metadata.prefix
334
+ cleaned[reference] = _InformationRulesSerializer(by_alias, prefix).clean(ref_dump, True)
335
+ return cleaned
315
336
 
316
337
  def as_domain_rules(self) -> DomainRules:
317
338
  from ._converter import _InformationRulesConverter
@@ -322,26 +343,3 @@ class InformationRules(RuleModel):
322
343
  from ._converter import _InformationRulesConverter
323
344
 
324
345
  return _InformationRulesConverter(self).as_dms_architect_rules()
325
-
326
- def reference_self(self) -> "InformationRules":
327
- new_self = self.model_copy(deep=True)
328
- for prop in new_self.properties:
329
- prop.reference = ReferenceEntity(
330
- prefix=(
331
- prop.class_.prefix if not isinstance(prop.class_.prefix, _UndefinedType) else self.metadata.prefix
332
- ),
333
- suffix=prop.class_.suffix,
334
- version=prop.class_.version,
335
- property=prop.property_,
336
- )
337
-
338
- for cls_ in new_self.classes:
339
- cls_.reference = ReferenceEntity(
340
- prefix=(
341
- cls_.class_.prefix if not isinstance(cls_.class_.prefix, _UndefinedType) else self.metadata.prefix
342
- ),
343
- suffix=cls_.class_.suffix,
344
- version=cls_.class_.version,
345
- )
346
-
347
- return new_self
@@ -1,8 +1,7 @@
1
- from typing import Any, ClassVar, cast
2
-
3
- from pydantic_core.core_schema import SerializationInfo
1
+ from typing import Any, ClassVar
4
2
 
5
3
  from cognite.neat.rules.models import InformationRules
4
+ from cognite.neat.rules.models.entities import ClassEntity, ReferenceEntity
6
5
  from cognite.neat.rules.models.information import InformationClass, InformationProperty
7
6
 
8
7
 
@@ -11,7 +10,7 @@ class _InformationRulesSerializer:
11
10
  PROPERTIES_FIELDS: ClassVar[list[str]] = ["class_", "value_type"]
12
11
  CLASSES_FIELDS: ClassVar[list[str]] = ["class_"]
13
12
 
14
- def __init__(self, info: SerializationInfo, default_prefix: str) -> None:
13
+ def __init__(self, by_alias: bool, default_prefix: str) -> None:
15
14
  self.default_prefix = f"{default_prefix}:"
16
15
 
17
16
  self.properties_fields = self.PROPERTIES_FIELDS
@@ -25,7 +24,8 @@ class _InformationRulesSerializer:
25
24
  self.prop_property = "property_"
26
25
  self.prop_class = "class_"
27
26
 
28
- if info.by_alias:
27
+ self.reference = "Reference" if by_alias else "reference"
28
+ if by_alias:
29
29
  self.properties_fields = [
30
30
  InformationProperty.model_fields[field].alias or field for field in self.properties_fields
31
31
  ]
@@ -38,20 +38,7 @@ class _InformationRulesSerializer:
38
38
  self.prop_property = InformationProperty.model_fields[self.prop_property].alias or self.prop_property
39
39
  self.prop_class = InformationProperty.model_fields[self.prop_class].alias or self.prop_class
40
40
 
41
- if isinstance(info.exclude, dict):
42
- # Just for happy mypy
43
- exclude = cast(dict, info.exclude)
44
- self.metadata_exclude = exclude.get("metadata", set()) or set()
45
- self.exclude_classes = exclude.get("classes", {}).get("__all__", set()) or set()
46
- self.exclude_properties = exclude.get("properties", {}).get("__all__", set())
47
- self.exclude_top = {k for k, v in exclude.items() if not v}
48
- else:
49
- self.exclude_top = set(info.exclude or {})
50
- self.exclude_properties = set()
51
- self.exclude_classes = set()
52
- self.metadata_exclude = set()
53
-
54
- def clean(self, dumped: dict[str, Any]) -> dict[str, Any]:
41
+ def clean(self, dumped: dict[str, Any], as_reference: bool) -> dict[str, Any]:
55
42
  # Sorting to get a deterministic order
56
43
  dumped[self.prop_name] = sorted(
57
44
  dumped[self.prop_name]["data"], key=lambda p: (p[self.prop_class], p[self.prop_property])
@@ -59,15 +46,21 @@ class _InformationRulesSerializer:
59
46
  dumped[self.class_name] = sorted(dumped[self.class_name]["data"], key=lambda v: v[self.prop_class])
60
47
 
61
48
  for prop in dumped[self.prop_name]:
49
+ if as_reference:
50
+ class_entity = ClassEntity.load(prop[self.prop_class])
51
+ prop[self.reference] = str(
52
+ ReferenceEntity(
53
+ prefix=str(class_entity.prefix), suffix=class_entity.suffix, property=prop[self.prop_property]
54
+ )
55
+ )
56
+
62
57
  for field_name in self.properties_fields:
63
58
  if value := prop.get(field_name):
64
59
  prop[field_name] = value.removeprefix(self.default_prefix)
65
60
 
66
- if self.exclude_properties:
67
- for field in self.exclude_properties:
68
- prop.pop(field, None)
69
-
70
61
  for class_ in dumped[self.class_name]:
62
+ if as_reference:
63
+ class_[self.reference] = class_[self.prop_class]
71
64
  for field_name in self.classes_fields:
72
65
  if value := class_.get(field_name):
73
66
  class_[field_name] = value.removeprefix(self.default_prefix)
@@ -77,9 +70,4 @@ class _InformationRulesSerializer:
77
70
  parent.strip().removeprefix(self.default_prefix) for parent in value.split(",")
78
71
  )
79
72
 
80
- if self.metadata_exclude:
81
- for field in self.metadata_exclude:
82
- dumped[self.metadata_name].pop(field, None)
83
- for field in self.exclude_top:
84
- dumped.pop(field, None)
85
73
  return dumped
@@ -222,9 +222,9 @@ class DMSToRules(Step):
222
222
  f"or 'my_space:my_data_model', failed to parse space from {datamodel_id_str}"
223
223
  )
224
224
  return FlowMessage(error_text=error_text, step_execution_status=StepExecutionStatus.ABORT_AND_FAIL)
225
- ref_datamodel_str = self.configs.get("Reference data model id")
225
+ ref_datamodel_str = self.configs.get("Reference data model id", "")
226
226
  ref_model_id: DataModelId | None = None
227
- if ref_datamodel_str is not None:
227
+ if ref_datamodel_str:
228
228
  ref_model = DataModelEntity.load(ref_datamodel_str)
229
229
  if isinstance(ref_model, DMSUnknownEntity):
230
230
  error_text = (
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cognite-neat
3
- Version: 0.77.4
3
+ Version: 0.77.6
4
4
  Summary: Knowledge graph transformation
5
5
  Home-page: https://cognite-neat.readthedocs-hosted.com/
6
6
  License: Apache-2.0
@@ -1,5 +1,5 @@
1
1
  cognite/neat/__init__.py,sha256=v-rRiDOgZ3sQSMQKq0vgUQZvpeOkoHFXissAx6Ktg84,61
2
- cognite/neat/_version.py,sha256=UI_UQyLNbZGZ2bCbRSUVSxQLP1GDhHBmVI29h7BNFAE,23
2
+ cognite/neat/_version.py,sha256=gRPwu1nLGE_QfxUJtmhbOlmhiHQAZRF63Q3wmAlYElY,23
3
3
  cognite/neat/app/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  cognite/neat/app/api/asgi/metrics.py,sha256=nxFy7L5cChTI0a-zkCiJ59Aq8yLuIJp5c9Dg0wRXtV0,152
5
5
  cognite/neat/app/api/configuration.py,sha256=2U5M6M252swvQPQyooA1EBzFUZNtcTmuSaywfJDgckM,4232
@@ -166,9 +166,9 @@ cognite/neat/rules/exporters/__init__.py,sha256=Gn3CjkVKHJF9Po1ZPH4wAJ-sRW9up7b2
166
166
  cognite/neat/rules/exporters/_base.py,sha256=m63iw8xjlZbZAxGL8mn7pjGf1pW3rVv8C20_RSiu4t0,1511
167
167
  cognite/neat/rules/exporters/_models.py,sha256=vRd0P_YsrZ1eaAGGHfdTeFunaqHdaa0ZtnWiVZBR1nc,1976
168
168
  cognite/neat/rules/exporters/_rules2dms.py,sha256=US2IO4YTJSF_CDzau1dTpXyeHntJWVSPkMCQoUoKeC0,13688
169
- cognite/neat/rules/exporters/_rules2excel.py,sha256=GvvjZB2Ayue08s5b6JPB1xGltp-MA7QmNk9rk1a54LE,14244
169
+ cognite/neat/rules/exporters/_rules2excel.py,sha256=HvUdXYHxfLMijYWdTnfqCsw3Izf8S-XDSve-2ZbqF8Y,14248
170
170
  cognite/neat/rules/exporters/_rules2ontology.py,sha256=NWS3cn2927LQqW_PdQ-92OLIlmIKGNk7xh5yOMAyj94,20120
171
- cognite/neat/rules/exporters/_rules2yaml.py,sha256=sOSdnTJ5mXuyAJECdNnNsX6oLvgETptkpgPUQbK0n2w,3026
171
+ cognite/neat/rules/exporters/_rules2yaml.py,sha256=GA8eUYRxUfIU6IMvlyGO5JidkOD5eUKSbH3qAiFiaCg,3026
172
172
  cognite/neat/rules/exporters/_validation.py,sha256=OlKIyf4nhSDehJwFHDQ8Zdf6HpNfW7dSe2s67eywHu4,4078
173
173
  cognite/neat/rules/importers/__init__.py,sha256=zqNbGpvdVhYkLjWx1i9dJ3FXzYGtuQyTydUYsj-BndQ,408
174
174
  cognite/neat/rules/importers/_base.py,sha256=GUiJrYwJ25thI71iS9hCeP_iSZ0Vv8ou3z6MfD07FAk,4274
@@ -194,7 +194,7 @@ cognite/neat/rules/issues/importing.py,sha256=l0VKmOWN-qyvOMukNAVJZrN2WGD2YcGXeC
194
194
  cognite/neat/rules/issues/spreadsheet.py,sha256=1NZQ3a_zCtErAoMKzEt5UMJsukaysoOVmW7WUF6JK1s,15825
195
195
  cognite/neat/rules/issues/spreadsheet_file.py,sha256=YCp0Pk_TsiqYuOPdWpjUpre-zvi2c5_MvrC_dxw10YY,4964
196
196
  cognite/neat/rules/models/__init__.py,sha256=aqhQUidHYgOk5_iqdi6s72s2g8qyMRFXShYzh-ctNpw,782
197
- cognite/neat/rules/models/_base.py,sha256=D9MQElY9baxmf-BJpuLYl3R3h_wIOdrBTufMThRx_Jo,11126
197
+ cognite/neat/rules/models/_base.py,sha256=oQ8f0bvdythJ2m54K1jl2OXEuEZ4N8JqHDXyhCPBVbY,11010
198
198
  cognite/neat/rules/models/_rdfpath.py,sha256=RoHnfWufjnDtwJh7UUzWKoJz8luvX7Gb5SDQORfkQTE,11030
199
199
  cognite/neat/rules/models/_types/__init__.py,sha256=l1tGxzE7ezNHIL72AoEvNHN2IFuitxOLxiHJG__s6t4,305
200
200
  cognite/neat/rules/models/_types/_base.py,sha256=2GhLUE1ukV8X8SGL_JDxpbWGZyAvOnSqAE6JmDh5wbI,929
@@ -202,19 +202,19 @@ cognite/neat/rules/models/_types/_field.py,sha256=74WfCSVbTubpK4n4VsysQqCch6VI8I
202
202
  cognite/neat/rules/models/data_types.py,sha256=lanwkhwG8iHKfjYfia4v2SBTJrMeXOsqaVkVEP2QMXs,6078
203
203
  cognite/neat/rules/models/dms/__init__.py,sha256=Wzyqzz2ZIjpUbDg04CMuuIAw-f2A02DayNeqO9R-2Hw,491
204
204
  cognite/neat/rules/models/dms/_converter.py,sha256=QaJT0z0Yo4gkG9tHPZkU8idF8PK7c-tDahbyIT-WJQU,5959
205
- cognite/neat/rules/models/dms/_exporter.py,sha256=u1E53704ly1cH6-tM3EKT7Vksq1JFlrbTfQI33qR1wc,18984
206
- cognite/neat/rules/models/dms/_rules.py,sha256=WyaUsIWM0a-SytRZ-5n2f_AFXwyV_brzzHqmq7O_n0I,15690
205
+ cognite/neat/rules/models/dms/_exporter.py,sha256=pWUt3z8qk71eZ-YO8NdKbVOaWaIKl8bSdEuRU591gU4,24486
206
+ cognite/neat/rules/models/dms/_rules.py,sha256=iqoPilY3tov3GvJ9N3K3go6xy9kiiMt9vEZ2hQK5_V8,16070
207
207
  cognite/neat/rules/models/dms/_rules_input.py,sha256=apDDTQll9UAyYL5gS2vDxHsujWrGBilTp7lK2kzJWO8,13467
208
208
  cognite/neat/rules/models/dms/_schema.py,sha256=A4z8CINmLQgWzHoScxejRPMRo40ngKlyp1gOdPto8yU,43808
209
- cognite/neat/rules/models/dms/_serializer.py,sha256=JeaTIOERpYY1Demihqg0OHJ1EvBrPMBIF1EPjeOgGDA,6669
210
- cognite/neat/rules/models/dms/_validation.py,sha256=k2UcLlo_WU6iO-p_wjHToiLExVc22KGLP3lBuW8I_EA,12963
211
- cognite/neat/rules/models/domain.py,sha256=13OhG-XavE5ipU2ICaYaUhz60volkuVfbJrsp0PhaUU,2993
209
+ cognite/neat/rules/models/dms/_serializer.py,sha256=iqp2zyyf8jEcU-R3PERuN8nu248xIqyxiWj4owAn92g,6406
210
+ cognite/neat/rules/models/dms/_validation.py,sha256=TUmBxfSB42lsgIKVIDPPbaTaJ8TKi-gTbayqgPAmnfs,13656
211
+ cognite/neat/rules/models/domain.py,sha256=tkKcHvDXnZ5IkOr1wHiuNBtE1h8OCFmf6GZSqzHzxjI,2814
212
212
  cognite/neat/rules/models/entities.py,sha256=iBG84Jr1qQ7PvkMJUJzJ1oWApeONb1IACixdJSztUhk,16395
213
213
  cognite/neat/rules/models/information/__init__.py,sha256=HR6g8xgyU53U7Ck8pPdbT70817Q4NC1r1pCRq5SA8iw,291
214
- cognite/neat/rules/models/information/_converter.py,sha256=TxVYzR41Rhejf7ep73oqMRNFPOdxAbamoEWnvsnXbng,8040
215
- cognite/neat/rules/models/information/_rules.py,sha256=JuP0RAmTju5WwDJJAoTI-gogRRBBa8eCf-SE2k8FGlQ,13365
214
+ cognite/neat/rules/models/information/_converter.py,sha256=qJl95-gPz6MwKLZpHyY_CIqf-ebZalHWZyYnsqr_FWc,9960
215
+ cognite/neat/rules/models/information/_rules.py,sha256=tdCjvAtqnFzEo2zcx-BZHmvjSs28gVun2wz8UaT-AOA,13268
216
216
  cognite/neat/rules/models/information/_rules_input.py,sha256=IZp_Wnrac0nVaHKth1YttWQOs-kULGKLMtngNQFY40A,10147
217
- cognite/neat/rules/models/information/_serializer.py,sha256=M4vOr0tgrD-n_pir-VDHwclgAjbBOu2HKsxt_PTGO7g,3989
217
+ cognite/neat/rules/models/information/_serializer.py,sha256=yti9I_xJruxrib66YIBInhze___Io-oPTQH6uWDumPE,3503
218
218
  cognite/neat/rules/models/information/_validation.py,sha256=Is2GzL2lZU3A5zPu3NjvlXfmIU2_Y10C5Nxi5Denz4g,7528
219
219
  cognite/neat/rules/models/wrapped_entities.py,sha256=ThhjnNNrpgz0HeORIQ8Q894trxP73P7T_TuZj6qH2CU,7157
220
220
  cognite/neat/utils/__init__.py,sha256=l5Nyqhqo25bcQXCOb_lk01cr-UXsG8cczz_y_I0u6bg,68
@@ -258,7 +258,7 @@ cognite/neat/workflows/steps/lib/current/graph_extractor.py,sha256=vW9UpJScx5dFV
258
258
  cognite/neat/workflows/steps/lib/current/graph_loader.py,sha256=HfGg1HRZhbV58TFu89FTjKeUxGsbCYLeFJIQFDN_pQM,2341
259
259
  cognite/neat/workflows/steps/lib/current/graph_store.py,sha256=r7VTxdaz8jJQU7FJbnRDMxvEYbSAZFNMABhPyfNwiFk,6295
260
260
  cognite/neat/workflows/steps/lib/current/rules_exporter.py,sha256=BWscuNvTu_u6QQSxkVz4X4A1wAHW2Kfku8lhgrIK51M,23886
261
- cognite/neat/workflows/steps/lib/current/rules_importer.py,sha256=BmbLsly6ZRhMJ48RAoVXVK7H3JTKRtR40SD9-qcQdi4,11459
261
+ cognite/neat/workflows/steps/lib/current/rules_importer.py,sha256=oGIBh9iFYP3w_K0kLpM-OKTw70R1Mw_opD62MGkCJlk,11451
262
262
  cognite/neat/workflows/steps/lib/current/rules_validator.py,sha256=LwF9lXlnuPOxDSsOMMTHBi2BHc5rk08IF5zahS9yQuw,4844
263
263
  cognite/neat/workflows/steps/lib/io/__init__.py,sha256=k7IPbIq3ey19oRc5sA_15F99-O6dxzqbm1LihGRRo5A,32
264
264
  cognite/neat/workflows/steps/lib/io/io_steps.py,sha256=QAGypoi1vP32BRiIgBZ0B4qsbFMcwhzpRiVUUnWysLA,16874
@@ -275,8 +275,8 @@ cognite/neat/workflows/steps_registry.py,sha256=fkTX14ZA7_gkUYfWIlx7A1XbCidvqR23
275
275
  cognite/neat/workflows/tasks.py,sha256=dqlJwKAb0jlkl7abbY8RRz3m7MT4SK8-7cntMWkOYjw,788
276
276
  cognite/neat/workflows/triggers.py,sha256=_BLNplzoz0iic367u1mhHMHiUrCwP-SLK6_CZzfODX0,7071
277
277
  cognite/neat/workflows/utils.py,sha256=gKdy3RLG7ctRhbCRwaDIWpL9Mi98zm56-d4jfHDqP1E,453
278
- cognite_neat-0.77.4.dist-info/LICENSE,sha256=W8VmvFia4WHa3Gqxq1Ygrq85McUNqIGDVgtdvzT-XqA,11351
279
- cognite_neat-0.77.4.dist-info/METADATA,sha256=WCnAyTXFO9SjdNMcEdoyjwic7YAm8nsAg0kTXDfjsyU,9316
280
- cognite_neat-0.77.4.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
281
- cognite_neat-0.77.4.dist-info/entry_points.txt,sha256=61FPqiWb25vbqB0KI7znG8nsg_ibLHBvTjYnkPvNFso,50
282
- cognite_neat-0.77.4.dist-info/RECORD,,
278
+ cognite_neat-0.77.6.dist-info/LICENSE,sha256=W8VmvFia4WHa3Gqxq1Ygrq85McUNqIGDVgtdvzT-XqA,11351
279
+ cognite_neat-0.77.6.dist-info/METADATA,sha256=XhW7PcK8bdv1WEsMS8Oehu1AapiwLD9PrbjOrXkV4yY,9316
280
+ cognite_neat-0.77.6.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
281
+ cognite_neat-0.77.6.dist-info/entry_points.txt,sha256=61FPqiWb25vbqB0KI7znG8nsg_ibLHBvTjYnkPvNFso,50
282
+ cognite_neat-0.77.6.dist-info/RECORD,,