cognite-neat 0.99.0__py3-none-any.whl → 0.99.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of cognite-neat might be problematic. Click here for more details.
- cognite/neat/_client/_api/data_modeling_loaders.py +77 -4
- cognite/neat/_client/_api/schema.py +63 -2
- cognite/neat/_client/data_classes/schema.py +2 -348
- cognite/neat/_constants.py +27 -4
- cognite/neat/_graph/extractors/_classic_cdf/_classic.py +5 -5
- cognite/neat/_graph/loaders/_rdf2dms.py +2 -2
- cognite/neat/_graph/transformers/_classic_cdf.py +24 -13
- cognite/neat/_issues/_base.py +26 -17
- cognite/neat/_issues/errors/__init__.py +4 -2
- cognite/neat/_issues/errors/_external.py +7 -0
- cognite/neat/_issues/errors/_properties.py +2 -7
- cognite/neat/_issues/errors/_resources.py +1 -1
- cognite/neat/_issues/warnings/__init__.py +4 -2
- cognite/neat/_issues/warnings/_external.py +9 -1
- cognite/neat/_issues/warnings/_resources.py +26 -2
- cognite/neat/_issues/warnings/user_modeling.py +4 -4
- cognite/neat/_rules/_constants.py +2 -6
- cognite/neat/_rules/exporters/_rules2dms.py +4 -6
- cognite/neat/_rules/importers/__init__.py +1 -3
- cognite/neat/_rules/importers/_base.py +1 -1
- cognite/neat/_rules/importers/_dms2rules.py +3 -25
- cognite/neat/_rules/importers/_rdf/__init__.py +5 -0
- cognite/neat/_rules/importers/_rdf/_base.py +34 -11
- cognite/neat/_rules/importers/_rdf/_imf2rules.py +91 -0
- cognite/neat/_rules/importers/_rdf/_inference2rules.py +18 -2
- cognite/neat/_rules/importers/_rdf/_owl2rules.py +80 -0
- cognite/neat/_rules/importers/_rdf/_shared.py +138 -441
- cognite/neat/_rules/models/dms/__init__.py +2 -0
- cognite/neat/_rules/models/dms/_exporter.py +32 -30
- cognite/neat/_rules/models/dms/_rules.py +3 -45
- cognite/neat/_rules/models/dms/_validation.py +389 -122
- cognite/neat/_rules/models/information/__init__.py +2 -0
- cognite/neat/_rules/models/information/_rules.py +0 -59
- cognite/neat/_rules/models/information/_validation.py +9 -9
- cognite/neat/_rules/models/mapping/_classic2core.py +1 -1
- cognite/neat/_rules/models/mapping/_classic2core.yaml +8 -4
- cognite/neat/_rules/transformers/_pipelines.py +1 -1
- cognite/neat/_rules/transformers/_verification.py +29 -4
- cognite/neat/_session/_base.py +16 -41
- cognite/neat/_session/_prepare.py +6 -5
- cognite/neat/_session/_to.py +5 -2
- cognite/neat/_session/exceptions.py +4 -0
- cognite/neat/_utils/rdf_.py +6 -4
- cognite/neat/_version.py +1 -1
- cognite/neat/_workflows/steps/lib/current/rules_exporter.py +0 -88
- cognite/neat/_workflows/steps/lib/current/rules_importer.py +2 -16
- cognite/neat/_workflows/steps/lib/current/rules_validator.py +3 -5
- {cognite_neat-0.99.0.dist-info → cognite_neat-0.99.1.dist-info}/METADATA +1 -1
- {cognite_neat-0.99.0.dist-info → cognite_neat-0.99.1.dist-info}/RECORD +52 -60
- cognite/neat/_rules/importers/_rdf/_imf2rules/__init__.py +0 -3
- cognite/neat/_rules/importers/_rdf/_imf2rules/_imf2classes.py +0 -86
- cognite/neat/_rules/importers/_rdf/_imf2rules/_imf2metadata.py +0 -29
- cognite/neat/_rules/importers/_rdf/_imf2rules/_imf2properties.py +0 -130
- cognite/neat/_rules/importers/_rdf/_imf2rules/_imf2rules.py +0 -154
- cognite/neat/_rules/importers/_rdf/_owl2rules/__init__.py +0 -3
- cognite/neat/_rules/importers/_rdf/_owl2rules/_owl2classes.py +0 -58
- cognite/neat/_rules/importers/_rdf/_owl2rules/_owl2metadata.py +0 -65
- cognite/neat/_rules/importers/_rdf/_owl2rules/_owl2properties.py +0 -59
- cognite/neat/_rules/importers/_rdf/_owl2rules/_owl2rules.py +0 -39
- {cognite_neat-0.99.0.dist-info → cognite_neat-0.99.1.dist-info}/LICENSE +0 -0
- {cognite_neat-0.99.0.dist-info → cognite_neat-0.99.1.dist-info}/WHEEL +0 -0
- {cognite_neat-0.99.0.dist-info → cognite_neat-0.99.1.dist-info}/entry_points.txt +0 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import math
|
|
2
|
-
import sys
|
|
3
2
|
from collections.abc import Hashable
|
|
4
3
|
from typing import TYPE_CHECKING, Any, ClassVar
|
|
5
4
|
|
|
@@ -34,8 +33,6 @@ from cognite.neat._rules.models.entities import (
|
|
|
34
33
|
ClassEntity,
|
|
35
34
|
ClassEntityList,
|
|
36
35
|
Entity,
|
|
37
|
-
MultiValueTypeInfo,
|
|
38
|
-
Undefined,
|
|
39
36
|
UnknownEntity,
|
|
40
37
|
)
|
|
41
38
|
|
|
@@ -43,12 +40,6 @@ if TYPE_CHECKING:
|
|
|
43
40
|
from cognite.neat._rules.models import DMSRules
|
|
44
41
|
|
|
45
42
|
|
|
46
|
-
if sys.version_info >= (3, 11):
|
|
47
|
-
from typing import Self
|
|
48
|
-
else:
|
|
49
|
-
from typing_extensions import Self
|
|
50
|
-
|
|
51
|
-
|
|
52
43
|
class InformationMetadata(BaseMetadata):
|
|
53
44
|
role: ClassVar[RoleTypes] = RoleTypes.information
|
|
54
45
|
aspect: ClassVar[DataModelAspect] = DataModelAspect.logical
|
|
@@ -155,21 +146,6 @@ class InformationProperty(SheetRow):
|
|
|
155
146
|
else:
|
|
156
147
|
raise NeatValueError(f"Invalid RDF Path: {value!s}")
|
|
157
148
|
|
|
158
|
-
@model_validator(mode="after")
|
|
159
|
-
def set_default_as_list(self):
|
|
160
|
-
if (
|
|
161
|
-
self.type_ == EntityTypes.data_property
|
|
162
|
-
and self.default
|
|
163
|
-
and self.is_list
|
|
164
|
-
and not isinstance(self.default, list)
|
|
165
|
-
):
|
|
166
|
-
if isinstance(self.default, str):
|
|
167
|
-
if self.default:
|
|
168
|
-
self.default = self.default.replace(", ", ",").split(",")
|
|
169
|
-
else:
|
|
170
|
-
self.default = [self.default]
|
|
171
|
-
return self
|
|
172
|
-
|
|
173
149
|
@model_validator(mode="after")
|
|
174
150
|
def set_type_for_default(self):
|
|
175
151
|
if self.type_ == EntityTypes.data_property and self.default:
|
|
@@ -243,41 +219,6 @@ class InformationRules(BaseRules):
|
|
|
243
219
|
values = get_default_prefixes()
|
|
244
220
|
return values
|
|
245
221
|
|
|
246
|
-
@model_validator(mode="after")
|
|
247
|
-
def update_entities_prefix(self) -> Self:
|
|
248
|
-
# update expected_value_types
|
|
249
|
-
for property_ in self.properties:
|
|
250
|
-
if isinstance(property_.value_type, ClassEntity) and property_.value_type.prefix is Undefined:
|
|
251
|
-
property_.value_type.prefix = self.metadata.prefix
|
|
252
|
-
|
|
253
|
-
if isinstance(property_.value_type, MultiValueTypeInfo):
|
|
254
|
-
property_.value_type.set_default_prefix(self.metadata.prefix)
|
|
255
|
-
|
|
256
|
-
if property_.class_.prefix is Undefined:
|
|
257
|
-
property_.class_.prefix = self.metadata.prefix
|
|
258
|
-
|
|
259
|
-
# update implements
|
|
260
|
-
for class_ in self.classes:
|
|
261
|
-
if class_.implements:
|
|
262
|
-
for parent in class_.implements:
|
|
263
|
-
if not isinstance(parent.prefix, str):
|
|
264
|
-
parent.prefix = self.metadata.prefix
|
|
265
|
-
if class_.class_.prefix is Undefined:
|
|
266
|
-
class_.class_.prefix = self.metadata.prefix
|
|
267
|
-
|
|
268
|
-
return self
|
|
269
|
-
|
|
270
|
-
@model_validator(mode="after")
|
|
271
|
-
def post_validation(self) -> "InformationRules":
|
|
272
|
-
from ._validation import InformationPostValidation
|
|
273
|
-
|
|
274
|
-
issue_list = InformationPostValidation(self).validate()
|
|
275
|
-
if issue_list.warnings:
|
|
276
|
-
issue_list.trigger_warnings()
|
|
277
|
-
if issue_list.has_errors:
|
|
278
|
-
raise issue_list.as_exception()
|
|
279
|
-
return self
|
|
280
|
-
|
|
281
222
|
def as_dms_rules(self) -> "DMSRules":
|
|
282
223
|
from cognite.neat._rules.transformers._converters import _InformationRulesConverter
|
|
283
224
|
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import itertools
|
|
2
2
|
from collections import Counter
|
|
3
|
-
from typing import cast
|
|
4
3
|
|
|
5
4
|
from cognite.neat._issues import IssueList
|
|
6
|
-
from cognite.neat._issues.errors import NeatValueError
|
|
5
|
+
from cognite.neat._issues.errors import NeatValueError
|
|
7
6
|
from cognite.neat._issues.warnings._models import UndefinedClassWarning
|
|
7
|
+
from cognite.neat._issues.warnings._resources import ResourceNotDefinedWarning
|
|
8
8
|
from cognite.neat._rules._constants import EntityTypes
|
|
9
9
|
from cognite.neat._rules.models.entities import ClassEntity, UnknownEntity
|
|
10
10
|
|
|
11
11
|
from ._rules import InformationRules
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
class
|
|
14
|
+
class InformationValidation:
|
|
15
15
|
"""This class does all the validation of the Information rules that have dependencies
|
|
16
16
|
between components."""
|
|
17
17
|
|
|
@@ -44,10 +44,10 @@ class InformationPostValidation:
|
|
|
44
44
|
# same prefix, meaning same space
|
|
45
45
|
if not class_parent_pairs[class_] and class_.prefix == self.metadata.prefix:
|
|
46
46
|
self.issue_list.append(
|
|
47
|
-
|
|
47
|
+
ResourceNotDefinedWarning(
|
|
48
48
|
resource_type="class",
|
|
49
49
|
identifier=class_,
|
|
50
|
-
location="
|
|
50
|
+
location="Properties sheet",
|
|
51
51
|
)
|
|
52
52
|
)
|
|
53
53
|
|
|
@@ -63,7 +63,7 @@ class InformationPostValidation:
|
|
|
63
63
|
self.issue_list.append(UndefinedClassWarning(class_id=str(parent)))
|
|
64
64
|
else:
|
|
65
65
|
self.issue_list.append(
|
|
66
|
-
|
|
66
|
+
ResourceNotDefinedWarning(
|
|
67
67
|
resource_type="class",
|
|
68
68
|
identifier=parent,
|
|
69
69
|
location="Classes sheet",
|
|
@@ -79,7 +79,7 @@ class InformationPostValidation:
|
|
|
79
79
|
if missing_classes := classes_with_explicit_properties.difference(defined_classes):
|
|
80
80
|
for class_ in missing_classes:
|
|
81
81
|
self.issue_list.append(
|
|
82
|
-
|
|
82
|
+
ResourceNotDefinedWarning(
|
|
83
83
|
resource_type="class",
|
|
84
84
|
identifier=class_,
|
|
85
85
|
location="Classes sheet",
|
|
@@ -99,9 +99,9 @@ class InformationPostValidation:
|
|
|
99
99
|
# Todo: include row and column number
|
|
100
100
|
for missing in missing_value_types:
|
|
101
101
|
self.issue_list.append(
|
|
102
|
-
|
|
102
|
+
ResourceNotDefinedWarning(
|
|
103
103
|
resource_type="class",
|
|
104
|
-
identifier=
|
|
104
|
+
identifier=missing,
|
|
105
105
|
location="Classes sheet",
|
|
106
106
|
)
|
|
107
107
|
)
|
|
@@ -32,7 +32,7 @@ def load_classic_to_core_mapping(org_name: str, source_space: str, source_versio
|
|
|
32
32
|
if not isinstance(read.rules, DMSInputRules):
|
|
33
33
|
raise NeatValueError(f"Expected DMS rules, but got {type(read.rules).__name__}")
|
|
34
34
|
|
|
35
|
-
verified = VerifyDMSRules(errors="raise",
|
|
35
|
+
verified = VerifyDMSRules(errors="raise", validate=False).transform(read)
|
|
36
36
|
|
|
37
37
|
if verified.rules is None:
|
|
38
38
|
raise NeatValueError("Failed to verify the rules.")
|
|
@@ -65,7 +65,7 @@ properties:
|
|
|
65
65
|
index: source
|
|
66
66
|
is_list: false
|
|
67
67
|
nullable: true
|
|
68
|
-
value_type: cdf_cdm:
|
|
68
|
+
value_type: cdf_cdm:CogniteSourceSystem(version=v1)
|
|
69
69
|
view: ClassicAsset
|
|
70
70
|
view_property: source
|
|
71
71
|
- container: cdf_cdm:CogniteDescribable
|
|
@@ -124,7 +124,7 @@ properties:
|
|
|
124
124
|
index: source
|
|
125
125
|
is_list: false
|
|
126
126
|
nullable: true
|
|
127
|
-
value_type: cdf_cdm:
|
|
127
|
+
value_type: cdf_cdm:CogniteSourceSystem(version=v1)
|
|
128
128
|
view: ClassicEvent
|
|
129
129
|
view_property: source
|
|
130
130
|
- container: cdf_cdm:CogniteSchedulable
|
|
@@ -196,7 +196,7 @@ properties:
|
|
|
196
196
|
index: source
|
|
197
197
|
is_list: false
|
|
198
198
|
nullable: true
|
|
199
|
-
value_type: cdf_cdm:
|
|
199
|
+
value_type: cdf_cdm:CogniteSourceSystem(version=v1)
|
|
200
200
|
view: ClassicFile
|
|
201
201
|
view_property: source
|
|
202
202
|
- container: cdf_cdm:CogniteSourceable
|
|
@@ -306,7 +306,7 @@ properties:
|
|
|
306
306
|
is_list: false
|
|
307
307
|
name: Unit
|
|
308
308
|
nullable: true
|
|
309
|
-
value_type:
|
|
309
|
+
value_type: cdf_cdm:CogniteUnit(version=v1)
|
|
310
310
|
view: ClassicTimeSeries
|
|
311
311
|
view_property: unitExternalId
|
|
312
312
|
views:
|
|
@@ -315,6 +315,10 @@ views:
|
|
|
315
315
|
implements: cdf_cdm:CogniteDescribable(version=v1)
|
|
316
316
|
in_model: true
|
|
317
317
|
view: cdf_cdm:CogniteSourceSystem(version=v1)
|
|
318
|
+
- description: Represents a single unit of measurement
|
|
319
|
+
implements: CogniteDescribable
|
|
320
|
+
in_model: true
|
|
321
|
+
view: cdf_cdm:CogniteUnit(version=v1)
|
|
318
322
|
- description: Assets represent systems that support industrial functions or processes.
|
|
319
323
|
Assets are often called 'functional location'.
|
|
320
324
|
implements: cdf_cdm:CogniteAsset(version=v1)
|
|
@@ -23,7 +23,7 @@ class ImporterPipeline(RulesPipeline[InputRules, VerifiedRules]):
|
|
|
23
23
|
|
|
24
24
|
@classmethod
|
|
25
25
|
def _create_pipeline(cls, importer: BaseImporter[InputRules], role: RoleTypes | None = None) -> "ImporterPipeline":
|
|
26
|
-
items: list[RulesTransformer] = [VerifyAnyRules(errors="continue")]
|
|
26
|
+
items: list[RulesTransformer] = [VerifyAnyRules(errors="continue", validate=True)]
|
|
27
27
|
if role is not None:
|
|
28
28
|
out_cls = VERIFIED_RULES_BY_ROLE[role]
|
|
29
29
|
items.append(ConvertToRules(out_cls))
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from abc import ABC
|
|
2
2
|
from typing import Any, Literal
|
|
3
3
|
|
|
4
|
-
from cognite.neat._issues import IssueList, NeatError, NeatWarning, catch_issues
|
|
4
|
+
from cognite.neat._issues import IssueList, MultiValueError, NeatError, NeatWarning, catch_issues
|
|
5
5
|
from cognite.neat._issues.errors import NeatTypeError
|
|
6
6
|
from cognite.neat._rules._shared import (
|
|
7
7
|
InputRules,
|
|
@@ -18,6 +18,8 @@ from cognite.neat._rules.models import (
|
|
|
18
18
|
InformationInputRules,
|
|
19
19
|
InformationRules,
|
|
20
20
|
)
|
|
21
|
+
from cognite.neat._rules.models.dms import DMSValidation
|
|
22
|
+
from cognite.neat._rules.models.information import InformationValidation
|
|
21
23
|
|
|
22
24
|
from ._base import RulesTransformer
|
|
23
25
|
|
|
@@ -26,10 +28,11 @@ class VerificationTransformer(RulesTransformer[T_InputRules, T_VerifiedRules], A
|
|
|
26
28
|
"""Base class for all verification transformers."""
|
|
27
29
|
|
|
28
30
|
_rules_cls: type[T_VerifiedRules]
|
|
31
|
+
_validation_cls: type
|
|
29
32
|
|
|
30
|
-
def __init__(self, errors: Literal["raise", "continue"],
|
|
33
|
+
def __init__(self, errors: Literal["raise", "continue"], validate: bool = True) -> None:
|
|
31
34
|
self.errors = errors
|
|
32
|
-
self.
|
|
35
|
+
self.validate = validate
|
|
33
36
|
|
|
34
37
|
def transform(self, rules: T_InputRules | OutRules[T_InputRules]) -> MaybeRules[T_VerifiedRules]:
|
|
35
38
|
issues = IssueList()
|
|
@@ -41,8 +44,17 @@ class VerificationTransformer(RulesTransformer[T_InputRules, T_VerifiedRules], A
|
|
|
41
44
|
with catch_issues(issues, NeatError, NeatWarning, error_args) as future:
|
|
42
45
|
rules_cls = self._get_rules_cls(in_)
|
|
43
46
|
dumped = in_.dump()
|
|
44
|
-
dumped["post_validate"] = self.post_validate
|
|
45
47
|
verified_rules = rules_cls.model_validate(dumped) # type: ignore[assignment]
|
|
48
|
+
if self.validate:
|
|
49
|
+
validation_cls = self._get_validation_cls(verified_rules) # type: ignore[arg-type]
|
|
50
|
+
validation_issues = validation_cls(verified_rules).validate()
|
|
51
|
+
# We need to trigger warnings are raise exceptions such that they are caught by the context manager
|
|
52
|
+
# and processed with the read context
|
|
53
|
+
if validation_issues.warnings:
|
|
54
|
+
validation_issues.trigger_warnings()
|
|
55
|
+
if validation_issues.has_errors:
|
|
56
|
+
verified_rules = None
|
|
57
|
+
raise MultiValueError(validation_issues.errors)
|
|
46
58
|
|
|
47
59
|
if (future.result == "failure" or issues.has_errors or verified_rules is None) and self.errors == "raise":
|
|
48
60
|
raise issues.as_errors()
|
|
@@ -54,17 +66,22 @@ class VerificationTransformer(RulesTransformer[T_InputRules, T_VerifiedRules], A
|
|
|
54
66
|
def _get_rules_cls(self, in_: T_InputRules) -> type[T_VerifiedRules]:
|
|
55
67
|
return self._rules_cls
|
|
56
68
|
|
|
69
|
+
def _get_validation_cls(self, rules: T_VerifiedRules) -> type:
|
|
70
|
+
return self._validation_cls
|
|
71
|
+
|
|
57
72
|
|
|
58
73
|
class VerifyDMSRules(VerificationTransformer[DMSInputRules, DMSRules]):
|
|
59
74
|
"""Class to verify DMS rules."""
|
|
60
75
|
|
|
61
76
|
_rules_cls = DMSRules
|
|
77
|
+
_validation_cls = DMSValidation
|
|
62
78
|
|
|
63
79
|
|
|
64
80
|
class VerifyInformationRules(VerificationTransformer[InformationInputRules, InformationRules]):
|
|
65
81
|
"""Class to verify Information rules."""
|
|
66
82
|
|
|
67
83
|
_rules_cls = InformationRules
|
|
84
|
+
_validation_cls = InformationValidation
|
|
68
85
|
|
|
69
86
|
|
|
70
87
|
class VerifyAnyRules(VerificationTransformer[InputRules, VerifiedRules]):
|
|
@@ -77,3 +94,11 @@ class VerifyAnyRules(VerificationTransformer[InputRules, VerifiedRules]):
|
|
|
77
94
|
return DMSRules
|
|
78
95
|
else:
|
|
79
96
|
raise NeatTypeError(f"Unsupported rules type: {type(in_)}")
|
|
97
|
+
|
|
98
|
+
def _get_validation_cls(self, rules: T_VerifiedRules) -> type:
|
|
99
|
+
if isinstance(rules, InformationRules):
|
|
100
|
+
return InformationValidation
|
|
101
|
+
elif isinstance(rules, DMSRules):
|
|
102
|
+
return DMSValidation
|
|
103
|
+
else:
|
|
104
|
+
raise NeatTypeError(f"Unsupported rules type: {type(rules)}")
|
cognite/neat/_session/_base.py
CHANGED
|
@@ -10,8 +10,9 @@ from cognite.neat._issues import IssueList, catch_issues
|
|
|
10
10
|
from cognite.neat._issues.errors import RegexViolationError
|
|
11
11
|
from cognite.neat._rules import importers
|
|
12
12
|
from cognite.neat._rules._shared import ReadRules, VerifiedRules
|
|
13
|
-
from cognite.neat._rules.
|
|
14
|
-
from cognite.neat._rules.models import
|
|
13
|
+
from cognite.neat._rules.models import DMSRules
|
|
14
|
+
from cognite.neat._rules.models.dms import DMSValidation
|
|
15
|
+
from cognite.neat._rules.models.information import InformationValidation
|
|
15
16
|
from cognite.neat._rules.models.information._rules import InformationRules
|
|
16
17
|
from cognite.neat._rules.models.information._rules_input import InformationInputRules
|
|
17
18
|
from cognite.neat._rules.transformers import ConvertToRules, VerifyAnyRules
|
|
@@ -66,31 +67,21 @@ class NeatSession:
|
|
|
66
67
|
|
|
67
68
|
def verify(self) -> IssueList:
|
|
68
69
|
source_id, last_unverified_rule = self._state.data_model.last_unverified_rule
|
|
69
|
-
|
|
70
|
-
reference_rules: DMSInputRules | None = None
|
|
71
|
-
if isinstance(last_unverified_rule.rules, DMSInputRules):
|
|
72
|
-
dms_rules = last_unverified_rule.rules
|
|
73
|
-
views_ids, containers_ids = dms_rules.imported_views_and_containers_ids()
|
|
74
|
-
if views_ids or containers_ids:
|
|
75
|
-
if self._client is None:
|
|
76
|
-
raise NeatSessionError(
|
|
77
|
-
"No client provided. You are referencing unknown views and containers in your data model, "
|
|
78
|
-
"NEAT needs a client to lookup the definitions. "
|
|
79
|
-
"Please set the client in the session, NeatSession(client=client)."
|
|
80
|
-
)
|
|
81
|
-
schema = self._client.schema.retrieve(list(views_ids), list(containers_ids))
|
|
82
|
-
|
|
83
|
-
importer = DMSImporter(schema)
|
|
84
|
-
reference_rules = importer.to_rules().rules
|
|
85
|
-
|
|
86
|
-
if reference_rules is not None:
|
|
87
|
-
dms_rules.views.extend(reference_rules.views)
|
|
88
|
-
if dms_rules.containers:
|
|
89
|
-
dms_rules.containers.extend(reference_rules.containers or [])
|
|
90
|
-
|
|
91
|
-
transformer = VerifyAnyRules("continue")
|
|
70
|
+
transformer = VerifyAnyRules("continue", validate=False)
|
|
92
71
|
start = datetime.now(timezone.utc)
|
|
93
72
|
output = transformer.try_transform(last_unverified_rule)
|
|
73
|
+
if isinstance(output.rules, DMSRules):
|
|
74
|
+
issues = DMSValidation(output.rules, self._client).validate()
|
|
75
|
+
elif isinstance(output.rules, InformationRules):
|
|
76
|
+
issues = InformationValidation(output.rules).validate()
|
|
77
|
+
else:
|
|
78
|
+
raise NeatSessionError("Unsupported rule type")
|
|
79
|
+
if issues.has_errors:
|
|
80
|
+
# This is up for discussion, but I think we should not return rules that
|
|
81
|
+
# only pass the verification but not the validation.
|
|
82
|
+
output.rules = None
|
|
83
|
+
output.issues.extend(issues)
|
|
84
|
+
|
|
94
85
|
end = datetime.now(timezone.utc)
|
|
95
86
|
|
|
96
87
|
if output.rules:
|
|
@@ -103,22 +94,6 @@ class NeatSession:
|
|
|
103
94
|
self._state.data_model.provenance.source_entity(source_id)
|
|
104
95
|
or self._state.data_model.provenance.target_entity(source_id),
|
|
105
96
|
)
|
|
106
|
-
if reference_rules is not None and isinstance(output.rules, DMSRules):
|
|
107
|
-
# Remove the referenced views and containers from the rules
|
|
108
|
-
ref_view_ids = set(reference_rules.as_view_entities())
|
|
109
|
-
if ref_view_ids:
|
|
110
|
-
output.rules.views = SheetList(
|
|
111
|
-
[view for view in output.rules.views if view.view not in ref_view_ids]
|
|
112
|
-
)
|
|
113
|
-
ref_container_ids = reference_rules.as_container_entities()
|
|
114
|
-
if output.rules.containers and ref_container_ids:
|
|
115
|
-
output.rules.containers = SheetList(
|
|
116
|
-
[
|
|
117
|
-
container
|
|
118
|
-
for container in output.rules.containers
|
|
119
|
-
if container.container not in ref_container_ids
|
|
120
|
-
]
|
|
121
|
-
)
|
|
122
97
|
|
|
123
98
|
self._state.data_model.write(output.rules, change)
|
|
124
99
|
|
|
@@ -13,6 +13,7 @@ from cognite.neat._graph.transformers._rdfpath import MakeConnectionOnExactMatch
|
|
|
13
13
|
from cognite.neat._rules._shared import InputRules, ReadRules
|
|
14
14
|
from cognite.neat._rules.importers import DMSImporter
|
|
15
15
|
from cognite.neat._rules.models import DMSRules
|
|
16
|
+
from cognite.neat._rules.models.dms import DMSValidation
|
|
16
17
|
from cognite.neat._rules.models.information._rules_input import InformationInputRules
|
|
17
18
|
from cognite.neat._rules.transformers import (
|
|
18
19
|
PrefixEntities,
|
|
@@ -316,7 +317,7 @@ class DataModelPrepareAPI:
|
|
|
316
317
|
source_id, rules = self._state.data_model.last_verified_dms_rules
|
|
317
318
|
|
|
318
319
|
dms_ref: DMSRules | None = None
|
|
319
|
-
view_ids, container_ids = rules.imported_views_and_containers_ids(
|
|
320
|
+
view_ids, container_ids = DMSValidation(rules, self._client).imported_views_and_containers_ids()
|
|
320
321
|
if view_ids or container_ids:
|
|
321
322
|
if self._client is None:
|
|
322
323
|
raise NeatSessionError(
|
|
@@ -324,7 +325,7 @@ class DataModelPrepareAPI:
|
|
|
324
325
|
"NEAT needs a client to lookup the definitions. "
|
|
325
326
|
"Please set the client in the session, NeatSession(client=client)."
|
|
326
327
|
)
|
|
327
|
-
schema = self._client.schema.retrieve(
|
|
328
|
+
schema = self._client.schema.retrieve([v.as_id() for v in view_ids], [c.as_id() for c in container_ids])
|
|
328
329
|
|
|
329
330
|
importer = DMSImporter(schema)
|
|
330
331
|
reference_rules = importer.to_rules().rules
|
|
@@ -409,7 +410,7 @@ class DataModelPrepareAPI:
|
|
|
409
410
|
start = datetime.now(timezone.utc)
|
|
410
411
|
|
|
411
412
|
source_id, rules = self._state.data_model.last_verified_dms_rules
|
|
412
|
-
view_ids, container_ids = rules.imported_views_and_containers_ids(
|
|
413
|
+
view_ids, container_ids = DMSValidation(rules, self._client).imported_views_and_containers_ids()
|
|
413
414
|
if not (view_ids or container_ids):
|
|
414
415
|
print(
|
|
415
416
|
f"Data model {rules.metadata.as_data_model_id()} does not have any referenced views or containers."
|
|
@@ -422,7 +423,7 @@ class DataModelPrepareAPI:
|
|
|
422
423
|
"NEAT needs a client to lookup the definitions. "
|
|
423
424
|
"Please set the client in the session, NeatSession(client=client)."
|
|
424
425
|
)
|
|
425
|
-
schema = self._client.schema.retrieve(
|
|
426
|
+
schema = self._client.schema.retrieve([v.as_id() for v in view_ids], [c.as_id() for c in container_ids])
|
|
426
427
|
copy_ = rules.model_copy(deep=True)
|
|
427
428
|
copy_.metadata.version = f"{rules.metadata.version}_completed"
|
|
428
429
|
importer = DMSImporter(schema)
|
|
@@ -433,7 +434,7 @@ class DataModelPrepareAPI:
|
|
|
433
434
|
"Could not import the referenced views and containers. "
|
|
434
435
|
"See `neat.inspect.issues()` for more information."
|
|
435
436
|
)
|
|
436
|
-
verified = VerifyDMSRules("continue",
|
|
437
|
+
verified = VerifyDMSRules("continue", validate=False).transform(imported.rules)
|
|
437
438
|
if verified.rules is None:
|
|
438
439
|
self._state.data_model.issue_lists.append(verified.issues)
|
|
439
440
|
raise NeatSessionError(
|
cognite/neat/_session/_to.py
CHANGED
|
@@ -66,9 +66,12 @@ class ToAPI:
|
|
|
66
66
|
"This is required for the 'toolkit' format."
|
|
67
67
|
)
|
|
68
68
|
dms_rule = self._state.data_model.last_verified_dms_rules[1]
|
|
69
|
-
|
|
69
|
+
user_path = Path(io)
|
|
70
|
+
if user_path.suffix == "" and not user_path.exists():
|
|
71
|
+
user_path.mkdir(parents=True)
|
|
72
|
+
exporters.DMSExporter().export_to_file(dms_rule, user_path)
|
|
70
73
|
else:
|
|
71
|
-
raise NeatSessionError("Please provide a valid format.
|
|
74
|
+
raise NeatSessionError("Please provide a valid format. 'neat' or 'toolkit'")
|
|
72
75
|
|
|
73
76
|
return None
|
|
74
77
|
|
|
@@ -2,6 +2,8 @@ import functools
|
|
|
2
2
|
from collections.abc import Callable
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
|
+
from cognite.neat._issues.errors import CDFMissingClientError
|
|
6
|
+
|
|
5
7
|
from ._collector import _COLLECTOR
|
|
6
8
|
|
|
7
9
|
try:
|
|
@@ -27,6 +29,8 @@ def _session_method_wrapper(func: Callable, cls_name: str):
|
|
|
27
29
|
except NeatSessionError as e:
|
|
28
30
|
action = _get_action()
|
|
29
31
|
print(f"{_PREFIX} Cannot {action}: {e}")
|
|
32
|
+
except CDFMissingClientError as e:
|
|
33
|
+
print(f"{_PREFIX} {e.as_message()}")
|
|
30
34
|
except ModuleNotFoundError as e:
|
|
31
35
|
if e.name == "neatengine":
|
|
32
36
|
action = _get_action()
|
cognite/neat/_utils/rdf_.py
CHANGED
|
@@ -115,13 +115,15 @@ def as_neat_compliant_uri(uri: URIRef) -> URIRef:
|
|
|
115
115
|
return URIRef(f"{namespace}{compliant_uri}")
|
|
116
116
|
|
|
117
117
|
|
|
118
|
-
def convert_rdflib_content(content: RdfLiteral | URIRef | dict | list) -> Any:
|
|
119
|
-
if isinstance(content, RdfLiteral)
|
|
118
|
+
def convert_rdflib_content(content: RdfLiteral | URIRef | dict | list, remove_namespace: bool = False) -> Any:
|
|
119
|
+
if isinstance(content, RdfLiteral):
|
|
120
120
|
return content.toPython()
|
|
121
|
+
elif isinstance(content, URIRef):
|
|
122
|
+
return remove_namespace_from_uri(content) if remove_namespace else content.toPython()
|
|
121
123
|
elif isinstance(content, dict):
|
|
122
|
-
return {key: convert_rdflib_content(value) for key, value in content.items()}
|
|
124
|
+
return {key: convert_rdflib_content(value, remove_namespace) for key, value in content.items()}
|
|
123
125
|
elif isinstance(content, list):
|
|
124
|
-
return [convert_rdflib_content(item) for item in content]
|
|
126
|
+
return [convert_rdflib_content(item, remove_namespace) for item in content]
|
|
125
127
|
else:
|
|
126
128
|
return content
|
|
127
129
|
|
cognite/neat/_version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
__version__ = "0.99.
|
|
1
|
+
__version__ = "0.99.1"
|
|
2
2
|
__engine__ = "^1.0.3"
|
|
@@ -21,7 +21,6 @@ __all__ = [
|
|
|
21
21
|
"RulesToOntology",
|
|
22
22
|
"RulesToSHACL",
|
|
23
23
|
"RulesToSemanticDataModel",
|
|
24
|
-
"RulesToCDFTransformations",
|
|
25
24
|
"DeleteDataModelFromCDF",
|
|
26
25
|
]
|
|
27
26
|
|
|
@@ -532,93 +531,6 @@ class RulesToSemanticDataModel(Step):
|
|
|
532
531
|
return FlowMessage(output_text=output_text)
|
|
533
532
|
|
|
534
533
|
|
|
535
|
-
class RulesToCDFTransformations(Step):
|
|
536
|
-
description = "This step exports transformations and RAW tables to populate a data model in CDF"
|
|
537
|
-
version = "private-alpha"
|
|
538
|
-
category = CATEGORY
|
|
539
|
-
configurables: ClassVar[list[Configurable]] = [
|
|
540
|
-
Configurable(
|
|
541
|
-
name="Dry run",
|
|
542
|
-
value="False",
|
|
543
|
-
label=("Whether to perform a dry run of the export. "),
|
|
544
|
-
options=["True", "False"],
|
|
545
|
-
),
|
|
546
|
-
Configurable(
|
|
547
|
-
name="Instance space",
|
|
548
|
-
value="",
|
|
549
|
-
label=(
|
|
550
|
-
"The space to use for the transformations instances. If provided, "
|
|
551
|
-
"the transformations will be set to populate"
|
|
552
|
-
"this space. If not provided, the space from the input rules will be used."
|
|
553
|
-
),
|
|
554
|
-
),
|
|
555
|
-
]
|
|
556
|
-
|
|
557
|
-
def run(self, rules: MultiRuleData, cdf_client: CogniteClient) -> FlowMessage: # type: ignore[override]
|
|
558
|
-
if self.configs is None or self.data_store_path is None:
|
|
559
|
-
raise WorkflowStepNotInitializedError(type(self).__name__)
|
|
560
|
-
|
|
561
|
-
input_rules = rules.dms or rules.information
|
|
562
|
-
if input_rules is None:
|
|
563
|
-
return FlowMessage(
|
|
564
|
-
error_text="Missing DMS or Information rules in the input data! "
|
|
565
|
-
"Please ensure that a DMS or Information rules is provided!",
|
|
566
|
-
step_execution_status=StepExecutionStatus.ABORT_AND_FAIL,
|
|
567
|
-
)
|
|
568
|
-
if isinstance(input_rules, DMSRules):
|
|
569
|
-
dms_rules = input_rules
|
|
570
|
-
elif isinstance(input_rules, InformationRules):
|
|
571
|
-
dms_rules = InformationToDMS().transform(input_rules).rules
|
|
572
|
-
else:
|
|
573
|
-
raise NotImplementedError(f"Unsupported rules type {type(input_rules)}")
|
|
574
|
-
|
|
575
|
-
instance_space = self.configs.get("Instance space") or dms_rules.metadata.space
|
|
576
|
-
dry_run = self.configs.get("Dry run", "False") == "True"
|
|
577
|
-
dms_exporter = exporters.DMSExporter(
|
|
578
|
-
export_pipeline=True, instance_space=instance_space, export_components=["spaces"]
|
|
579
|
-
)
|
|
580
|
-
output_dir = self.config.staging_path
|
|
581
|
-
output_dir.mkdir(parents=True, exist_ok=True)
|
|
582
|
-
file_name = dms_rules.metadata.external_id.replace(":", "_")
|
|
583
|
-
schema_zip = f"{file_name}_pipeline.zip"
|
|
584
|
-
schema_full_path = output_dir / schema_zip
|
|
585
|
-
|
|
586
|
-
dms_exporter.export_to_file(dms_rules, schema_full_path)
|
|
587
|
-
|
|
588
|
-
report_lines = ["# DMS Schema Export to CDF\n\n"]
|
|
589
|
-
errors = []
|
|
590
|
-
for result in dms_exporter.export_to_cdf_iterable(
|
|
591
|
-
rules=dms_rules, client=NeatClient(cdf_client), dry_run=dry_run
|
|
592
|
-
):
|
|
593
|
-
report_lines.append(str(result))
|
|
594
|
-
errors.extend(result.error_messages)
|
|
595
|
-
|
|
596
|
-
report_lines.append("\n\n# ERRORS\n\n")
|
|
597
|
-
report_lines.extend(errors)
|
|
598
|
-
|
|
599
|
-
output_dir = self.config.staging_path
|
|
600
|
-
output_dir.mkdir(parents=True, exist_ok=True)
|
|
601
|
-
report_file = "pipeline_creation_report.txt"
|
|
602
|
-
report_full_path = output_dir / report_file
|
|
603
|
-
report_full_path.write_text("\n".join(report_lines))
|
|
604
|
-
|
|
605
|
-
output_text = (
|
|
606
|
-
"<p></p>"
|
|
607
|
-
"Download Pipeline Export "
|
|
608
|
-
f'<a href="/data/staging/{report_file}?{time.time()}" '
|
|
609
|
-
f'target="_blank">Report</a>'
|
|
610
|
-
"<p></p>"
|
|
611
|
-
"Download Pipeline exported schema"
|
|
612
|
-
f'- <a href="/data/staging/{schema_zip}?{time.time()}" '
|
|
613
|
-
f'target="_blank">{schema_zip}</a>'
|
|
614
|
-
)
|
|
615
|
-
|
|
616
|
-
if errors:
|
|
617
|
-
return FlowMessage(error_text=output_text, step_execution_status=StepExecutionStatus.ABORT_AND_FAIL)
|
|
618
|
-
else:
|
|
619
|
-
return FlowMessage(output_text=output_text)
|
|
620
|
-
|
|
621
|
-
|
|
622
534
|
def _get_default_file_name(rules: MultiRuleData, file_category: str = "ontology", extension: str = "ttl") -> str:
|
|
623
535
|
name = rules.information.metadata.prefix if rules.information else cast(DMSRules, rules.dms).metadata.space
|
|
624
536
|
version = rules.information.metadata.version if rules.information else cast(DMSRules, rules.dms).metadata.version
|
|
@@ -3,7 +3,6 @@ from pathlib import Path
|
|
|
3
3
|
from typing import ClassVar
|
|
4
4
|
|
|
5
5
|
from cognite.client import CogniteClient
|
|
6
|
-
from cognite.client.data_classes.data_modeling import DataModelId
|
|
7
6
|
|
|
8
7
|
from cognite.neat._client import NeatClient
|
|
9
8
|
from cognite.neat._issues.errors import WorkflowStepNotInitializedError
|
|
@@ -288,21 +287,8 @@ class DMSToRules(Step):
|
|
|
288
287
|
f"or 'my_space:my_data_model', failed to parse space from {datamodel_id_str}"
|
|
289
288
|
)
|
|
290
289
|
return FlowMessage(error_text=error_text, step_execution_status=StepExecutionStatus.ABORT_AND_FAIL)
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
if ref_datamodel_str:
|
|
294
|
-
ref_model = DataModelEntity.load(ref_datamodel_str)
|
|
295
|
-
if isinstance(ref_model, DMSUnknownEntity):
|
|
296
|
-
error_text = (
|
|
297
|
-
f"Reference data model id should be in the format 'my_space:my_data_model(version=1)' "
|
|
298
|
-
f"or 'my_space:my_data_model', failed to parse space from {ref_datamodel_str}"
|
|
299
|
-
)
|
|
300
|
-
return FlowMessage(error_text=error_text, step_execution_status=StepExecutionStatus.ABORT_AND_FAIL)
|
|
301
|
-
ref_model_id = ref_model.as_id()
|
|
302
|
-
|
|
303
|
-
dms_importer = importers.DMSImporter.from_data_model_id(
|
|
304
|
-
NeatClient(cdf_client), datamodel_entity.as_id(), ref_model_id
|
|
305
|
-
)
|
|
290
|
+
|
|
291
|
+
dms_importer = importers.DMSImporter.from_data_model_id(NeatClient(cdf_client), datamodel_entity.as_id())
|
|
306
292
|
|
|
307
293
|
# if role is None, it will be inferred from the rules file
|
|
308
294
|
role = self.configs.get("Role")
|
|
@@ -11,6 +11,7 @@ from cognite.neat._issues import NeatIssueList
|
|
|
11
11
|
from cognite.neat._issues.errors import ResourceNotFoundError, WorkflowStepNotInitializedError
|
|
12
12
|
from cognite.neat._issues.formatters import FORMATTER_BY_NAME
|
|
13
13
|
from cognite.neat._rules.models import DMSRules
|
|
14
|
+
from cognite.neat._rules.models.dms import DMSValidation
|
|
14
15
|
from cognite.neat._workflows.model import FlowMessage, StepExecutionStatus
|
|
15
16
|
from cognite.neat._workflows.steps.data_contracts import MultiRuleData
|
|
16
17
|
from cognite.neat._workflows.steps.step_model import Configurable, Step
|
|
@@ -51,10 +52,7 @@ class ValidateRulesAgainstCDF(Step):
|
|
|
51
52
|
)
|
|
52
53
|
dms_rules = rules.dms
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
errors = schema.validate()
|
|
56
|
-
if not errors:
|
|
57
|
-
return FlowMessage(output_text="Rules are complete and valid. No need to fetch from CDF.")
|
|
55
|
+
errors = DMSValidation(dms_rules, NeatClient(cdf_client)).validate()
|
|
58
56
|
|
|
59
57
|
missing_spaces = [
|
|
60
58
|
error.identifier
|
|
@@ -85,11 +83,11 @@ class ValidateRulesAgainstCDF(Step):
|
|
|
85
83
|
f"and {len(retrieved_views)} views from CDF."
|
|
86
84
|
)
|
|
87
85
|
|
|
86
|
+
schema = dms_rules.as_schema()
|
|
88
87
|
schema.spaces.update({space.space: space for space in retrieved_spaces})
|
|
89
88
|
schema.containers.update({container.as_id(): container for container in retrieved_containers})
|
|
90
89
|
schema.views.update({view.as_id(): view for view in retrieved_views})
|
|
91
90
|
|
|
92
|
-
errors = schema.validate()
|
|
93
91
|
if errors:
|
|
94
92
|
output_dir = self.data_store_path / Path("staging")
|
|
95
93
|
report_writer = FORMATTER_BY_NAME[self.configs["Report Formatter"]]()
|