cognite-neat 0.88.3__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/extractors/_mock_graph_generator.py +2 -1
- cognite/neat/issues/_base.py +2 -1
- cognite/neat/issues/errors/__init__.py +2 -1
- cognite/neat/issues/errors/_general.py +7 -0
- cognite/neat/issues/warnings/_models.py +1 -1
- cognite/neat/issues/warnings/user_modeling.py +1 -1
- cognite/neat/rules/_shared.py +49 -6
- cognite/neat/rules/analysis/_base.py +1 -1
- cognite/neat/rules/exporters/_base.py +7 -18
- cognite/neat/rules/exporters/_rules2dms.py +8 -18
- cognite/neat/rules/exporters/_rules2excel.py +5 -12
- cognite/neat/rules/exporters/_rules2ontology.py +9 -19
- cognite/neat/rules/exporters/_rules2yaml.py +3 -6
- cognite/neat/rules/importers/_base.py +7 -52
- cognite/neat/rules/importers/_dms2rules.py +171 -115
- cognite/neat/rules/importers/_dtdl2rules/dtdl_converter.py +26 -18
- cognite/neat/rules/importers/_dtdl2rules/dtdl_importer.py +14 -30
- cognite/neat/rules/importers/_rdf/_imf2rules/_imf2classes.py +7 -3
- 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 +9 -18
- cognite/neat/rules/importers/_rdf/_inference2rules.py +10 -33
- cognite/neat/rules/importers/_rdf/_owl2rules/_owl2rules.py +9 -20
- cognite/neat/rules/importers/_rdf/_shared.py +1 -1
- cognite/neat/rules/importers/_spreadsheet2rules.py +22 -86
- cognite/neat/rules/importers/_yaml2rules.py +14 -41
- 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/asset/__init__.py +5 -2
- cognite/neat/rules/models/asset/_rules.py +2 -20
- cognite/neat/rules/models/asset/_rules_input.py +40 -115
- cognite/neat/rules/models/asset/_validation.py +1 -1
- 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 +72 -26
- cognite/neat/rules/models/dms/_rules.py +42 -155
- cognite/neat/rules/models/dms/_rules_input.py +186 -254
- cognite/neat/rules/models/dms/_serializer.py +44 -3
- cognite/neat/rules/models/dms/_validation.py +3 -4
- 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 +3 -14
- cognite/neat/rules/models/information/_rules_input.py +57 -204
- cognite/neat/rules/models/information/_validation.py +1 -1
- cognite/neat/rules/transformers/__init__.py +21 -0
- cognite/neat/rules/transformers/_base.py +69 -3
- cognite/neat/rules/{models/information/_converter.py → transformers/_converters.py} +216 -20
- 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/store/_provenance.py +10 -1
- cognite/neat/utils/cdf/data_classes.py +20 -0
- cognite/neat/utils/regex_patterns.py +6 -0
- cognite/neat/workflows/steps/lib/current/rules_exporter.py +106 -37
- cognite/neat/workflows/steps/lib/current/rules_importer.py +24 -22
- {cognite_neat-0.88.3.dist-info → cognite_neat-0.89.0.dist-info}/METADATA +1 -1
- {cognite_neat-0.88.3.dist-info → cognite_neat-0.89.0.dist-info}/RECORD +71 -66
- cognite/neat/rules/models/_constants.py +0 -2
- 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 -143
- /cognite/neat/rules/models/{_types/_field.py → _types.py} +0 -0
- {cognite_neat-0.88.3.dist-info → cognite_neat-0.89.0.dist-info}/LICENSE +0 -0
- {cognite_neat-0.88.3.dist-info → cognite_neat-0.89.0.dist-info}/WHEEL +0 -0
- {cognite_neat-0.88.3.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(),
|
|
@@ -4,7 +4,7 @@ from typing import cast
|
|
|
4
4
|
|
|
5
5
|
from cognite.neat.issues import IssueList
|
|
6
6
|
from cognite.neat.issues.errors import NeatValueError, ResourceNotDefinedError
|
|
7
|
-
from cognite.neat.rules.models.
|
|
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
|
|
|
@@ -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
|
+
]
|
|
@@ -1,15 +1,81 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
+
from collections.abc import MutableSequence
|
|
2
3
|
from typing import Generic, TypeVar
|
|
3
4
|
|
|
4
|
-
from cognite.neat.
|
|
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
|
+
)
|
|
5
15
|
|
|
6
16
|
T_RulesIn = TypeVar("T_RulesIn", bound=Rules)
|
|
7
17
|
T_RulesOut = TypeVar("T_RulesOut", bound=Rules)
|
|
8
18
|
|
|
9
19
|
|
|
10
20
|
class RulesTransformer(ABC, Generic[T_RulesIn, T_RulesOut]):
|
|
11
|
-
"""This is the base class for all rule transformers.
|
|
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
|
+
"""
|
|
12
26
|
|
|
13
27
|
@abstractmethod
|
|
14
|
-
def transform(self, rules: T_RulesIn) -> T_RulesOut:
|
|
28
|
+
def transform(self, rules: T_RulesIn | OutRules[T_RulesIn]) -> OutRules[T_RulesOut]:
|
|
29
|
+
"""Transform the input rules into the output rules."""
|
|
15
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}")
|