cognite-neat 0.75.9__py3-none-any.whl → 0.76.1__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.
@@ -4,13 +4,18 @@ import re
4
4
  import sys
5
5
  import warnings
6
6
  from collections import defaultdict
7
+ from collections.abc import Callable
7
8
  from datetime import datetime
8
9
  from typing import TYPE_CHECKING, Any, ClassVar, Literal, cast
9
10
 
10
11
  from cognite.client import data_modeling as dm
11
12
  from cognite.client.data_classes.data_modeling import PropertyType as CognitePropertyType
12
13
  from cognite.client.data_classes.data_modeling.containers import BTreeIndex
13
- from cognite.client.data_classes.data_modeling.views import SingleReverseDirectRelationApply, ViewPropertyApply
14
+ from cognite.client.data_classes.data_modeling.views import (
15
+ SingleEdgeConnectionApply,
16
+ SingleReverseDirectRelationApply,
17
+ ViewPropertyApply,
18
+ )
14
19
  from pydantic import Field, field_serializer, field_validator, model_serializer, model_validator
15
20
  from pydantic_core.core_schema import SerializationInfo, ValidationInfo
16
21
  from rdflib import Namespace
@@ -22,6 +27,7 @@ from cognite.neat.rules.models.entities import (
22
27
  ClassEntity,
23
28
  ContainerEntity,
24
29
  ContainerEntityList,
30
+ DMSNodeEntity,
25
31
  DMSUnknownEntity,
26
32
  ParentClassEntity,
27
33
  ReferenceEntity,
@@ -32,8 +38,18 @@ from cognite.neat.rules.models.entities import (
32
38
  ViewPropertyEntity,
33
39
  )
34
40
  from cognite.neat.rules.models.rules._domain_rules import DomainRules
35
-
36
- from ._base import BaseMetadata, BaseRules, ExtensionCategory, RoleTypes, SchemaCompleteness, SheetEntity, SheetList
41
+ from cognite.neat.rules.models.wrapped_entities import DMSFilter, HasDataFilter, NodeTypeFilter
42
+
43
+ from ._base import (
44
+ BaseMetadata,
45
+ BaseRules,
46
+ DataModelType,
47
+ ExtensionCategory,
48
+ RoleTypes,
49
+ SchemaCompleteness,
50
+ SheetEntity,
51
+ SheetList,
52
+ )
37
53
  from ._dms_schema import DMSSchema, PipelineSchema
38
54
  from ._types import (
39
55
  ExternalIdType,
@@ -67,6 +83,7 @@ del subclasses # cleanup namespace
67
83
 
68
84
  class DMSMetadata(BaseMetadata):
69
85
  role: ClassVar[RoleTypes] = RoleTypes.dms_architect
86
+ data_model_type: DataModelType = Field(DataModelType.solution, alias="dataModelType")
70
87
  schema_: SchemaCompleteness = Field(alias="schema")
71
88
  extension: ExtensionCategory = ExtensionCategory.addition
72
89
  space: ExternalIdType
@@ -86,8 +103,6 @@ class DMSMetadata(BaseMetadata):
86
103
  updated: datetime = Field(
87
104
  description=("Date of the data model update"),
88
105
  )
89
- # MyPy does not account for the field validator below that sets the default value
90
- default_view_version: VersionType = Field(None) # type: ignore[assignment]
91
106
 
92
107
  @field_validator("*", mode="before")
93
108
  def strip_string(cls, value: Any) -> Any:
@@ -95,15 +110,22 @@ class DMSMetadata(BaseMetadata):
95
110
  return value.strip()
96
111
  return value
97
112
 
113
+ @field_serializer("schema_", "extension", "data_model_type", when_used="always")
114
+ @staticmethod
115
+ def as_string(value: SchemaCompleteness | ExtensionCategory | DataModelType) -> str:
116
+ return str(value)
117
+
98
118
  @field_validator("schema_", mode="plain")
99
- def as_enum(cls, value: str) -> SchemaCompleteness:
119
+ def as_enum_schema(cls, value: str) -> SchemaCompleteness:
100
120
  return SchemaCompleteness(value)
101
121
 
102
- @model_validator(mode="before")
103
- def set_default_view_version_if_missing(cls, values):
104
- if "default_view_version" not in values:
105
- values["default_view_version"] = values["version"]
106
- return values
122
+ @field_validator("extension", mode="plain")
123
+ def as_enum_extension(cls, value: str) -> ExtensionCategory:
124
+ return ExtensionCategory(value)
125
+
126
+ @field_validator("data_model_type", mode="plain")
127
+ def as_enum_model_type(cls, value: str) -> DataModelType:
128
+ return DataModelType(value)
107
129
 
108
130
  @field_validator("description", mode="before")
109
131
  def nan_as_none(cls, value):
@@ -116,6 +138,9 @@ class DMSMetadata(BaseMetadata):
116
138
  space=self.space,
117
139
  )
118
140
 
141
+ def as_data_model_id(self) -> dm.DataModelId:
142
+ return dm.DataModelId(space=self.space, external_id=self.external_id, version=self.version)
143
+
119
144
  def as_data_model(self) -> dm.DataModelApply:
120
145
  suffix = f"Creator: {', '.join(self.creator)}"
121
146
  if self.description:
@@ -162,44 +187,42 @@ class DMSMetadata(BaseMetadata):
162
187
 
163
188
 
164
189
  class DMSProperty(SheetEntity):
165
- property_: PropertyType = Field(alias="Property")
166
- relation: Literal["direct", "reversedirect", "multiedge"] | None = Field(None, alias="Relation")
190
+ view: ViewEntity = Field(alias="View")
191
+ view_property: str = Field(alias="View Property")
192
+ name: str | None = Field(alias="Name", default=None)
193
+ description: str | None = Field(alias="Description", default=None)
194
+ connection: Literal["direct", "edge", "reverse"] | None = Field(None, alias="Connection")
167
195
  value_type: DataType | ViewPropertyEntity | ViewEntity | DMSUnknownEntity = Field(alias="Value Type")
168
196
  nullable: bool | None = Field(default=None, alias="Nullable")
169
- is_list: bool | None = Field(default=None, alias="IsList")
197
+ is_list: bool | None = Field(default=None, alias="Is List")
170
198
  default: str | int | dict | None = Field(None, alias="Default")
171
199
  reference: URLEntity | ReferenceEntity | None = Field(default=None, alias="Reference", union_mode="left_to_right")
172
200
  container: ContainerEntity | None = Field(None, alias="Container")
173
- container_property: str | None = Field(None, alias="ContainerProperty")
174
- view: ViewEntity = Field(alias="View")
175
- view_property: str = Field(alias="ViewProperty")
201
+ container_property: str | None = Field(None, alias="Container Property")
176
202
  index: StrListType | None = Field(None, alias="Index")
177
203
  constraint: StrListType | None = Field(None, alias="Constraint")
204
+ class_: ClassEntity = Field(alias="Class (linage)")
205
+ property_: PropertyType = Field(alias="Property (linage)")
178
206
 
179
207
  @field_validator("nullable")
180
208
  def direct_relation_must_be_nullable(cls, value: Any, info: ValidationInfo) -> None:
181
- if info.data.get("relation") == "direct" and value is False:
209
+ if info.data.get("connection") == "direct" and value is False:
182
210
  raise ValueError("Direct relation must be nullable")
183
211
  return value
184
212
 
185
- @field_validator("container_property", "container")
186
- def reverse_direct_relation_has_no_container(cls, value, info: ValidationInfo) -> None:
187
- if info.data.get("relation") == "reversedirect" and value is not None:
188
- raise ValueError(f"Reverse direct relation must not have container or container property, got {value}")
189
- return value
190
-
191
213
  @field_validator("value_type", mode="after")
192
- def relations_value_type(cls, value: DataType | ClassEntity, info: ValidationInfo) -> DataType | ClassEntity:
193
- if (relation := info.data["relation"]) is None:
214
+ def connections_value_type(
215
+ cls, value: ViewPropertyEntity | ViewEntity | DMSUnknownEntity, info: ValidationInfo
216
+ ) -> DataType | ViewPropertyEntity | ViewEntity | DMSUnknownEntity:
217
+ if (connection := info.data.get("connection")) is None:
194
218
  return value
195
- if not isinstance(value, ViewEntity | ViewPropertyEntity | DMSUnknownEntity):
196
- raise ValueError(f"Relations must have a value type that points to another view, got {value}")
197
- if relation == "reversedirect" and value.property_ is None:
198
- # Todo fix this error message. It have the wrong syntax for how to have a property
219
+ if connection == "direct" and not isinstance(value, ViewEntity | DMSUnknownEntity):
220
+ raise ValueError(f"Direct relation must have a value type that points to a view, got {value}")
221
+ elif connection == "edge" and not isinstance(value, ViewEntity):
222
+ raise ValueError(f"Edge connection must have a value type that points to a view, got {value}")
223
+ elif connection == "reverse" and not isinstance(value, ViewPropertyEntity | ViewEntity):
199
224
  raise ValueError(
200
- "Reverse direct relation must set what it is the reverse property of. "
201
- f"Which property in {value.versioned_id} is this the reverse of? Expecting"
202
- f"{value.versioned_id}:<property>"
225
+ f"Reverse connection must have a value type that points to a view or view property, got {value}"
203
226
  )
204
227
  return value
205
228
 
@@ -214,8 +237,11 @@ class DMSProperty(SheetEntity):
214
237
 
215
238
  class DMSContainer(SheetEntity):
216
239
  container: ContainerEntity = Field(alias="Container")
240
+ name: str | None = Field(alias="Name", default=None)
241
+ description: str | None = Field(alias="Description", default=None)
217
242
  reference: URLEntity | ReferenceEntity | None = Field(alias="Reference", default=None, union_mode="left_to_right")
218
243
  constraint: ContainerEntityList | None = Field(None, alias="Constraint")
244
+ class_: ClassEntity = Field(alias="Class (linage)")
219
245
 
220
246
  def as_container(self) -> dm.ContainerApply:
221
247
  container_id = self.container.as_id()
@@ -240,9 +266,10 @@ class DMSContainer(SheetEntity):
240
266
  if isinstance(constraint_obj, dm.RequiresConstraint):
241
267
  constraints.append(ContainerEntity.from_id(constraint_obj.require))
242
268
  # UniquenessConstraint it handled in the properties
269
+ container_entity = ContainerEntity.from_id(container.as_id())
243
270
  return cls(
244
- class_=ClassEntity(prefix=container.space, suffix=container.external_id),
245
- container=ContainerEntity(space=container.space, externalId=container.external_id),
271
+ class_=container_entity.as_class(),
272
+ container=container_entity,
246
273
  name=container.name or None,
247
274
  description=container.description,
248
275
  constraint=constraints or None,
@@ -251,10 +278,13 @@ class DMSContainer(SheetEntity):
251
278
 
252
279
  class DMSView(SheetEntity):
253
280
  view: ViewEntity = Field(alias="View")
281
+ name: str | None = Field(alias="Name", default=None)
282
+ description: str | None = Field(alias="Description", default=None)
254
283
  implements: ViewEntityList | None = Field(None, alias="Implements")
255
284
  reference: URLEntity | ReferenceEntity | None = Field(alias="Reference", default=None, union_mode="left_to_right")
256
- filter_: Literal["hasData", "nodeType"] | None = Field(None, alias="Filter")
257
- in_model: bool = Field(True, alias="InModel")
285
+ filter_: HasDataFilter | NodeTypeFilter | None = Field(None, alias="Filter")
286
+ in_model: bool = Field(True, alias="In Model")
287
+ class_: ClassEntity = Field(alias="Class (linage)")
258
288
 
259
289
  def as_view(self) -> dm.ViewApply:
260
290
  view_id = self.view.as_id()
@@ -269,20 +299,17 @@ class DMSView(SheetEntity):
269
299
  )
270
300
 
271
301
  @classmethod
272
- def from_view(cls, view: dm.ViewApply, data_model_view_ids: set[dm.ViewId]) -> "DMSView":
302
+ def from_view(cls, view: dm.ViewApply, in_model: bool) -> "DMSView":
303
+ view_entity = ViewEntity.from_id(view.as_id())
304
+ class_entity = view_entity.as_class(skip_version=True)
305
+
273
306
  return cls(
274
- class_=ClassEntity(prefix=view.space, suffix=view.external_id),
275
- view=ViewEntity(space=view.space, externalId=view.external_id, version=view.version),
307
+ class_=class_entity,
308
+ view=view_entity,
276
309
  description=view.description,
277
310
  name=view.name,
278
- implements=[
279
- ViewEntity(
280
- space=parent.space, externalId=parent.external_id, version=parent.version or _DEFAULT_VERSION
281
- )
282
- for parent in view.implements
283
- ]
284
- or None,
285
- in_model=view.as_id() in data_model_view_ids,
311
+ implements=[ViewEntity.from_id(parent, _DEFAULT_VERSION) for parent in view.implements] or None,
312
+ in_model=in_model,
286
313
  )
287
314
 
288
315
 
@@ -293,6 +320,41 @@ class DMSRules(BaseRules):
293
320
  containers: SheetList[DMSContainer] | None = Field(None, alias="Containers")
294
321
  reference: "DMSRules | None" = Field(None, alias="Reference")
295
322
 
323
+ @field_validator("reference")
324
+ def check_reference_of_reference(cls, value: "DMSRules | None", info: ValidationInfo) -> "DMSRules | None":
325
+ if value is None:
326
+ return None
327
+ if value.reference is not None:
328
+ raise ValueError("Reference rules cannot have a reference")
329
+ if value.metadata.data_model_type == DataModelType.solution and (metadata := info.data.get("metadata")):
330
+ warnings.warn(
331
+ issues.dms.SolutionOnTopOfSolutionModelWarning(
332
+ metadata.as_data_model_id(), value.metadata.as_data_model_id()
333
+ ),
334
+ stacklevel=2,
335
+ )
336
+ return value
337
+
338
+ @field_validator("views")
339
+ def matching_version_and_space(cls, value: SheetList[DMSView], info: ValidationInfo) -> SheetList[DMSView]:
340
+ if not (metadata := info.data.get("metadata")):
341
+ return value
342
+ model_version = metadata.version
343
+ if different_version := [view.view.as_id() for view in value if view.view.version != model_version]:
344
+ warnings.warn(issues.dms.ViewModelVersionNotMatchingWarning(different_version, model_version), stacklevel=2)
345
+ if different_space := [view.view.as_id() for view in value if view.view.space != metadata.space]:
346
+ warnings.warn(issues.dms.ViewModelSpaceNotMatchingWarning(different_space, metadata.space), stacklevel=2)
347
+ return value
348
+
349
+ @field_validator("views")
350
+ def matching_version(cls, value: SheetList[DMSView], info: ValidationInfo) -> SheetList[DMSView]:
351
+ if not (metadata := info.data.get("metadata")):
352
+ return value
353
+ model_version = metadata.version
354
+ if different_version := [view.view.as_id() for view in value if view.view.version != model_version]:
355
+ warnings.warn(issues.dms.ViewModelVersionNotMatchingWarning(different_version, model_version), stacklevel=2)
356
+ return value
357
+
296
358
  @model_validator(mode="after")
297
359
  def consistent_container_properties(self) -> "DMSRules":
298
360
  container_properties_by_id: dict[tuple[ContainerEntity, str], list[tuple[int, DMSProperty]]] = defaultdict(list)
@@ -439,7 +501,7 @@ class DMSRules(BaseRules):
439
501
  # Everything is allowed
440
502
  return self
441
503
  # Is an extension of an existing model.
442
- user_schema = self.as_schema()
504
+ user_schema = self.as_schema(include_ref=False)
443
505
  ref_schema = self.reference.as_schema()
444
506
  new_containers = {container.as_id(): container for container in user_schema.containers}
445
507
  existing_containers = {container.as_id(): container for container in ref_schema.containers}
@@ -540,70 +602,20 @@ class DMSRules(BaseRules):
540
602
  raise issues.MultiValueError(errors)
541
603
  return self
542
604
 
543
- @model_serializer(mode="plain", when_used="always")
544
- def dms_rules_serialization(self, info: SerializationInfo) -> dict[str, Any]:
545
- kwargs = vars(info)
546
- default_space = f"{self.metadata.space}:"
547
- default_version = f"version={self.metadata.default_view_version}"
548
- default_version_wrapped = f"({default_version})"
549
- properties = []
550
- field_names = (
551
- ["Class", "View", "Value Type", "Container"]
552
- if info.by_alias
553
- else ["class_", "view", "value_type", "container"]
554
- )
555
- value_type_name = "Value Type" if info.by_alias else "value_type"
556
- for prop in self.properties:
557
- dumped = prop.model_dump(**kwargs)
558
- for field_name in field_names:
559
- if value := dumped.get(field_name):
560
- dumped[field_name] = value.removeprefix(default_space).removesuffix(default_version_wrapped)
561
- # Value type can have a property as well
562
- dumped[value_type_name] = dumped[value_type_name].replace(default_version, "")
563
- properties.append(dumped)
564
-
565
- views = []
566
- field_names = ["Class", "View", "Implements"] if info.by_alias else ["class_", "view", "implements"]
567
- implements_name = "Implements" if info.by_alias else "implements"
568
- for view in self.views:
569
- dumped = view.model_dump(**kwargs)
570
- for field_name in field_names:
571
- if value := dumped.get(field_name):
572
- dumped[field_name] = value.removeprefix(default_space).removesuffix(default_version_wrapped)
573
- if value := dumped.get(implements_name):
574
- dumped[implements_name] = ",".join(
575
- parent.strip().removeprefix(default_space).removesuffix(default_version_wrapped)
576
- for parent in value.split(",")
577
- )
578
- views.append(dumped)
579
-
580
- containers = []
581
- field_names = ["Class", "Container"] if info.by_alias else ["class_", "container"]
582
- constraint_name = "Constraint" if info.by_alias else "constraint"
583
- for container in self.containers or []:
584
- dumped = container.model_dump(**kwargs)
585
- for field_name in field_names:
586
- if value := dumped.get(field_name):
587
- dumped[field_name] = value.removeprefix(default_space).removesuffix(default_version_wrapped)
588
- if value := dumped.get(constraint_name):
589
- dumped[constraint_name] = ",".join(
590
- constraint.strip().removeprefix(default_space).removesuffix(default_version_wrapped)
591
- for constraint in value.split(",")
592
- )
593
- containers.append(dumped)
594
-
595
- output = {
596
- "Metadata" if info.by_alias else "metadata": self.metadata.model_dump(**kwargs),
597
- "Properties" if info.by_alias else "properties": properties,
598
- "Views" if info.by_alias else "views": views,
599
- "Containers" if info.by_alias else "containers": containers,
600
- }
601
- if self.reference is not None:
602
- output["Reference" if info.by_alias else "reference"] = self.reference.model_dump(**kwargs)
603
- return output
604
-
605
- def as_schema(self, include_pipeline: bool = False, instance_space: str | None = None) -> DMSSchema:
606
- return _DMSExporter(include_pipeline, instance_space).to_schema(self)
605
+ @model_serializer(mode="wrap", when_used="always")
606
+ def dms_rules_serialization(
607
+ self,
608
+ handler: Callable,
609
+ info: SerializationInfo,
610
+ ) -> dict[str, Any]:
611
+ dumped = cast(dict[str, Any], handler(self, info))
612
+ space, version = self.metadata.space, self.metadata.version
613
+ return _DMSRulesSerializer(info, space, version).clean(dumped)
614
+
615
+ def as_schema(
616
+ self, include_ref: bool = False, include_pipeline: bool = False, instance_space: str | None = None
617
+ ) -> DMSSchema:
618
+ return _DMSExporter(self, include_ref, include_pipeline, instance_space).to_schema()
607
619
 
608
620
  def as_information_architect_rules(self) -> "InformationRules":
609
621
  return _DMSRulesConverter(self).as_information_architect_rules()
@@ -640,16 +652,30 @@ class _DMSExporter:
640
652
  instance_space (str): The space to use for the instance. Defaults to None,`Rules.metadata.space` will be used
641
653
  """
642
654
 
643
- def __init__(self, include_pipeline: bool = False, instance_space: str | None = None):
655
+ def __init__(
656
+ self,
657
+ rules: DMSRules,
658
+ include_ref: bool = True,
659
+ include_pipeline: bool = False,
660
+ instance_space: str | None = None,
661
+ ):
662
+ self.include_ref = include_ref
644
663
  self.include_pipeline = include_pipeline
645
664
  self.instance_space = instance_space
665
+ self.rules = rules
666
+ self._ref_schema = rules.reference.as_schema() if rules.reference else None
667
+ if self._ref_schema:
668
+ # We skip version as that will always be missing in the reference
669
+ self._ref_views_by_id = {dm.ViewId(view.space, view.external_id): view for view in self._ref_schema.views}
670
+ else:
671
+ self._ref_views_by_id = {}
646
672
 
647
- def to_schema(self, rules: DMSRules) -> DMSSchema:
648
- container_properties_by_id, view_properties_by_id = self._gather_properties(rules)
649
-
650
- containers = self._create_containers(rules.containers, container_properties_by_id)
673
+ def to_schema(self) -> DMSSchema:
674
+ rules = self.rules
675
+ container_properties_by_id, view_properties_by_id = self._gather_properties()
676
+ containers = self._create_containers(container_properties_by_id)
651
677
 
652
- views, node_types = self._create_views_with_node_types(rules.views, view_properties_by_id)
678
+ views, node_types = self._create_views_with_node_types(view_properties_by_id)
653
679
 
654
680
  views_not_in_model = {view.view.as_id() for view in rules.views if not view.in_model}
655
681
  data_model = rules.metadata.as_data_model()
@@ -669,6 +695,16 @@ class _DMSExporter:
669
695
  )
670
696
  if self.include_pipeline:
671
697
  return PipelineSchema.from_dms(output, self.instance_space)
698
+
699
+ if self._ref_schema and self.include_ref:
700
+ output.frozen_ids.update(self._ref_schema.node_types.as_ids())
701
+ output.frozen_ids.update(self._ref_schema.views.as_ids())
702
+ output.frozen_ids.update(self._ref_schema.containers.as_ids())
703
+ output.node_types.extend(self._ref_schema.node_types)
704
+ output.views.extend(self._ref_schema.views)
705
+ output.containers.extend(self._ref_schema.containers)
706
+ output.data_models.extend(self._ref_schema.data_models)
707
+
672
708
  return output
673
709
 
674
710
  def _create_spaces(
@@ -692,11 +728,10 @@ class _DMSExporter:
692
728
 
693
729
  def _create_views_with_node_types(
694
730
  self,
695
- dms_views: SheetList[DMSView],
696
731
  view_properties_by_id: dict[dm.ViewId, list[DMSProperty]],
697
732
  ) -> tuple[dm.ViewApplyList, dm.NodeApplyList]:
698
- views = dm.ViewApplyList([dms_view.as_view() for dms_view in dms_views])
699
- dms_view_by_id = {dms_view.view.as_id(): dms_view for dms_view in dms_views}
733
+ views = dm.ViewApplyList([dms_view.as_view() for dms_view in self.rules.views])
734
+ dms_view_by_id = {dms_view.view.as_id(): dms_view for dms_view in self.rules.views}
700
735
 
701
736
  for view in views:
702
737
  view_id = view.as_id()
@@ -704,193 +739,66 @@ class _DMSExporter:
704
739
  if not (view_properties := view_properties_by_id.get(view_id)):
705
740
  continue
706
741
  for prop in view_properties:
707
- view_property: ViewPropertyApply
708
- if prop.is_list and prop.relation == "direct":
709
- # This is not yet supported in the CDF API, a warning has already been issued, here we convert it to
710
- # a multi-edge connection.
711
- if isinstance(prop.value_type, ViewEntity):
712
- source_view_id = prop.value_type.as_id()
713
- elif isinstance(prop.value_type, ViewPropertyEntity):
714
- source_view_id = prop.value_type.as_view_id()
715
- else:
716
- raise ValueError(
717
- "Direct relation must have a view as value type. "
718
- "This should have been validated in the rules"
719
- )
720
- view_property = dm.MultiEdgeConnectionApply(
721
- type=dm.DirectRelationReference(
722
- space=source_view_id.space,
723
- external_id=f"{prop.view.external_id}.{prop.view_property}",
724
- ),
725
- source=source_view_id,
726
- direction="outwards",
727
- name=prop.name,
728
- description=prop.description,
729
- )
730
- elif prop.container and prop.container_property and prop.view_property:
731
- container_prop_identifier = prop.container_property
732
- extra_args: dict[str, Any] = {}
733
- if prop.relation == "direct" and isinstance(prop.value_type, ViewEntity):
734
- extra_args["source"] = prop.value_type.as_id()
735
- elif isinstance(prop.value_type, DMSUnknownEntity):
736
- extra_args["source"] = None
737
- elif prop.relation == "direct" and not isinstance(prop.value_type, ViewEntity):
738
- raise ValueError(
739
- "Direct relation must have a view as value type. "
740
- "This should have been validated in the rules"
741
- )
742
- view_property = dm.MappedPropertyApply(
743
- container=prop.container.as_id(),
744
- container_property_identifier=container_prop_identifier,
745
- name=prop.name,
746
- description=prop.description,
747
- **extra_args,
748
- )
749
- elif prop.view and prop.view_property and prop.relation == "multiedge":
750
- if isinstance(prop.value_type, ViewEntity):
751
- source_view_id = prop.value_type.as_id()
752
- else:
753
- raise ValueError(
754
- "Multiedge relation must have a view as value type. "
755
- "This should have been validated in the rules"
756
- )
757
- if isinstance(prop.reference, ReferenceEntity):
758
- ref_view_prop = prop.reference.as_view_property_id()
759
- edge_type = dm.DirectRelationReference(
760
- space=ref_view_prop.source.space,
761
- external_id=f"{ref_view_prop.source.external_id}.{ref_view_prop.property}",
762
- )
763
- else:
764
- edge_type = dm.DirectRelationReference(
765
- space=source_view_id.space,
766
- external_id=f"{prop.view.external_id}.{prop.view_property}",
767
- )
768
-
769
- view_property = dm.MultiEdgeConnectionApply(
770
- type=edge_type,
771
- source=source_view_id,
772
- direction="outwards",
773
- name=prop.name,
774
- description=prop.description,
775
- )
776
- elif prop.view and prop.view_property and prop.relation == "reversedirect":
777
- if isinstance(prop.value_type, ViewPropertyEntity):
778
- source_prop_id = prop.value_type.as_id()
779
- source_view_id = cast(dm.ViewId, source_prop_id.source)
780
- else:
781
- raise ValueError(
782
- "Reverse direct relation must have a view as value type. "
783
- "This should have been validated in the rules"
784
- )
785
- source_prop = prop.value_type.property_
786
- if source_prop is None:
787
- raise ValueError(
788
- "Reverse direct relation must set what it is the reverse property of. "
789
- f"Which property in {prop.value_type.versioned_id} is this the reverse of? Expecting "
790
- f"{prop.value_type.versioned_id}:<property>"
791
- )
792
- reverse_prop = next(
793
- (
794
- prop
795
- for prop in view_properties_by_id.get(source_view_id, [])
796
- if prop.property_ == source_prop
797
- ),
798
- None,
799
- )
800
- if reverse_prop and reverse_prop.relation == "direct" and reverse_prop.is_list:
801
- warnings.warn(
802
- issues.dms.ReverseOfDirectRelationListWarning(view_id, prop.property_), stacklevel=2
803
- )
804
- if isinstance(reverse_prop.reference, ReferenceEntity):
805
- ref_view_prop = reverse_prop.reference.as_view_property_id()
806
- edge_type = dm.DirectRelationReference(
807
- space=ref_view_prop.source.space,
808
- external_id=f"{ref_view_prop.source.external_id}.{ref_view_prop.property}",
809
- )
810
- else:
811
- edge_type = dm.DirectRelationReference(
812
- space=source_prop_id.source.space,
813
- external_id=f"{reverse_prop.view.external_id}.{reverse_prop.view_property}",
814
- )
815
- view_property = dm.MultiEdgeConnectionApply(
816
- type=edge_type,
817
- source=source_prop_id.source, # type: ignore[arg-type]
818
- direction="inwards",
819
- name=prop.name,
820
- description=prop.description,
821
- )
822
- else:
823
- args: dict[str, Any] = dict(
824
- source=source_view_id,
825
- through=dm.PropertyId(source_prop_id.source, source_prop),
826
- description=prop.description,
827
- name=prop.name,
828
- )
829
- reverse_direct_cls: dict[
830
- bool | None,
831
- type[dm.MultiReverseDirectRelationApply] | type[SingleReverseDirectRelationApply],
832
- ] = {
833
- True: dm.MultiReverseDirectRelationApply,
834
- False: SingleReverseDirectRelationApply,
835
- None: dm.MultiReverseDirectRelationApply,
836
- }
837
-
838
- view_property = reverse_direct_cls[prop.is_list](**args)
839
- elif prop.view and prop.view_property and prop.relation:
840
- warnings.warn(
841
- issues.dms.UnsupportedRelationWarning(view_id, prop.view_property, prop.relation), stacklevel=2
842
- )
843
- continue
844
- else:
845
- continue
846
- prop_id = prop.view_property
847
- view.properties[prop_id] = view_property
742
+ view_property = self._create_view_property(prop, view_properties_by_id)
743
+ if view_property is not None:
744
+ view.properties[prop.view_property] = view_property
848
745
 
849
- node_types = dm.NodeApplyList([])
746
+ data_model_type = self.rules.metadata.data_model_type
747
+ unique_node_types: set[dm.NodeId] = set()
850
748
  parent_views = {parent for view in views for parent in view.implements or []}
851
749
  for view in views:
852
- ref_containers = sorted(view.referenced_containers(), key=lambda c: c.as_tuple())
853
750
  dms_view = dms_view_by_id.get(view.as_id())
854
- has_data = dm.filters.HasData(containers=list(ref_containers)) if ref_containers else None
855
- if dms_view and isinstance(dms_view.reference, ReferenceEntity):
856
- # If the view is a reference, we implement the reference view,
857
- # and need the filter to match the reference
858
- ref_view = dms_view.reference.as_view_id()
859
- node_type = dm.filters.Equals(
860
- ["node", "type"], {"space": ref_view.space, "externalId": ref_view.external_id}
861
- )
862
- else:
863
- node_type = dm.filters.Equals(["node", "type"], {"space": view.space, "externalId": view.external_id})
864
- if view.as_id() in parent_views:
865
- if dms_view and dms_view.filter_ == "nodeType":
751
+ dms_properties = view_properties_by_id.get(view.as_id(), [])
752
+ view_filter = self._create_view_filter(view, dms_view, data_model_type, dms_properties)
753
+
754
+ view.filter = view_filter.as_dms_filter()
755
+
756
+ if isinstance(view_filter, NodeTypeFilter):
757
+ unique_node_types.update(view_filter.nodes)
758
+ if view.as_id() in parent_views:
866
759
  warnings.warn(issues.dms.NodeTypeFilterOnParentViewWarning(view.as_id()), stacklevel=2)
867
- view.filter = node_type
868
- node_types.append(dm.NodeApply(space=view.space, external_id=view.external_id, sources=[]))
760
+ elif isinstance(view_filter, HasDataFilter) and data_model_type == DataModelType.solution:
761
+ if dms_view and isinstance(dms_view.reference, ReferenceEntity):
762
+ references = {dms_view.reference.as_view_id()}
763
+ elif any(True for prop in dms_properties if isinstance(prop.reference, ReferenceEntity)):
764
+ references = {
765
+ prop.reference.as_view_id()
766
+ for prop in dms_properties
767
+ if isinstance(prop.reference, ReferenceEntity)
768
+ }
869
769
  else:
870
- view.filter = has_data
871
- elif has_data is None:
872
- # Child filter without container properties
873
- if dms_view and dms_view.filter_ == "hasData":
874
- warnings.warn(issues.dms.HasDataFilterOnNoPropertiesViewWarning(view.as_id()), stacklevel=2)
875
- view.filter = node_type
876
- node_types.append(dm.NodeApply(space=view.space, external_id=view.external_id, sources=[]))
877
- else:
878
- if dms_view and (dms_view.filter_ == "hasData" or dms_view.filter_ is None):
879
- # Default option
880
- view.filter = has_data
881
- elif dms_view and dms_view.filter_ == "nodeType":
882
- view.filter = node_type
883
- node_types.append(dm.NodeApply(space=view.space, external_id=view.external_id, sources=[]))
884
- else:
885
- view.filter = has_data
886
- return views, node_types
770
+ continue
771
+ warnings.warn(
772
+ issues.dms.HasDataFilterOnViewWithReferencesWarning(view.as_id(), list(references)), stacklevel=2
773
+ )
774
+
775
+ return views, dm.NodeApplyList(
776
+ [dm.NodeApply(space=node.space, external_id=node.external_id) for node in unique_node_types]
777
+ )
778
+
779
+ @classmethod
780
+ def _create_edge_type_from_prop(cls, prop: DMSProperty) -> dm.DirectRelationReference:
781
+ if isinstance(prop.reference, ReferenceEntity):
782
+ ref_view_prop = prop.reference.as_view_property_id()
783
+ return cls._create_edge_type_from_view_id(cast(dm.ViewId, ref_view_prop.source), ref_view_prop.property)
784
+ else:
785
+ return cls._create_edge_type_from_view_id(prop.view.as_id(), prop.view_property)
786
+
787
+ @staticmethod
788
+ def _create_edge_type_from_view_id(view_id: dm.ViewId, property_: str) -> dm.DirectRelationReference:
789
+ return dm.DirectRelationReference(
790
+ space=view_id.space,
791
+ # This is the same convention as used when converting GraphQL to DMS
792
+ external_id=f"{view_id.external_id}.{property_}",
793
+ )
887
794
 
888
795
  def _create_containers(
889
796
  self,
890
- dms_container: SheetList[DMSContainer] | None,
891
797
  container_properties_by_id: dict[dm.ContainerId, list[DMSProperty]],
892
798
  ) -> dm.ContainerApplyList:
893
- containers = dm.ContainerApplyList([dms_container.as_container() for dms_container in dms_container or []])
799
+ containers = dm.ContainerApplyList(
800
+ [dms_container.as_container() for dms_container in self.rules.containers or []]
801
+ )
894
802
  container_to_drop = set()
895
803
  for container in containers:
896
804
  container_id = container.as_id()
@@ -906,26 +814,14 @@ class _DMSExporter:
906
814
  else:
907
815
  type_cls = dm.DirectRelation
908
816
 
909
- prop_id = prop.container_property
910
-
911
- if type_cls is dm.DirectRelation:
912
- container.properties[prop_id] = dm.ContainerProperty(
913
- type=dm.DirectRelation(),
914
- nullable=prop.nullable if prop.nullable is not None else True,
915
- default_value=prop.default,
916
- name=prop.name,
917
- description=prop.description,
918
- )
919
- else:
920
- type_: CognitePropertyType
921
- type_ = type_cls(is_list=prop.is_list or False)
922
- container.properties[prop_id] = dm.ContainerProperty(
923
- type=type_,
924
- nullable=prop.nullable if prop.nullable is not None else True,
925
- default_value=prop.default,
926
- name=prop.name,
927
- description=prop.description,
928
- )
817
+ type_ = type_cls(is_list=prop.is_list or False)
818
+ container.properties[prop.container_property] = dm.ContainerProperty(
819
+ type=type_,
820
+ nullable=prop.nullable if prop.nullable is not None else True,
821
+ default_value=prop.default,
822
+ name=prop.name,
823
+ description=prop.description,
824
+ )
929
825
 
930
826
  uniqueness_properties: dict[str, set[str]] = defaultdict(set)
931
827
  for prop in container_properties:
@@ -958,31 +854,175 @@ class _DMSExporter:
958
854
  [container for container in containers if container.as_id() not in container_to_drop]
959
855
  )
960
856
 
961
- def _gather_properties(
962
- self, rules: DMSRules
963
- ) -> tuple[dict[dm.ContainerId, list[DMSProperty]], dict[dm.ViewId, list[DMSProperty]]]:
857
+ def _gather_properties(self) -> tuple[dict[dm.ContainerId, list[DMSProperty]], dict[dm.ViewId, list[DMSProperty]]]:
964
858
  container_properties_by_id: dict[dm.ContainerId, list[DMSProperty]] = defaultdict(list)
965
859
  view_properties_by_id: dict[dm.ViewId, list[DMSProperty]] = defaultdict(list)
966
- for prop in rules.properties:
860
+ for prop in self.rules.properties:
967
861
  view_id = prop.view.as_id()
968
862
  view_properties_by_id[view_id].append(prop)
969
863
 
970
864
  if prop.container and prop.container_property:
971
- if prop.relation == "direct" and prop.is_list:
972
- warnings.warn(
973
- issues.dms.DirectRelationListWarning(
974
- container_id=prop.container.as_id(),
975
- view_id=prop.view.as_id(),
976
- property=prop.container_property,
977
- ),
978
- stacklevel=2,
979
- )
980
- continue
981
865
  container_id = prop.container.as_id()
982
866
  container_properties_by_id[container_id].append(prop)
983
867
 
984
868
  return container_properties_by_id, view_properties_by_id
985
869
 
870
+ def _create_view_filter(
871
+ self,
872
+ view: dm.ViewApply,
873
+ dms_view: DMSView | None,
874
+ data_model_type: DataModelType,
875
+ dms_properties: list[DMSProperty],
876
+ ) -> DMSFilter:
877
+ selected_filter_name = (dms_view and dms_view.filter_ and dms_view.filter_.name) or ""
878
+ if dms_view and dms_view.filter_ and not dms_view.filter_.is_empty:
879
+ # Has Explicit Filter, use it
880
+ return dms_view.filter_
881
+
882
+ if data_model_type == DataModelType.solution and selected_filter_name in [NodeTypeFilter.name, ""]:
883
+ if (
884
+ dms_view
885
+ and isinstance(dms_view.reference, ReferenceEntity)
886
+ and not dms_properties
887
+ and (ref_view := self._ref_views_by_id.get(dms_view.reference.as_view_id()))
888
+ and ref_view.filter
889
+ ):
890
+ # No new properties, only reference, reuse the reference filter
891
+ return DMSFilter.from_dms_filter(ref_view.filter)
892
+ else:
893
+ referenced_node_ids = {
894
+ prop.reference.as_node_entity()
895
+ for prop in dms_properties
896
+ if isinstance(prop.reference, ReferenceEntity)
897
+ }
898
+ if dms_view and isinstance(dms_view.reference, ReferenceEntity):
899
+ referenced_node_ids.add(dms_view.reference.as_node_entity())
900
+ if referenced_node_ids:
901
+ return NodeTypeFilter(inner=list(referenced_node_ids))
902
+
903
+ # Enterprise Model or (Solution + HasData)
904
+ ref_containers = view.referenced_containers()
905
+ if not ref_containers or selected_filter_name == HasDataFilter.name:
906
+ # Child filter without container properties
907
+ if selected_filter_name == HasDataFilter.name:
908
+ warnings.warn(issues.dms.HasDataFilterOnNoPropertiesViewWarning(view.as_id()), stacklevel=2)
909
+ return NodeTypeFilter(inner=[DMSNodeEntity(space=view.space, externalId=view.external_id)])
910
+ else:
911
+ # HasData or not provided (this is the default)
912
+ return HasDataFilter(inner=[ContainerEntity.from_id(id_) for id_ in ref_containers])
913
+
914
+ def _create_view_property(
915
+ self, prop: DMSProperty, view_properties_by_id: dict[dm.ViewId, list[DMSProperty]]
916
+ ) -> ViewPropertyApply | None:
917
+ if prop.container and prop.container_property:
918
+ container_prop_identifier = prop.container_property
919
+ extra_args: dict[str, Any] = {}
920
+ if prop.connection == "direct":
921
+ if isinstance(prop.value_type, ViewEntity):
922
+ extra_args["source"] = prop.value_type.as_id()
923
+ elif isinstance(prop.value_type, DMSUnknownEntity):
924
+ extra_args["source"] = None
925
+ else:
926
+ # Should have been validated.
927
+ raise ValueError(
928
+ "If this error occurs it is a bug in NEAT, please report"
929
+ f"Debug Info, Invalid valueType direct: {prop.model_dump_json()}"
930
+ )
931
+ elif prop.connection is not None:
932
+ # Should have been validated.
933
+ raise ValueError(
934
+ "If this error occurs it is a bug in NEAT, please report"
935
+ f"Debug Info, Invalid connection: {prop.model_dump_json()}"
936
+ )
937
+ return dm.MappedPropertyApply(
938
+ container=prop.container.as_id(),
939
+ container_property_identifier=container_prop_identifier,
940
+ name=prop.name,
941
+ description=prop.description,
942
+ **extra_args,
943
+ )
944
+ elif prop.connection == "edge":
945
+ if isinstance(prop.value_type, ViewEntity):
946
+ source_view_id = prop.value_type.as_id()
947
+ else:
948
+ # Should have been validated.
949
+ raise ValueError(
950
+ "If this error occurs it is a bug in NEAT, please report"
951
+ f"Debug Info, Invalid valueType edge: {prop.model_dump_json()}"
952
+ )
953
+ edge_cls: type[dm.EdgeConnectionApply] = dm.MultiEdgeConnectionApply
954
+ # If is_list is not set, we default to a MultiEdgeConnection
955
+ if prop.is_list is False:
956
+ edge_cls = SingleEdgeConnectionApply
957
+
958
+ return edge_cls(
959
+ type=self._create_edge_type_from_prop(prop),
960
+ source=source_view_id,
961
+ direction="outwards",
962
+ name=prop.name,
963
+ description=prop.description,
964
+ )
965
+ elif prop.connection == "reverse":
966
+ reverse_prop_id: str | None = None
967
+ if isinstance(prop.value_type, ViewPropertyEntity):
968
+ source_view_id = prop.value_type.as_view_id()
969
+ reverse_prop_id = prop.value_type.property_
970
+ elif isinstance(prop.value_type, ViewEntity):
971
+ source_view_id = prop.value_type.as_id()
972
+ else:
973
+ # Should have been validated.
974
+ raise ValueError(
975
+ "If this error occurs it is a bug in NEAT, please report"
976
+ f"Debug Info, Invalid valueType reverse connection: {prop.model_dump_json()}"
977
+ )
978
+ reverse_prop: DMSProperty | None = None
979
+ if reverse_prop_id is not None:
980
+ reverse_prop = next(
981
+ (
982
+ prop
983
+ for prop in view_properties_by_id.get(source_view_id, [])
984
+ if prop.property_ == reverse_prop_id
985
+ ),
986
+ None,
987
+ )
988
+
989
+ if reverse_prop is None:
990
+ warnings.warn(
991
+ issues.dms.ReverseRelationMissingOtherSideWarning(source_view_id, prop.view_property),
992
+ stacklevel=2,
993
+ )
994
+
995
+ if reverse_prop is None or reverse_prop.connection == "edge":
996
+ inwards_edge_cls = (
997
+ dm.MultiEdgeConnectionApply if prop.is_list in [True, None] else SingleEdgeConnectionApply
998
+ )
999
+ return inwards_edge_cls(
1000
+ type=self._create_edge_type_from_prop(reverse_prop or prop),
1001
+ source=source_view_id,
1002
+ name=prop.name,
1003
+ description=prop.description,
1004
+ direction="inwards",
1005
+ )
1006
+ elif reverse_prop_id and reverse_prop and reverse_prop.connection == "direct":
1007
+ reverse_direct_cls = (
1008
+ dm.MultiReverseDirectRelationApply if prop.is_list is True else SingleReverseDirectRelationApply
1009
+ )
1010
+ return reverse_direct_cls(
1011
+ source=source_view_id,
1012
+ through=dm.PropertyId(source=source_view_id, property=reverse_prop_id),
1013
+ name=prop.name,
1014
+ description=prop.description,
1015
+ )
1016
+ else:
1017
+ return None
1018
+
1019
+ elif prop.view and prop.view_property and prop.connection:
1020
+ warnings.warn(
1021
+ issues.dms.UnsupportedConnectionWarning(prop.view.as_id(), prop.view_property, prop.connection or ""),
1022
+ stacklevel=2,
1023
+ )
1024
+ return None
1025
+
986
1026
 
987
1027
  class _DMSRulesConverter:
988
1028
  def __init__(self, dms: DMSRules):
@@ -1093,3 +1133,123 @@ class _DMSRulesConverter:
1093
1133
  suffix=container.suffix,
1094
1134
  property=property_.container_property,
1095
1135
  )
1136
+
1137
+
1138
+ class _DMSRulesSerializer:
1139
+ # These are the fields that need to be cleaned from the default space and version
1140
+ PROPERTIES_FIELDS: ClassVar[list[str]] = ["class_", "view", "value_type", "container"]
1141
+ VIEWS_FIELDS: ClassVar[list[str]] = ["class_", "view", "implements"]
1142
+ CONTAINERS_FIELDS: ClassVar[list[str]] = ["class_", "container"]
1143
+
1144
+ def __init__(self, info: SerializationInfo, default_space: str, default_version: str) -> None:
1145
+ self.default_space = f"{default_space}:"
1146
+ self.default_version = f"version={default_version}"
1147
+ self.default_version_wrapped = f"({self.default_version})"
1148
+
1149
+ self.properties_fields = self.PROPERTIES_FIELDS
1150
+ self.views_fields = self.VIEWS_FIELDS
1151
+ self.containers_fields = self.CONTAINERS_FIELDS
1152
+ self.prop_name = "properties"
1153
+ self.view_name = "views"
1154
+ self.container_name = "containers"
1155
+ self.metadata_name = "metadata"
1156
+ self.prop_view = "view"
1157
+ self.prop_view_property = "view_property"
1158
+ self.prop_value_type = "value_type"
1159
+ self.view_view = "view"
1160
+ self.view_implements = "implements"
1161
+ self.container_container = "container"
1162
+ self.container_constraint = "constraint"
1163
+
1164
+ if info.by_alias:
1165
+ self.properties_fields = [
1166
+ DMSProperty.model_fields[field].alias or field for field in self.properties_fields
1167
+ ]
1168
+ self.views_fields = [DMSView.model_fields[field].alias or field for field in self.views_fields]
1169
+ self.container_fields = [
1170
+ DMSContainer.model_fields[field].alias or field for field in self.containers_fields
1171
+ ]
1172
+ self.prop_view = DMSProperty.model_fields[self.prop_view].alias or self.prop_view
1173
+ self.prop_view_property = DMSProperty.model_fields[self.prop_view_property].alias or self.prop_view_property
1174
+ self.prop_value_type = DMSProperty.model_fields[self.prop_value_type].alias or self.prop_value_type
1175
+ self.view_view = DMSView.model_fields[self.view_view].alias or self.view_view
1176
+ self.view_implements = DMSView.model_fields[self.view_implements].alias or self.view_implements
1177
+ self.container_container = (
1178
+ DMSContainer.model_fields[self.container_container].alias or self.container_container
1179
+ )
1180
+ self.container_constraint = (
1181
+ DMSContainer.model_fields[self.container_constraint].alias or self.container_constraint
1182
+ )
1183
+ self.prop_name = DMSRules.model_fields[self.prop_name].alias or self.prop_name
1184
+ self.view_name = DMSRules.model_fields[self.view_name].alias or self.view_name
1185
+ self.container_name = DMSRules.model_fields[self.container_name].alias or self.container_name
1186
+ self.metadata_name = DMSRules.model_fields[self.metadata_name].alias or self.metadata_name
1187
+
1188
+ if isinstance(info.exclude, dict):
1189
+ # Just for happy mypy
1190
+ exclude = cast(dict, info.exclude)
1191
+ self.exclude_properties = exclude.get("properties", {}).get("__all__", set())
1192
+ self.exclude_views = exclude.get("views", {}).get("__all__", set()) or set()
1193
+ self.exclude_containers = exclude.get("containers", {}).get("__all__", set()) or set()
1194
+ self.metadata_exclude = exclude.get("metadata", set()) or set()
1195
+ self.exclude_top = {k for k, v in exclude.items() if not v}
1196
+ else:
1197
+ self.exclude_top = set(info.exclude or {})
1198
+ self.exclude_properties = set()
1199
+ self.exclude_views = set()
1200
+ self.exclude_containers = set()
1201
+ self.metadata_exclude = set()
1202
+
1203
+ def clean(self, dumped: dict[str, Any]) -> dict[str, Any]:
1204
+ # Sorting to get a deterministic order
1205
+ dumped[self.prop_name] = sorted(
1206
+ dumped[self.prop_name]["data"], key=lambda p: (p[self.prop_view], p[self.prop_view_property])
1207
+ )
1208
+ dumped[self.view_name] = sorted(dumped[self.view_name]["data"], key=lambda v: v[self.view_view])
1209
+ if self.container_name in dumped:
1210
+ dumped[self.container_name] = sorted(
1211
+ dumped[self.container_name]["data"], key=lambda c: c[self.container_container]
1212
+ )
1213
+
1214
+ for prop in dumped[self.prop_name]:
1215
+ for field_name in self.properties_fields:
1216
+ if value := prop.get(field_name):
1217
+ prop[field_name] = value.removeprefix(self.default_space).removesuffix(self.default_version_wrapped)
1218
+ # Value type can have a property as well
1219
+ prop[self.prop_value_type] = prop[self.prop_value_type].replace(self.default_version, "")
1220
+ if self.exclude_properties:
1221
+ for field in self.exclude_properties:
1222
+ prop.pop(field, None)
1223
+
1224
+ for view in dumped[self.view_name]:
1225
+ for field_name in self.views_fields:
1226
+ if value := view.get(field_name):
1227
+ view[field_name] = value.removeprefix(self.default_space).removesuffix(self.default_version_wrapped)
1228
+ if value := view.get(self.view_implements):
1229
+ view[self.view_implements] = ",".join(
1230
+ parent.strip().removeprefix(self.default_space).removesuffix(self.default_version_wrapped)
1231
+ for parent in value.split(",")
1232
+ )
1233
+ if self.exclude_views:
1234
+ for field in self.exclude_views:
1235
+ view.pop(field, None)
1236
+
1237
+ for container in dumped[self.container_name]:
1238
+ for field_name in self.containers_fields:
1239
+ if value := container.get(field_name):
1240
+ container[field_name] = value.removeprefix(self.default_space)
1241
+
1242
+ if value := container.get(self.container_constraint):
1243
+ container[self.container_constraint] = ",".join(
1244
+ constraint.strip().removeprefix(self.default_space) for constraint in value.split(",")
1245
+ )
1246
+ if self.exclude_containers:
1247
+ for field in self.exclude_containers:
1248
+ container.pop(field, None)
1249
+
1250
+ if self.metadata_exclude:
1251
+ for field in self.metadata_exclude:
1252
+ dumped[self.metadata_name].pop(field, None)
1253
+ for field in self.exclude_top:
1254
+ dumped.pop(field, None)
1255
+ return dumped