cognite-neat 0.88.2__py3-none-any.whl → 0.89.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/_version.py +1 -1
- cognite/neat/constants.py +3 -0
- cognite/neat/graph/__init__.py +0 -3
- cognite/neat/graph/extractors/_mock_graph_generator.py +2 -1
- cognite/neat/graph/loaders/_base.py +3 -3
- cognite/neat/graph/loaders/_rdf2asset.py +24 -25
- cognite/neat/graph/loaders/_rdf2dms.py +20 -15
- cognite/neat/issues/__init__.py +1 -3
- cognite/neat/issues/_base.py +261 -71
- cognite/neat/issues/errors/__init__.py +73 -0
- cognite/neat/issues/errors/_external.py +67 -0
- cognite/neat/issues/errors/_general.py +35 -0
- cognite/neat/issues/errors/_properties.py +62 -0
- cognite/neat/issues/errors/_resources.py +111 -0
- cognite/neat/issues/errors/_workflow.py +36 -0
- cognite/neat/issues/formatters.py +1 -1
- cognite/neat/issues/warnings/__init__.py +66 -0
- cognite/neat/issues/warnings/_external.py +40 -0
- cognite/neat/issues/warnings/_general.py +29 -0
- cognite/neat/issues/warnings/_models.py +92 -0
- cognite/neat/issues/warnings/_properties.py +44 -0
- cognite/neat/issues/warnings/_resources.py +55 -0
- cognite/neat/issues/warnings/user_modeling.py +113 -0
- cognite/neat/rules/_shared.py +53 -2
- cognite/neat/rules/analysis/_base.py +1 -1
- cognite/neat/rules/exporters/_base.py +7 -18
- cognite/neat/rules/exporters/_rules2dms.py +17 -20
- cognite/neat/rules/exporters/_rules2excel.py +9 -16
- cognite/neat/rules/exporters/_rules2ontology.py +77 -64
- cognite/neat/rules/exporters/_rules2yaml.py +6 -9
- cognite/neat/rules/exporters/_validation.py +11 -96
- cognite/neat/rules/importers/_base.py +9 -58
- cognite/neat/rules/importers/_dms2rules.py +188 -135
- cognite/neat/rules/importers/_dtdl2rules/dtdl_converter.py +48 -35
- cognite/neat/rules/importers/_dtdl2rules/dtdl_importer.py +36 -45
- cognite/neat/rules/importers/_dtdl2rules/spec.py +7 -0
- cognite/neat/rules/importers/_rdf/_imf2rules/_imf2classes.py +8 -4
- cognite/neat/rules/importers/_rdf/_imf2rules/_imf2metadata.py +3 -3
- cognite/neat/rules/importers/_rdf/_imf2rules/_imf2properties.py +18 -11
- cognite/neat/rules/importers/_rdf/_imf2rules/_imf2rules.py +12 -19
- cognite/neat/rules/importers/_rdf/_inference2rules.py +14 -37
- cognite/neat/rules/importers/_rdf/_owl2rules/_owl2classes.py +1 -0
- cognite/neat/rules/importers/_rdf/_owl2rules/_owl2properties.py +1 -0
- cognite/neat/rules/importers/_rdf/_owl2rules/_owl2rules.py +9 -20
- cognite/neat/rules/importers/_rdf/_shared.py +4 -4
- cognite/neat/rules/importers/_spreadsheet2rules.py +46 -97
- cognite/neat/rules/importers/_yaml2rules.py +32 -58
- cognite/neat/rules/models/__init__.py +21 -5
- cognite/neat/rules/models/_base_input.py +162 -0
- cognite/neat/rules/models/{_base.py → _base_rules.py} +1 -12
- cognite/neat/rules/models/_rdfpath.py +4 -4
- cognite/neat/rules/models/{_types/_field.py → _types.py} +5 -10
- cognite/neat/rules/models/asset/__init__.py +5 -2
- cognite/neat/rules/models/asset/_rules.py +3 -23
- cognite/neat/rules/models/asset/_rules_input.py +40 -115
- cognite/neat/rules/models/asset/_validation.py +14 -10
- cognite/neat/rules/models/data_types.py +150 -44
- cognite/neat/rules/models/dms/__init__.py +19 -7
- cognite/neat/rules/models/dms/_exporter.py +102 -34
- cognite/neat/rules/models/dms/_rules.py +65 -162
- cognite/neat/rules/models/dms/_rules_input.py +186 -254
- cognite/neat/rules/models/dms/_schema.py +87 -78
- cognite/neat/rules/models/dms/_serializer.py +44 -3
- cognite/neat/rules/models/dms/_validation.py +106 -68
- cognite/neat/rules/models/domain.py +52 -1
- cognite/neat/rules/models/entities/__init__.py +63 -0
- cognite/neat/rules/models/entities/_constants.py +73 -0
- cognite/neat/rules/models/entities/_loaders.py +76 -0
- cognite/neat/rules/models/entities/_multi_value.py +67 -0
- cognite/neat/rules/models/{entities.py → entities/_single_value.py} +74 -232
- cognite/neat/rules/models/entities/_types.py +86 -0
- cognite/neat/rules/models/{wrapped_entities.py → entities/_wrapped.py} +1 -1
- cognite/neat/rules/models/information/__init__.py +10 -2
- cognite/neat/rules/models/information/_rules.py +10 -22
- cognite/neat/rules/models/information/_rules_input.py +57 -204
- cognite/neat/rules/models/information/_validation.py +48 -25
- cognite/neat/rules/transformers/__init__.py +21 -0
- cognite/neat/rules/transformers/_base.py +81 -0
- cognite/neat/rules/{models/information/_converter.py → transformers/_converters.py} +217 -21
- cognite/neat/rules/transformers/_map_onto.py +97 -0
- cognite/neat/rules/transformers/_pipelines.py +61 -0
- cognite/neat/rules/transformers/_verification.py +136 -0
- cognite/neat/{graph/stores → store}/_provenance.py +10 -1
- cognite/neat/utils/auxiliary.py +2 -35
- cognite/neat/utils/cdf/data_classes.py +20 -0
- cognite/neat/utils/regex_patterns.py +6 -0
- cognite/neat/utils/text.py +17 -0
- cognite/neat/workflows/base.py +4 -4
- cognite/neat/workflows/cdf_store.py +3 -3
- cognite/neat/workflows/steps/data_contracts.py +1 -1
- cognite/neat/workflows/steps/lib/current/graph_extractor.py +3 -3
- cognite/neat/workflows/steps/lib/current/graph_loader.py +2 -2
- cognite/neat/workflows/steps/lib/current/graph_store.py +1 -1
- cognite/neat/workflows/steps/lib/current/rules_exporter.py +116 -47
- cognite/neat/workflows/steps/lib/current/rules_importer.py +30 -28
- cognite/neat/workflows/steps/lib/current/rules_validator.py +5 -6
- cognite/neat/workflows/steps/lib/io/io_steps.py +5 -5
- cognite/neat/workflows/steps_registry.py +4 -5
- {cognite_neat-0.88.2.dist-info → cognite_neat-0.89.0.dist-info}/METADATA +1 -1
- {cognite_neat-0.88.2.dist-info → cognite_neat-0.89.0.dist-info}/RECORD +105 -106
- cognite/neat/exceptions.py +0 -145
- cognite/neat/graph/exceptions.py +0 -90
- cognite/neat/issues/errors/external.py +0 -21
- cognite/neat/issues/errors/properties.py +0 -75
- cognite/neat/issues/errors/resources.py +0 -123
- cognite/neat/issues/errors/schema.py +0 -0
- cognite/neat/issues/neat_warnings/__init__.py +0 -2
- cognite/neat/issues/neat_warnings/identifier.py +0 -27
- cognite/neat/issues/neat_warnings/models.py +0 -22
- cognite/neat/issues/neat_warnings/properties.py +0 -77
- cognite/neat/issues/neat_warnings/resources.py +0 -125
- cognite/neat/rules/issues/__init__.py +0 -22
- cognite/neat/rules/issues/base.py +0 -63
- cognite/neat/rules/issues/dms.py +0 -549
- cognite/neat/rules/issues/fileread.py +0 -197
- cognite/neat/rules/issues/ontology.py +0 -298
- cognite/neat/rules/issues/spreadsheet.py +0 -563
- cognite/neat/rules/issues/spreadsheet_file.py +0 -151
- cognite/neat/rules/issues/tables.py +0 -72
- cognite/neat/rules/models/_constants.py +0 -1
- cognite/neat/rules/models/_types/__init__.py +0 -19
- cognite/neat/rules/models/asset/_converter.py +0 -4
- cognite/neat/rules/models/dms/_converter.py +0 -145
- cognite/neat/workflows/_exceptions.py +0 -41
- /cognite/neat/{graph/stores → store}/__init__.py +0 -0
- /cognite/neat/{graph/stores → store}/_base.py +0 -0
- {cognite_neat-0.88.2.dist-info → cognite_neat-0.89.0.dist-info}/LICENSE +0 -0
- {cognite_neat-0.88.2.dist-info → cognite_neat-0.89.0.dist-info}/WHEEL +0 -0
- {cognite_neat-0.88.2.dist-info → cognite_neat-0.89.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,40 +1,114 @@
|
|
|
1
1
|
import re
|
|
2
|
+
import warnings
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
2
4
|
from collections import Counter, defaultdict
|
|
3
5
|
from collections.abc import Collection
|
|
4
6
|
from datetime import date, datetime
|
|
5
|
-
from typing import
|
|
7
|
+
from typing import Literal, TypeVar, cast
|
|
6
8
|
|
|
7
9
|
from cognite.client.data_classes import data_modeling as dms
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
from cognite.neat.
|
|
10
|
+
from rdflib import Namespace
|
|
11
|
+
|
|
12
|
+
from cognite.neat.constants import DMS_CONTAINER_PROPERTY_SIZE_LIMIT
|
|
13
|
+
from cognite.neat.issues.warnings.user_modeling import ParentInDifferentSpaceWarning
|
|
14
|
+
from cognite.neat.rules._shared import JustRules, OutRules, VerifiedRules
|
|
15
|
+
from cognite.neat.rules.models import (
|
|
16
|
+
AssetRules,
|
|
17
|
+
DMSRules,
|
|
18
|
+
DomainRules,
|
|
11
19
|
ExtensionCategory,
|
|
20
|
+
InformationRules,
|
|
12
21
|
SchemaCompleteness,
|
|
13
22
|
SheetList,
|
|
23
|
+
data_types,
|
|
14
24
|
)
|
|
15
|
-
from cognite.neat.rules.models._constants import DMS_CONTAINER_SIZE_LIMIT
|
|
16
25
|
from cognite.neat.rules.models.data_types import DataType
|
|
17
|
-
from cognite.neat.rules.models.
|
|
26
|
+
from cognite.neat.rules.models.dms import DMSMetadata, DMSProperty, DMSView
|
|
18
27
|
from cognite.neat.rules.models.entities import (
|
|
19
28
|
AssetEntity,
|
|
20
29
|
AssetFields,
|
|
21
30
|
ClassEntity,
|
|
22
31
|
ContainerEntity,
|
|
23
32
|
DMSUnknownEntity,
|
|
33
|
+
EdgeEntity,
|
|
24
34
|
EntityTypes,
|
|
25
35
|
MultiValueTypeInfo,
|
|
26
36
|
ReferenceEntity,
|
|
27
37
|
RelationshipEntity,
|
|
38
|
+
ReverseConnectionEntity,
|
|
28
39
|
UnknownEntity,
|
|
29
40
|
ViewEntity,
|
|
30
|
-
ViewPropertyEntity,
|
|
31
41
|
)
|
|
42
|
+
from cognite.neat.rules.models.information import InformationClass, InformationMetadata, InformationProperty
|
|
43
|
+
|
|
44
|
+
from ._base import RulesTransformer
|
|
45
|
+
|
|
46
|
+
T_VerifiedInRules = TypeVar("T_VerifiedInRules", bound=VerifiedRules)
|
|
47
|
+
T_VerifiedOutRules = TypeVar("T_VerifiedOutRules", bound=VerifiedRules)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ConversionTransformer(RulesTransformer[T_VerifiedInRules, T_VerifiedOutRules], ABC):
|
|
51
|
+
"""Base class for all conversion transformers."""
|
|
52
|
+
|
|
53
|
+
def transform(self, rules: T_VerifiedInRules | OutRules[T_VerifiedInRules]) -> JustRules[T_VerifiedOutRules]:
|
|
54
|
+
out = self._transform(self._to_rules(rules))
|
|
55
|
+
return JustRules(out)
|
|
56
|
+
|
|
57
|
+
@abstractmethod
|
|
58
|
+
def _transform(self, rules: T_VerifiedInRules) -> T_VerifiedOutRules:
|
|
59
|
+
raise NotImplementedError()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class InformationToDMS(ConversionTransformer[InformationRules, DMSRules]):
|
|
63
|
+
"""Converts InformationRules to DMSRules."""
|
|
64
|
+
|
|
65
|
+
def _transform(self, rules: InformationRules) -> DMSRules:
|
|
66
|
+
return _InformationRulesConverter(rules).as_dms_rules()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class InformationToAsset(ConversionTransformer[InformationRules, AssetRules]):
|
|
70
|
+
"""Converts InformationRules to AssetRules."""
|
|
71
|
+
|
|
72
|
+
def _transform(self, rules: InformationRules) -> AssetRules:
|
|
73
|
+
return _InformationRulesConverter(rules).as_asset_architect_rules()
|
|
74
|
+
|
|
32
75
|
|
|
33
|
-
|
|
76
|
+
class AssetToInformation(ConversionTransformer[AssetRules, InformationRules]):
|
|
77
|
+
"""Converts AssetRules to InformationRules."""
|
|
34
78
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
79
|
+
def _transform(self, rules: AssetRules) -> InformationRules:
|
|
80
|
+
return InformationRules.model_validate(rules.model_dump())
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class DMSToInformation(ConversionTransformer[DMSRules, InformationRules]):
|
|
84
|
+
"""Converts DMSRules to InformationRules."""
|
|
85
|
+
|
|
86
|
+
def _transform(self, rules: DMSRules) -> InformationRules:
|
|
87
|
+
return _DMSRulesConverter(rules).as_information_rules()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class ConvertToRules(ConversionTransformer[VerifiedRules, VerifiedRules]):
|
|
91
|
+
"""Converts any rules to any rules."""
|
|
92
|
+
|
|
93
|
+
def __init__(self, out_cls: type[VerifiedRules]):
|
|
94
|
+
self._out_cls = out_cls
|
|
95
|
+
|
|
96
|
+
def _transform(self, rules: VerifiedRules) -> VerifiedRules:
|
|
97
|
+
if isinstance(rules, self._out_cls):
|
|
98
|
+
return rules
|
|
99
|
+
if isinstance(rules, InformationRules) and self._out_cls is DMSRules:
|
|
100
|
+
return InformationToDMS().transform(rules).rules
|
|
101
|
+
if isinstance(rules, InformationRules) and self._out_cls is AssetRules:
|
|
102
|
+
return InformationToAsset().transform(rules).rules
|
|
103
|
+
if isinstance(rules, AssetRules) and self._out_cls is InformationRules:
|
|
104
|
+
return AssetToInformation().transform(rules).rules
|
|
105
|
+
if isinstance(rules, AssetRules) and self._out_cls is DMSRules:
|
|
106
|
+
return InformationToDMS().transform(AssetToInformation().transform(rules)).rules
|
|
107
|
+
if isinstance(rules, DMSRules) and self._out_cls is InformationRules:
|
|
108
|
+
return DMSToInformation().transform(rules).rules
|
|
109
|
+
if isinstance(rules, DMSRules) and self._out_cls is AssetRules:
|
|
110
|
+
return InformationToAsset().transform(DMSToInformation().transform(rules)).rules
|
|
111
|
+
raise ValueError(f"Unsupported conversion from {type(rules)} to {self._out_cls}")
|
|
38
112
|
|
|
39
113
|
|
|
40
114
|
class _InformationRulesConverter:
|
|
@@ -112,8 +186,10 @@ class _InformationRulesConverter:
|
|
|
112
186
|
for cls_ in self.rules.classes
|
|
113
187
|
]
|
|
114
188
|
|
|
115
|
-
last_dms_rules = self.rules.last.as_dms_rules() if self.rules.last else None
|
|
116
|
-
ref_dms_rules =
|
|
189
|
+
last_dms_rules = _InformationRulesConverter(self.rules.last).as_dms_rules() if self.rules.last else None
|
|
190
|
+
ref_dms_rules = (
|
|
191
|
+
_InformationRulesConverter(self.rules.reference).as_dms_rules() if self.rules.reference else None
|
|
192
|
+
)
|
|
117
193
|
|
|
118
194
|
class_by_entity = {cls_.class_: cls_ for cls_ in self.rules.classes}
|
|
119
195
|
if self.rules.last:
|
|
@@ -197,7 +273,7 @@ class _InformationRulesConverter:
|
|
|
197
273
|
from cognite.neat.rules.models.dms._rules import DMSProperty
|
|
198
274
|
|
|
199
275
|
# returns property type, which can be ObjectProperty or DatatypeProperty
|
|
200
|
-
value_type: DataType | ViewEntity |
|
|
276
|
+
value_type: DataType | ViewEntity | DMSUnknownEntity
|
|
201
277
|
if isinstance(prop.value_type, DataType):
|
|
202
278
|
value_type = prop.value_type
|
|
203
279
|
elif isinstance(prop.value_type, UnknownEntity):
|
|
@@ -209,17 +285,18 @@ class _InformationRulesConverter:
|
|
|
209
285
|
else:
|
|
210
286
|
raise ValueError(f"Unsupported value type: {prop.value_type.type_}")
|
|
211
287
|
|
|
212
|
-
|
|
213
|
-
if isinstance(value_type, ViewEntity
|
|
214
|
-
|
|
288
|
+
connection: Literal["direct"] | ReverseConnectionEntity | EdgeEntity | None = None
|
|
289
|
+
if isinstance(value_type, ViewEntity):
|
|
290
|
+
# Default connection type.
|
|
291
|
+
connection = EdgeEntity() if prop.is_list else "direct"
|
|
215
292
|
|
|
216
293
|
container: ContainerEntity | None = None
|
|
217
294
|
container_property: str | None = None
|
|
218
295
|
is_list: bool | None = prop.is_list
|
|
219
296
|
nullable: bool | None = not prop.is_mandatory
|
|
220
|
-
if
|
|
297
|
+
if isinstance(connection, EdgeEntity):
|
|
221
298
|
nullable = None
|
|
222
|
-
elif
|
|
299
|
+
elif connection == "direct":
|
|
223
300
|
nullable = True
|
|
224
301
|
container, container_property = self._get_container(prop, default_space)
|
|
225
302
|
else:
|
|
@@ -232,7 +309,7 @@ class _InformationRulesConverter:
|
|
|
232
309
|
value_type=value_type,
|
|
233
310
|
nullable=nullable,
|
|
234
311
|
is_list=is_list,
|
|
235
|
-
connection=
|
|
312
|
+
connection=connection,
|
|
236
313
|
default=prop.default,
|
|
237
314
|
reference=prop.reference,
|
|
238
315
|
container=container,
|
|
@@ -266,7 +343,7 @@ class _InformationRulesConverter:
|
|
|
266
343
|
else:
|
|
267
344
|
container_entity = prop.class_.as_container_entity(default_space)
|
|
268
345
|
|
|
269
|
-
while self.property_count_by_container[container_entity] >=
|
|
346
|
+
while self.property_count_by_container[container_entity] >= DMS_CONTAINER_PROPERTY_SIZE_LIMIT:
|
|
270
347
|
container_entity.suffix = self._bump_suffix(container_entity.suffix)
|
|
271
348
|
|
|
272
349
|
self.property_count_by_container[container_entity] += 1
|
|
@@ -326,3 +403,122 @@ class _InformationRulesConverter:
|
|
|
326
403
|
return data_types.DateTime()
|
|
327
404
|
|
|
328
405
|
return data_types.String()
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
class _DMSRulesConverter:
|
|
409
|
+
def __init__(self, dms: DMSRules):
|
|
410
|
+
self.dms = dms
|
|
411
|
+
|
|
412
|
+
def as_domain_rules(self) -> "DomainRules":
|
|
413
|
+
raise NotImplementedError("DomainRules not implemented yet")
|
|
414
|
+
|
|
415
|
+
def as_information_rules(
|
|
416
|
+
self,
|
|
417
|
+
) -> "InformationRules":
|
|
418
|
+
from cognite.neat.rules.models.information._rules import (
|
|
419
|
+
InformationClass,
|
|
420
|
+
InformationProperty,
|
|
421
|
+
InformationRules,
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
dms = self.dms.metadata
|
|
425
|
+
|
|
426
|
+
metadata = self._convert_metadata_to_info(dms)
|
|
427
|
+
|
|
428
|
+
classes = [
|
|
429
|
+
InformationClass(
|
|
430
|
+
# we do not want a version in class as we use URI for the class
|
|
431
|
+
class_=ClassEntity(prefix=view.class_.prefix, suffix=view.class_.suffix),
|
|
432
|
+
description=view.description,
|
|
433
|
+
parent=[
|
|
434
|
+
# we do not want a version in class as we use URI for the class
|
|
435
|
+
implemented_view.as_class(skip_version=True)
|
|
436
|
+
# We only want parents in the same namespace, parent in a different namespace is a reference
|
|
437
|
+
for implemented_view in view.implements or []
|
|
438
|
+
if implemented_view.prefix == view.class_.prefix
|
|
439
|
+
],
|
|
440
|
+
reference=self._get_class_reference(view),
|
|
441
|
+
)
|
|
442
|
+
for view in self.dms.views
|
|
443
|
+
]
|
|
444
|
+
|
|
445
|
+
properties: list[InformationProperty] = []
|
|
446
|
+
value_type: DataType | ClassEntity | str
|
|
447
|
+
for property_ in self.dms.properties:
|
|
448
|
+
if isinstance(property_.value_type, DataType):
|
|
449
|
+
value_type = property_.value_type
|
|
450
|
+
elif isinstance(property_.value_type, ViewEntity):
|
|
451
|
+
value_type = ClassEntity(
|
|
452
|
+
prefix=property_.value_type.prefix,
|
|
453
|
+
suffix=property_.value_type.suffix,
|
|
454
|
+
)
|
|
455
|
+
elif isinstance(property_.value_type, DMSUnknownEntity):
|
|
456
|
+
value_type = UnknownEntity()
|
|
457
|
+
else:
|
|
458
|
+
raise ValueError(f"Unsupported value type: {property_.value_type.type_}")
|
|
459
|
+
|
|
460
|
+
properties.append(
|
|
461
|
+
InformationProperty(
|
|
462
|
+
# Removing version
|
|
463
|
+
class_=ClassEntity(suffix=property_.class_.suffix, prefix=property_.class_.prefix),
|
|
464
|
+
property_=property_.view_property,
|
|
465
|
+
value_type=value_type,
|
|
466
|
+
description=property_.description,
|
|
467
|
+
min_count=0 if property_.nullable or property_.nullable is None else 1,
|
|
468
|
+
max_count=float("inf") if property_.is_list or property_.nullable is None else 1,
|
|
469
|
+
reference=self._get_property_reference(property_),
|
|
470
|
+
)
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
return InformationRules(
|
|
474
|
+
metadata=metadata,
|
|
475
|
+
properties=SheetList[InformationProperty](data=properties),
|
|
476
|
+
classes=SheetList[InformationClass](data=classes),
|
|
477
|
+
last=_DMSRulesConverter(self.dms.last).as_information_rules() if self.dms.last else None,
|
|
478
|
+
reference=_DMSRulesConverter(self.dms.reference).as_information_rules() if self.dms.reference else None,
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
@classmethod
|
|
482
|
+
def _convert_metadata_to_info(cls, metadata: DMSMetadata) -> "InformationMetadata":
|
|
483
|
+
from cognite.neat.rules.models.information._rules import InformationMetadata
|
|
484
|
+
|
|
485
|
+
prefix = metadata.space
|
|
486
|
+
return InformationMetadata(
|
|
487
|
+
schema_=metadata.schema_,
|
|
488
|
+
data_model_type=metadata.data_model_type,
|
|
489
|
+
extension=metadata.extension,
|
|
490
|
+
prefix=prefix,
|
|
491
|
+
namespace=Namespace(f"https://purl.orgl/neat/{prefix}/"),
|
|
492
|
+
version=metadata.version,
|
|
493
|
+
description=metadata.description,
|
|
494
|
+
name=metadata.name or metadata.external_id,
|
|
495
|
+
creator=metadata.creator,
|
|
496
|
+
created=metadata.created,
|
|
497
|
+
updated=metadata.updated,
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
@classmethod
|
|
501
|
+
def _get_class_reference(cls, view: DMSView) -> ReferenceEntity | None:
|
|
502
|
+
parents_other_namespace = [parent for parent in view.implements or [] if parent.prefix != view.class_.prefix]
|
|
503
|
+
if len(parents_other_namespace) == 0:
|
|
504
|
+
return None
|
|
505
|
+
if len(parents_other_namespace) > 1:
|
|
506
|
+
warnings.warn(
|
|
507
|
+
ParentInDifferentSpaceWarning(view.view.as_id()),
|
|
508
|
+
stacklevel=2,
|
|
509
|
+
)
|
|
510
|
+
other_parent = parents_other_namespace[0]
|
|
511
|
+
|
|
512
|
+
return ReferenceEntity(prefix=other_parent.prefix, suffix=other_parent.suffix)
|
|
513
|
+
|
|
514
|
+
@classmethod
|
|
515
|
+
def _get_property_reference(cls, property_: DMSProperty) -> ReferenceEntity | None:
|
|
516
|
+
has_container_other_namespace = property_.container and property_.container.prefix != property_.class_.prefix
|
|
517
|
+
if not has_container_other_namespace:
|
|
518
|
+
return None
|
|
519
|
+
container = cast(ContainerEntity, property_.container)
|
|
520
|
+
return ReferenceEntity(
|
|
521
|
+
prefix=container.prefix,
|
|
522
|
+
suffix=container.suffix,
|
|
523
|
+
property=property_.container_property,
|
|
524
|
+
)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
from collections import defaultdict
|
|
3
|
+
|
|
4
|
+
from cognite.neat.rules._shared import JustRules, OutRules
|
|
5
|
+
from cognite.neat.rules.models import DMSRules
|
|
6
|
+
from cognite.neat.rules.models.dms import DMSProperty
|
|
7
|
+
from cognite.neat.rules.models.entities import ReferenceEntity
|
|
8
|
+
|
|
9
|
+
from ._base import RulesTransformer
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MapOntoTransformers(RulesTransformer[DMSRules, DMSRules], ABC):
|
|
13
|
+
"""Base class for transformers that map one rule onto another."""
|
|
14
|
+
|
|
15
|
+
...
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MapOneToOne(MapOntoTransformers):
|
|
19
|
+
"""Takes transform data models and makes it into an extension of the reference data model.
|
|
20
|
+
|
|
21
|
+
Note this transformer mutates the input rules.
|
|
22
|
+
|
|
23
|
+
The argument view_extension_mapping is a dictionary where the keys are views of this data model,
|
|
24
|
+
and each value is the view of the reference data model that the view should extend. For example:
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
view_extension_mapping = {"Pump": "Asset"}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
This would make the view "Pump" in this data model extend the view "Asset" in the reference data model.
|
|
31
|
+
Note that all the keys in the dictionary must be external ids of views in this data model,
|
|
32
|
+
and all the values must be external ids of views in the reference data model.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
reference: The reference data model
|
|
36
|
+
view_extension_mapping: A dictionary mapping views in this data model to views in the reference data model
|
|
37
|
+
default_extension: The default view in the reference data model that views in this
|
|
38
|
+
data model should extend if no mapping is provided.
|
|
39
|
+
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self, reference: DMSRules, view_extension_mapping: dict[str, str], default_extension: str | None = None
|
|
44
|
+
) -> None:
|
|
45
|
+
self.reference = reference
|
|
46
|
+
self.view_extension_mapping = view_extension_mapping
|
|
47
|
+
self.default_extension = default_extension
|
|
48
|
+
|
|
49
|
+
def transform(self, rules: DMSRules | OutRules[DMSRules]) -> JustRules[DMSRules]:
|
|
50
|
+
solution: DMSRules = self._to_rules(rules)
|
|
51
|
+
if solution.reference is not None:
|
|
52
|
+
raise ValueError("Reference already exists")
|
|
53
|
+
solution.reference = self.reference
|
|
54
|
+
view_by_external_id = {view.view.external_id: view for view in solution.views}
|
|
55
|
+
ref_view_by_external_id = {view.view.external_id: view for view in self.reference.views}
|
|
56
|
+
|
|
57
|
+
if invalid_views := set(self.view_extension_mapping.keys()) - set(view_by_external_id.keys()):
|
|
58
|
+
raise ValueError(f"Views are not in this dat model {invalid_views}")
|
|
59
|
+
if invalid_views := set(self.view_extension_mapping.values()) - set(ref_view_by_external_id.keys()):
|
|
60
|
+
raise ValueError(f"Views are not in the reference data model {invalid_views}")
|
|
61
|
+
if self.default_extension and self.default_extension not in ref_view_by_external_id:
|
|
62
|
+
raise ValueError(f"Default extension view not in the reference data model {self.default_extension}")
|
|
63
|
+
|
|
64
|
+
properties_by_view_external_id: dict[str, dict[str, DMSProperty]] = defaultdict(dict)
|
|
65
|
+
for prop in solution.properties:
|
|
66
|
+
properties_by_view_external_id[prop.view.external_id][prop.view_property] = prop
|
|
67
|
+
|
|
68
|
+
ref_properties_by_view_external_id: dict[str, dict[str, DMSProperty]] = defaultdict(dict)
|
|
69
|
+
for prop in self.reference.properties:
|
|
70
|
+
ref_properties_by_view_external_id[prop.view.external_id][prop.view_property] = prop
|
|
71
|
+
|
|
72
|
+
for view_external_id, view in view_by_external_id.items():
|
|
73
|
+
if view_external_id in self.view_extension_mapping:
|
|
74
|
+
ref_external_id = self.view_extension_mapping[view_external_id]
|
|
75
|
+
elif self.default_extension:
|
|
76
|
+
ref_external_id = self.default_extension
|
|
77
|
+
else:
|
|
78
|
+
continue
|
|
79
|
+
|
|
80
|
+
ref_view = ref_view_by_external_id[ref_external_id]
|
|
81
|
+
shared_properties = set(properties_by_view_external_id[view_external_id].keys()) & set(
|
|
82
|
+
ref_properties_by_view_external_id[ref_external_id].keys()
|
|
83
|
+
)
|
|
84
|
+
if shared_properties:
|
|
85
|
+
if view.implements is None:
|
|
86
|
+
view.implements = [ref_view.view]
|
|
87
|
+
elif isinstance(view.implements, list) and ref_view.view not in view.implements:
|
|
88
|
+
view.implements.append(ref_view.view)
|
|
89
|
+
for prop_name in shared_properties:
|
|
90
|
+
prop = properties_by_view_external_id[view_external_id][prop_name]
|
|
91
|
+
ref_prop = ref_properties_by_view_external_id[ref_external_id][prop_name]
|
|
92
|
+
if ref_prop.container and ref_prop.container_property:
|
|
93
|
+
prop.container = ref_prop.container
|
|
94
|
+
prop.container_property = ref_prop.container_property
|
|
95
|
+
prop.reference = ReferenceEntity.from_entity(ref_prop.view, ref_prop.view_property)
|
|
96
|
+
|
|
97
|
+
return JustRules(solution)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from collections.abc import Iterable
|
|
2
|
+
|
|
3
|
+
from cognite.neat.issues.errors import NeatValueError
|
|
4
|
+
from cognite.neat.rules._shared import InputRules, MaybeRules, VerifiedRules
|
|
5
|
+
from cognite.neat.rules.importers import BaseImporter
|
|
6
|
+
from cognite.neat.rules.models import VERIFIED_RULES_BY_ROLE, RoleTypes
|
|
7
|
+
|
|
8
|
+
from ._base import RulesPipeline, RulesTransformer
|
|
9
|
+
from ._converters import ConvertToRules
|
|
10
|
+
from ._verification import VerifyAnyRules
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ImporterPipeline(RulesPipeline[InputRules, VerifiedRules]):
|
|
14
|
+
"""This is a standard pipeline that verifies, convert and return the rules from the importer."""
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
importer: BaseImporter[InputRules],
|
|
19
|
+
items: Iterable[RulesTransformer[InputRules, VerifiedRules]] | None = None,
|
|
20
|
+
) -> None:
|
|
21
|
+
super().__init__(items or [])
|
|
22
|
+
self._importer = importer
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def _create_pipeline(cls, importer: BaseImporter[InputRules], role: RoleTypes | None = None) -> "ImporterPipeline":
|
|
26
|
+
items: list[RulesTransformer] = [VerifyAnyRules(errors="continue")]
|
|
27
|
+
if role is not None:
|
|
28
|
+
out_cls = VERIFIED_RULES_BY_ROLE[role]
|
|
29
|
+
items.append(ConvertToRules(out_cls))
|
|
30
|
+
return cls(importer, items)
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def try_verify(cls, importer: BaseImporter, role: RoleTypes | None = None) -> MaybeRules[VerifiedRules]:
|
|
34
|
+
"""This is a standard pipeline that verifies, convert and return the rules from the importer.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
importer: The importer to use.
|
|
38
|
+
role: The type of rules to convert to. If None, the rules will not be converted.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
The verified rules.
|
|
42
|
+
"""
|
|
43
|
+
return cls._create_pipeline(importer, role).try_execute()
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def verify(cls, importer: BaseImporter, role: RoleTypes | None = None) -> VerifiedRules:
|
|
47
|
+
"""Verify the rules."""
|
|
48
|
+
return cls._create_pipeline(importer, role).execute()
|
|
49
|
+
|
|
50
|
+
def try_execute(self) -> MaybeRules[VerifiedRules]:
|
|
51
|
+
"""Try to execute the pipeline from importer to rules."""
|
|
52
|
+
rules = self._importer.to_rules()
|
|
53
|
+
return self.try_transform(rules)
|
|
54
|
+
|
|
55
|
+
def execute(self) -> VerifiedRules:
|
|
56
|
+
"""Execute the pipeline from importer to rules."""
|
|
57
|
+
rules = self._importer.to_rules()
|
|
58
|
+
out = self.transform(rules).get_rules()
|
|
59
|
+
if out is None:
|
|
60
|
+
raise NeatValueError("Failed to convert rules")
|
|
61
|
+
return out
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
from abc import ABC
|
|
3
|
+
from collections.abc import Iterator
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
from typing import Any, Literal
|
|
6
|
+
|
|
7
|
+
from pydantic import ValidationError
|
|
8
|
+
|
|
9
|
+
from cognite.neat.issues import IssueList, NeatError, NeatWarning
|
|
10
|
+
from cognite.neat.issues.errors import NeatTypeError
|
|
11
|
+
from cognite.neat.rules._shared import (
|
|
12
|
+
InputRules,
|
|
13
|
+
MaybeRules,
|
|
14
|
+
OutRules,
|
|
15
|
+
ReadRules,
|
|
16
|
+
T_InputRules,
|
|
17
|
+
T_VerifiedRules,
|
|
18
|
+
VerifiedRules,
|
|
19
|
+
)
|
|
20
|
+
from cognite.neat.rules.models import (
|
|
21
|
+
AssetInputRules,
|
|
22
|
+
AssetRules,
|
|
23
|
+
DMSInputRules,
|
|
24
|
+
DMSRules,
|
|
25
|
+
DomainInputRules,
|
|
26
|
+
DomainRules,
|
|
27
|
+
InformationInputRules,
|
|
28
|
+
InformationRules,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
from ._base import RulesTransformer
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class VerificationTransformer(RulesTransformer[T_InputRules, T_VerifiedRules], ABC):
|
|
35
|
+
"""Base class for all verification transformers."""
|
|
36
|
+
|
|
37
|
+
_rules_cls: type[T_VerifiedRules]
|
|
38
|
+
|
|
39
|
+
def __init__(self, errors: Literal["raise", "continue"]) -> None:
|
|
40
|
+
self.errors = errors
|
|
41
|
+
|
|
42
|
+
def transform(self, rules: T_InputRules | OutRules[T_InputRules]) -> MaybeRules[T_VerifiedRules]:
|
|
43
|
+
issues = IssueList()
|
|
44
|
+
in_: T_InputRules = self._to_rules(rules)
|
|
45
|
+
error_args: dict[str, Any] = {}
|
|
46
|
+
if isinstance(rules, ReadRules):
|
|
47
|
+
error_args = rules.read_context
|
|
48
|
+
verified_rules: T_VerifiedRules | None = None
|
|
49
|
+
with _handle_issues(issues, NeatError, NeatWarning, error_args) as future:
|
|
50
|
+
rules_cls = self._get_rules_cls(in_)
|
|
51
|
+
verified_rules = rules_cls.model_validate(in_.dump()) # type: ignore[assignment]
|
|
52
|
+
|
|
53
|
+
if (future.result == "failure" or issues.has_errors or verified_rules is None) and self.errors == "raise":
|
|
54
|
+
raise issues.as_errors()
|
|
55
|
+
return MaybeRules[T_VerifiedRules](
|
|
56
|
+
rules=verified_rules,
|
|
57
|
+
issues=issues,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def _get_rules_cls(self, in_: T_InputRules) -> type[T_VerifiedRules]:
|
|
61
|
+
return self._rules_cls
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class VerifyDMSRules(VerificationTransformer[DMSInputRules, DMSRules]):
|
|
65
|
+
"""Class to verify DMS rules."""
|
|
66
|
+
|
|
67
|
+
_rules_cls = DMSRules
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class VerifyInformationRules(VerificationTransformer[InformationInputRules, InformationRules]):
|
|
71
|
+
"""Class to verify Information rules."""
|
|
72
|
+
|
|
73
|
+
_rules_cls = InformationRules
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class VerifyAssetRules(VerificationTransformer[AssetInputRules, AssetRules]):
|
|
77
|
+
"""Class to verify Asset rules."""
|
|
78
|
+
|
|
79
|
+
_rules_cls = AssetRules
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class VerifyAnyRules(VerificationTransformer[InputRules, VerifiedRules]):
|
|
83
|
+
"""Class to verify arbitrary rules"""
|
|
84
|
+
|
|
85
|
+
def _get_rules_cls(self, in_: InputRules) -> type[VerifiedRules]:
|
|
86
|
+
if isinstance(in_, InformationInputRules):
|
|
87
|
+
return InformationRules
|
|
88
|
+
elif isinstance(in_, DMSInputRules):
|
|
89
|
+
return DMSRules
|
|
90
|
+
elif isinstance(in_, AssetInputRules):
|
|
91
|
+
return AssetRules
|
|
92
|
+
elif isinstance(in_, DomainInputRules):
|
|
93
|
+
return DomainRules
|
|
94
|
+
else:
|
|
95
|
+
raise NeatTypeError(f"Unsupported rules type: {type(in_)}")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class _FutureResult:
|
|
99
|
+
def __init__(self) -> None:
|
|
100
|
+
self._result: Literal["success", "failure", "pending"] = "pending"
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def result(self) -> Literal["success", "failure", "pending"]:
|
|
104
|
+
return self._result
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@contextmanager
|
|
108
|
+
def _handle_issues(
|
|
109
|
+
issues: IssueList,
|
|
110
|
+
error_cls: type[NeatError] = NeatError,
|
|
111
|
+
warning_cls: type[NeatWarning] = NeatWarning,
|
|
112
|
+
error_args: dict[str, Any] | None = None,
|
|
113
|
+
) -> Iterator[_FutureResult]:
|
|
114
|
+
"""This is an internal help function to handle issues and warnings.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
issues: The issues list to append to.
|
|
118
|
+
error_cls: The class used to convert errors to issues.
|
|
119
|
+
warning_cls: The class used to convert warnings to issues.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
FutureResult: A future result object that can be used to check the result of the context manager.
|
|
123
|
+
"""
|
|
124
|
+
with warnings.catch_warnings(record=True) as warning_logger:
|
|
125
|
+
warnings.simplefilter("always")
|
|
126
|
+
future_result = _FutureResult()
|
|
127
|
+
try:
|
|
128
|
+
yield future_result
|
|
129
|
+
except ValidationError as e:
|
|
130
|
+
issues.extend(error_cls.from_pydantic_errors(e.errors(), **(error_args or {})))
|
|
131
|
+
future_result._result = "failure"
|
|
132
|
+
else:
|
|
133
|
+
future_result._result = "success"
|
|
134
|
+
finally:
|
|
135
|
+
if warning_logger:
|
|
136
|
+
issues.extend([warning_cls.from_warning(warning) for warning in warning_logger]) # type: ignore[misc]
|
|
@@ -69,6 +69,10 @@ class Change(FrozenNeatObject):
|
|
|
69
69
|
activity: Activity
|
|
70
70
|
entity: Entity
|
|
71
71
|
description: str
|
|
72
|
+
# triples that were added to the graph store
|
|
73
|
+
addition: list[tuple[URIRef, URIRef, URIRef | Literal]] | None = None
|
|
74
|
+
# triples that were removed from the graph store
|
|
75
|
+
subtraction: list[tuple[URIRef, URIRef, URIRef | Literal]] | None = None
|
|
72
76
|
|
|
73
77
|
def as_triples(self):
|
|
74
78
|
return self.agent.as_triples() + self.activity.as_triples() + self.entity.as_triples()
|
|
@@ -77,7 +81,12 @@ class Change(FrozenNeatObject):
|
|
|
77
81
|
def record(cls, activity: str, start: datetime, end: datetime, description: str):
|
|
78
82
|
"""User friendly method to record a change that occurred in the graph store."""
|
|
79
83
|
agent = Agent()
|
|
80
|
-
activity = Activity(
|
|
84
|
+
activity = Activity(
|
|
85
|
+
used=activity,
|
|
86
|
+
was_associated_with=agent,
|
|
87
|
+
started_at_time=start,
|
|
88
|
+
ended_at_time=end,
|
|
89
|
+
)
|
|
81
90
|
entity = Entity(was_generated_by=activity, was_attributed_to=agent)
|
|
82
91
|
return cls(agent, activity, entity, description)
|
|
83
92
|
|
cognite/neat/utils/auxiliary.py
CHANGED
|
@@ -3,15 +3,14 @@ import importlib
|
|
|
3
3
|
import inspect
|
|
4
4
|
import logging
|
|
5
5
|
import time
|
|
6
|
-
from collections.abc import Callable
|
|
6
|
+
from collections.abc import Callable
|
|
7
7
|
from datetime import datetime
|
|
8
8
|
from functools import wraps
|
|
9
9
|
from types import ModuleType
|
|
10
10
|
|
|
11
11
|
from cognite.client.exceptions import CogniteDuplicatedError, CogniteReadTimeout
|
|
12
|
-
from pydantic_core import ErrorDetails
|
|
13
12
|
|
|
14
|
-
from cognite.neat.
|
|
13
|
+
from cognite.neat.issues.errors import NeatImportError
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
def local_import(module: str, extra: str) -> ModuleType:
|
|
@@ -119,38 +118,6 @@ def create_sha256_hash(string: str) -> str:
|
|
|
119
118
|
return hash_value
|
|
120
119
|
|
|
121
120
|
|
|
122
|
-
# Will likely be removed with legacy code
|
|
123
|
-
def generate_exception_report(exceptions: list[dict] | list[ErrorDetails] | None, category: str = "") -> str:
|
|
124
|
-
exceptions_as_dict = _order_expectations_by_type(exceptions) if exceptions else {}
|
|
125
|
-
report = ""
|
|
126
|
-
|
|
127
|
-
for exception_type in exceptions_as_dict.keys():
|
|
128
|
-
title = f"# {category}: {exception_type}" if category else ""
|
|
129
|
-
warnings = "\n- " + "\n- ".join(exceptions_as_dict[exception_type])
|
|
130
|
-
report += title + warnings + "\n\n"
|
|
131
|
-
|
|
132
|
-
return report
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
def _order_expectations_by_type(
|
|
136
|
-
exceptions: list[dict] | list[ErrorDetails],
|
|
137
|
-
) -> dict[str, list[str]]:
|
|
138
|
-
exception_dict: dict[str, list[str]] = {}
|
|
139
|
-
for exception in exceptions:
|
|
140
|
-
if not isinstance(exception["loc"], str) and isinstance(exception["loc"], Iterable):
|
|
141
|
-
location = f"[{'/'.join(str(e) for e in exception['loc'])}]"
|
|
142
|
-
else:
|
|
143
|
-
location = ""
|
|
144
|
-
|
|
145
|
-
issue = f"{exception['msg']} {location}"
|
|
146
|
-
|
|
147
|
-
if exception_dict.get(exception["type"]) is None:
|
|
148
|
-
exception_dict[exception["type"]] = [issue]
|
|
149
|
-
else:
|
|
150
|
-
exception_dict[exception["type"]].append(issue)
|
|
151
|
-
return exception_dict
|
|
152
|
-
|
|
153
|
-
|
|
154
121
|
def string_to_ideal_type(input_string: str) -> int | bool | float | datetime | str:
|
|
155
122
|
try:
|
|
156
123
|
# Try converting to int
|