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.
- cognite/neat/_version.py +1 -1
- cognite/neat/rules/analysis/_information_rules.py +2 -12
- cognite/neat/rules/exporters/_rules2excel.py +10 -10
- cognite/neat/rules/exporters/_rules2yaml.py +3 -3
- cognite/neat/rules/importers/_dms2rules.py +15 -6
- cognite/neat/rules/importers/_dtdl2rules/dtdl_importer.py +1 -0
- cognite/neat/rules/importers/_spreadsheet2rules.py +21 -8
- cognite/neat/rules/issues/spreadsheet.py +60 -5
- cognite/neat/rules/models/_base.py +29 -18
- cognite/neat/rules/models/dms/_converter.py +2 -0
- cognite/neat/rules/models/dms/_exporter.py +131 -17
- cognite/neat/rules/models/dms/_rules.py +43 -31
- cognite/neat/rules/models/dms/_rules_input.py +4 -11
- cognite/neat/rules/models/dms/_schema.py +5 -4
- cognite/neat/rules/models/dms/_serializer.py +26 -36
- cognite/neat/rules/models/dms/_validation.py +39 -61
- cognite/neat/rules/models/domain.py +2 -6
- cognite/neat/rules/models/information/__init__.py +8 -1
- cognite/neat/rules/models/information/_converter.py +53 -14
- cognite/neat/rules/models/information/_rules.py +63 -106
- cognite/neat/rules/models/information/_rules_input.py +266 -0
- cognite/neat/rules/models/information/_serializer.py +73 -0
- cognite/neat/rules/models/information/_validation.py +164 -0
- cognite/neat/utils/cdf.py +35 -0
- cognite/neat/workflows/steps/lib/current/rules_exporter.py +30 -7
- cognite/neat/workflows/steps/lib/current/rules_importer.py +21 -2
- {cognite_neat-0.77.3.dist-info → cognite_neat-0.77.5.dist-info}/METADATA +1 -1
- {cognite_neat-0.77.3.dist-info → cognite_neat-0.77.5.dist-info}/RECORD +31 -28
- {cognite_neat-0.77.3.dist-info → cognite_neat-0.77.5.dist-info}/LICENSE +0 -0
- {cognite_neat-0.77.3.dist-info → cognite_neat-0.77.5.dist-info}/WHEEL +0 -0
- {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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
116
|
-
|
|
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 =
|
|
180
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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=
|
|
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=
|
|
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
|
|
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,
|
|
11
|
-
from
|
|
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
|
-
|
|
52
|
+
pass
|
|
53
53
|
else:
|
|
54
|
-
|
|
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.
|
|
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=
|
|
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
|
-
|
|
337
|
-
def dms_rules_serialization(
|
|
344
|
+
def dump(
|
|
338
345
|
self,
|
|
339
|
-
|
|
340
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
48
|
-
|
|
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
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
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,
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|