cognite-neat 0.103.0__py3-none-any.whl → 0.104.0__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.
Potentially problematic release.
This version of cognite-neat might be problematic. Click here for more details.
- cognite/neat/_graph/extractors/_mock_graph_generator.py +1 -1
- cognite/neat/_graph/transformers/_base.py +109 -1
- cognite/neat/_graph/transformers/_classic_cdf.py +4 -0
- cognite/neat/_graph/transformers/_prune_graph.py +103 -47
- cognite/neat/_graph/transformers/_rdfpath.py +41 -17
- cognite/neat/_graph/transformers/_value_type.py +119 -154
- cognite/neat/_issues/_base.py +35 -8
- cognite/neat/_issues/warnings/_resources.py +1 -1
- cognite/neat/_rules/_shared.py +18 -34
- cognite/neat/_rules/exporters/_base.py +28 -2
- cognite/neat/_rules/exporters/_rules2dms.py +4 -0
- cognite/neat/_rules/exporters/_rules2excel.py +11 -0
- cognite/neat/_rules/exporters/_rules2instance_template.py +4 -0
- cognite/neat/_rules/exporters/_rules2ontology.py +13 -1
- cognite/neat/_rules/exporters/_rules2yaml.py +4 -0
- cognite/neat/_rules/importers/_base.py +9 -0
- cognite/neat/_rules/importers/_dms2rules.py +17 -5
- cognite/neat/_rules/importers/_dtdl2rules/dtdl_importer.py +5 -2
- cognite/neat/_rules/importers/_rdf/_base.py +10 -8
- cognite/neat/_rules/importers/_rdf/_imf2rules.py +4 -0
- cognite/neat/_rules/importers/_rdf/_inference2rules.py +7 -0
- cognite/neat/_rules/importers/_rdf/_owl2rules.py +4 -0
- cognite/neat/_rules/importers/_spreadsheet2rules.py +17 -8
- cognite/neat/_rules/importers/_yaml2rules.py +21 -7
- cognite/neat/_rules/models/_base_input.py +1 -1
- cognite/neat/_rules/models/_base_rules.py +5 -0
- cognite/neat/_rules/models/dms/_rules.py +4 -0
- cognite/neat/_rules/models/dms/_rules_input.py +9 -0
- cognite/neat/_rules/models/dms/_validation.py +2 -0
- cognite/neat/_rules/models/entities/_single_value.py +25 -5
- cognite/neat/_rules/models/information/_rules.py +4 -0
- cognite/neat/_rules/models/information/_rules_input.py +9 -0
- cognite/neat/_rules/models/mapping/_classic2core.py +2 -5
- cognite/neat/_rules/transformers/__init__.py +5 -4
- cognite/neat/_rules/transformers/_base.py +41 -65
- cognite/neat/_rules/transformers/_converters.py +149 -62
- cognite/neat/_rules/transformers/_mapping.py +17 -12
- cognite/neat/_rules/transformers/_verification.py +50 -37
- cognite/neat/_session/_base.py +32 -121
- cognite/neat/_session/_inspect.py +3 -3
- cognite/neat/_session/_mapping.py +17 -105
- cognite/neat/_session/_prepare.py +36 -254
- cognite/neat/_session/_read.py +11 -130
- cognite/neat/_session/_set.py +6 -30
- cognite/neat/_session/_show.py +49 -30
- cognite/neat/_session/_state.py +49 -107
- cognite/neat/_session/_to.py +42 -31
- cognite/neat/_shared.py +23 -2
- cognite/neat/_store/_provenance.py +3 -82
- cognite/neat/_store/_rules_store.py +372 -10
- cognite/neat/_store/exceptions.py +23 -0
- cognite/neat/_utils/graph_transformations_report.py +36 -0
- cognite/neat/_utils/io_.py +11 -0
- cognite/neat/_utils/rdf_.py +8 -0
- cognite/neat/_utils/spreadsheet.py +5 -4
- cognite/neat/_version.py +1 -1
- cognite/neat/_workflows/steps/lib/current/rules_exporter.py +7 -7
- cognite/neat/_workflows/steps/lib/current/rules_importer.py +24 -99
- {cognite_neat-0.103.0.dist-info → cognite_neat-0.104.0.dist-info}/METADATA +1 -1
- {cognite_neat-0.103.0.dist-info → cognite_neat-0.104.0.dist-info}/RECORD +63 -61
- cognite/neat/_rules/transformers/_pipelines.py +0 -70
- {cognite_neat-0.103.0.dist-info → cognite_neat-0.104.0.dist-info}/LICENSE +0 -0
- {cognite_neat-0.103.0.dist-info → cognite_neat-0.104.0.dist-info}/WHEEL +0 -0
- {cognite_neat-0.103.0.dist-info → cognite_neat-0.104.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import dataclasses
|
|
1
2
|
import re
|
|
2
3
|
import warnings
|
|
3
|
-
from abc import ABC
|
|
4
|
+
from abc import ABC
|
|
4
5
|
from collections import Counter, defaultdict
|
|
5
6
|
from collections.abc import Collection, Mapping
|
|
6
7
|
from datetime import date, datetime
|
|
@@ -9,11 +10,12 @@ from typing import ClassVar, Literal, TypeVar, cast, overload
|
|
|
9
10
|
from cognite.client.data_classes import data_modeling as dms
|
|
10
11
|
from cognite.client.data_classes.data_modeling import DataModelId, DataModelIdentifier, ViewId
|
|
11
12
|
|
|
13
|
+
from cognite.neat._client import NeatClient
|
|
14
|
+
from cognite.neat._client.data_classes.data_modeling import ContainerApplyDict, ViewApplyDict
|
|
12
15
|
from cognite.neat._constants import (
|
|
13
16
|
COGNITE_MODELS,
|
|
14
17
|
DMS_CONTAINER_PROPERTY_SIZE_LIMIT,
|
|
15
18
|
)
|
|
16
|
-
from cognite.neat._issues._base import IssueList
|
|
17
19
|
from cognite.neat._issues.errors import NeatValueError
|
|
18
20
|
from cognite.neat._issues.warnings import NeatValueWarning
|
|
19
21
|
from cognite.neat._issues.warnings._models import (
|
|
@@ -21,13 +23,13 @@ from cognite.neat._issues.warnings._models import (
|
|
|
21
23
|
SolutionModelBuildOnTopOfCDMWarning,
|
|
22
24
|
)
|
|
23
25
|
from cognite.neat._rules._shared import (
|
|
24
|
-
|
|
25
|
-
JustRules,
|
|
26
|
-
OutRules,
|
|
26
|
+
ReadInputRules,
|
|
27
27
|
ReadRules,
|
|
28
|
+
T_InputRules,
|
|
28
29
|
VerifiedRules,
|
|
29
30
|
)
|
|
30
31
|
from cognite.neat._rules.analysis import DMSAnalysis
|
|
32
|
+
from cognite.neat._rules.importers import DMSImporter
|
|
31
33
|
from cognite.neat._rules.models import (
|
|
32
34
|
DMSInputRules,
|
|
33
35
|
DMSRules,
|
|
@@ -36,7 +38,7 @@ from cognite.neat._rules.models import (
|
|
|
36
38
|
data_types,
|
|
37
39
|
)
|
|
38
40
|
from cognite.neat._rules.models.data_types import AnyURI, DataType, String
|
|
39
|
-
from cognite.neat._rules.models.dms import DMSMetadata, DMSProperty, DMSView
|
|
41
|
+
from cognite.neat._rules.models.dms import DMSMetadata, DMSProperty, DMSValidation, DMSView
|
|
40
42
|
from cognite.neat._rules.models.dms._rules import DMSContainer
|
|
41
43
|
from cognite.neat._rules.models.entities import (
|
|
42
44
|
ClassEntity,
|
|
@@ -60,40 +62,35 @@ from cognite.neat._utils.collection_ import remove_list_elements
|
|
|
60
62
|
from cognite.neat._utils.text import to_camel
|
|
61
63
|
|
|
62
64
|
from ._base import RulesTransformer
|
|
65
|
+
from ._verification import VerifyDMSRules
|
|
63
66
|
|
|
64
67
|
T_VerifiedInRules = TypeVar("T_VerifiedInRules", bound=VerifiedRules)
|
|
65
68
|
T_VerifiedOutRules = TypeVar("T_VerifiedOutRules", bound=VerifiedRules)
|
|
66
|
-
T_InputInRules = TypeVar("T_InputInRules", bound=
|
|
67
|
-
T_InputOutRules = TypeVar("T_InputOutRules", bound=
|
|
69
|
+
T_InputInRules = TypeVar("T_InputInRules", bound=ReadInputRules)
|
|
70
|
+
T_InputOutRules = TypeVar("T_InputOutRules", bound=ReadInputRules)
|
|
68
71
|
|
|
69
72
|
|
|
70
73
|
class ConversionTransformer(RulesTransformer[T_VerifiedInRules, T_VerifiedOutRules], ABC):
|
|
71
74
|
"""Base class for all conversion transformers."""
|
|
72
75
|
|
|
73
|
-
|
|
74
|
-
out = self._transform(self._to_rules(rules))
|
|
75
|
-
return JustRules(out)
|
|
76
|
+
...
|
|
76
77
|
|
|
77
|
-
@abstractmethod
|
|
78
|
-
def _transform(self, rules: T_VerifiedInRules) -> T_VerifiedOutRules:
|
|
79
|
-
raise NotImplementedError()
|
|
80
78
|
|
|
81
|
-
|
|
82
|
-
class ToCompliantEntities(RulesTransformer[InformationInputRules, InformationInputRules]): # type: ignore[misc]
|
|
79
|
+
class ToCompliantEntities(RulesTransformer[ReadRules[InformationInputRules], ReadRules[InformationInputRules]]): # type: ignore[misc]
|
|
83
80
|
"""Converts input rules to rules with compliant entity IDs that match regex patters used
|
|
84
81
|
by DMS schema components."""
|
|
85
82
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return ReadRules(self._transform(self._to_rules(rules)), IssueList(), {})
|
|
90
|
-
|
|
91
|
-
def _transform(self, rules: InformationInputRules) -> InformationInputRules:
|
|
92
|
-
rules.classes = self._fix_classes(rules.classes)
|
|
93
|
-
rules.properties = self._fix_properties(rules.properties)
|
|
94
|
-
rules.metadata.version += "_dms_compliant"
|
|
83
|
+
@property
|
|
84
|
+
def description(self) -> str:
|
|
85
|
+
return "Ensures externalIDs are compliant with CDF"
|
|
95
86
|
|
|
96
|
-
|
|
87
|
+
def transform(self, rules: ReadRules[InformationInputRules]) -> ReadRules[InformationInputRules]:
|
|
88
|
+
if rules.rules is None:
|
|
89
|
+
return rules
|
|
90
|
+
copy: InformationInputRules = dataclasses.replace(rules.rules)
|
|
91
|
+
copy.classes = self._fix_classes(copy.classes)
|
|
92
|
+
copy.properties = self._fix_properties(copy.properties)
|
|
93
|
+
return ReadRules(copy, rules.read_context)
|
|
97
94
|
|
|
98
95
|
@classmethod
|
|
99
96
|
def _fix_entity(cls, entity: str) -> str:
|
|
@@ -179,46 +176,47 @@ class ToCompliantEntities(RulesTransformer[InformationInputRules, InformationInp
|
|
|
179
176
|
return fixed_definitions
|
|
180
177
|
|
|
181
178
|
|
|
182
|
-
class PrefixEntities(RulesTransformer[
|
|
179
|
+
class PrefixEntities(RulesTransformer[ReadRules[T_InputRules], ReadRules[T_InputRules]]): # type: ignore[type-var]
|
|
183
180
|
"""Prefixes all entities with a given prefix."""
|
|
184
181
|
|
|
185
182
|
def __init__(self, prefix: str) -> None:
|
|
186
183
|
self._prefix = prefix
|
|
187
184
|
|
|
188
|
-
|
|
189
|
-
|
|
185
|
+
@property
|
|
186
|
+
def description(self) -> str:
|
|
187
|
+
return f"Prefixes all views with {self._prefix!r}"
|
|
190
188
|
|
|
191
|
-
def
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
189
|
+
def transform(self, rules: ReadRules[T_InputRules]) -> ReadRules[T_InputRules]:
|
|
190
|
+
in_ = rules.rules
|
|
191
|
+
if in_ is None:
|
|
192
|
+
return rules
|
|
193
|
+
copy: T_InputRules = dataclasses.replace(in_)
|
|
194
|
+
if isinstance(copy, InformationInputRules):
|
|
196
195
|
prefixed_by_class: dict[str, str] = {}
|
|
197
|
-
for cls in
|
|
196
|
+
for cls in copy.classes:
|
|
198
197
|
prefixed = str(self._with_prefix(cls.class_))
|
|
199
198
|
prefixed_by_class[str(cls.class_)] = prefixed
|
|
200
199
|
cls.class_ = prefixed
|
|
201
|
-
for prop in
|
|
200
|
+
for prop in copy.properties:
|
|
202
201
|
prop.class_ = self._with_prefix(prop.class_)
|
|
203
202
|
if str(prop.value_type) in prefixed_by_class:
|
|
204
203
|
prop.value_type = prefixed_by_class[str(prop.value_type)]
|
|
205
|
-
return rules
|
|
206
|
-
elif isinstance(
|
|
207
|
-
# Todo not mutate input class new_dms = copy.deepcopy(rules)
|
|
204
|
+
return ReadRules(copy, rules.read_context) # type: ignore[arg-type]
|
|
205
|
+
elif isinstance(copy, DMSInputRules):
|
|
208
206
|
prefixed_by_view: dict[str, str] = {}
|
|
209
|
-
for view in
|
|
207
|
+
for view in copy.views:
|
|
210
208
|
prefixed = str(self._with_prefix(view.view))
|
|
211
209
|
prefixed_by_view[str(view.view)] = prefixed
|
|
212
210
|
view.view = prefixed
|
|
213
|
-
for dms_prop in
|
|
211
|
+
for dms_prop in copy.properties:
|
|
214
212
|
dms_prop.view = self._with_prefix(dms_prop.view)
|
|
215
213
|
if str(dms_prop.value_type) in prefixed_by_view:
|
|
216
214
|
dms_prop.value_type = prefixed_by_view[str(dms_prop.value_type)]
|
|
217
|
-
if
|
|
218
|
-
for container in
|
|
215
|
+
if copy.containers:
|
|
216
|
+
for container in copy.containers:
|
|
219
217
|
container.container = self._with_prefix(container.container)
|
|
220
|
-
return rules
|
|
221
|
-
raise NeatValueError(f"Unsupported rules type: {type(
|
|
218
|
+
return ReadRules(copy, rules.read_context)
|
|
219
|
+
raise NeatValueError(f"Unsupported rules type: {type(copy)}")
|
|
222
220
|
|
|
223
221
|
@overload
|
|
224
222
|
def _with_prefix(self, raw: str) -> str: ...
|
|
@@ -254,14 +252,14 @@ class InformationToDMS(ConversionTransformer[InformationRules, DMSRules]):
|
|
|
254
252
|
self.ignore_undefined_value_types = ignore_undefined_value_types
|
|
255
253
|
self.mode = mode
|
|
256
254
|
|
|
257
|
-
def
|
|
255
|
+
def transform(self, rules: InformationRules) -> DMSRules:
|
|
258
256
|
return _InformationRulesConverter(rules).as_dms_rules(self.ignore_undefined_value_types, self.mode)
|
|
259
257
|
|
|
260
258
|
|
|
261
259
|
class DMSToInformation(ConversionTransformer[DMSRules, InformationRules]):
|
|
262
260
|
"""Converts DMSRules to InformationRules."""
|
|
263
261
|
|
|
264
|
-
def
|
|
262
|
+
def transform(self, rules: DMSRules) -> InformationRules:
|
|
265
263
|
return _DMSRulesConverter(rules).as_information_rules()
|
|
266
264
|
|
|
267
265
|
|
|
@@ -271,13 +269,13 @@ class ConvertToRules(ConversionTransformer[VerifiedRules, VerifiedRules]):
|
|
|
271
269
|
def __init__(self, out_cls: type[VerifiedRules]):
|
|
272
270
|
self._out_cls = out_cls
|
|
273
271
|
|
|
274
|
-
def
|
|
272
|
+
def transform(self, rules: VerifiedRules) -> VerifiedRules:
|
|
275
273
|
if isinstance(rules, self._out_cls):
|
|
276
274
|
return rules
|
|
277
275
|
if isinstance(rules, InformationRules) and self._out_cls is DMSRules:
|
|
278
|
-
return InformationToDMS().transform(rules)
|
|
276
|
+
return InformationToDMS().transform(rules)
|
|
279
277
|
if isinstance(rules, DMSRules) and self._out_cls is InformationRules:
|
|
280
|
-
return DMSToInformation().transform(rules)
|
|
278
|
+
return DMSToInformation().transform(rules)
|
|
281
279
|
raise ValueError(f"Unsupported conversion from {type(rules)} to {self._out_cls}")
|
|
282
280
|
|
|
283
281
|
|
|
@@ -288,16 +286,20 @@ class SetIDDMSModel(RulesTransformer[DMSRules, DMSRules]):
|
|
|
288
286
|
def __init__(self, new_id: DataModelId | tuple[str, str, str]):
|
|
289
287
|
self.new_id = DataModelId.load(new_id)
|
|
290
288
|
|
|
291
|
-
|
|
289
|
+
@property
|
|
290
|
+
def description(self) -> str:
|
|
291
|
+
return f"Sets the Data Model ID to {self.new_id.as_tuple()}"
|
|
292
|
+
|
|
293
|
+
def transform(self, rules: DMSRules) -> DMSRules:
|
|
292
294
|
if self.new_id.version is None:
|
|
293
295
|
raise NeatValueError("Version is required when setting a new Data Model ID")
|
|
294
|
-
dump =
|
|
296
|
+
dump = rules.dump()
|
|
295
297
|
dump["metadata"]["space"] = self.new_id.space
|
|
296
298
|
dump["metadata"]["external_id"] = self.new_id.external_id
|
|
297
299
|
dump["metadata"]["version"] = self.new_id.version
|
|
298
300
|
# Serialize and deserialize to set the new space and external_id
|
|
299
301
|
# as the default values for the new model.
|
|
300
|
-
return
|
|
302
|
+
return DMSRules.model_validate(DMSInputRules.load(dump).dump())
|
|
301
303
|
|
|
302
304
|
|
|
303
305
|
class ToExtension(RulesTransformer[DMSRules, DMSRules]):
|
|
@@ -322,9 +324,9 @@ class ToExtension(RulesTransformer[DMSRules, DMSRules]):
|
|
|
322
324
|
self.move_connections = move_connections
|
|
323
325
|
self.include = include
|
|
324
326
|
|
|
325
|
-
def transform(self, rules: DMSRules
|
|
327
|
+
def transform(self, rules: DMSRules) -> DMSRules:
|
|
326
328
|
# Copy to ensure immutability
|
|
327
|
-
reference_model =
|
|
329
|
+
reference_model = rules
|
|
328
330
|
reference_model_id = reference_model.metadata.as_data_model_id()
|
|
329
331
|
|
|
330
332
|
# if model is solution then we need to get correct space for views and containers
|
|
@@ -365,7 +367,7 @@ class ToExtension(RulesTransformer[DMSRules, DMSRules]):
|
|
|
365
367
|
def _has_views_in_multiple_space(self, rules: DMSRules) -> bool:
|
|
366
368
|
return any(view.view.space != rules.metadata.space for view in rules.views)
|
|
367
369
|
|
|
368
|
-
def _to_solution(self, reference_rules: DMSRules, remove_views_in_other_space: bool = True) ->
|
|
370
|
+
def _to_solution(self, reference_rules: DMSRules, remove_views_in_other_space: bool = True) -> DMSRules:
|
|
369
371
|
"""For creation of solution data model / rules specifically for mapping over existing containers."""
|
|
370
372
|
|
|
371
373
|
dump = reference_rules.dump()
|
|
@@ -420,9 +422,9 @@ class ToExtension(RulesTransformer[DMSRules, DMSRules]):
|
|
|
420
422
|
solution_model.containers = new_containers
|
|
421
423
|
solution_model.properties.extend(new_properties)
|
|
422
424
|
|
|
423
|
-
return
|
|
425
|
+
return solution_model
|
|
424
426
|
|
|
425
|
-
def _to_enterprise(self, reference_model: DMSRules) ->
|
|
427
|
+
def _to_enterprise(self, reference_model: DMSRules) -> DMSRules:
|
|
426
428
|
dump = reference_model.dump()
|
|
427
429
|
|
|
428
430
|
# This will create reference model components in the enterprise model space
|
|
@@ -454,7 +456,7 @@ class ToExtension(RulesTransformer[DMSRules, DMSRules]):
|
|
|
454
456
|
|
|
455
457
|
enterprise_properties.extend(enterprise_connections)
|
|
456
458
|
|
|
457
|
-
return
|
|
459
|
+
return enterprise_model
|
|
458
460
|
|
|
459
461
|
@staticmethod
|
|
460
462
|
def _expand_properties(rules: DMSRules) -> DMSRules:
|
|
@@ -558,6 +560,17 @@ class ToExtension(RulesTransformer[DMSRules, DMSRules]):
|
|
|
558
560
|
|
|
559
561
|
return new_properties
|
|
560
562
|
|
|
563
|
+
@property
|
|
564
|
+
def description(self) -> str:
|
|
565
|
+
if self.type_ == "enterprise":
|
|
566
|
+
return f"Prepared data model {self.new_model_id} to be enterprise data model."
|
|
567
|
+
elif self.type_ == "solution":
|
|
568
|
+
return f"Prepared data model {self.new_model_id} to be solution data model."
|
|
569
|
+
elif self.type_ == "data_product":
|
|
570
|
+
return f"Prepared data model {self.new_model_id} to be data product model."
|
|
571
|
+
else:
|
|
572
|
+
return f"Unsupported data model type: {self.type_}"
|
|
573
|
+
|
|
561
574
|
|
|
562
575
|
class ReduceCogniteModel(RulesTransformer[DMSRules, DMSRules]):
|
|
563
576
|
_ASSET_VIEW = ViewId("cdf_cdm", "CogniteAsset", "v1")
|
|
@@ -605,8 +618,8 @@ class ReduceCogniteModel(RulesTransformer[DMSRules, DMSRules]):
|
|
|
605
618
|
)
|
|
606
619
|
self.drop_external_ids = {external_id for external_id in drop if external_id not in self._VIEW_BY_COLLECTION}
|
|
607
620
|
|
|
608
|
-
def transform(self, rules: DMSRules
|
|
609
|
-
verified =
|
|
621
|
+
def transform(self, rules: DMSRules) -> DMSRules:
|
|
622
|
+
verified = rules
|
|
610
623
|
if verified.metadata.as_data_model_id() not in COGNITE_MODELS:
|
|
611
624
|
raise NeatValueError(f"Can only reduce Cognite Data Models, not {verified.metadata.as_data_model_id()}")
|
|
612
625
|
|
|
@@ -630,13 +643,87 @@ class ReduceCogniteModel(RulesTransformer[DMSRules, DMSRules]):
|
|
|
630
643
|
|
|
631
644
|
new_model.properties = new_properties
|
|
632
645
|
|
|
633
|
-
return
|
|
646
|
+
return new_model
|
|
634
647
|
|
|
635
648
|
def _is_asset_3D_property(self, prop: DMSProperty) -> bool:
|
|
636
649
|
if "3D" not in self.drop_collection:
|
|
637
650
|
return False
|
|
638
651
|
return prop.view.as_id() == self._ASSET_VIEW and prop.view_property == "object3D"
|
|
639
652
|
|
|
653
|
+
@property
|
|
654
|
+
def description(self) -> str:
|
|
655
|
+
return f"Removed {len(self.drop_external_ids) + len(self.drop_collection)} views from data model"
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
class IncludeReferenced(RulesTransformer[DMSRules, DMSRules]):
|
|
659
|
+
def __init__(self, client: NeatClient, include_properties: bool = False) -> None:
|
|
660
|
+
self._client = client
|
|
661
|
+
self.include_properties = include_properties
|
|
662
|
+
|
|
663
|
+
def transform(self, rules: DMSRules) -> DMSRules:
|
|
664
|
+
dms_rules = rules
|
|
665
|
+
view_ids, container_ids = DMSValidation(dms_rules, self._client).imported_views_and_containers_ids()
|
|
666
|
+
if not (view_ids or container_ids):
|
|
667
|
+
warnings.warn(
|
|
668
|
+
NeatValueWarning(
|
|
669
|
+
f"Data model {dms_rules.metadata.as_data_model_id()} does not have any "
|
|
670
|
+
"referenced views or containers."
|
|
671
|
+
"that is not already included in the data model."
|
|
672
|
+
),
|
|
673
|
+
stacklevel=2,
|
|
674
|
+
)
|
|
675
|
+
return dms_rules
|
|
676
|
+
|
|
677
|
+
schema = self._client.schema.retrieve([v.as_id() for v in view_ids], [c.as_id() for c in container_ids])
|
|
678
|
+
copy_ = dms_rules.model_copy(deep=True)
|
|
679
|
+
# Sorting to ensure deterministic order
|
|
680
|
+
schema.containers = ContainerApplyDict(sorted(schema.containers.items(), key=lambda x: x[0].as_tuple()))
|
|
681
|
+
schema.views = ViewApplyDict(sorted(schema.views.items(), key=lambda x: x[0].as_tuple()))
|
|
682
|
+
importer = DMSImporter(schema)
|
|
683
|
+
|
|
684
|
+
imported = importer.to_rules()
|
|
685
|
+
if imported.rules is None:
|
|
686
|
+
raise NeatValueError("Could not import the referenced views and containers.")
|
|
687
|
+
|
|
688
|
+
verified = VerifyDMSRules(validate=False).transform(imported)
|
|
689
|
+
if copy_.containers is None:
|
|
690
|
+
copy_.containers = verified.containers
|
|
691
|
+
else:
|
|
692
|
+
existing_containers = {c.container for c in copy_.containers}
|
|
693
|
+
copy_.containers.extend([c for c in verified.containers or [] if c.container not in existing_containers])
|
|
694
|
+
existing_views = {v.view for v in copy_.views}
|
|
695
|
+
copy_.views.extend([v for v in verified.views if v.view not in existing_views])
|
|
696
|
+
if self.include_properties:
|
|
697
|
+
existing_properties = {(p.view, p.view_property) for p in copy_.properties}
|
|
698
|
+
copy_.properties.extend(
|
|
699
|
+
[p for p in verified.properties if (p.view, p.view_property) not in existing_properties]
|
|
700
|
+
)
|
|
701
|
+
|
|
702
|
+
return copy_
|
|
703
|
+
|
|
704
|
+
@property
|
|
705
|
+
def description(self) -> str:
|
|
706
|
+
return "Included referenced views and containers in the data model."
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
class AddClassImplements(RulesTransformer[InformationRules, InformationRules]):
|
|
710
|
+
def __init__(self, implements: str, suffix: str):
|
|
711
|
+
self.implements = implements
|
|
712
|
+
self.suffix = suffix
|
|
713
|
+
|
|
714
|
+
def transform(self, rules: InformationRules) -> InformationRules:
|
|
715
|
+
info_rules = rules
|
|
716
|
+
output = info_rules.model_copy(deep=True)
|
|
717
|
+
for class_ in output.classes:
|
|
718
|
+
if class_.class_.suffix.endswith(self.suffix):
|
|
719
|
+
class_.implements = [ClassEntity(prefix=class_.class_.prefix, suffix=self.implements)]
|
|
720
|
+
output.metadata.version = f"{output.metadata.version}.implements_{self.implements}"
|
|
721
|
+
return output
|
|
722
|
+
|
|
723
|
+
@property
|
|
724
|
+
def description(self) -> str:
|
|
725
|
+
return f"Added implements property to classes with suffix {self.suffix}"
|
|
726
|
+
|
|
640
727
|
|
|
641
728
|
class _InformationRulesConverter:
|
|
642
729
|
_edge_properties: ClassVar[frozenset[str]] = frozenset({"endNode", "end_node", "startNode", "start_node"})
|
|
@@ -9,7 +9,6 @@ from cognite.client import data_modeling as dm
|
|
|
9
9
|
from cognite.neat._client import NeatClient
|
|
10
10
|
from cognite.neat._issues.errors import CDFMissingClientError, NeatValueError, ResourceNotFoundError
|
|
11
11
|
from cognite.neat._issues.warnings import NeatValueWarning, PropertyOverwritingWarning
|
|
12
|
-
from cognite.neat._rules._shared import JustRules, OutRules
|
|
13
12
|
from cognite.neat._rules.models import DMSRules, SheetList
|
|
14
13
|
from cognite.neat._rules.models.data_types import Enum
|
|
15
14
|
from cognite.neat._rules.models.dms import DMSEnum, DMSProperty, DMSView
|
|
@@ -55,8 +54,8 @@ class MapOneToOne(MapOntoTransformers):
|
|
|
55
54
|
self.view_extension_mapping = view_extension_mapping
|
|
56
55
|
self.default_extension = default_extension
|
|
57
56
|
|
|
58
|
-
def transform(self, rules: DMSRules
|
|
59
|
-
solution: DMSRules =
|
|
57
|
+
def transform(self, rules: DMSRules) -> DMSRules:
|
|
58
|
+
solution: DMSRules = rules
|
|
60
59
|
view_by_external_id = {view.view.external_id: view for view in solution.views}
|
|
61
60
|
ref_view_by_external_id = {view.view.external_id: view for view in self.reference.views}
|
|
62
61
|
|
|
@@ -99,7 +98,7 @@ class MapOneToOne(MapOntoTransformers):
|
|
|
99
98
|
prop.container = ref_prop.container
|
|
100
99
|
prop.container_property = ref_prop.container_property
|
|
101
100
|
|
|
102
|
-
return
|
|
101
|
+
return solution
|
|
103
102
|
|
|
104
103
|
|
|
105
104
|
class RuleMapper(RulesTransformer[DMSRules, DMSRules]):
|
|
@@ -128,12 +127,11 @@ class RuleMapper(RulesTransformer[DMSRules, DMSRules]):
|
|
|
128
127
|
def _property_by_view_property(self) -> dict[tuple[str, str], DMSProperty]:
|
|
129
128
|
return {(prop.view.external_id, prop.view_property): prop for prop in self.mapping.properties}
|
|
130
129
|
|
|
131
|
-
def transform(self, rules: DMSRules
|
|
130
|
+
def transform(self, rules: DMSRules) -> DMSRules:
|
|
132
131
|
if self.data_type_conflict != "overwrite":
|
|
133
132
|
raise NeatValueError(f"Invalid data_type_conflict: {self.data_type_conflict}")
|
|
134
|
-
input_rules =
|
|
133
|
+
input_rules = rules
|
|
135
134
|
new_rules = input_rules.model_copy(deep=True)
|
|
136
|
-
new_rules.metadata.version += "_mapped"
|
|
137
135
|
|
|
138
136
|
for view in new_rules.views:
|
|
139
137
|
if mapping_view := self._view_by_entity_id.get(view.view.external_id):
|
|
@@ -183,7 +181,7 @@ class RuleMapper(RulesTransformer[DMSRules, DMSRules]):
|
|
|
183
181
|
if item.collection in new_enums:
|
|
184
182
|
new_rules.enum.append(item)
|
|
185
183
|
|
|
186
|
-
return
|
|
184
|
+
return new_rules
|
|
187
185
|
|
|
188
186
|
def _find_overwrites(self, prop: DMSProperty, mapping_prop: DMSProperty) -> tuple[dict[str, Any], list[str]]:
|
|
189
187
|
"""Finds the properties that need to be overwritten and returns them.
|
|
@@ -212,6 +210,10 @@ class RuleMapper(RulesTransformer[DMSRules, DMSRules]):
|
|
|
212
210
|
conflicts.append(mapping_prop.model_fields[field_name].alias or field_name)
|
|
213
211
|
return to_overwrite, conflicts
|
|
214
212
|
|
|
213
|
+
@property
|
|
214
|
+
def description(self) -> str:
|
|
215
|
+
return f"Mapping to {self.mapping.metadata.as_data_model_id()!r}."
|
|
216
|
+
|
|
215
217
|
|
|
216
218
|
class AsParentPropertyId(RulesTransformer[DMSRules, DMSRules]):
|
|
217
219
|
"""Looks up all view properties that map to the same container property,
|
|
@@ -221,10 +223,9 @@ class AsParentPropertyId(RulesTransformer[DMSRules, DMSRules]):
|
|
|
221
223
|
def __init__(self, client: NeatClient | None = None) -> None:
|
|
222
224
|
self._client = client
|
|
223
225
|
|
|
224
|
-
def transform(self, rules: DMSRules
|
|
225
|
-
input_rules =
|
|
226
|
+
def transform(self, rules: DMSRules) -> DMSRules:
|
|
227
|
+
input_rules = rules
|
|
226
228
|
new_rules = input_rules.model_copy(deep=True)
|
|
227
|
-
new_rules.metadata.version += "_as_parent_name"
|
|
228
229
|
|
|
229
230
|
path_by_view = self._inheritance_path_by_view(new_rules)
|
|
230
231
|
view_by_container_property = self._view_by_container_properties(new_rules)
|
|
@@ -240,7 +241,7 @@ class AsParentPropertyId(RulesTransformer[DMSRules, DMSRules]):
|
|
|
240
241
|
):
|
|
241
242
|
prop.view_property = parent_name
|
|
242
243
|
|
|
243
|
-
return
|
|
244
|
+
return new_rules
|
|
244
245
|
|
|
245
246
|
# Todo: Move into Probe class. Note this means that the Probe class must take a NeatClient as an argument.
|
|
246
247
|
def _inheritance_path_by_view(self, rules: DMSRules) -> dict[ViewEntity, list[ViewEntity]]:
|
|
@@ -338,3 +339,7 @@ class AsParentPropertyId(RulesTransformer[DMSRules, DMSRules]):
|
|
|
338
339
|
_, prop_name = min(view_properties, key=lambda prop: len(path_by_view[prop[0]]))
|
|
339
340
|
parent_name_by_container_property[(container, container_property)] = prop_name
|
|
340
341
|
return parent_name_by_container_property
|
|
342
|
+
|
|
343
|
+
@property
|
|
344
|
+
def description(self) -> str:
|
|
345
|
+
return "Renaming property names to parent name"
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
from abc import ABC
|
|
2
|
-
from typing import Any, Literal
|
|
3
2
|
|
|
3
|
+
from cognite.neat._client import NeatClient
|
|
4
4
|
from cognite.neat._issues import IssueList, MultiValueError, NeatError, NeatWarning, catch_issues
|
|
5
|
-
from cognite.neat._issues.errors import NeatTypeError
|
|
5
|
+
from cognite.neat._issues.errors import NeatTypeError, NeatValueError
|
|
6
6
|
from cognite.neat._rules._shared import (
|
|
7
|
-
InputRules,
|
|
8
|
-
MaybeRules,
|
|
9
|
-
OutRules,
|
|
10
7
|
ReadRules,
|
|
11
|
-
|
|
8
|
+
T_ReadInputRules,
|
|
12
9
|
T_VerifiedRules,
|
|
13
10
|
VerifiedRules,
|
|
14
11
|
)
|
|
@@ -24,78 +21,94 @@ from cognite.neat._rules.models.information import InformationValidation
|
|
|
24
21
|
from ._base import RulesTransformer
|
|
25
22
|
|
|
26
23
|
|
|
27
|
-
class VerificationTransformer(RulesTransformer[
|
|
24
|
+
class VerificationTransformer(RulesTransformer[T_ReadInputRules, T_VerifiedRules], ABC):
|
|
28
25
|
"""Base class for all verification transformers."""
|
|
29
26
|
|
|
30
27
|
_rules_cls: type[T_VerifiedRules]
|
|
31
28
|
_validation_cls: type
|
|
32
29
|
|
|
33
|
-
def __init__(self,
|
|
34
|
-
self.errors = errors
|
|
30
|
+
def __init__(self, validate: bool = True, client: NeatClient | None = None) -> None:
|
|
35
31
|
self.validate = validate
|
|
32
|
+
self._client = client
|
|
36
33
|
|
|
37
|
-
def transform(self, rules:
|
|
38
|
-
|
|
39
|
-
in_
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
error_args = rules.read_context
|
|
34
|
+
def transform(self, rules: T_ReadInputRules) -> T_VerifiedRules:
|
|
35
|
+
in_ = rules.rules
|
|
36
|
+
if in_ is None:
|
|
37
|
+
raise NeatValueError("Cannot verify rules. The reading of the rules failed.")
|
|
38
|
+
error_args = rules.read_context
|
|
43
39
|
verified_rules: T_VerifiedRules | None = None
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
# We need to catch issues as we use the error args to provide extra context for the errors/warnings
|
|
41
|
+
# For example, which row in the spreadsheet the error occurred o
|
|
42
|
+
issues = IssueList()
|
|
43
|
+
with catch_issues(issues, NeatError, NeatWarning, error_args) as _:
|
|
44
|
+
rules_cls = self._get_rules_cls(rules)
|
|
46
45
|
dumped = in_.dump()
|
|
47
46
|
verified_rules = rules_cls.model_validate(dumped) # type: ignore[assignment]
|
|
48
47
|
if self.validate:
|
|
49
48
|
validation_cls = self._get_validation_cls(verified_rules) # type: ignore[arg-type]
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
if issubclass(validation_cls, DMSValidation):
|
|
50
|
+
validation_issues = DMSValidation(verified_rules, self._client).validate() # type: ignore[arg-type]
|
|
51
|
+
elif issubclass(validation_cls, InformationValidation):
|
|
52
|
+
validation_issues = InformationValidation(verified_rules).validate() # type: ignore[arg-type]
|
|
53
|
+
else:
|
|
54
|
+
raise NeatValueError("Unsupported rule type")
|
|
55
|
+
|
|
56
|
+
# Need to trigger and raise such that the catch_issues can add the extra context
|
|
57
|
+
validation_issues.trigger_warnings()
|
|
55
58
|
if validation_issues.has_errors:
|
|
56
|
-
verified_rules = None
|
|
57
59
|
raise MultiValueError(validation_issues.errors)
|
|
58
60
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
# Raise issues which is expected to be handled outside of this method
|
|
62
|
+
issues.trigger_warnings()
|
|
63
|
+
if issues.has_errors:
|
|
64
|
+
raise MultiValueError(issues.errors)
|
|
65
|
+
if verified_rules is None:
|
|
66
|
+
raise NeatValueError("Rules were not verified")
|
|
67
|
+
return verified_rules
|
|
65
68
|
|
|
66
|
-
def _get_rules_cls(self, in_:
|
|
69
|
+
def _get_rules_cls(self, in_: T_ReadInputRules) -> type[T_VerifiedRules]:
|
|
67
70
|
return self._rules_cls
|
|
68
71
|
|
|
69
72
|
def _get_validation_cls(self, rules: T_VerifiedRules) -> type:
|
|
70
73
|
return self._validation_cls
|
|
71
74
|
|
|
75
|
+
@property
|
|
76
|
+
def description(self) -> str:
|
|
77
|
+
return "Verify rules"
|
|
78
|
+
|
|
72
79
|
|
|
73
|
-
class VerifyDMSRules(VerificationTransformer[DMSInputRules, DMSRules]):
|
|
80
|
+
class VerifyDMSRules(VerificationTransformer[ReadRules[DMSInputRules], DMSRules]):
|
|
74
81
|
"""Class to verify DMS rules."""
|
|
75
82
|
|
|
76
83
|
_rules_cls = DMSRules
|
|
77
84
|
_validation_cls = DMSValidation
|
|
78
85
|
|
|
86
|
+
def transform(self, rules: ReadRules[DMSInputRules]) -> DMSRules:
|
|
87
|
+
return super().transform(rules)
|
|
79
88
|
|
|
80
|
-
|
|
89
|
+
|
|
90
|
+
class VerifyInformationRules(VerificationTransformer[ReadRules[InformationInputRules], InformationRules]):
|
|
81
91
|
"""Class to verify Information rules."""
|
|
82
92
|
|
|
83
93
|
_rules_cls = InformationRules
|
|
84
94
|
_validation_cls = InformationValidation
|
|
85
95
|
|
|
96
|
+
def transform(self, rules: ReadRules[InformationInputRules]) -> InformationRules:
|
|
97
|
+
return super().transform(rules)
|
|
98
|
+
|
|
86
99
|
|
|
87
|
-
class VerifyAnyRules(VerificationTransformer[
|
|
100
|
+
class VerifyAnyRules(VerificationTransformer[T_ReadInputRules, VerifiedRules]):
|
|
88
101
|
"""Class to verify arbitrary rules"""
|
|
89
102
|
|
|
90
|
-
def _get_rules_cls(self, in_:
|
|
91
|
-
if isinstance(in_, InformationInputRules):
|
|
103
|
+
def _get_rules_cls(self, in_: T_ReadInputRules) -> type[VerifiedRules]:
|
|
104
|
+
if isinstance(in_.rules, InformationInputRules):
|
|
92
105
|
return InformationRules
|
|
93
|
-
elif isinstance(in_, DMSInputRules):
|
|
106
|
+
elif isinstance(in_.rules, DMSInputRules):
|
|
94
107
|
return DMSRules
|
|
95
108
|
else:
|
|
96
109
|
raise NeatTypeError(f"Unsupported rules type: {type(in_)}")
|
|
97
110
|
|
|
98
|
-
def _get_validation_cls(self, rules:
|
|
111
|
+
def _get_validation_cls(self, rules: VerifiedRules) -> type:
|
|
99
112
|
if isinstance(rules, InformationRules):
|
|
100
113
|
return InformationValidation
|
|
101
114
|
elif isinstance(rules, DMSRules):
|