cognite-neat 0.81.12__py3-none-any.whl → 0.82.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/_version.py +1 -1
- cognite/neat/graph/extractors/_mock_graph_generator.py +2 -8
- cognite/neat/graph/loaders/_rdf2dms.py +1 -1
- cognite/neat/graph/queries/__init__.py +3 -0
- cognite/neat/graph/queries/_base.py +99 -0
- cognite/neat/graph/queries/_construct.py +185 -0
- cognite/neat/graph/queries/_shared.py +159 -0
- cognite/neat/graph/stores/_base.py +24 -87
- cognite/neat/rules/_shared.py +2 -2
- cognite/neat/rules/analysis/_information_rules.py +34 -58
- cognite/neat/rules/importers/_inference2rules.py +5 -1
- cognite/neat/rules/importers/_spreadsheet2rules.py +6 -1
- cognite/neat/rules/models/__init__.py +4 -1
- cognite/neat/rules/models/_base.py +1 -0
- cognite/neat/rules/models/asset/__init__.py +10 -0
- cognite/neat/rules/models/asset/_converter.py +4 -0
- cognite/neat/rules/models/asset/_rules.py +156 -0
- cognite/neat/rules/models/asset/_rules_input.py +171 -0
- cognite/neat/rules/models/asset/_serializer.py +73 -0
- cognite/neat/rules/models/asset/_validation.py +4 -0
- cognite/neat/rules/models/entities.py +56 -1
- cognite/neat/rules/models/information/_rules.py +5 -0
- cognite/neat/rules/models/information/_rules_input.py +3 -6
- cognite/neat/utils/utils.py +6 -1
- cognite/neat/workflows/steps/data_contracts.py +5 -2
- {cognite_neat-0.81.12.dist-info → cognite_neat-0.82.1.dist-info}/METADATA +1 -1
- {cognite_neat-0.81.12.dist-info → cognite_neat-0.82.1.dist-info}/RECORD +30 -20
- {cognite_neat-0.81.12.dist-info → cognite_neat-0.82.1.dist-info}/LICENSE +0 -0
- {cognite_neat-0.81.12.dist-info → cognite_neat-0.82.1.dist-info}/WHEEL +0 -0
- {cognite_neat-0.81.12.dist-info → cognite_neat-0.82.1.dist-info}/entry_points.txt +0 -0
|
@@ -2,7 +2,7 @@ import itertools
|
|
|
2
2
|
import logging
|
|
3
3
|
import warnings
|
|
4
4
|
from collections import defaultdict
|
|
5
|
-
from typing import Any
|
|
5
|
+
from typing import Any
|
|
6
6
|
|
|
7
7
|
import pandas as pd
|
|
8
8
|
from pydantic import ValidationError
|
|
@@ -17,24 +17,11 @@ from ._base import BaseAnalysis
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class InformationArchitectRulesAnalysis(BaseAnalysis):
|
|
20
|
+
"""Assumes analysis over only the complete schema"""
|
|
21
|
+
|
|
20
22
|
def __init__(self, rules: InformationRules):
|
|
21
23
|
self.rules = rules
|
|
22
24
|
|
|
23
|
-
@property
|
|
24
|
-
def referred_classes(self) -> set[ClassEntity]:
|
|
25
|
-
return self.directly_referred_classes.union(self.inherited_referred_classes)
|
|
26
|
-
|
|
27
|
-
@property
|
|
28
|
-
def referred_classes_properties(self) -> list[InformationProperty]:
|
|
29
|
-
referred_classes_properties = []
|
|
30
|
-
class_properties_dict = self.classes_with_properties(use_reference=True)
|
|
31
|
-
|
|
32
|
-
for class_ in self.referred_classes:
|
|
33
|
-
if class_ in class_properties_dict:
|
|
34
|
-
referred_classes_properties.extend(class_properties_dict[class_])
|
|
35
|
-
|
|
36
|
-
return referred_classes_properties
|
|
37
|
-
|
|
38
25
|
@property
|
|
39
26
|
def directly_referred_classes(self) -> set[ClassEntity]:
|
|
40
27
|
return {
|
|
@@ -51,19 +38,17 @@ class InformationArchitectRulesAnalysis(BaseAnalysis):
|
|
|
51
38
|
dir_referred_classes = self.directly_referred_classes
|
|
52
39
|
inherited_referred_classes = []
|
|
53
40
|
for class_ in dir_referred_classes:
|
|
54
|
-
inherited_referred_classes.extend(self.class_inheritance_path(class_
|
|
41
|
+
inherited_referred_classes.extend(self.class_inheritance_path(class_))
|
|
55
42
|
return set(inherited_referred_classes)
|
|
56
43
|
|
|
57
|
-
def class_parent_pairs(self
|
|
44
|
+
def class_parent_pairs(self) -> dict[ClassEntity, list[ParentClassEntity]]:
|
|
58
45
|
"""This only returns class - parent pairs only if parent is in the same data model"""
|
|
59
46
|
class_subclass_pairs: dict[ClassEntity, list[ParentClassEntity]] = {}
|
|
60
47
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if not rules:
|
|
48
|
+
if not self.rules:
|
|
64
49
|
return class_subclass_pairs
|
|
65
50
|
|
|
66
|
-
for definition in rules.classes:
|
|
51
|
+
for definition in self.rules.classes:
|
|
67
52
|
class_subclass_pairs[definition.class_] = []
|
|
68
53
|
|
|
69
54
|
if definition.parent is None:
|
|
@@ -81,13 +66,12 @@ class InformationArchitectRulesAnalysis(BaseAnalysis):
|
|
|
81
66
|
return class_subclass_pairs
|
|
82
67
|
|
|
83
68
|
def classes_with_properties(
|
|
84
|
-
self, consider_inheritance: bool = False
|
|
69
|
+
self, consider_inheritance: bool = False
|
|
85
70
|
) -> dict[ClassEntity, list[InformationProperty]]:
|
|
86
71
|
"""Returns classes that have been defined in the data model.
|
|
87
72
|
|
|
88
73
|
Args:
|
|
89
74
|
consider_inheritance: Whether to consider inheritance or not. Defaults False
|
|
90
|
-
use_reference: Whether to use reference rules or not. Defaults False
|
|
91
75
|
|
|
92
76
|
Returns:
|
|
93
77
|
Dictionary of classes with a list of properties defined for them
|
|
@@ -103,21 +87,19 @@ class InformationArchitectRulesAnalysis(BaseAnalysis):
|
|
|
103
87
|
|
|
104
88
|
class_property_pairs: dict[ClassEntity, list[InformationProperty]] = defaultdict(list)
|
|
105
89
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
for property_ in rules.properties:
|
|
90
|
+
for property_ in self.rules.properties:
|
|
109
91
|
class_property_pairs[property_.class_].append(property_)
|
|
110
92
|
|
|
111
93
|
if consider_inheritance:
|
|
112
|
-
class_parent_pairs = self.class_parent_pairs(
|
|
94
|
+
class_parent_pairs = self.class_parent_pairs()
|
|
113
95
|
for class_ in class_parent_pairs:
|
|
114
96
|
self._add_inherited_properties(class_, class_property_pairs, class_parent_pairs)
|
|
115
97
|
|
|
116
98
|
return class_property_pairs
|
|
117
99
|
|
|
118
|
-
def class_inheritance_path(self, class_: ClassEntity | str
|
|
100
|
+
def class_inheritance_path(self, class_: ClassEntity | str) -> list[ClassEntity]:
|
|
119
101
|
class_ = class_ if isinstance(class_, ClassEntity) else ClassEntity.load(class_)
|
|
120
|
-
class_parent_pairs = self.class_parent_pairs(
|
|
102
|
+
class_parent_pairs = self.class_parent_pairs()
|
|
121
103
|
return get_inheritance_path(class_, class_parent_pairs)
|
|
122
104
|
|
|
123
105
|
@classmethod
|
|
@@ -133,8 +115,13 @@ class InformationArchitectRulesAnalysis(BaseAnalysis):
|
|
|
133
115
|
if parent.as_class_entity() in class_property_pairs:
|
|
134
116
|
for property_ in class_property_pairs[parent.as_class_entity()]:
|
|
135
117
|
property_ = property_.model_copy()
|
|
136
|
-
|
|
118
|
+
|
|
119
|
+
# This corresponds to importing properties from parent class
|
|
120
|
+
# making sure that the property is attached to desired child class
|
|
137
121
|
property_.class_ = class_
|
|
122
|
+
property_.inherited = True
|
|
123
|
+
|
|
124
|
+
# need same if we have RDF path to make sure that the starting class is the
|
|
138
125
|
|
|
139
126
|
if class_ in class_property_pairs:
|
|
140
127
|
class_property_pairs[class_].append(property_)
|
|
@@ -142,14 +129,13 @@ class InformationArchitectRulesAnalysis(BaseAnalysis):
|
|
|
142
129
|
class_property_pairs[class_] = [property_]
|
|
143
130
|
|
|
144
131
|
def class_property_pairs(
|
|
145
|
-
self, only_rdfpath: bool = False, consider_inheritance: bool = False
|
|
132
|
+
self, only_rdfpath: bool = False, consider_inheritance: bool = False
|
|
146
133
|
) -> dict[ClassEntity, dict[str, InformationProperty]]:
|
|
147
134
|
"""Returns a dictionary of classes with a dictionary of properties associated with them.
|
|
148
135
|
|
|
149
136
|
Args:
|
|
150
137
|
only_rdfpath : To consider only properties which have rule `rdfpath` set. Defaults False
|
|
151
138
|
consider_inheritance: Whether to consider inheritance or not. Defaults False
|
|
152
|
-
use_reference : Whether to use reference rules or not. Defaults False
|
|
153
139
|
|
|
154
140
|
Returns:
|
|
155
141
|
Dictionary of classes with a dictionary of properties associated with them.
|
|
@@ -178,7 +164,7 @@ class InformationArchitectRulesAnalysis(BaseAnalysis):
|
|
|
178
164
|
|
|
179
165
|
class_property_pairs = {}
|
|
180
166
|
|
|
181
|
-
for class_, properties in self.classes_with_properties(consider_inheritance
|
|
167
|
+
for class_, properties in self.classes_with_properties(consider_inheritance).items():
|
|
182
168
|
processed_properties = {}
|
|
183
169
|
for property_ in properties:
|
|
184
170
|
if property_.property_ in processed_properties:
|
|
@@ -197,12 +183,11 @@ class InformationArchitectRulesAnalysis(BaseAnalysis):
|
|
|
197
183
|
|
|
198
184
|
return class_property_pairs
|
|
199
185
|
|
|
200
|
-
def class_linkage(self, consider_inheritance: bool = False
|
|
186
|
+
def class_linkage(self, consider_inheritance: bool = False) -> pd.DataFrame:
|
|
201
187
|
"""Returns a dataframe with the class linkage of the data model.
|
|
202
188
|
|
|
203
189
|
Args:
|
|
204
190
|
consider_inheritance: Whether to consider inheritance or not. Defaults False
|
|
205
|
-
use_reference: Whether to use reference rules or not. Defaults False
|
|
206
191
|
|
|
207
192
|
Returns:
|
|
208
193
|
Dataframe with the class linkage of the data model
|
|
@@ -210,7 +195,7 @@ class InformationArchitectRulesAnalysis(BaseAnalysis):
|
|
|
210
195
|
|
|
211
196
|
class_linkage = pd.DataFrame(columns=["source_class", "target_class", "connecting_property", "max_occurrence"])
|
|
212
197
|
|
|
213
|
-
class_property_pairs = self.classes_with_properties(consider_inheritance
|
|
198
|
+
class_property_pairs = self.classes_with_properties(consider_inheritance)
|
|
214
199
|
properties = list(itertools.chain.from_iterable(class_property_pairs.values()))
|
|
215
200
|
|
|
216
201
|
for property_ in properties:
|
|
@@ -230,56 +215,50 @@ class InformationArchitectRulesAnalysis(BaseAnalysis):
|
|
|
230
215
|
|
|
231
216
|
return class_linkage
|
|
232
217
|
|
|
233
|
-
def connected_classes(self, consider_inheritance: bool = False
|
|
218
|
+
def connected_classes(self, consider_inheritance: bool = False) -> set[ClassEntity]:
|
|
234
219
|
"""Return a set of classes that are connected to other classes.
|
|
235
220
|
|
|
236
221
|
Args:
|
|
237
222
|
consider_inheritance: Whether to consider inheritance or not. Defaults False
|
|
238
|
-
use_reference: Whether to use reference rules or not. Defaults False
|
|
239
223
|
|
|
240
224
|
Returns:
|
|
241
225
|
Set of classes that are connected to other classes
|
|
242
226
|
"""
|
|
243
|
-
class_linkage = self.class_linkage(consider_inheritance
|
|
227
|
+
class_linkage = self.class_linkage(consider_inheritance)
|
|
244
228
|
return set(class_linkage.source_class.values).union(set(class_linkage.target_class.values))
|
|
245
229
|
|
|
246
|
-
def defined_classes(self, consider_inheritance: bool = False
|
|
230
|
+
def defined_classes(self, consider_inheritance: bool = False) -> set[ClassEntity]:
|
|
247
231
|
"""Returns classes that have properties defined for them in the data model.
|
|
248
232
|
|
|
249
233
|
Args:
|
|
250
234
|
consider_inheritance: Whether to consider inheritance or not. Defaults False
|
|
251
|
-
use_reference: Whether to use reference rules or not. Defaults False
|
|
252
235
|
|
|
253
236
|
Returns:
|
|
254
237
|
Set of classes that have been defined in the data model
|
|
255
238
|
"""
|
|
256
|
-
class_property_pairs = self.classes_with_properties(consider_inheritance
|
|
239
|
+
class_property_pairs = self.classes_with_properties(consider_inheritance)
|
|
257
240
|
properties = list(itertools.chain.from_iterable(class_property_pairs.values()))
|
|
258
241
|
|
|
259
242
|
return {property.class_ for property in properties}
|
|
260
243
|
|
|
261
|
-
def disconnected_classes(self, consider_inheritance: bool = False
|
|
244
|
+
def disconnected_classes(self, consider_inheritance: bool = False) -> set[ClassEntity]:
|
|
262
245
|
"""Return a set of classes that are disconnected (i.e. isolated) from other classes.
|
|
263
246
|
|
|
264
247
|
Args:
|
|
265
248
|
consider_inheritance: Whether to consider inheritance or not. Defaults False
|
|
266
|
-
use_reference: Whether to use reference rules or not. Defaults False
|
|
267
249
|
|
|
268
250
|
Returns:
|
|
269
251
|
Set of classes that are disconnected from other classes
|
|
270
252
|
"""
|
|
271
|
-
return self.defined_classes(consider_inheritance
|
|
272
|
-
consider_inheritance, use_reference
|
|
273
|
-
)
|
|
253
|
+
return self.defined_classes(consider_inheritance) - self.connected_classes(consider_inheritance)
|
|
274
254
|
|
|
275
255
|
def symmetrically_connected_classes(
|
|
276
|
-
self, consider_inheritance: bool = False
|
|
256
|
+
self, consider_inheritance: bool = False
|
|
277
257
|
) -> set[tuple[ClassEntity, ClassEntity]]:
|
|
278
258
|
"""Returns a set of pairs of symmetrically linked classes.
|
|
279
259
|
|
|
280
260
|
Args:
|
|
281
261
|
consider_inheritance: Whether to consider inheritance or not. Defaults False
|
|
282
|
-
use_reference: Whether to use reference rules or not. Defaults False
|
|
283
262
|
|
|
284
263
|
Returns:
|
|
285
264
|
Set of pairs of symmetrically linked classes
|
|
@@ -293,7 +272,7 @@ class InformationArchitectRulesAnalysis(BaseAnalysis):
|
|
|
293
272
|
# TODO: Find better name for this method
|
|
294
273
|
sym_pairs: set[tuple[ClassEntity, ClassEntity]] = set()
|
|
295
274
|
|
|
296
|
-
class_linkage = self.class_linkage(consider_inheritance
|
|
275
|
+
class_linkage = self.class_linkage(consider_inheritance)
|
|
297
276
|
if class_linkage.empty:
|
|
298
277
|
return sym_pairs
|
|
299
278
|
|
|
@@ -321,13 +300,12 @@ class InformationArchitectRulesAnalysis(BaseAnalysis):
|
|
|
321
300
|
class_dict[str(definition.class_.suffix)] = definition
|
|
322
301
|
return class_dict
|
|
323
302
|
|
|
324
|
-
def subset_rules(self, desired_classes: set[ClassEntity]
|
|
303
|
+
def subset_rules(self, desired_classes: set[ClassEntity]) -> InformationRules:
|
|
325
304
|
"""
|
|
326
305
|
Subset rules to only include desired classes and their properties.
|
|
327
306
|
|
|
328
307
|
Args:
|
|
329
308
|
desired_classes: Desired classes to include in the reduced data model
|
|
330
|
-
use_reference: Whether to use reference rules or not. Defaults False
|
|
331
309
|
|
|
332
310
|
Returns:
|
|
333
311
|
Instance of InformationRules
|
|
@@ -350,9 +328,7 @@ class InformationArchitectRulesAnalysis(BaseAnalysis):
|
|
|
350
328
|
only with base Pydantic validators.
|
|
351
329
|
"""
|
|
352
330
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
if rules.metadata.schema_ is not SchemaCompleteness.complete:
|
|
331
|
+
if self.rules.metadata.schema_ is not SchemaCompleteness.complete:
|
|
356
332
|
raise ValueError("Rules are not complete cannot perform reduction!")
|
|
357
333
|
class_as_dict = self.as_class_dict()
|
|
358
334
|
class_parents_pairs = self.class_parent_pairs()
|
|
@@ -380,8 +356,8 @@ class InformationArchitectRulesAnalysis(BaseAnalysis):
|
|
|
380
356
|
)
|
|
381
357
|
|
|
382
358
|
reduced_data_model: dict[str, Any] = {
|
|
383
|
-
"metadata": rules.metadata.model_copy(),
|
|
384
|
-
"prefixes": (rules.prefixes or {}).copy(),
|
|
359
|
+
"metadata": self.rules.metadata.model_copy(),
|
|
360
|
+
"prefixes": (self.rules.prefixes or {}).copy(),
|
|
385
361
|
"classes": [],
|
|
386
362
|
"properties": [],
|
|
387
363
|
}
|
|
@@ -17,7 +17,7 @@ from cognite.neat.rules.models.information import (
|
|
|
17
17
|
InformationMetadata,
|
|
18
18
|
InformationRulesInput,
|
|
19
19
|
)
|
|
20
|
-
from cognite.neat.utils.utils import get_namespace, remove_namespace
|
|
20
|
+
from cognite.neat.utils.utils import get_namespace, remove_namespace, uri_to_short_form
|
|
21
21
|
|
|
22
22
|
ORDERED_CLASSES_QUERY = """SELECT ?class (count(?s) as ?instances )
|
|
23
23
|
WHERE { ?s a ?class . }
|
|
@@ -176,6 +176,10 @@ class InferenceImporter(BaseImporter):
|
|
|
176
176
|
"max_count": cast(RdfLiteral, occurrence).value,
|
|
177
177
|
"value_type": value_type_id,
|
|
178
178
|
"reference": property_uri,
|
|
179
|
+
"transformation": (
|
|
180
|
+
f"{uri_to_short_form(class_definition['reference'], prefixes)}"
|
|
181
|
+
f"({uri_to_short_form(cast(URIRef, property_uri), prefixes)})"
|
|
182
|
+
),
|
|
179
183
|
"comment": (
|
|
180
184
|
f"Class <{class_id}> has property <{property_id}> with "
|
|
181
185
|
f"value type <{value_type_id}> which occurs <1> times in the graph"
|
|
@@ -15,12 +15,14 @@ from cognite.neat.rules import issues
|
|
|
15
15
|
from cognite.neat.rules.issues import IssueList
|
|
16
16
|
from cognite.neat.rules.models import (
|
|
17
17
|
RULES_PER_ROLE,
|
|
18
|
+
AssetRules,
|
|
18
19
|
DMSRules,
|
|
19
20
|
DomainRules,
|
|
20
21
|
InformationRules,
|
|
21
22
|
RoleTypes,
|
|
22
23
|
SchemaCompleteness,
|
|
23
24
|
)
|
|
25
|
+
from cognite.neat.rules.models.asset import AssetRulesInput
|
|
24
26
|
from cognite.neat.rules.models.dms import DMSRulesInput
|
|
25
27
|
from cognite.neat.rules.models.information import InformationRulesInput
|
|
26
28
|
from cognite.neat.utils.auxiliary import local_import
|
|
@@ -35,6 +37,7 @@ SOURCE_SHEET__TARGET_FIELD__HEADERS = [
|
|
|
35
37
|
{
|
|
36
38
|
RoleTypes.domain_expert: "Property",
|
|
37
39
|
RoleTypes.information_architect: "Property",
|
|
40
|
+
RoleTypes.asset_architect: "Property",
|
|
38
41
|
RoleTypes.dms_architect: "View Property",
|
|
39
42
|
},
|
|
40
43
|
),
|
|
@@ -266,6 +269,8 @@ class ExcelImporter(BaseImporter):
|
|
|
266
269
|
rules = DMSRulesInput.load(sheets).as_rules()
|
|
267
270
|
elif rules_cls is InformationRules:
|
|
268
271
|
rules = InformationRulesInput.load(sheets).as_rules()
|
|
272
|
+
elif rules_cls is AssetRules:
|
|
273
|
+
rules = AssetRulesInput.load(sheets).as_rules()
|
|
269
274
|
else:
|
|
270
275
|
rules = rules_cls.model_validate(sheets) # type: ignore[attr-defined]
|
|
271
276
|
|
|
@@ -300,7 +305,7 @@ class GoogleSheetImporter(BaseImporter):
|
|
|
300
305
|
import gspread # type: ignore[import]
|
|
301
306
|
|
|
302
307
|
role = role or RoleTypes.domain_expert
|
|
303
|
-
rules_model = cast(DomainRules | InformationRules | DMSRules, RULES_PER_ROLE[role])
|
|
308
|
+
rules_model = cast(DomainRules | InformationRules | AssetRules | DMSRules, RULES_PER_ROLE[role])
|
|
304
309
|
|
|
305
310
|
client_google = gspread.service_account()
|
|
306
311
|
google_sheet = client_google.open_by_key(self.sheet_id)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from cognite.neat.rules.models.asset import AssetRules
|
|
1
2
|
from cognite.neat.rules.models.domain import DomainRules
|
|
2
3
|
from cognite.neat.rules.models.information._rules import InformationRules
|
|
3
4
|
|
|
@@ -5,9 +6,10 @@ from ._base import DataModelType, ExtensionCategory, RoleTypes, SchemaCompletene
|
|
|
5
6
|
from .dms._rules import DMSRules
|
|
6
7
|
from .dms._schema import DMSSchema
|
|
7
8
|
|
|
8
|
-
RULES_PER_ROLE: dict[RoleTypes, type[DomainRules] | type[InformationRules] | type[DMSRules]] = {
|
|
9
|
+
RULES_PER_ROLE: dict[RoleTypes, type[DomainRules] | type[InformationRules] | type[AssetRules] | type[DMSRules]] = {
|
|
9
10
|
RoleTypes.domain_expert: DomainRules,
|
|
10
11
|
RoleTypes.information_architect: InformationRules,
|
|
12
|
+
RoleTypes.asset_architect: AssetRules,
|
|
11
13
|
RoleTypes.dms_architect: DMSRules,
|
|
12
14
|
}
|
|
13
15
|
|
|
@@ -15,6 +17,7 @@ RULES_PER_ROLE: dict[RoleTypes, type[DomainRules] | type[InformationRules] | typ
|
|
|
15
17
|
__all__ = [
|
|
16
18
|
"DomainRules",
|
|
17
19
|
"InformationRules",
|
|
20
|
+
"AssetRules",
|
|
18
21
|
"DMSRules",
|
|
19
22
|
"RULES_PER_ROLE",
|
|
20
23
|
"DMSSchema",
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Literal, cast
|
|
3
|
+
|
|
4
|
+
from pydantic import Field, field_validator, model_validator
|
|
5
|
+
from pydantic.main import IncEx
|
|
6
|
+
from rdflib import Namespace
|
|
7
|
+
|
|
8
|
+
from cognite.neat.constants import PREFIXES
|
|
9
|
+
from cognite.neat.issues import MultiValueError
|
|
10
|
+
from cognite.neat.rules import issues
|
|
11
|
+
from cognite.neat.rules.models._base import BaseRules, RoleTypes, SheetList
|
|
12
|
+
from cognite.neat.rules.models.domain import DomainRules
|
|
13
|
+
from cognite.neat.rules.models.entities import (
|
|
14
|
+
CdfResourceEntityList,
|
|
15
|
+
ClassEntity,
|
|
16
|
+
MultiValueTypeInfo,
|
|
17
|
+
ParentClassEntity,
|
|
18
|
+
Undefined,
|
|
19
|
+
)
|
|
20
|
+
from cognite.neat.rules.models.information import (
|
|
21
|
+
InformationClass,
|
|
22
|
+
InformationMetadata,
|
|
23
|
+
InformationProperty,
|
|
24
|
+
InformationRules,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from cognite.neat.rules.models.dms._rules import DMSRules
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
if sys.version_info >= (3, 11):
|
|
32
|
+
from typing import Self
|
|
33
|
+
else:
|
|
34
|
+
from typing_extensions import Self
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class AssetMetadata(InformationMetadata):
|
|
38
|
+
role: ClassVar[RoleTypes] = RoleTypes.asset_architect
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class AssetClass(InformationClass): ...
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class AssetProperty(InformationProperty):
|
|
45
|
+
"""
|
|
46
|
+
A property is a characteristic of a class. It is a named attribute of a class that describes a range of values
|
|
47
|
+
or a relationship to another class.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
class_: Class ID to which property belongs
|
|
51
|
+
property_: Property ID of the property
|
|
52
|
+
name: Property name.
|
|
53
|
+
value_type: Type of value property will hold (data or link to another class)
|
|
54
|
+
min_count: Minimum count of the property values. Defaults to 0
|
|
55
|
+
max_count: Maximum count of the property values. Defaults to None
|
|
56
|
+
default: Default value of the property
|
|
57
|
+
reference: Reference to the source of the information, HTTP URI
|
|
58
|
+
match_type: The match type of the resource being described and the source entity.
|
|
59
|
+
transformation: Actual rule for the transformation from source to target representation of
|
|
60
|
+
knowledge graph. Defaults to None (no transformation)
|
|
61
|
+
implementation: Details on how given class-property is implemented in the classic CDF
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
implementation: CdfResourceEntityList | None = Field(alias="Implementation", default=None)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class AssetRules(BaseRules):
|
|
68
|
+
metadata: AssetMetadata = Field(alias="Metadata")
|
|
69
|
+
properties: SheetList[AssetProperty] = Field(alias="Properties")
|
|
70
|
+
classes: SheetList[AssetClass] = Field(alias="Classes")
|
|
71
|
+
prefixes: dict[str, Namespace] = Field(default_factory=lambda: PREFIXES.copy())
|
|
72
|
+
last: "AssetRules | None" = Field(None, alias="Last")
|
|
73
|
+
reference: "AssetRules | None" = Field(None, alias="Reference")
|
|
74
|
+
|
|
75
|
+
@field_validator("prefixes", mode="before")
|
|
76
|
+
def parse_str(cls, values: Any) -> Any:
|
|
77
|
+
if isinstance(values, dict):
|
|
78
|
+
return {key: Namespace(value) if isinstance(value, str) else value for key, value in values.items()}
|
|
79
|
+
return values
|
|
80
|
+
|
|
81
|
+
@model_validator(mode="after")
|
|
82
|
+
def update_entities_prefix(self) -> Self:
|
|
83
|
+
# update expected_value_types
|
|
84
|
+
for property_ in self.properties:
|
|
85
|
+
if isinstance(property_.value_type, ClassEntity) and property_.value_type.prefix is Undefined:
|
|
86
|
+
property_.value_type.prefix = self.metadata.prefix
|
|
87
|
+
|
|
88
|
+
if isinstance(property_.value_type, MultiValueTypeInfo):
|
|
89
|
+
property_.value_type.set_default_prefix(self.metadata.prefix)
|
|
90
|
+
|
|
91
|
+
if property_.class_.prefix is Undefined:
|
|
92
|
+
property_.class_.prefix = self.metadata.prefix
|
|
93
|
+
|
|
94
|
+
# update parent classes
|
|
95
|
+
for class_ in self.classes:
|
|
96
|
+
if class_.parent:
|
|
97
|
+
for parent in cast(list[ParentClassEntity], class_.parent):
|
|
98
|
+
if not isinstance(parent.prefix, str):
|
|
99
|
+
parent.prefix = self.metadata.prefix
|
|
100
|
+
if class_.class_.prefix is Undefined:
|
|
101
|
+
class_.class_.prefix = self.metadata.prefix
|
|
102
|
+
|
|
103
|
+
return self
|
|
104
|
+
|
|
105
|
+
@model_validator(mode="after")
|
|
106
|
+
def post_validation(self) -> "AssetRules":
|
|
107
|
+
from ._validation import AssetPostValidation
|
|
108
|
+
|
|
109
|
+
issue_list = AssetPostValidation(cast(InformationRules, self)).validate()
|
|
110
|
+
if issue_list.warnings:
|
|
111
|
+
issue_list.trigger_warnings()
|
|
112
|
+
if issue_list.has_errors:
|
|
113
|
+
raise MultiValueError([error for error in issue_list if isinstance(error, issues.NeatValidationError)])
|
|
114
|
+
return self
|
|
115
|
+
|
|
116
|
+
def dump(
|
|
117
|
+
self,
|
|
118
|
+
mode: Literal["python", "json"] = "python",
|
|
119
|
+
by_alias: bool = False,
|
|
120
|
+
exclude: IncEx = None,
|
|
121
|
+
exclude_none: bool = False,
|
|
122
|
+
exclude_unset: bool = False,
|
|
123
|
+
exclude_defaults: bool = False,
|
|
124
|
+
as_reference: bool = False,
|
|
125
|
+
) -> dict[str, Any]:
|
|
126
|
+
from ._serializer import _AssetRulesSerializer
|
|
127
|
+
|
|
128
|
+
dumped = self.model_dump(
|
|
129
|
+
mode=mode,
|
|
130
|
+
by_alias=by_alias,
|
|
131
|
+
exclude=exclude,
|
|
132
|
+
exclude_none=exclude_none,
|
|
133
|
+
exclude_unset=exclude_unset,
|
|
134
|
+
exclude_defaults=exclude_defaults,
|
|
135
|
+
)
|
|
136
|
+
prefix = self.metadata.prefix
|
|
137
|
+
serializer = _AssetRulesSerializer(by_alias, prefix)
|
|
138
|
+
cleaned = serializer.clean(dumped, as_reference)
|
|
139
|
+
last = "Last" if by_alias else "last"
|
|
140
|
+
if last_dump := cleaned.get(last):
|
|
141
|
+
cleaned[last] = serializer.clean(last_dump, False)
|
|
142
|
+
reference = "Reference" if by_alias else "reference"
|
|
143
|
+
if self.reference and (ref_dump := cleaned.get(reference)):
|
|
144
|
+
prefix = self.reference.metadata.prefix
|
|
145
|
+
cleaned[reference] = _AssetRulesSerializer(by_alias, prefix).clean(ref_dump, True)
|
|
146
|
+
return cleaned
|
|
147
|
+
|
|
148
|
+
def as_domain_rules(self) -> DomainRules:
|
|
149
|
+
from ._converter import _AssetRulesConverter
|
|
150
|
+
|
|
151
|
+
return _AssetRulesConverter(cast(InformationRules, self)).as_domain_rules()
|
|
152
|
+
|
|
153
|
+
def as_dms_architect_rules(self) -> "DMSRules":
|
|
154
|
+
from ._converter import _AssetRulesConverter
|
|
155
|
+
|
|
156
|
+
return _AssetRulesConverter(cast(InformationRules, self)).as_dms_architect_rules()
|