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,22 +1,16 @@
|
|
|
1
|
-
from
|
|
2
|
-
from dataclasses import dataclass
|
|
1
|
+
from dataclasses import dataclass, field
|
|
3
2
|
from datetime import datetime
|
|
4
|
-
from typing import Any, Literal
|
|
3
|
+
from typing import Any, Literal
|
|
5
4
|
|
|
6
5
|
from rdflib import Namespace
|
|
7
6
|
|
|
8
|
-
from cognite.neat.rules.models.
|
|
9
|
-
DataModelType,
|
|
10
|
-
ExtensionCategory,
|
|
11
|
-
SchemaCompleteness,
|
|
12
|
-
_add_alias,
|
|
13
|
-
)
|
|
7
|
+
from cognite.neat.rules.models._base_input import InputComponent, InputRules
|
|
14
8
|
from cognite.neat.rules.models.data_types import DataType
|
|
15
9
|
from cognite.neat.rules.models.entities import (
|
|
16
10
|
ClassEntity,
|
|
17
11
|
MultiValueTypeInfo,
|
|
18
|
-
Unknown,
|
|
19
12
|
UnknownEntity,
|
|
13
|
+
load_value_type,
|
|
20
14
|
)
|
|
21
15
|
|
|
22
16
|
from ._rules import (
|
|
@@ -28,7 +22,7 @@ from ._rules import (
|
|
|
28
22
|
|
|
29
23
|
|
|
30
24
|
@dataclass
|
|
31
|
-
class
|
|
25
|
+
class InformationInputMetadata(InputComponent[InformationMetadata]):
|
|
32
26
|
schema_: Literal["complete", "partial", "extended"]
|
|
33
27
|
prefix: str
|
|
34
28
|
namespace: str
|
|
@@ -44,47 +38,23 @@ class InformationMetadataInput:
|
|
|
44
38
|
rights: str | None = None
|
|
45
39
|
|
|
46
40
|
@classmethod
|
|
47
|
-
def
|
|
48
|
-
|
|
49
|
-
return None
|
|
50
|
-
_add_alias(data, InformationMetadata)
|
|
51
|
-
return cls(
|
|
52
|
-
data_model_type=data.get("data_model_type", "enterprise"),
|
|
53
|
-
extension=data.get("extension", "addition"),
|
|
54
|
-
schema_=data.get("schema_", "partial"), # type: ignore[arg-type]
|
|
55
|
-
version=data.get("version"), # type: ignore[arg-type]
|
|
56
|
-
namespace=data.get("namespace"), # type: ignore[arg-type]
|
|
57
|
-
prefix=data.get("prefix"), # type: ignore[arg-type]
|
|
58
|
-
name=data.get("name"),
|
|
59
|
-
creator=data.get("creator"), # type: ignore[arg-type]
|
|
60
|
-
description=data.get("description"),
|
|
61
|
-
created=data.get("created"),
|
|
62
|
-
updated=data.get("updated"),
|
|
63
|
-
license=data.get("license"),
|
|
64
|
-
rights=data.get("rights"),
|
|
65
|
-
)
|
|
41
|
+
def _get_verified_cls(cls) -> type[InformationMetadata]:
|
|
42
|
+
return InformationMetadata
|
|
66
43
|
|
|
67
|
-
def dump(self) -> dict[str, Any]:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
version=self.version,
|
|
75
|
-
name=self.name,
|
|
76
|
-
creator=self.creator,
|
|
77
|
-
description=self.description,
|
|
78
|
-
created=self.created or datetime.now(),
|
|
79
|
-
updated=self.updated or datetime.now(),
|
|
80
|
-
)
|
|
44
|
+
def dump(self, **kwargs) -> dict[str, Any]:
|
|
45
|
+
output = super().dump()
|
|
46
|
+
if self.created is None:
|
|
47
|
+
output["created"] = datetime.now()
|
|
48
|
+
if self.updated is None:
|
|
49
|
+
output["updated"] = datetime.now()
|
|
50
|
+
return output
|
|
81
51
|
|
|
82
52
|
|
|
83
53
|
@dataclass
|
|
84
|
-
class
|
|
85
|
-
class_: str
|
|
54
|
+
class InformationInputProperty(InputComponent[InformationProperty]):
|
|
55
|
+
class_: ClassEntity | str
|
|
86
56
|
property_: str
|
|
87
|
-
value_type: str
|
|
57
|
+
value_type: DataType | ClassEntity | MultiValueTypeInfo | UnknownEntity | str
|
|
88
58
|
name: str | None = None
|
|
89
59
|
description: str | None = None
|
|
90
60
|
comment: str | None = None
|
|
@@ -94,194 +64,77 @@ class InformationPropertyInput:
|
|
|
94
64
|
reference: str | None = None
|
|
95
65
|
match_type: str | None = None
|
|
96
66
|
transformation: str | None = None
|
|
67
|
+
# Only used internally
|
|
68
|
+
inherited: bool = False
|
|
97
69
|
|
|
98
70
|
@classmethod
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
@classmethod
|
|
103
|
-
@overload
|
|
104
|
-
def load(cls, data: dict[str, Any]) -> "InformationPropertyInput": ...
|
|
105
|
-
|
|
106
|
-
@classmethod
|
|
107
|
-
@overload
|
|
108
|
-
def load(cls, data: list[dict[str, Any]]) -> list["InformationPropertyInput"]: ...
|
|
109
|
-
|
|
110
|
-
@classmethod
|
|
111
|
-
def load(
|
|
112
|
-
cls, data: dict[str, Any] | list[dict[str, Any]] | None
|
|
113
|
-
) -> "InformationPropertyInput | list[InformationPropertyInput] | None":
|
|
114
|
-
if data is None:
|
|
115
|
-
return None
|
|
116
|
-
if isinstance(data, list) or (isinstance(data, dict) and isinstance(data.get("data"), list)):
|
|
117
|
-
items = cast(
|
|
118
|
-
list[dict[str, Any]],
|
|
119
|
-
data.get("data") if isinstance(data, dict) else data,
|
|
120
|
-
)
|
|
121
|
-
return [loaded for item in items if (loaded := cls.load(item)) is not None]
|
|
122
|
-
|
|
123
|
-
_add_alias(data, InformationProperty)
|
|
124
|
-
return cls(
|
|
125
|
-
class_=data.get("class_"), # type: ignore[arg-type]
|
|
126
|
-
property_=data.get("property_"), # type: ignore[arg-type]
|
|
127
|
-
name=data.get("name", None),
|
|
128
|
-
description=data.get("description", None),
|
|
129
|
-
comment=data.get("comment", None),
|
|
130
|
-
value_type=data.get("value_type"), # type: ignore[arg-type]
|
|
131
|
-
min_count=data.get("min_count", None),
|
|
132
|
-
max_count=data.get("max_count", None),
|
|
133
|
-
default=data.get("default", None),
|
|
134
|
-
reference=data.get("reference", None),
|
|
135
|
-
match_type=data.get("match_type", None),
|
|
136
|
-
transformation=data.get("transformation", None),
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
def dump(self, default_prefix: str) -> dict[str, Any]:
|
|
140
|
-
value_type: MultiValueTypeInfo | DataType | ClassEntity | UnknownEntity
|
|
141
|
-
|
|
142
|
-
# property holding xsd data type
|
|
143
|
-
# check if it is multi value type
|
|
144
|
-
if "|" in self.value_type:
|
|
145
|
-
value_type = MultiValueTypeInfo.load(self.value_type)
|
|
146
|
-
value_type.set_default_prefix(default_prefix)
|
|
147
|
-
|
|
148
|
-
elif DataType.is_data_type(self.value_type):
|
|
149
|
-
value_type = DataType.load(self.value_type)
|
|
71
|
+
def _get_verified_cls(cls) -> type[InformationProperty]:
|
|
72
|
+
return InformationProperty
|
|
150
73
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
else:
|
|
157
|
-
value_type = ClassEntity.load(self.value_type, prefix=default_prefix)
|
|
158
|
-
|
|
159
|
-
return {
|
|
160
|
-
"Class": ClassEntity.load(self.class_, prefix=default_prefix),
|
|
161
|
-
"Property": self.property_,
|
|
162
|
-
"Name": self.name,
|
|
163
|
-
"Description": self.description,
|
|
164
|
-
"Comment": self.comment,
|
|
165
|
-
"Value Type": value_type,
|
|
166
|
-
"Min Count": self.min_count,
|
|
167
|
-
"Max Count": self.max_count,
|
|
168
|
-
"Default": self.default,
|
|
169
|
-
"Reference": self.reference,
|
|
170
|
-
"Match Type": self.match_type,
|
|
171
|
-
"Transformation": self.transformation,
|
|
172
|
-
}
|
|
74
|
+
def dump(self, default_prefix: str, **kwargs) -> dict[str, Any]: # type: ignore[override]
|
|
75
|
+
output = super().dump()
|
|
76
|
+
output["Class"] = ClassEntity.load(self.class_, prefix=default_prefix)
|
|
77
|
+
output["Value Type"] = load_value_type(self.value_type, default_prefix)
|
|
78
|
+
return output
|
|
173
79
|
|
|
174
80
|
|
|
175
81
|
@dataclass
|
|
176
|
-
class
|
|
177
|
-
class_: str
|
|
82
|
+
class InformationInputClass(InputComponent[InformationClass]):
|
|
83
|
+
class_: ClassEntity | str
|
|
178
84
|
name: str | None = None
|
|
179
85
|
description: str | None = None
|
|
180
86
|
comment: str | None = None
|
|
181
|
-
parent: str | None = None
|
|
87
|
+
parent: str | list[ClassEntity] | None = None
|
|
182
88
|
reference: str | None = None
|
|
183
89
|
match_type: str | None = None
|
|
184
90
|
|
|
185
91
|
@classmethod
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
@classmethod
|
|
190
|
-
@overload
|
|
191
|
-
def load(cls, data: dict[str, Any]) -> "InformationClassInput": ...
|
|
92
|
+
def _get_verified_cls(cls) -> type[InformationClass]:
|
|
93
|
+
return InformationClass
|
|
192
94
|
|
|
193
|
-
@
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
@classmethod
|
|
198
|
-
def load(
|
|
199
|
-
cls, data: dict[str, Any] | list[dict[str, Any]] | None
|
|
200
|
-
) -> "InformationClassInput | list[InformationClassInput] | None":
|
|
201
|
-
if data is None:
|
|
202
|
-
return None
|
|
203
|
-
if isinstance(data, list) or (isinstance(data, dict) and isinstance(data.get("data"), list)):
|
|
204
|
-
items = cast(
|
|
205
|
-
list[dict[str, Any]],
|
|
206
|
-
data.get("data") if isinstance(data, dict) else data,
|
|
207
|
-
)
|
|
208
|
-
return [loaded for item in items if (loaded := cls.load(item)) is not None]
|
|
209
|
-
_add_alias(data, InformationClass)
|
|
210
|
-
return cls(
|
|
211
|
-
class_=data.get("class_"), # type: ignore[arg-type]
|
|
212
|
-
name=data.get("name", None),
|
|
213
|
-
description=data.get("description", None),
|
|
214
|
-
comment=data.get("comment", None),
|
|
215
|
-
parent=data.get("parent", None),
|
|
216
|
-
reference=data.get("reference", None),
|
|
217
|
-
match_type=data.get("match_type", None),
|
|
218
|
-
)
|
|
95
|
+
@property
|
|
96
|
+
def class_str(self) -> str:
|
|
97
|
+
return str(self.class_)
|
|
219
98
|
|
|
220
|
-
def dump(self, default_prefix: str) -> dict[str, Any]:
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
if self.parent
|
|
231
|
-
else None
|
|
232
|
-
),
|
|
233
|
-
}
|
|
99
|
+
def dump(self, default_prefix: str, **kwargs) -> dict[str, Any]: # type: ignore[override]
|
|
100
|
+
output = super().dump()
|
|
101
|
+
parent: list[ClassEntity] | None = None
|
|
102
|
+
if isinstance(self.parent, str):
|
|
103
|
+
parent = [ClassEntity.load(parent, prefix=default_prefix) for parent in self.parent.split(",")]
|
|
104
|
+
elif isinstance(self.parent, list):
|
|
105
|
+
parent = [ClassEntity.load(parent_, prefix=default_prefix) for parent_ in self.parent]
|
|
106
|
+
output["Class"] = ClassEntity.load(self.class_, prefix=default_prefix)
|
|
107
|
+
output["Parent Class"] = parent
|
|
108
|
+
return output
|
|
234
109
|
|
|
235
110
|
|
|
236
111
|
@dataclass
|
|
237
|
-
class
|
|
238
|
-
metadata:
|
|
239
|
-
properties:
|
|
240
|
-
classes:
|
|
241
|
-
prefixes:
|
|
242
|
-
last: "
|
|
243
|
-
reference: "
|
|
112
|
+
class InformationInputRules(InputRules[InformationRules]):
|
|
113
|
+
metadata: InformationInputMetadata
|
|
114
|
+
properties: list[InformationInputProperty] = field(default_factory=list)
|
|
115
|
+
classes: list[InformationInputClass] = field(default_factory=list)
|
|
116
|
+
prefixes: dict[str, Namespace] | None = None
|
|
117
|
+
last: "InformationInputRules | None" = None
|
|
118
|
+
reference: "InformationInputRules | None" = None
|
|
244
119
|
|
|
245
120
|
@classmethod
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
@classmethod
|
|
250
|
-
@overload
|
|
251
|
-
def load(cls, data: None) -> None: ...
|
|
252
|
-
|
|
253
|
-
@classmethod
|
|
254
|
-
def load(cls, data: dict | None) -> "InformationRulesInput | None":
|
|
255
|
-
if data is None:
|
|
256
|
-
return None
|
|
257
|
-
_add_alias(data, InformationRules)
|
|
258
|
-
|
|
259
|
-
return cls(
|
|
260
|
-
metadata=InformationMetadataInput.load(data.get("metadata")), # type: ignore[arg-type]
|
|
261
|
-
properties=InformationPropertyInput.load(data.get("properties")), # type: ignore[arg-type]
|
|
262
|
-
classes=InformationClassInput.load(data.get("classes")), # type: ignore[arg-type]
|
|
263
|
-
prefixes=data.get("prefixes"),
|
|
264
|
-
last=InformationRulesInput.load(data.get("last")),
|
|
265
|
-
reference=InformationRulesInput.load(data.get("reference")),
|
|
266
|
-
)
|
|
267
|
-
|
|
268
|
-
def as_rules(self) -> InformationRules:
|
|
269
|
-
return InformationRules.model_validate(self.dump())
|
|
121
|
+
def _get_verified_cls(cls) -> type[InformationRules]:
|
|
122
|
+
return InformationRules
|
|
270
123
|
|
|
271
124
|
def dump(self) -> dict[str, Any]:
|
|
272
125
|
default_prefix = self.metadata.prefix
|
|
273
126
|
reference: dict[str, Any] | None = None
|
|
274
|
-
if isinstance(self.reference,
|
|
127
|
+
if isinstance(self.reference, InformationInputRules):
|
|
275
128
|
reference = self.reference.dump()
|
|
276
129
|
elif isinstance(self.reference, InformationRules):
|
|
277
130
|
# We need to load through the InformationRulesInput to set the correct default space and version
|
|
278
|
-
reference =
|
|
131
|
+
reference = InformationInputRules.load(self.reference.model_dump()).dump()
|
|
279
132
|
last: dict[str, Any] | None = None
|
|
280
|
-
if isinstance(self.last,
|
|
133
|
+
if isinstance(self.last, InformationInputRules):
|
|
281
134
|
last = self.last.dump()
|
|
282
135
|
elif isinstance(self.last, InformationRules):
|
|
283
136
|
# We need to load through the InformationRulesInput to set the correct default space and version
|
|
284
|
-
last =
|
|
137
|
+
last = InformationInputRules.load(self.last.model_dump()).dump()
|
|
285
138
|
|
|
286
139
|
return dict(
|
|
287
140
|
Metadata=self.metadata.dump(),
|
|
@@ -3,8 +3,8 @@ from collections import Counter
|
|
|
3
3
|
from typing import cast
|
|
4
4
|
|
|
5
5
|
from cognite.neat.issues import IssueList
|
|
6
|
-
from cognite.neat.
|
|
7
|
-
from cognite.neat.rules.models.
|
|
6
|
+
from cognite.neat.issues.errors import NeatValueError, ResourceNotDefinedError
|
|
7
|
+
from cognite.neat.rules.models._base_rules import DataModelType, SchemaCompleteness
|
|
8
8
|
from cognite.neat.rules.models.entities import ClassEntity, EntityTypes, UnknownEntity
|
|
9
9
|
from cognite.neat.utils.rdf_ import get_inheritance_path
|
|
10
10
|
|
|
@@ -58,9 +58,9 @@ class InformationPostValidation:
|
|
|
58
58
|
):
|
|
59
59
|
dangling_classes.add(class_)
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
for class_ in dangling_classes:
|
|
62
62
|
self.issue_list.append(
|
|
63
|
-
|
|
63
|
+
NeatValueError(f"Class {class_} has no properties and is not a parent of any class with properties")
|
|
64
64
|
)
|
|
65
65
|
|
|
66
66
|
def _referenced_parent_classes_exist(self) -> None:
|
|
@@ -70,9 +70,15 @@ class InformationPostValidation:
|
|
|
70
70
|
parents = set(itertools.chain.from_iterable(class_parent_pairs.values()))
|
|
71
71
|
|
|
72
72
|
if undefined_parents := parents.difference(classes):
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
for parent in undefined_parents:
|
|
74
|
+
# Todo: include row and column number
|
|
75
|
+
self.issue_list.append(
|
|
76
|
+
ResourceNotDefinedError[ClassEntity](
|
|
77
|
+
resource_type="class",
|
|
78
|
+
identifier=parent,
|
|
79
|
+
location="Classes sheet",
|
|
80
|
+
)
|
|
81
|
+
)
|
|
76
82
|
|
|
77
83
|
def _referenced_classes_exist(self) -> None:
|
|
78
84
|
# needs to be complete for this validation to pass
|
|
@@ -83,21 +89,27 @@ class InformationPostValidation:
|
|
|
83
89
|
if self.metadata.schema_ == SchemaCompleteness.complete and (
|
|
84
90
|
missing_classes := referred_classes.difference(defined_classes)
|
|
85
91
|
):
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
[
|
|
92
|
+
for class_ in missing_classes:
|
|
93
|
+
self.issue_list.append(
|
|
94
|
+
ResourceNotDefinedError[ClassEntity](
|
|
95
|
+
resource_type="class",
|
|
96
|
+
identifier=class_,
|
|
97
|
+
location="Classes sheet",
|
|
98
|
+
)
|
|
89
99
|
)
|
|
90
|
-
)
|
|
91
100
|
|
|
92
101
|
# USE CASE: models are extended (user + last = complete)
|
|
93
102
|
if self.metadata.schema_ == SchemaCompleteness.extended:
|
|
94
103
|
defined_classes |= {class_.class_ for class_ in cast(InformationRules, self.rules.last).classes}
|
|
95
104
|
if missing_classes := referred_classes.difference(defined_classes):
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
[
|
|
105
|
+
for class_ in missing_classes:
|
|
106
|
+
self.issue_list.append(
|
|
107
|
+
ResourceNotDefinedError[ClassEntity](
|
|
108
|
+
resource_type="class",
|
|
109
|
+
identifier=class_,
|
|
110
|
+
location="Classes sheet",
|
|
111
|
+
)
|
|
99
112
|
)
|
|
100
|
-
)
|
|
101
113
|
|
|
102
114
|
def _referenced_value_types_exist(self) -> None:
|
|
103
115
|
# adding UnknownEntity to the set of defined classes to handle the case where a property references an unknown
|
|
@@ -112,21 +124,29 @@ class InformationPostValidation:
|
|
|
112
124
|
if self.metadata.schema_ == SchemaCompleteness.complete and (
|
|
113
125
|
missing_value_types := referred_object_types.difference(defined_classes)
|
|
114
126
|
):
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
127
|
+
# Todo: include row and column number
|
|
128
|
+
for missing in missing_value_types:
|
|
129
|
+
self.issue_list.append(
|
|
130
|
+
ResourceNotDefinedError[ClassEntity](
|
|
131
|
+
resource_type="class",
|
|
132
|
+
identifier=cast(ClassEntity, missing),
|
|
133
|
+
location="Classes sheet",
|
|
134
|
+
)
|
|
118
135
|
)
|
|
119
|
-
)
|
|
120
136
|
|
|
121
137
|
# USE CASE: models are extended (user + last = complete)
|
|
122
138
|
if self.metadata.schema_ == SchemaCompleteness.extended:
|
|
123
139
|
defined_classes |= {class_.class_ for class_ in cast(InformationRules, self.rules.last).classes}
|
|
124
140
|
if missing_value_types := referred_object_types.difference(defined_classes):
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
141
|
+
# Todo: include row and column number
|
|
142
|
+
for missing in missing_value_types:
|
|
143
|
+
self.issue_list.append(
|
|
144
|
+
ResourceNotDefinedError(
|
|
145
|
+
resource_type="class",
|
|
146
|
+
identifier=cast(ClassEntity, missing),
|
|
147
|
+
location="Classes sheet",
|
|
148
|
+
)
|
|
128
149
|
)
|
|
129
|
-
)
|
|
130
150
|
|
|
131
151
|
def _class_parent_pairs(self) -> dict[ClassEntity, list[ClassEntity]]:
|
|
132
152
|
class_subclass_pairs: dict[ClassEntity, list[ClassEntity]] = {}
|
|
@@ -173,7 +193,10 @@ class InformationPostValidation:
|
|
|
173
193
|
reused_namespaces = [value for value, count in Counter(prefixes.values()).items() if count > 1]
|
|
174
194
|
impacted_prefixes = [key for key, value in prefixes.items() if value in reused_namespaces]
|
|
175
195
|
self.issue_list.append(
|
|
176
|
-
|
|
177
|
-
prefixes
|
|
196
|
+
NeatValueError(
|
|
197
|
+
"Namespace collision detected. The following prefixes "
|
|
198
|
+
f"are assigned to the same namespace: {impacted_prefixes}"
|
|
199
|
+
f"\nImpacted namespaces: {reused_namespaces}"
|
|
200
|
+
"\nMake sure that each unique namespace is assigned to a unique prefix"
|
|
178
201
|
)
|
|
179
202
|
)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from ._base import RulesPipeline, RulesTransformer
|
|
2
|
+
from ._converters import AssetToInformation, ConvertToRules, DMSToInformation, InformationToAsset, InformationToDMS
|
|
3
|
+
from ._map_onto import MapOneToOne
|
|
4
|
+
from ._pipelines import ImporterPipeline
|
|
5
|
+
from ._verification import VerifyAnyRules, VerifyAssetRules, VerifyDMSRules, VerifyInformationRules
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"ImporterPipeline",
|
|
9
|
+
"RulesTransformer",
|
|
10
|
+
"RulesPipeline",
|
|
11
|
+
"InformationToDMS",
|
|
12
|
+
"InformationToAsset",
|
|
13
|
+
"ConvertToRules",
|
|
14
|
+
"AssetToInformation",
|
|
15
|
+
"DMSToInformation",
|
|
16
|
+
"VerifyAssetRules",
|
|
17
|
+
"VerifyDMSRules",
|
|
18
|
+
"VerifyInformationRules",
|
|
19
|
+
"VerifyAnyRules",
|
|
20
|
+
"MapOneToOne",
|
|
21
|
+
]
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from collections.abc import MutableSequence
|
|
3
|
+
from typing import Generic, TypeVar
|
|
4
|
+
|
|
5
|
+
from cognite.neat.issues import IssueList, NeatError
|
|
6
|
+
from cognite.neat.issues.errors import NeatTypeError, NeatValueError
|
|
7
|
+
from cognite.neat.rules._shared import (
|
|
8
|
+
InputRules,
|
|
9
|
+
JustRules,
|
|
10
|
+
MaybeRules,
|
|
11
|
+
OutRules,
|
|
12
|
+
Rules,
|
|
13
|
+
VerifiedRules,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
T_RulesIn = TypeVar("T_RulesIn", bound=Rules)
|
|
17
|
+
T_RulesOut = TypeVar("T_RulesOut", bound=Rules)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class RulesTransformer(ABC, Generic[T_RulesIn, T_RulesOut]):
|
|
21
|
+
"""This is the base class for all rule transformers.
|
|
22
|
+
|
|
23
|
+
Note transformers follow the functional pattern Monad
|
|
24
|
+
https://en.wikipedia.org/wiki/Monad_(functional_programming)
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def transform(self, rules: T_RulesIn | OutRules[T_RulesIn]) -> OutRules[T_RulesOut]:
|
|
29
|
+
"""Transform the input rules into the output rules."""
|
|
30
|
+
raise NotImplementedError()
|
|
31
|
+
|
|
32
|
+
def try_transform(self, rules: MaybeRules[T_RulesIn]) -> MaybeRules[T_RulesOut]:
|
|
33
|
+
"""Try to transform the input rules into the output rules."""
|
|
34
|
+
try:
|
|
35
|
+
result = self.transform(rules)
|
|
36
|
+
except NeatError:
|
|
37
|
+
# Any error caught during transformation will be returned as issues
|
|
38
|
+
return MaybeRules(None, rules.issues)
|
|
39
|
+
issues = IssueList(rules.issues, title=rules.issues.title)
|
|
40
|
+
if isinstance(result, MaybeRules):
|
|
41
|
+
issues.extend(result.issues)
|
|
42
|
+
return MaybeRules(result.get_rules(), issues)
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def _to_rules(cls, rules: T_RulesIn | OutRules[T_RulesIn]) -> T_RulesIn:
|
|
46
|
+
if isinstance(rules, JustRules):
|
|
47
|
+
return rules.rules
|
|
48
|
+
elif isinstance(rules, MaybeRules):
|
|
49
|
+
if rules.rules is None:
|
|
50
|
+
raise NeatValueError("Rules is missing cannot convert")
|
|
51
|
+
return rules.rules
|
|
52
|
+
elif isinstance(rules, VerifiedRules | InputRules):
|
|
53
|
+
return rules # type: ignore[return-value]
|
|
54
|
+
else:
|
|
55
|
+
raise NeatTypeError(f"Unsupported type: {type(rules)}")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class RulesPipeline(list, MutableSequence[RulesTransformer], Generic[T_RulesIn, T_RulesOut]):
|
|
59
|
+
def transform(self, rules: T_RulesIn | OutRules[T_RulesIn]) -> OutRules[T_RulesOut]:
|
|
60
|
+
"""Transform the input rules into the output rules."""
|
|
61
|
+
for transformer in self:
|
|
62
|
+
rules = transformer.transform(rules)
|
|
63
|
+
return rules # type: ignore[return-value]
|
|
64
|
+
|
|
65
|
+
def try_transform(self, rules: MaybeRules[T_RulesIn]) -> MaybeRules[T_RulesOut]:
|
|
66
|
+
"""Try to transform the input rules into the output rules."""
|
|
67
|
+
for transformer in self:
|
|
68
|
+
rules = transformer.try_transform(rules)
|
|
69
|
+
return rules # type: ignore[return-value]
|
|
70
|
+
|
|
71
|
+
def run(self, rules: T_RulesIn | OutRules[T_RulesIn]) -> T_RulesOut:
|
|
72
|
+
"""Run the pipeline from the input rules to the output rules."""
|
|
73
|
+
output = self.transform(rules)
|
|
74
|
+
if isinstance(output, MaybeRules):
|
|
75
|
+
if output.rules is None:
|
|
76
|
+
raise NeatValueError(f"Rule transformation failed: {output.issues}")
|
|
77
|
+
return output.rules
|
|
78
|
+
elif isinstance(output, JustRules):
|
|
79
|
+
return output.rules
|
|
80
|
+
else:
|
|
81
|
+
raise NeatTypeError(f"Rule transformation failed: {output}")
|