cognite-neat 0.77.3__py3-none-any.whl → 0.77.5__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.
Files changed (31) hide show
  1. cognite/neat/_version.py +1 -1
  2. cognite/neat/rules/analysis/_information_rules.py +2 -12
  3. cognite/neat/rules/exporters/_rules2excel.py +10 -10
  4. cognite/neat/rules/exporters/_rules2yaml.py +3 -3
  5. cognite/neat/rules/importers/_dms2rules.py +15 -6
  6. cognite/neat/rules/importers/_dtdl2rules/dtdl_importer.py +1 -0
  7. cognite/neat/rules/importers/_spreadsheet2rules.py +21 -8
  8. cognite/neat/rules/issues/spreadsheet.py +60 -5
  9. cognite/neat/rules/models/_base.py +29 -18
  10. cognite/neat/rules/models/dms/_converter.py +2 -0
  11. cognite/neat/rules/models/dms/_exporter.py +131 -17
  12. cognite/neat/rules/models/dms/_rules.py +43 -31
  13. cognite/neat/rules/models/dms/_rules_input.py +4 -11
  14. cognite/neat/rules/models/dms/_schema.py +5 -4
  15. cognite/neat/rules/models/dms/_serializer.py +26 -36
  16. cognite/neat/rules/models/dms/_validation.py +39 -61
  17. cognite/neat/rules/models/domain.py +2 -6
  18. cognite/neat/rules/models/information/__init__.py +8 -1
  19. cognite/neat/rules/models/information/_converter.py +53 -14
  20. cognite/neat/rules/models/information/_rules.py +63 -106
  21. cognite/neat/rules/models/information/_rules_input.py +266 -0
  22. cognite/neat/rules/models/information/_serializer.py +73 -0
  23. cognite/neat/rules/models/information/_validation.py +164 -0
  24. cognite/neat/utils/cdf.py +35 -0
  25. cognite/neat/workflows/steps/lib/current/rules_exporter.py +30 -7
  26. cognite/neat/workflows/steps/lib/current/rules_importer.py +21 -2
  27. {cognite_neat-0.77.3.dist-info → cognite_neat-0.77.5.dist-info}/METADATA +1 -1
  28. {cognite_neat-0.77.3.dist-info → cognite_neat-0.77.5.dist-info}/RECORD +31 -28
  29. {cognite_neat-0.77.3.dist-info → cognite_neat-0.77.5.dist-info}/LICENSE +0 -0
  30. {cognite_neat-0.77.3.dist-info → cognite_neat-0.77.5.dist-info}/WHEEL +0 -0
  31. {cognite_neat-0.77.3.dist-info → cognite_neat-0.77.5.dist-info}/entry_points.txt +0 -0
@@ -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,7 +151,8 @@ class _DMSExporter:
86
151
 
87
152
  if self._ref_schema:
88
153
  output.reference = self._ref_schema
89
-
154
+ if last_schema:
155
+ output.last = last_schema
90
156
  return output
91
157
 
92
158
  def _create_spaces(
@@ -112,8 +178,18 @@ class _DMSExporter:
112
178
  self,
113
179
  view_properties_by_id: dict[dm.ViewId, list[DMSProperty]],
114
180
  ) -> tuple[ViewApplyDict, NodeApplyDict]:
115
- views = ViewApplyDict([dms_view.as_view() for dms_view in self.rules.views])
116
- 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}
117
193
 
118
194
  for view_id, view in views.items():
119
195
  view.properties = {}
@@ -176,9 +252,17 @@ class _DMSExporter:
176
252
  self,
177
253
  container_properties_by_id: dict[dm.ContainerId, list[DMSProperty]],
178
254
  ) -> ContainerApplyDict:
179
- containers = dm.ContainerApplyList(
180
- [dms_container.as_container() for dms_container in self.rules.containers or []]
181
- )
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])
182
266
  container_to_drop = set()
183
267
  for container in containers:
184
268
  container_id = container.as_id()
@@ -232,10 +316,13 @@ class _DMSExporter:
232
316
  }
233
317
  return ContainerApplyDict([container for container in containers if container.as_id() not in container_to_drop])
234
318
 
235
- 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]]]:
236
323
  container_properties_by_id: dict[dm.ContainerId, list[DMSProperty]] = defaultdict(list)
237
324
  view_properties_by_id: dict[dm.ViewId, list[DMSProperty]] = defaultdict(list)
238
- for prop in self.rules.properties:
325
+ for prop in properties:
239
326
  view_id = prop.view.as_id()
240
327
  view_properties_by_id[view_id].append(prop)
241
328
 
@@ -245,6 +332,32 @@ class _DMSExporter:
245
332
 
246
333
  return container_properties_by_id, view_properties_by_id
247
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
+
248
361
  def _create_view_filter(
249
362
  self,
250
363
  view: dm.ViewApply,
@@ -290,8 +403,9 @@ class _DMSExporter:
290
403
  # HasData or not provided (this is the default)
291
404
  return HasDataFilter(inner=[ContainerEntity.from_id(id_) for id_ in ref_containers])
292
405
 
406
+ @classmethod
293
407
  def _create_view_property(
294
- 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]]
295
409
  ) -> ViewPropertyApply | None:
296
410
  if prop.container and prop.container_property:
297
411
  container_prop_identifier = prop.container_property
@@ -335,7 +449,7 @@ class _DMSExporter:
335
449
  edge_cls = SingleEdgeConnectionApply
336
450
 
337
451
  return edge_cls(
338
- type=self._create_edge_type_from_prop(prop),
452
+ type=cls._create_edge_type_from_prop(prop),
339
453
  source=source_view_id,
340
454
  direction="outwards",
341
455
  name=prop.name,
@@ -376,7 +490,7 @@ class _DMSExporter:
376
490
  dm.MultiEdgeConnectionApply if prop.is_list in [True, None] else SingleEdgeConnectionApply
377
491
  )
378
492
  return inwards_edge_cls(
379
- type=self._create_edge_type_from_prop(reverse_prop or prop),
493
+ type=cls._create_edge_type_from_prop(reverse_prop or prop),
380
494
  source=source_view_id,
381
495
  name=prop.name,
382
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,16 +49,16 @@ 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
 
58
58
 
59
59
  class DMSMetadata(BaseMetadata):
60
60
  role: ClassVar[RoleTypes] = RoleTypes.dms_architect
61
- data_model_type: DataModelType = Field(DataModelType.solution, alias="dataModelType")
61
+ data_model_type: DataModelType = Field(DataModelType.enterprise, alias="dataModelType")
62
62
  schema_: SchemaCompleteness = Field(alias="schema")
63
63
  extension: ExtensionCategory = ExtensionCategory.addition
64
64
  space: ExternalIdType
@@ -146,10 +146,11 @@ class DMSMetadata(BaseMetadata):
146
146
  return description, creator
147
147
 
148
148
  @classmethod
149
- def from_data_model(cls, data_model: dm.DataModelApply) -> "DMSMetadata":
149
+ def from_data_model(cls, data_model: dm.DataModelApply, has_reference: bool) -> "DMSMetadata":
150
150
  description, creator = cls._get_description_and_creator(data_model.description)
151
151
  return cls(
152
152
  schema_=SchemaCompleteness.complete,
153
+ data_model_type=DataModelType.solution if has_reference else DataModelType.enterprise,
153
154
  space=data_model.space,
154
155
  name=data_model.name or None,
155
156
  description=description,
@@ -263,13 +264,20 @@ class DMSView(SheetEntity):
263
264
 
264
265
  def as_view(self) -> dm.ViewApply:
265
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
+
266
274
  return dm.ViewApply(
267
275
  space=view_id.space,
268
276
  external_id=view_id.external_id,
269
277
  version=view_id.version or _DEFAULT_VERSION,
270
278
  name=self.name or None,
271
279
  description=self.description,
272
- implements=[parent.as_id() for parent in self.implements or []] or None,
280
+ implements=implements,
273
281
  properties={},
274
282
  )
275
283
 
@@ -333,17 +341,37 @@ class DMSRules(BaseRules):
333
341
  raise MultiValueError([error for error in issue_list if isinstance(error, issues.NeatValidationError)])
334
342
  return self
335
343
 
336
- @model_serializer(mode="wrap", when_used="always")
337
- def dms_rules_serialization(
344
+ def dump(
338
345
  self,
339
- handler: Callable,
340
- 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,
341
353
  ) -> dict[str, Any]:
342
354
  from ._serializer import _DMSRulesSerializer
343
355
 
344
- 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
+ )
345
364
  space, version = self.metadata.space, self.metadata.version
346
- 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
347
375
 
348
376
  def as_schema(self, include_pipeline: bool = False, instance_space: str | None = None) -> DMSSchema:
349
377
  from ._exporter import _DMSExporter
@@ -359,19 +387,3 @@ class DMSRules(BaseRules):
359
387
  from ._converter import _DMSRulesConverter
360
388
 
361
389
  return _DMSRulesConverter(self).as_domain_rules()
362
-
363
- def reference_self(self) -> Self:
364
- new_rules = self.model_copy(deep=True)
365
- for prop in new_rules.properties:
366
- prop.reference = ReferenceEntity(
367
- prefix=prop.view.prefix, suffix=prop.view.suffix, version=prop.view.version, property=prop.property_
368
- )
369
- view: DMSView
370
- for view in new_rules.views:
371
- view.reference = ReferenceEntity(
372
- prefix=view.view.prefix, suffix=view.view.suffix, version=view.view.version
373
- )
374
- container: DMSContainer
375
- for container in new_rules.containers or []:
376
- container.reference = ReferenceEntity(prefix=container.container.prefix, suffix=container.container.suffix)
377
- return new_rules
@@ -3,9 +3,7 @@ from dataclasses import dataclass
3
3
  from datetime import datetime
4
4
  from typing import Any, Literal, cast, overload
5
5
 
6
- from pydantic import BaseModel
7
-
8
- from cognite.neat.rules.models._base import DataModelType, ExtensionCategory, SchemaCompleteness
6
+ from cognite.neat.rules.models._base import DataModelType, ExtensionCategory, SchemaCompleteness, _add_alias
9
7
  from cognite.neat.rules.models.data_types import DataType
10
8
  from cognite.neat.rules.models.entities import (
11
9
  ClassEntity,
@@ -44,8 +42,9 @@ class DMSMetadataInput:
44
42
  external_id=data.get("external_id"), # type: ignore[arg-type]
45
43
  creator=data.get("creator"), # type: ignore[arg-type]
46
44
  version=data.get("version"), # type: ignore[arg-type]
47
- extension=data.get("extension", "addition"),
48
- data_model_type=data.get("data_model_type", "solution"),
45
+ # safeguard from empty cell, i.e. if key provided by value None
46
+ extension=data.get("extension", "addition") or "addition",
47
+ data_model_type=data.get("data_model_type", "solution") or "solution",
49
48
  name=data.get("name"),
50
49
  description=data.get("description"),
51
50
  created=data.get("created"),
@@ -355,9 +354,3 @@ class DMSRulesInput:
355
354
  Last=last,
356
355
  Reference=reference,
357
356
  )
358
-
359
-
360
- def _add_alias(data: dict[str, Any], base_model: type[BaseModel]) -> None:
361
- for field_name, field_ in base_model.model_fields.items():
362
- if field_name not in data and field_.alias in data:
363
- data[field_name] = data[field_.alias]
@@ -516,10 +516,11 @@ class DMSSchema:
516
516
  defined_spaces = self.spaces.copy()
517
517
  defined_containers = self.containers.copy()
518
518
  defined_views = self.views.copy()
519
- if self.reference:
520
- defined_spaces |= self.reference.spaces
521
- defined_containers |= self.reference.containers
522
- defined_views |= self.reference.views
519
+ for other_schema in [self.reference, self.last]:
520
+ if other_schema:
521
+ defined_spaces |= other_schema.spaces
522
+ defined_containers |= other_schema.containers
523
+ defined_views |= other_schema.views
523
524
 
524
525
  for container in self.containers.values():
525
526
  if container.space not in defined_spaces:
@@ -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,22 +24,25 @@ 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
  ]
39
40
  self.views_fields = [DMSView.model_fields[field].alias or field for field in self.views_fields]
40
- self.container_fields = [
41
+ self.containers_fields = [
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