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