cognite-neat 0.103.1__py3-none-any.whl → 0.104.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/_graph/extractors/_mock_graph_generator.py +1 -1
- cognite/neat/_graph/transformers/_base.py +109 -1
- cognite/neat/_graph/transformers/_classic_cdf.py +4 -0
- cognite/neat/_graph/transformers/_prune_graph.py +103 -47
- cognite/neat/_graph/transformers/_rdfpath.py +41 -17
- cognite/neat/_graph/transformers/_value_type.py +119 -154
- cognite/neat/_issues/_base.py +35 -8
- cognite/neat/_issues/warnings/_resources.py +1 -1
- cognite/neat/_rules/_shared.py +18 -34
- cognite/neat/_rules/exporters/_base.py +28 -2
- cognite/neat/_rules/exporters/_rules2dms.py +4 -0
- cognite/neat/_rules/exporters/_rules2excel.py +11 -0
- cognite/neat/_rules/exporters/_rules2instance_template.py +4 -0
- cognite/neat/_rules/exporters/_rules2ontology.py +13 -1
- cognite/neat/_rules/exporters/_rules2yaml.py +4 -0
- cognite/neat/_rules/importers/_base.py +9 -0
- cognite/neat/_rules/importers/_dms2rules.py +17 -5
- cognite/neat/_rules/importers/_dtdl2rules/dtdl_importer.py +5 -2
- cognite/neat/_rules/importers/_rdf/_base.py +10 -8
- cognite/neat/_rules/importers/_rdf/_imf2rules.py +4 -0
- cognite/neat/_rules/importers/_rdf/_inference2rules.py +7 -0
- cognite/neat/_rules/importers/_rdf/_owl2rules.py +4 -0
- cognite/neat/_rules/importers/_spreadsheet2rules.py +17 -8
- cognite/neat/_rules/importers/_yaml2rules.py +21 -7
- cognite/neat/_rules/models/_base_input.py +1 -1
- cognite/neat/_rules/models/_base_rules.py +5 -0
- cognite/neat/_rules/models/dms/_rules.py +4 -0
- cognite/neat/_rules/models/dms/_rules_input.py +9 -0
- cognite/neat/_rules/models/information/_rules.py +4 -0
- cognite/neat/_rules/models/information/_rules_input.py +9 -0
- cognite/neat/_rules/models/mapping/_classic2core.py +2 -5
- cognite/neat/_rules/transformers/__init__.py +5 -4
- cognite/neat/_rules/transformers/_base.py +41 -65
- cognite/neat/_rules/transformers/_converters.py +149 -62
- cognite/neat/_rules/transformers/_mapping.py +17 -12
- cognite/neat/_rules/transformers/_verification.py +50 -37
- cognite/neat/_session/_base.py +32 -121
- cognite/neat/_session/_inspect.py +3 -3
- cognite/neat/_session/_mapping.py +17 -105
- cognite/neat/_session/_prepare.py +36 -254
- cognite/neat/_session/_read.py +11 -130
- cognite/neat/_session/_set.py +6 -30
- cognite/neat/_session/_show.py +40 -21
- cognite/neat/_session/_state.py +49 -107
- cognite/neat/_session/_to.py +42 -31
- cognite/neat/_shared.py +23 -2
- cognite/neat/_store/_provenance.py +3 -82
- cognite/neat/_store/_rules_store.py +372 -10
- cognite/neat/_store/exceptions.py +23 -0
- cognite/neat/_utils/graph_transformations_report.py +36 -0
- cognite/neat/_utils/rdf_.py +8 -0
- cognite/neat/_utils/spreadsheet.py +5 -4
- cognite/neat/_version.py +1 -1
- cognite/neat/_workflows/steps/lib/current/rules_exporter.py +7 -7
- cognite/neat/_workflows/steps/lib/current/rules_importer.py +24 -99
- {cognite_neat-0.103.1.dist-info → cognite_neat-0.104.0.dist-info}/METADATA +1 -1
- {cognite_neat-0.103.1.dist-info → cognite_neat-0.104.0.dist-info}/RECORD +60 -59
- cognite/neat/_rules/transformers/_pipelines.py +0 -70
- {cognite_neat-0.103.1.dist-info → cognite_neat-0.104.0.dist-info}/LICENSE +0 -0
- {cognite_neat-0.103.1.dist-info → cognite_neat-0.104.0.dist-info}/WHEEL +0 -0
- {cognite_neat-0.103.1.dist-info → cognite_neat-0.104.0.dist-info}/entry_points.txt +0 -0
|
@@ -5,17 +5,18 @@ from urllib.parse import quote
|
|
|
5
5
|
|
|
6
6
|
import rdflib
|
|
7
7
|
from rdflib import RDF, XSD, Graph, Namespace, URIRef
|
|
8
|
+
from rdflib.query import ResultRow
|
|
8
9
|
|
|
9
10
|
from cognite.neat._constants import UNKNOWN_TYPE
|
|
10
11
|
from cognite.neat._graph.queries import Queries
|
|
11
|
-
from cognite.neat._issues.warnings import
|
|
12
|
+
from cognite.neat._issues.warnings import PropertyDataTypeConversionWarning
|
|
12
13
|
from cognite.neat._utils.auxiliary import string_to_ideal_type
|
|
13
|
-
from cognite.neat._utils.collection_ import iterate_progress_bar
|
|
14
14
|
from cognite.neat._utils.rdf_ import get_namespace, remove_namespace_from_uri
|
|
15
15
|
|
|
16
|
-
from ._base import BaseTransformer
|
|
16
|
+
from ._base import BaseTransformer, BaseTransformerStandardised, RowTransformationOutput
|
|
17
17
|
|
|
18
18
|
|
|
19
|
+
# TODO: Standardise
|
|
19
20
|
class SplitMultiValueProperty(BaseTransformer):
|
|
20
21
|
description: str = (
|
|
21
22
|
"SplitMultiValueProperty is a transformer that splits a "
|
|
@@ -75,34 +76,11 @@ class SplitMultiValueProperty(BaseTransformer):
|
|
|
75
76
|
graph.add((s, new_property, o))
|
|
76
77
|
|
|
77
78
|
|
|
78
|
-
class ConvertLiteral(
|
|
79
|
+
class ConvertLiteral(BaseTransformerStandardised):
|
|
79
80
|
description: str = "ConvertLiteral is a transformer that improve data typing of a literal value."
|
|
80
81
|
_use_only_once: bool = False
|
|
81
82
|
_need_changes = frozenset({})
|
|
82
83
|
|
|
83
|
-
_count_by_properties = """SELECT (COUNT(?value) AS ?valueCount)
|
|
84
|
-
WHERE {{
|
|
85
|
-
?instance a <{subject_type}> .
|
|
86
|
-
?instance <{subject_predicate}> ?value
|
|
87
|
-
FILTER(isLiteral(?value))
|
|
88
|
-
}}"""
|
|
89
|
-
|
|
90
|
-
_count_by_properties_uri = """SELECT (COUNT(?value) AS ?valueCount)
|
|
91
|
-
WHERE {{
|
|
92
|
-
?instance a <{subject_type}> .
|
|
93
|
-
?instance <{subject_predicate}> ?value
|
|
94
|
-
FILTER(isIRI(?value))
|
|
95
|
-
}}"""
|
|
96
|
-
|
|
97
|
-
_properties = """SELECT ?instance ?value
|
|
98
|
-
WHERE {{
|
|
99
|
-
?instance a <{subject_type}> .
|
|
100
|
-
?instance <{subject_predicate}> ?value
|
|
101
|
-
|
|
102
|
-
FILTER(isLiteral(?value))
|
|
103
|
-
|
|
104
|
-
}}"""
|
|
105
|
-
|
|
106
84
|
def __init__(
|
|
107
85
|
self,
|
|
108
86
|
subject_type: URIRef,
|
|
@@ -115,89 +93,55 @@ class ConvertLiteral(BaseTransformer):
|
|
|
115
93
|
self._type_name = remove_namespace_from_uri(subject_type)
|
|
116
94
|
self._property_name = remove_namespace_from_uri(subject_predicate)
|
|
117
95
|
|
|
118
|
-
def
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
96
|
+
def _skip_count_query(self) -> str:
|
|
97
|
+
query = """SELECT (COUNT(?value) AS ?valueCount)
|
|
98
|
+
WHERE {{
|
|
99
|
+
?instance a <{subject_type}> .
|
|
100
|
+
?instance <{subject_predicate}> ?value
|
|
101
|
+
FILTER(isIRI(?value))
|
|
102
|
+
}}"""
|
|
103
|
+
return query.format(subject_type=self.subject_type, subject_predicate=self.subject_predicate)
|
|
104
|
+
|
|
105
|
+
def _count_query(self) -> str:
|
|
106
|
+
query = """SELECT (COUNT(?value) AS ?valueCount)
|
|
107
|
+
WHERE {{
|
|
108
|
+
?instance a <{subject_type}> .
|
|
109
|
+
?instance <{subject_predicate}> ?value
|
|
110
|
+
FILTER(isLiteral(?value))
|
|
111
|
+
}}"""
|
|
112
|
+
return query.format(subject_type=self.subject_type, subject_predicate=self.subject_predicate)
|
|
113
|
+
|
|
114
|
+
def _iterate_query(self) -> str:
|
|
115
|
+
query = """SELECT ?instance ?value
|
|
116
|
+
WHERE {{
|
|
117
|
+
?instance a <{subject_type}> .
|
|
118
|
+
?instance <{subject_predicate}> ?value
|
|
119
|
+
FILTER(isLiteral(?value))
|
|
120
|
+
}}"""
|
|
121
|
+
return query.format(subject_type=self.subject_type, subject_predicate=self.subject_predicate)
|
|
122
|
+
|
|
123
|
+
def operation(self, query_result_row: ResultRow) -> RowTransformationOutput:
|
|
124
|
+
row_output = RowTransformationOutput()
|
|
125
|
+
|
|
126
|
+
instance, literal = query_result_row
|
|
127
|
+
value = cast(rdflib.Literal, literal).toPython()
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
converted_value = self.conversion(value)
|
|
131
|
+
except Exception as e:
|
|
126
132
|
warnings.warn(
|
|
127
|
-
|
|
128
|
-
f"Skipping {connection_count} of {self._type_name}.{self._property_name} "
|
|
129
|
-
f"as these are connections and not data values."
|
|
130
|
-
),
|
|
133
|
+
PropertyDataTypeConversionWarning(str(instance), self._type_name, self._property_name, str(e)),
|
|
131
134
|
stacklevel=2,
|
|
132
135
|
)
|
|
136
|
+
row_output.add_triples.append((instance, self.subject_predicate, rdflib.Literal(converted_value))) # type: ignore[arg-type]
|
|
137
|
+
row_output.remove_triples.append((instance, self.subject_predicate, literal)) # type: ignore[arg-type]
|
|
138
|
+
row_output.instances_modified_count += 1
|
|
139
|
+
|
|
140
|
+
return row_output
|
|
133
141
|
|
|
134
|
-
count_query = self._count_by_properties.format(
|
|
135
|
-
subject_type=self.subject_type, subject_predicate=self.subject_predicate
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
property_count_res = list(graph.query(count_query))
|
|
139
|
-
property_count = int(property_count_res[0][0]) # type: ignore [index, arg-type]
|
|
140
|
-
iterate_query = self._properties.format(
|
|
141
|
-
subject_type=self.subject_type, subject_predicate=self.subject_predicate
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
for instance, literal in iterate_progress_bar( # type: ignore[misc]
|
|
145
|
-
graph.query(iterate_query),
|
|
146
|
-
total=property_count,
|
|
147
|
-
description=f"Converting {self._type_name}.{self._property_name}.",
|
|
148
|
-
):
|
|
149
|
-
value = cast(rdflib.Literal, literal).toPython()
|
|
150
|
-
try:
|
|
151
|
-
converted_value = self.conversion(value)
|
|
152
|
-
except Exception as e:
|
|
153
|
-
warnings.warn(
|
|
154
|
-
PropertyDataTypeConversionWarning(str(instance), self._type_name, self._property_name, str(e)),
|
|
155
|
-
stacklevel=2,
|
|
156
|
-
)
|
|
157
|
-
continue
|
|
158
|
-
|
|
159
|
-
graph.add((instance, self.subject_predicate, rdflib.Literal(converted_value)))
|
|
160
|
-
graph.remove((instance, self.subject_predicate, literal))
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
class LiteralToEntity(BaseTransformer):
|
|
164
|
-
description = "Converts a literal value to new entity"
|
|
165
142
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
?instance a <{subject_type}> .
|
|
169
|
-
?instance <{subject_predicate}> ?property
|
|
170
|
-
FILTER(isLiteral(?property))
|
|
171
|
-
}}"""
|
|
172
|
-
_count_connections_of_type = """SELECT (COUNT(?property) AS ?propertyCount)
|
|
173
|
-
WHERE {{
|
|
174
|
-
?instance a <{subject_type}> .
|
|
175
|
-
?instance <{subject_predicate}> ?property
|
|
176
|
-
FILTER(isIRI(?property))
|
|
177
|
-
}}"""
|
|
178
|
-
|
|
179
|
-
_properties_of_type = """SELECT ?instance ?property
|
|
180
|
-
WHERE {{
|
|
181
|
-
?instance a <{subject_type}> .
|
|
182
|
-
?instance <{subject_predicate}> ?property
|
|
183
|
-
FILTER(isLiteral(?property))
|
|
184
|
-
}}"""
|
|
185
|
-
|
|
186
|
-
_count_properties = """SELECT (COUNT(?property) AS ?propertyCount)
|
|
187
|
-
WHERE {{
|
|
188
|
-
?instance <{subject_predicate}> ?property
|
|
189
|
-
FILTER(isLiteral(?property))
|
|
190
|
-
}}"""
|
|
191
|
-
_count_connections = """SELECT (COUNT(?property) AS ?propertyCount)
|
|
192
|
-
WHERE {{
|
|
193
|
-
?instance <{subject_predicate}> ?property
|
|
194
|
-
FILTER(isIRI(?property))
|
|
195
|
-
}}"""
|
|
196
|
-
_properties = """SELECT ?instance ?property
|
|
197
|
-
WHERE {{
|
|
198
|
-
?instance <{subject_predicate}> ?property
|
|
199
|
-
FILTER(isLiteral(?property))
|
|
200
|
-
}}"""
|
|
143
|
+
class LiteralToEntity(BaseTransformerStandardised):
|
|
144
|
+
description = "Converts a literal value to new entity"
|
|
201
145
|
|
|
202
146
|
def __init__(
|
|
203
147
|
self, subject_type: URIRef | None, subject_predicate: URIRef, entity_type: str, new_property: str | None = None
|
|
@@ -207,54 +151,75 @@ class LiteralToEntity(BaseTransformer):
|
|
|
207
151
|
self.entity_type = entity_type
|
|
208
152
|
self.new_property = new_property
|
|
209
153
|
|
|
210
|
-
def
|
|
154
|
+
def _iterate_query(self) -> str:
|
|
211
155
|
if self.subject_type is None:
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
156
|
+
query = """SELECT ?instance ?property
|
|
157
|
+
WHERE {{
|
|
158
|
+
?instance <{subject_predicate}> ?property
|
|
159
|
+
FILTER(isLiteral(?property))
|
|
160
|
+
}}"""
|
|
161
|
+
return query.format(subject_predicate=self.subject_predicate)
|
|
215
162
|
else:
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
if self.subject_type is
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
163
|
+
query = """SELECT ?instance ?property
|
|
164
|
+
WHERE {{
|
|
165
|
+
?instance a <{subject_type}> .
|
|
166
|
+
?instance <{subject_predicate}> ?property
|
|
167
|
+
FILTER(isLiteral(?property))
|
|
168
|
+
}}"""
|
|
169
|
+
return query.format(subject_type=self.subject_type, subject_predicate=self.subject_predicate)
|
|
170
|
+
|
|
171
|
+
def _skip_count_query(self) -> str:
|
|
172
|
+
if self.subject_type is None:
|
|
173
|
+
query = """SELECT (COUNT(?property) AS ?propertyCount)
|
|
174
|
+
WHERE {{
|
|
175
|
+
?instance <{subject_predicate}> ?property
|
|
176
|
+
FILTER(isIRI(?property))
|
|
177
|
+
}}"""
|
|
178
|
+
return query.format(subject_predicate=self.subject_predicate)
|
|
179
|
+
else:
|
|
180
|
+
query = """SELECT (COUNT(?property) AS ?propertyCount)
|
|
181
|
+
WHERE {{
|
|
182
|
+
?instance a <{subject_type}> .
|
|
183
|
+
?instance <{subject_predicate}> ?property
|
|
184
|
+
FILTER(isIRI(?property))
|
|
185
|
+
}}"""
|
|
186
|
+
return query.format(subject_type=self.subject_type, subject_predicate=self.subject_predicate)
|
|
187
|
+
|
|
188
|
+
def _count_query(self) -> str:
|
|
189
|
+
if self.subject_type is None:
|
|
190
|
+
query = """SELECT (COUNT(?property) AS ?propertyCount)
|
|
191
|
+
WHERE {{
|
|
192
|
+
?instance <{subject_predicate}> ?property
|
|
193
|
+
FILTER(isLiteral(?property))
|
|
194
|
+
}}"""
|
|
195
|
+
return query.format(subject_predicate=self.subject_predicate)
|
|
196
|
+
else:
|
|
197
|
+
query = """SELECT (COUNT(?property) AS ?propertyCount)
|
|
198
|
+
WHERE {{
|
|
199
|
+
?instance a <{subject_type}> .
|
|
200
|
+
?instance <{subject_predicate}> ?property
|
|
201
|
+
FILTER(isLiteral(?property))
|
|
202
|
+
}}"""
|
|
203
|
+
|
|
204
|
+
return query.format(subject_type=self.subject_type, subject_predicate=self.subject_predicate)
|
|
205
|
+
|
|
206
|
+
def operation(self, query_result_row: ResultRow) -> RowTransformationOutput:
|
|
207
|
+
row_output = RowTransformationOutput()
|
|
208
|
+
|
|
209
|
+
instance, literal = query_result_row
|
|
210
|
+
value = cast(rdflib.Literal, literal).toPython()
|
|
211
|
+
namespace = Namespace(get_namespace(instance)) # type: ignore[arg-type]
|
|
212
|
+
entity_type = namespace[self.entity_type]
|
|
213
|
+
new_entity = namespace[f"{self.entity_type}_{quote(value)!s}"]
|
|
214
|
+
row_output.add_triples.append((new_entity, RDF.type, entity_type))
|
|
215
|
+
row_output.instances_added_count += 1 # we add one new entity
|
|
216
|
+
|
|
217
|
+
if self.new_property is not None:
|
|
218
|
+
row_output.add_triples.append((new_entity, namespace[self.new_property], rdflib.Literal(value))) # type: ignore[arg-type]
|
|
219
|
+
row_output.instances_modified_count += 1 # we modify the new entity
|
|
220
|
+
|
|
221
|
+
row_output.add_triples.append((instance, self.subject_predicate, new_entity)) # type: ignore[arg-type]
|
|
222
|
+
row_output.remove_triples.append((instance, self.subject_predicate, literal)) # type: ignore[arg-type]
|
|
223
|
+
row_output.instances_modified_count += 1 # we modify the old entity
|
|
224
|
+
|
|
225
|
+
return row_output
|
cognite/neat/_issues/_base.py
CHANGED
|
@@ -390,11 +390,16 @@ class NeatIssueList(list, Sequence[T_NeatIssue], ABC):
|
|
|
390
390
|
"""This is a generic list of NeatIssues."""
|
|
391
391
|
|
|
392
392
|
def __init__(
|
|
393
|
-
self,
|
|
393
|
+
self,
|
|
394
|
+
issues: Sequence[T_NeatIssue] | None = None,
|
|
395
|
+
title: str | None = None,
|
|
396
|
+
action: str | None = None,
|
|
397
|
+
hint: str | None = None,
|
|
394
398
|
):
|
|
395
399
|
super().__init__(issues or [])
|
|
396
400
|
self.title = title
|
|
397
401
|
self.action = action
|
|
402
|
+
self.hint = hint
|
|
398
403
|
|
|
399
404
|
@property
|
|
400
405
|
def errors(self) -> Self:
|
|
@@ -406,6 +411,11 @@ class NeatIssueList(list, Sequence[T_NeatIssue], ABC):
|
|
|
406
411
|
"""Return True if this list contains any errors."""
|
|
407
412
|
return any(isinstance(issue, NeatError) for issue in self)
|
|
408
413
|
|
|
414
|
+
@property
|
|
415
|
+
def has_warnings(self) -> bool:
|
|
416
|
+
"""Return True if this list contains any warnings."""
|
|
417
|
+
return any(isinstance(issue, NeatWarning) for issue in self)
|
|
418
|
+
|
|
409
419
|
@property
|
|
410
420
|
def warnings(self) -> Self:
|
|
411
421
|
"""Return all the warnings in this list."""
|
|
@@ -455,14 +465,31 @@ class IssueList(NeatIssueList[NeatIssue]):
|
|
|
455
465
|
"""This is a list of NeatIssues."""
|
|
456
466
|
|
|
457
467
|
def _repr_html_(self) -> str | None:
|
|
458
|
-
if not self:
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
468
|
+
if self.action and not self:
|
|
469
|
+
header = f"Success: {self.action}"
|
|
470
|
+
elif self.action and self.has_errors:
|
|
471
|
+
header = f"Failed: {self.action}"
|
|
472
|
+
elif self.action and self.has_warnings:
|
|
473
|
+
header = f"Succeeded with warnings: {self.action}"
|
|
474
|
+
elif not self:
|
|
475
|
+
header = "No issues found"
|
|
464
476
|
else:
|
|
465
|
-
|
|
477
|
+
header = ""
|
|
478
|
+
|
|
479
|
+
body = f"<p>{header}</p>"
|
|
480
|
+
|
|
481
|
+
if self:
|
|
482
|
+
df = self.to_pandas()
|
|
483
|
+
agg_df = df["NeatIssue"].value_counts().to_frame()
|
|
484
|
+
if len(agg_df) < 10:
|
|
485
|
+
table = agg_df._repr_html_() # type: ignore[operator]
|
|
486
|
+
else:
|
|
487
|
+
table = agg_df.head()._repr_html_() # type: ignore[operator]
|
|
488
|
+
body += f"<br />{table}"
|
|
489
|
+
|
|
490
|
+
if self.hint:
|
|
491
|
+
body += f"<br />Hint: {self.hint}"
|
|
492
|
+
return body
|
|
466
493
|
|
|
467
494
|
|
|
468
495
|
T_Cls = TypeVar("T_Cls")
|
|
@@ -21,7 +21,7 @@ class ResourceRegexViolationWarning(ResourceNeatWarning):
|
|
|
21
21
|
|
|
22
22
|
fix = (
|
|
23
23
|
"Either export the data model and make the necessary changes manually"
|
|
24
|
-
" or run prepare.cdf_compliant_external_ids."
|
|
24
|
+
" or run prepare.data_model.cdf_compliant_external_ids."
|
|
25
25
|
)
|
|
26
26
|
|
|
27
27
|
location: str
|
cognite/neat/_rules/_shared.py
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
from abc import ABC, abstractmethod
|
|
2
1
|
from dataclasses import dataclass
|
|
3
2
|
from typing import Any, Generic, TypeAlias, TypeVar
|
|
4
3
|
|
|
5
|
-
from cognite.neat._issues import IssueList
|
|
6
4
|
from cognite.neat._rules.models import (
|
|
7
5
|
DMSRules,
|
|
8
6
|
InformationRules,
|
|
@@ -11,46 +9,32 @@ from cognite.neat._rules.models.dms._rules_input import DMSInputRules
|
|
|
11
9
|
from cognite.neat._rules.models.information._rules_input import InformationInputRules
|
|
12
10
|
|
|
13
11
|
VerifiedRules: TypeAlias = InformationRules | DMSRules
|
|
14
|
-
|
|
15
|
-
Rules: TypeAlias = DMSInputRules | InformationInputRules | InformationRules | DMSRules
|
|
16
|
-
T_Rules = TypeVar("T_Rules", bound=Rules)
|
|
12
|
+
|
|
17
13
|
T_VerifiedRules = TypeVar("T_VerifiedRules", bound=VerifiedRules)
|
|
14
|
+
InputRules: TypeAlias = DMSInputRules | InformationInputRules
|
|
18
15
|
T_InputRules = TypeVar("T_InputRules", bound=InputRules)
|
|
19
16
|
|
|
20
17
|
|
|
21
18
|
@dataclass
|
|
22
|
-
class
|
|
23
|
-
"""This
|
|
24
|
-
|
|
25
|
-
@abstractmethod
|
|
26
|
-
def get_rules(self) -> T_Rules | None:
|
|
27
|
-
"""Get the rules from the state."""
|
|
28
|
-
raise NotImplementedError()
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
@dataclass
|
|
32
|
-
class JustRules(OutRules[T_Rules]):
|
|
33
|
-
"""This represents a rule that exists"""
|
|
19
|
+
class ReadRules(Generic[T_InputRules]):
|
|
20
|
+
"""This represents a rules that has been read."""
|
|
34
21
|
|
|
35
|
-
rules:
|
|
36
|
-
|
|
37
|
-
def get_rules(self) -> T_Rules:
|
|
38
|
-
return self.rules
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
@dataclass
|
|
42
|
-
class MaybeRules(OutRules[T_Rules]):
|
|
43
|
-
"""This represents a rule that may or may not exist"""
|
|
22
|
+
rules: T_InputRules | None
|
|
23
|
+
read_context: dict[str, Any]
|
|
44
24
|
|
|
45
|
-
|
|
46
|
-
|
|
25
|
+
@classmethod
|
|
26
|
+
def display_type_name(cls) -> str:
|
|
27
|
+
return "UnverifiedModel"
|
|
47
28
|
|
|
48
|
-
|
|
49
|
-
|
|
29
|
+
@property
|
|
30
|
+
def display_name(self):
|
|
31
|
+
if self.rules is None:
|
|
32
|
+
return "FailedRead"
|
|
33
|
+
return self.rules.display_name
|
|
50
34
|
|
|
51
35
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"""This represents a rule that does not exist"""
|
|
36
|
+
ReadInputRules: TypeAlias = ReadRules[DMSInputRules] | ReadRules[InformationInputRules]
|
|
37
|
+
T_ReadInputRules = TypeVar("T_ReadInputRules", bound=ReadInputRules)
|
|
55
38
|
|
|
56
|
-
|
|
39
|
+
Rules: TypeAlias = InformationRules | DMSRules | ReadRules[DMSInputRules] | ReadRules[InformationInputRules]
|
|
40
|
+
T_Rules = TypeVar("T_Rules", bound=Rules)
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
from collections.abc import Iterable
|
|
3
|
+
from functools import lru_cache
|
|
3
4
|
from pathlib import Path
|
|
4
|
-
from
|
|
5
|
+
from types import UnionType
|
|
6
|
+
from typing import TYPE_CHECKING, Generic, TypeVar, Union, get_args, get_origin
|
|
5
7
|
|
|
6
8
|
from cognite.neat._client import NeatClient
|
|
9
|
+
from cognite.neat._constants import DEFAULT_NAMESPACE
|
|
7
10
|
from cognite.neat._rules._shared import T_VerifiedRules
|
|
8
11
|
from cognite.neat._utils.auxiliary import class_html_doc
|
|
9
12
|
from cognite.neat._utils.upload import UploadResult, UploadResultList
|
|
10
13
|
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from cognite.neat._store._provenance import Agent as ProvenanceAgent
|
|
16
|
+
|
|
11
17
|
T_Export = TypeVar("T_Export")
|
|
12
18
|
|
|
13
19
|
|
|
@@ -27,8 +33,28 @@ class BaseExporter(ABC, Generic[T_VerifiedRules, T_Export]):
|
|
|
27
33
|
def _repr_html_(cls) -> str:
|
|
28
34
|
return class_html_doc(cls, include_factory_methods=False)
|
|
29
35
|
|
|
36
|
+
@property
|
|
37
|
+
def agent(self) -> "ProvenanceAgent":
|
|
38
|
+
"""Provenance agent for the importer."""
|
|
39
|
+
from cognite.neat._store._provenance import Agent as ProvenanceAgent
|
|
40
|
+
|
|
41
|
+
return ProvenanceAgent(id_=DEFAULT_NAMESPACE[f"agent/{type(self).__name__}"])
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def description(self) -> str:
|
|
45
|
+
return "MISSING DESCRIPTION"
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
@lru_cache(maxsize=1)
|
|
49
|
+
def source_types(cls) -> tuple[type, ...]:
|
|
50
|
+
base_exporter = cls.__orig_bases__[0] # type: ignore[attr-defined]
|
|
51
|
+
source_type = get_args(base_exporter)[0]
|
|
52
|
+
if get_origin(source_type) in [Union, UnionType]:
|
|
53
|
+
return get_args(source_type)
|
|
54
|
+
return (source_type,)
|
|
55
|
+
|
|
30
56
|
|
|
31
|
-
class CDFExporter(BaseExporter[T_VerifiedRules, T_Export]):
|
|
57
|
+
class CDFExporter(BaseExporter[T_VerifiedRules, T_Export], ABC):
|
|
32
58
|
@abstractmethod
|
|
33
59
|
def export_to_cdf_iterable(
|
|
34
60
|
self, rules: T_VerifiedRules, client: NeatClient, dry_run: bool = False
|
|
@@ -109,6 +109,10 @@ class DMSExporter(CDFExporter[DMSRules, DMSSchema]):
|
|
|
109
109
|
self._schema: DMSSchema | None = None
|
|
110
110
|
self.remove_cdf_spaces = remove_cdf_spaces
|
|
111
111
|
|
|
112
|
+
@property
|
|
113
|
+
def description(self) -> str:
|
|
114
|
+
return "Export verified DMS Model to CDF."
|
|
115
|
+
|
|
112
116
|
def export_to_file(self, rules: DMSRules, filepath: Path) -> None:
|
|
113
117
|
"""Export the rules to a file(s).
|
|
114
118
|
|
|
@@ -68,6 +68,7 @@ class ExcelExporter(BaseExporter[VerifiedRules, Workbook]):
|
|
|
68
68
|
styling: Style = "default",
|
|
69
69
|
new_model_id: tuple[str, str] | None = None,
|
|
70
70
|
sheet_prefix: str | None = None,
|
|
71
|
+
reference_rules_with_prefix: tuple[VerifiedRules, str] | None = None,
|
|
71
72
|
):
|
|
72
73
|
self.sheet_prefix = sheet_prefix or ""
|
|
73
74
|
if styling not in self.style_options:
|
|
@@ -75,6 +76,11 @@ class ExcelExporter(BaseExporter[VerifiedRules, Workbook]):
|
|
|
75
76
|
self.styling = styling
|
|
76
77
|
self._styling_level = self.style_options.index(styling)
|
|
77
78
|
self.new_model_id = new_model_id
|
|
79
|
+
self.reference_rules_with_prefix = reference_rules_with_prefix
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def description(self) -> str:
|
|
83
|
+
return "Export verified model to Excel."
|
|
78
84
|
|
|
79
85
|
def export_to_file(self, rules: VerifiedRules, filepath: Path) -> None:
|
|
80
86
|
"""Exports transformation rules to excel file."""
|
|
@@ -94,6 +100,11 @@ class ExcelExporter(BaseExporter[VerifiedRules, Workbook]):
|
|
|
94
100
|
|
|
95
101
|
self._write_metadata_sheet(workbook, dumped_user_rules["Metadata"], sheet_prefix=self.sheet_prefix)
|
|
96
102
|
self._write_sheets(workbook, dumped_user_rules, rules, sheet_prefix=self.sheet_prefix)
|
|
103
|
+
if self.reference_rules_with_prefix:
|
|
104
|
+
reference_rules, prefix = self.reference_rules_with_prefix
|
|
105
|
+
dumped_reference_rules = reference_rules.dump(entities_exclude_defaults=False, by_alias=True)
|
|
106
|
+
self._write_sheets(workbook, dumped_reference_rules, reference_rules, sheet_prefix=prefix)
|
|
107
|
+
self._write_metadata_sheet(workbook, dumped_reference_rules["Metadata"], sheet_prefix=prefix)
|
|
97
108
|
|
|
98
109
|
if isinstance(rules, InformationRules) and rules.prefixes:
|
|
99
110
|
self._write_prefixes_sheet(workbook, rules.prefixes)
|
|
@@ -42,6 +42,10 @@ class InstanceTemplateExporter(BaseExporter[InformationRules, Workbook]):
|
|
|
42
42
|
self.auto_identifier_type = auto_identifier_type
|
|
43
43
|
self.add_drop_down_list = add_drop_down_list
|
|
44
44
|
|
|
45
|
+
@property
|
|
46
|
+
def description(self) -> str:
|
|
47
|
+
return "Export verified information instance template to Excel."
|
|
48
|
+
|
|
45
49
|
def export(
|
|
46
50
|
self,
|
|
47
51
|
rules: InformationRules,
|
|
@@ -46,6 +46,10 @@ class OWLExporter(GraphExporter):
|
|
|
46
46
|
def export(self, rules: InformationRules) -> Graph:
|
|
47
47
|
return Ontology.from_rules(rules).as_owl()
|
|
48
48
|
|
|
49
|
+
@property
|
|
50
|
+
def description(self) -> str:
|
|
51
|
+
return "Export verified information model to OWL."
|
|
52
|
+
|
|
49
53
|
|
|
50
54
|
class SHACLExporter(GraphExporter):
|
|
51
55
|
"""Exports rules to a SHACL graph."""
|
|
@@ -53,13 +57,21 @@ class SHACLExporter(GraphExporter):
|
|
|
53
57
|
def export(self, rules: InformationRules) -> Graph:
|
|
54
58
|
return Ontology.from_rules(rules).as_shacl()
|
|
55
59
|
|
|
60
|
+
@property
|
|
61
|
+
def description(self) -> str:
|
|
62
|
+
return "Export verified information model to SHACL."
|
|
63
|
+
|
|
56
64
|
|
|
57
65
|
class SemanticDataModelExporter(GraphExporter):
|
|
58
|
-
"""Exports verified information
|
|
66
|
+
"""Exports verified information model to a semantic data model."""
|
|
59
67
|
|
|
60
68
|
def export(self, rules: InformationRules) -> Graph:
|
|
61
69
|
return Ontology.from_rules(rules).as_semantic_data_model()
|
|
62
70
|
|
|
71
|
+
@property
|
|
72
|
+
def description(self) -> str:
|
|
73
|
+
return "Export verified information model to a semantic data model."
|
|
74
|
+
|
|
63
75
|
|
|
64
76
|
class OntologyModel(BaseModel):
|
|
65
77
|
model_config: ClassVar[ConfigDict] = ConfigDict(arbitrary_types_allowed=True, strict=False, extra="allow")
|
|
@@ -43,6 +43,10 @@ class YAMLExporter(BaseExporter[VerifiedRules, str]):
|
|
|
43
43
|
self.files = files
|
|
44
44
|
self.output = output
|
|
45
45
|
|
|
46
|
+
@property
|
|
47
|
+
def description(self) -> str:
|
|
48
|
+
return "Export verified model to YAML."
|
|
49
|
+
|
|
46
50
|
def export_to_file(self, rules: VerifiedRules, filepath: Path) -> None:
|
|
47
51
|
"""Exports transformation rules to YAML/JSON file(s)."""
|
|
48
52
|
if self.files == "single":
|
|
@@ -6,6 +6,7 @@ from datetime import datetime
|
|
|
6
6
|
from typing import TYPE_CHECKING, Any, Generic, Literal
|
|
7
7
|
|
|
8
8
|
from pydantic import ValidationError
|
|
9
|
+
from rdflib import URIRef
|
|
9
10
|
|
|
10
11
|
from cognite.neat._constants import DEFAULT_NAMESPACE
|
|
11
12
|
from cognite.neat._issues import IssueList, NeatError, NeatWarning
|
|
@@ -56,6 +57,14 @@ class BaseImporter(ABC, Generic[T_InputRules]):
|
|
|
56
57
|
|
|
57
58
|
return ProvenanceAgent(id_=DEFAULT_NAMESPACE[f"agent/{type(self).__name__}"])
|
|
58
59
|
|
|
60
|
+
@property
|
|
61
|
+
def description(self) -> str:
|
|
62
|
+
return "MISSING DESCRIPTION"
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def source_uri(self) -> URIRef:
|
|
66
|
+
return DEFAULT_NAMESPACE["UNKNOWN"]
|
|
67
|
+
|
|
59
68
|
|
|
60
69
|
class _FutureResult:
|
|
61
70
|
def __init__(self) -> None:
|