cognite-neat 0.97.2__py3-none-any.whl → 0.98.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/loaders/__init__.py +1 -2
- cognite/neat/_graph/queries/_base.py +25 -4
- cognite/neat/_issues/warnings/_models.py +9 -0
- cognite/neat/_rules/_shared.py +3 -8
- cognite/neat/_rules/analysis/__init__.py +1 -2
- cognite/neat/_rules/analysis/_base.py +2 -23
- cognite/neat/_rules/analysis/_dms.py +4 -10
- cognite/neat/_rules/analysis/_information.py +2 -10
- cognite/neat/_rules/catalog/info-rules-imf.xlsx +0 -0
- cognite/neat/_rules/exporters/_rules2excel.py +15 -72
- cognite/neat/_rules/exporters/_rules2ontology.py +4 -4
- cognite/neat/_rules/importers/_base.py +3 -4
- cognite/neat/_rules/importers/_dms2rules.py +17 -40
- cognite/neat/_rules/importers/_dtdl2rules/dtdl_converter.py +1 -7
- cognite/neat/_rules/importers/_dtdl2rules/dtdl_importer.py +7 -10
- cognite/neat/_rules/importers/_rdf/_base.py +17 -29
- cognite/neat/_rules/importers/_rdf/_imf2rules/_imf2classes.py +2 -2
- cognite/neat/_rules/importers/_rdf/_imf2rules/_imf2metadata.py +5 -10
- cognite/neat/_rules/importers/_rdf/_imf2rules/_imf2properties.py +1 -2
- cognite/neat/_rules/importers/_rdf/_inference2rules.py +30 -18
- cognite/neat/_rules/importers/_rdf/_owl2rules/_owl2classes.py +2 -2
- cognite/neat/_rules/importers/_rdf/_owl2rules/_owl2metadata.py +5 -8
- cognite/neat/_rules/importers/_rdf/_owl2rules/_owl2properties.py +1 -2
- cognite/neat/_rules/importers/_rdf/_shared.py +25 -140
- cognite/neat/_rules/importers/_spreadsheet2rules.py +10 -41
- cognite/neat/_rules/models/__init__.py +2 -16
- cognite/neat/_rules/models/_base_rules.py +98 -52
- cognite/neat/_rules/models/dms/_exporter.py +7 -160
- cognite/neat/_rules/models/dms/_rules.py +18 -126
- cognite/neat/_rules/models/dms/_rules_input.py +20 -48
- cognite/neat/_rules/models/dms/_schema.py +11 -0
- cognite/neat/_rules/models/dms/_validation.py +9 -122
- cognite/neat/_rules/models/information/_rules.py +19 -114
- cognite/neat/_rules/models/information/_rules_input.py +32 -41
- cognite/neat/_rules/models/information/_validation.py +34 -102
- cognite/neat/_rules/transformers/__init__.py +1 -4
- cognite/neat/_rules/transformers/_converters.py +18 -195
- cognite/neat/_rules/transformers/_mapping.py +1 -5
- cognite/neat/_rules/transformers/_verification.py +0 -14
- cognite/neat/_session/_base.py +37 -13
- cognite/neat/_session/_collector.py +126 -0
- cognite/neat/_session/_inspect.py +5 -5
- cognite/neat/_session/_prepare.py +37 -11
- cognite/neat/_session/_read.py +62 -9
- cognite/neat/_session/_set.py +2 -2
- cognite/neat/_session/_show.py +11 -11
- cognite/neat/_session/_to.py +24 -11
- cognite/neat/_session/exceptions.py +20 -3
- cognite/neat/_store/_provenance.py +2 -2
- cognite/neat/_utils/auxiliary.py +19 -0
- cognite/neat/_version.py +1 -1
- cognite/neat/_workflows/steps/data_contracts.py +2 -10
- cognite/neat/_workflows/steps/lib/current/rules_exporter.py +6 -46
- cognite/neat/_workflows/steps/lib/current/rules_validator.py +2 -7
- {cognite_neat-0.97.2.dist-info → cognite_neat-0.98.0.dist-info}/METADATA +2 -1
- {cognite_neat-0.97.2.dist-info → cognite_neat-0.98.0.dist-info}/RECORD +59 -65
- cognite/neat/_graph/loaders/_rdf2asset.py +0 -416
- cognite/neat/_rules/analysis/_asset.py +0 -173
- cognite/neat/_rules/models/asset/__init__.py +0 -13
- cognite/neat/_rules/models/asset/_rules.py +0 -109
- cognite/neat/_rules/models/asset/_rules_input.py +0 -101
- cognite/neat/_rules/models/asset/_validation.py +0 -45
- cognite/neat/_rules/models/domain.py +0 -136
- {cognite_neat-0.97.2.dist-info → cognite_neat-0.98.0.dist-info}/LICENSE +0 -0
- {cognite_neat-0.97.2.dist-info → cognite_neat-0.98.0.dist-info}/WHEEL +0 -0
- {cognite_neat-0.97.2.dist-info → cognite_neat-0.98.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,416 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from collections.abc import Iterable, Sequence
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from typing import Any, cast
|
|
5
|
-
|
|
6
|
-
import yaml
|
|
7
|
-
from cognite.client import CogniteClient
|
|
8
|
-
from cognite.client.data_classes import (
|
|
9
|
-
AssetWrite,
|
|
10
|
-
LabelDefinitionWrite,
|
|
11
|
-
RelationshipWrite,
|
|
12
|
-
)
|
|
13
|
-
from cognite.client.data_classes.capabilities import (
|
|
14
|
-
AssetsAcl,
|
|
15
|
-
Capability,
|
|
16
|
-
RelationshipsAcl,
|
|
17
|
-
)
|
|
18
|
-
from cognite.client.exceptions import CogniteAPIError, CogniteDuplicatedError
|
|
19
|
-
|
|
20
|
-
from cognite.neat._graph._tracking.base import Tracker
|
|
21
|
-
from cognite.neat._graph._tracking.log import LogTracker
|
|
22
|
-
from cognite.neat._issues import IssueList, NeatError, NeatIssue
|
|
23
|
-
from cognite.neat._issues.errors import ResourceCreationError, ResourceNotFoundError
|
|
24
|
-
from cognite.neat._rules._constants import EntityTypes
|
|
25
|
-
from cognite.neat._rules.analysis._asset import AssetAnalysis
|
|
26
|
-
from cognite.neat._rules.models import AssetRules
|
|
27
|
-
from cognite.neat._rules.models.entities import ClassEntity
|
|
28
|
-
from cognite.neat._store import NeatGraphStore
|
|
29
|
-
from cognite.neat._utils.auxiliary import create_sha256_hash
|
|
30
|
-
from cognite.neat._utils.upload import UploadResult
|
|
31
|
-
|
|
32
|
-
from ._base import _END_OF_CLASS, CDFLoader
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class AssetLoader(CDFLoader[AssetWrite]):
|
|
36
|
-
"""Load Assets and their relationships from NeatGraph to Cognite Data Fusions.
|
|
37
|
-
|
|
38
|
-
Args:
|
|
39
|
-
graph_store (NeatGraphStore): The graph store to load the data into.
|
|
40
|
-
rules (AssetRules): The rules to load the assets with.
|
|
41
|
-
data_set_id (int): The CDF data set id to load the Assets into.
|
|
42
|
-
use_orphanage (bool): Whether to use an orphanage for assets that are not part
|
|
43
|
-
of the hierarchy. Defaults to False.
|
|
44
|
-
use_labels (bool): Whether to use labels for assets. Defaults to False.
|
|
45
|
-
external_id_prefix (str | None): The prefix to use for the external ids. Defaults to None.
|
|
46
|
-
create_issues (Sequence[NeatIssue] | None): A list of issues that occurred during reading. Defaults to None.
|
|
47
|
-
tracker (type[Tracker] | None): The tracker to use. Defaults to None.
|
|
48
|
-
"""
|
|
49
|
-
|
|
50
|
-
def __init__(
|
|
51
|
-
self,
|
|
52
|
-
graph_store: NeatGraphStore,
|
|
53
|
-
rules: AssetRules,
|
|
54
|
-
data_set_id: int,
|
|
55
|
-
use_orphanage: bool = False,
|
|
56
|
-
use_labels: bool = False,
|
|
57
|
-
external_id_prefix: str | None = None,
|
|
58
|
-
create_issues: Sequence[NeatIssue] | None = None,
|
|
59
|
-
tracker: type[Tracker] | None = None,
|
|
60
|
-
):
|
|
61
|
-
super().__init__(graph_store)
|
|
62
|
-
|
|
63
|
-
self.rules = rules
|
|
64
|
-
self.data_set_id = data_set_id
|
|
65
|
-
|
|
66
|
-
self.use_labels = use_labels
|
|
67
|
-
|
|
68
|
-
self.orphanage = (
|
|
69
|
-
AssetWrite.load(
|
|
70
|
-
{
|
|
71
|
-
"dataSetId": self.data_set_id,
|
|
72
|
-
"externalId": (f"{external_id_prefix or ''}orphanage-{data_set_id}" if use_orphanage else None),
|
|
73
|
-
"name": "Orphanage",
|
|
74
|
-
"description": "Orphanage for assets whose parents do not exist",
|
|
75
|
-
}
|
|
76
|
-
)
|
|
77
|
-
if use_orphanage
|
|
78
|
-
else None
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
self.external_id_prefix = external_id_prefix
|
|
82
|
-
|
|
83
|
-
self.processed_assets: set[str] = set()
|
|
84
|
-
self._issues = IssueList(create_issues or [])
|
|
85
|
-
self._tracker: type[Tracker] = tracker or LogTracker
|
|
86
|
-
|
|
87
|
-
def _load(self, stop_on_exception: bool = False) -> Iterable[AssetWrite | NeatIssue | type[_END_OF_CLASS]]:
|
|
88
|
-
if self._issues.has_errors and stop_on_exception:
|
|
89
|
-
raise self._issues.as_exception()
|
|
90
|
-
elif self._issues.has_errors:
|
|
91
|
-
yield from self._issues
|
|
92
|
-
return
|
|
93
|
-
if not self.rules:
|
|
94
|
-
# There should already be an error in this case.
|
|
95
|
-
return
|
|
96
|
-
|
|
97
|
-
ordered_classes = AssetAnalysis(self.rules).class_topological_sort()
|
|
98
|
-
|
|
99
|
-
tracker = self._tracker(
|
|
100
|
-
type(self).__name__,
|
|
101
|
-
[repr(class_.id) for class_ in ordered_classes],
|
|
102
|
-
"classes",
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
if self.use_labels:
|
|
106
|
-
yield from self._create_labels()
|
|
107
|
-
|
|
108
|
-
if self.orphanage:
|
|
109
|
-
yield self.orphanage
|
|
110
|
-
self.processed_assets.add(cast(str, self.orphanage.external_id))
|
|
111
|
-
|
|
112
|
-
yield from self._create_assets(ordered_classes, tracker, stop_on_exception)
|
|
113
|
-
yield from self._create_relationship(ordered_classes, tracker, stop_on_exception)
|
|
114
|
-
|
|
115
|
-
def _create_labels(self) -> Iterable[Any]:
|
|
116
|
-
for label in AssetAnalysis(self.rules).define_labels():
|
|
117
|
-
yield LabelDefinitionWrite(name=label, external_id=label, data_set_id=self.data_set_id)
|
|
118
|
-
yield _END_OF_CLASS
|
|
119
|
-
|
|
120
|
-
def _create_assets(
|
|
121
|
-
self,
|
|
122
|
-
ordered_classes: list[ClassEntity],
|
|
123
|
-
tracker: Tracker,
|
|
124
|
-
stop_on_exception: bool,
|
|
125
|
-
) -> Iterable[Any]:
|
|
126
|
-
error: NeatError
|
|
127
|
-
for class_ in ordered_classes:
|
|
128
|
-
tracker.start(repr(class_.id))
|
|
129
|
-
|
|
130
|
-
property_renaming_config = AssetAnalysis(self.rules).define_asset_property_renaming_config(class_)
|
|
131
|
-
|
|
132
|
-
for identifier, properties in self.graph_store.read(class_.suffix):
|
|
133
|
-
identifier = f"{self.external_id_prefix or ''}{identifier}"
|
|
134
|
-
|
|
135
|
-
fields = _process_asset_properties(properties, property_renaming_config)
|
|
136
|
-
# set data set id and external id
|
|
137
|
-
fields["dataSetId"] = self.data_set_id
|
|
138
|
-
fields["externalId"] = identifier
|
|
139
|
-
|
|
140
|
-
if self.use_labels:
|
|
141
|
-
fields["labels"] = [class_.suffix]
|
|
142
|
-
|
|
143
|
-
if parent_external_id := fields.get("parentExternalId", None):
|
|
144
|
-
fields["parentExternalId"] = f"{self.external_id_prefix or ''}{parent_external_id}"
|
|
145
|
-
|
|
146
|
-
# check on parent
|
|
147
|
-
if "parentExternalId" in fields and fields["parentExternalId"] not in self.processed_assets:
|
|
148
|
-
error = ResourceNotFoundError(
|
|
149
|
-
fields["parentExternalId"],
|
|
150
|
-
EntityTypes.asset,
|
|
151
|
-
identifier,
|
|
152
|
-
EntityTypes.asset,
|
|
153
|
-
f"Moving the asset {identifier} under orphanage {self.orphanage.external_id}"
|
|
154
|
-
if self.orphanage
|
|
155
|
-
else "",
|
|
156
|
-
)
|
|
157
|
-
tracker.issue(error)
|
|
158
|
-
if stop_on_exception:
|
|
159
|
-
raise error
|
|
160
|
-
yield error
|
|
161
|
-
|
|
162
|
-
# if orphanage is set asset will use orphanage as parent
|
|
163
|
-
if self.orphanage:
|
|
164
|
-
fields["parentExternalId"] = self.orphanage.external_id
|
|
165
|
-
|
|
166
|
-
# otherwise asset will be skipped
|
|
167
|
-
else:
|
|
168
|
-
continue
|
|
169
|
-
|
|
170
|
-
try:
|
|
171
|
-
yield AssetWrite.load(fields)
|
|
172
|
-
self.processed_assets.add(identifier)
|
|
173
|
-
except KeyError as e:
|
|
174
|
-
error = ResourceCreationError(identifier, EntityTypes.asset, error=str(e))
|
|
175
|
-
tracker.issue(error)
|
|
176
|
-
if stop_on_exception:
|
|
177
|
-
raise error from e
|
|
178
|
-
yield error
|
|
179
|
-
|
|
180
|
-
yield _END_OF_CLASS
|
|
181
|
-
|
|
182
|
-
def _create_relationship(
|
|
183
|
-
self,
|
|
184
|
-
ordered_classes: list[ClassEntity],
|
|
185
|
-
tracker: Tracker,
|
|
186
|
-
stop_on_exception: bool,
|
|
187
|
-
) -> Iterable[Any]:
|
|
188
|
-
for class_ in ordered_classes:
|
|
189
|
-
tracker.start(repr(class_.id))
|
|
190
|
-
|
|
191
|
-
property_renaming_config = AssetAnalysis(self.rules).define_relationship_property_renaming_config(class_)
|
|
192
|
-
|
|
193
|
-
# class does not have any relationship properties
|
|
194
|
-
if not property_renaming_config:
|
|
195
|
-
continue
|
|
196
|
-
|
|
197
|
-
for source_external_id, properties in self.graph_store.read(class_.suffix):
|
|
198
|
-
relationships = _process_relationship_properties(properties, property_renaming_config)
|
|
199
|
-
|
|
200
|
-
source_external_id = f"{self.external_id_prefix or ''}{source_external_id}"
|
|
201
|
-
|
|
202
|
-
# check if source asset exists
|
|
203
|
-
if source_external_id not in self.processed_assets:
|
|
204
|
-
error = ResourceCreationError(
|
|
205
|
-
resource_type=EntityTypes.relationship,
|
|
206
|
-
identifier=source_external_id,
|
|
207
|
-
error=(
|
|
208
|
-
f"Asset {source_external_id} does not exist! "
|
|
209
|
-
"Aborting creation of relationships which use this asset as the source."
|
|
210
|
-
),
|
|
211
|
-
)
|
|
212
|
-
tracker.issue(error)
|
|
213
|
-
if stop_on_exception:
|
|
214
|
-
raise error
|
|
215
|
-
yield error
|
|
216
|
-
continue
|
|
217
|
-
|
|
218
|
-
for label, target_external_ids in relationships.items():
|
|
219
|
-
# we can have 1-many relationships
|
|
220
|
-
for target_external_id in target_external_ids:
|
|
221
|
-
target_external_id = f"{self.external_id_prefix or ''}{target_external_id}"
|
|
222
|
-
# check if source asset exists
|
|
223
|
-
if target_external_id not in self.processed_assets:
|
|
224
|
-
error = ResourceCreationError(
|
|
225
|
-
resource_type=EntityTypes.relationship,
|
|
226
|
-
identifier=target_external_id,
|
|
227
|
-
error=(
|
|
228
|
-
f"Asset {target_external_id} does not exist! "
|
|
229
|
-
f"Cannot create relationship between {source_external_id}"
|
|
230
|
-
f" and {target_external_id}. "
|
|
231
|
-
),
|
|
232
|
-
)
|
|
233
|
-
tracker.issue(error)
|
|
234
|
-
if stop_on_exception:
|
|
235
|
-
raise error
|
|
236
|
-
yield error
|
|
237
|
-
continue
|
|
238
|
-
|
|
239
|
-
external_id = "relationship_" + create_sha256_hash(f"{source_external_id}_{target_external_id}")
|
|
240
|
-
try:
|
|
241
|
-
yield RelationshipWrite(
|
|
242
|
-
external_id=external_id,
|
|
243
|
-
source_external_id=source_external_id,
|
|
244
|
-
target_external_id=target_external_id,
|
|
245
|
-
source_type="asset",
|
|
246
|
-
target_type="asset",
|
|
247
|
-
data_set_id=self.data_set_id,
|
|
248
|
-
labels=[label] if self.use_labels else None,
|
|
249
|
-
)
|
|
250
|
-
except KeyError as e:
|
|
251
|
-
error = ResourceCreationError(
|
|
252
|
-
resource_type=EntityTypes.relationship,
|
|
253
|
-
identifier=external_id,
|
|
254
|
-
error=str(e),
|
|
255
|
-
)
|
|
256
|
-
tracker.issue(error)
|
|
257
|
-
if stop_on_exception:
|
|
258
|
-
raise error from e
|
|
259
|
-
yield error
|
|
260
|
-
|
|
261
|
-
yield _END_OF_CLASS
|
|
262
|
-
|
|
263
|
-
def _get_required_capabilities(self) -> list[Capability]:
|
|
264
|
-
return [
|
|
265
|
-
AssetsAcl(
|
|
266
|
-
actions=[
|
|
267
|
-
AssetsAcl.Action.Write,
|
|
268
|
-
AssetsAcl.Action.Read,
|
|
269
|
-
],
|
|
270
|
-
scope=AssetsAcl.Scope.DataSet([self.data_set_id]),
|
|
271
|
-
),
|
|
272
|
-
RelationshipsAcl(
|
|
273
|
-
actions=[
|
|
274
|
-
RelationshipsAcl.Action.Write,
|
|
275
|
-
RelationshipsAcl.Action.Read,
|
|
276
|
-
],
|
|
277
|
-
scope=RelationshipsAcl.Scope.DataSet([self.data_set_id]),
|
|
278
|
-
),
|
|
279
|
-
]
|
|
280
|
-
|
|
281
|
-
def _upload_to_cdf(
|
|
282
|
-
self,
|
|
283
|
-
client: CogniteClient,
|
|
284
|
-
items: list[AssetWrite] | list[RelationshipWrite] | list[LabelDefinitionWrite],
|
|
285
|
-
dry_run: bool,
|
|
286
|
-
read_issues: IssueList,
|
|
287
|
-
) -> Iterable[UploadResult]:
|
|
288
|
-
if isinstance(items[0], AssetWrite) and all(isinstance(item, AssetWrite) for item in items):
|
|
289
|
-
yield from self._upload_assets_to_cdf(client, cast(list[AssetWrite], items), dry_run, read_issues)
|
|
290
|
-
elif isinstance(items[0], RelationshipWrite) and all(isinstance(item, RelationshipWrite) for item in items):
|
|
291
|
-
yield from self._upload_relationships_to_cdf(
|
|
292
|
-
client, cast(list[RelationshipWrite], items), dry_run, read_issues
|
|
293
|
-
)
|
|
294
|
-
elif isinstance(items[0], LabelDefinitionWrite) and all(
|
|
295
|
-
isinstance(item, LabelDefinitionWrite) for item in items
|
|
296
|
-
):
|
|
297
|
-
yield from self._upload_labels_to_cdf(client, cast(list[LabelDefinitionWrite], items), dry_run, read_issues)
|
|
298
|
-
else:
|
|
299
|
-
raise ValueError(f"Item {items[0]} is not supported. This is a bug in neat please report it.")
|
|
300
|
-
|
|
301
|
-
def _upload_labels_to_cdf(
|
|
302
|
-
self,
|
|
303
|
-
client: CogniteClient,
|
|
304
|
-
items: list[LabelDefinitionWrite],
|
|
305
|
-
dry_run: bool,
|
|
306
|
-
read_issues: IssueList,
|
|
307
|
-
) -> Iterable[UploadResult]:
|
|
308
|
-
try:
|
|
309
|
-
created = client.labels.create(items)
|
|
310
|
-
except (CogniteAPIError, CogniteDuplicatedError) as e:
|
|
311
|
-
result = UploadResult[str](name="Label", issues=read_issues)
|
|
312
|
-
result.error_messages.append(str(e))
|
|
313
|
-
result.failed_created.update(item.external_id for item in e.failed + e.unknown)
|
|
314
|
-
result.created.update(item.external_id for item in e.successful)
|
|
315
|
-
yield result
|
|
316
|
-
else:
|
|
317
|
-
for label in created:
|
|
318
|
-
result = UploadResult[str](name="Label", issues=read_issues)
|
|
319
|
-
result.upserted.add(cast(str, label.external_id))
|
|
320
|
-
yield result
|
|
321
|
-
|
|
322
|
-
def _upload_assets_to_cdf(
|
|
323
|
-
self,
|
|
324
|
-
client: CogniteClient,
|
|
325
|
-
items: list[AssetWrite],
|
|
326
|
-
dry_run: bool,
|
|
327
|
-
read_issues: IssueList,
|
|
328
|
-
) -> Iterable[UploadResult]:
|
|
329
|
-
try:
|
|
330
|
-
upserted = client.assets.upsert(items, mode="replace")
|
|
331
|
-
except CogniteAPIError as e:
|
|
332
|
-
result = UploadResult[str](name="Asset", issues=read_issues)
|
|
333
|
-
result.error_messages.append(str(e))
|
|
334
|
-
result.failed_upserted.update(item.external_id for item in e.failed + e.unknown)
|
|
335
|
-
result.upserted.update(item.external_id for item in e.successful)
|
|
336
|
-
yield result
|
|
337
|
-
else:
|
|
338
|
-
for asset in upserted:
|
|
339
|
-
result = UploadResult[str](name="Asset", issues=read_issues)
|
|
340
|
-
result.upserted.add(cast(str, asset.external_id))
|
|
341
|
-
yield result
|
|
342
|
-
|
|
343
|
-
def _upload_relationships_to_cdf(
|
|
344
|
-
self,
|
|
345
|
-
client: CogniteClient,
|
|
346
|
-
items: list[RelationshipWrite],
|
|
347
|
-
dry_run: bool,
|
|
348
|
-
read_issues: IssueList,
|
|
349
|
-
) -> Iterable[UploadResult]:
|
|
350
|
-
try:
|
|
351
|
-
upserted = client.relationships.upsert(items, mode="replace")
|
|
352
|
-
except CogniteAPIError as e:
|
|
353
|
-
result = UploadResult[str](name="Relationship", issues=read_issues)
|
|
354
|
-
result.error_messages.append(str(e))
|
|
355
|
-
result.failed_upserted.update(item.external_id for item in e.failed + e.unknown)
|
|
356
|
-
result.upserted.update(item.external_id for item in e.successful)
|
|
357
|
-
yield result
|
|
358
|
-
else:
|
|
359
|
-
for relationship in upserted:
|
|
360
|
-
result = UploadResult[str](name="relationship", issues=read_issues)
|
|
361
|
-
result.upserted.add(cast(str, relationship.external_id))
|
|
362
|
-
yield result
|
|
363
|
-
|
|
364
|
-
def write_to_file(self, filepath: Path) -> None:
|
|
365
|
-
if filepath.suffix not in [".json", ".yaml", ".yml"]:
|
|
366
|
-
raise ValueError(f"File format {filepath.suffix} is not supported")
|
|
367
|
-
dumped: dict[str, list] = {"assets": [], "relationship": []}
|
|
368
|
-
for item in self.load(stop_on_exception=False):
|
|
369
|
-
key = {
|
|
370
|
-
AssetWrite: "assets",
|
|
371
|
-
RelationshipWrite: "relationship",
|
|
372
|
-
NeatIssue: "issues",
|
|
373
|
-
_END_OF_CLASS: "end_of_class",
|
|
374
|
-
}.get(type(item))
|
|
375
|
-
if key is None:
|
|
376
|
-
# This should never happen, and is a bug in neat
|
|
377
|
-
raise ValueError(f"Item {item} is not supported. This is a bug in neat please report it.")
|
|
378
|
-
if key == "end_of_class":
|
|
379
|
-
continue
|
|
380
|
-
dumped[key].append(item.dump())
|
|
381
|
-
with filepath.open("w", encoding=self._encoding, newline=self._new_line) as f:
|
|
382
|
-
if filepath.suffix == ".json":
|
|
383
|
-
json.dump(dumped, f, indent=2)
|
|
384
|
-
else:
|
|
385
|
-
yaml.safe_dump(dumped, f, sort_keys=False)
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
def _process_asset_properties(properties: dict[str, list[str]], property_renaming_config: dict[str, str]) -> dict:
|
|
389
|
-
metadata: dict[str, str] = {}
|
|
390
|
-
fields: dict[str, str | dict] = {}
|
|
391
|
-
|
|
392
|
-
for original_property, values in properties.items():
|
|
393
|
-
if renamed_property := property_renaming_config.get(original_property, None):
|
|
394
|
-
if renamed_property.startswith("metadata."):
|
|
395
|
-
# Asset metadata contains only string values
|
|
396
|
-
metadata[original_property] = ", ".join(values)
|
|
397
|
-
else:
|
|
398
|
-
# Asset fields can contain only one value
|
|
399
|
-
fields[renamed_property] = values[0]
|
|
400
|
-
|
|
401
|
-
if metadata:
|
|
402
|
-
fields["metadata"] = metadata
|
|
403
|
-
|
|
404
|
-
return fields
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
def _process_relationship_properties(
|
|
408
|
-
properties: dict[str, list[str]], property_renaming_config: dict[str, str]
|
|
409
|
-
) -> dict:
|
|
410
|
-
relationships: dict[str, list[str]] = {}
|
|
411
|
-
|
|
412
|
-
for original_property, values in properties.items():
|
|
413
|
-
if renamed_property := property_renaming_config.get(original_property, None):
|
|
414
|
-
relationships[renamed_property] = values
|
|
415
|
-
|
|
416
|
-
return relationships
|
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
import warnings
|
|
2
|
-
from graphlib import TopologicalSorter
|
|
3
|
-
from typing import cast
|
|
4
|
-
|
|
5
|
-
from cognite.neat._rules._constants import EntityTypes
|
|
6
|
-
from cognite.neat._rules.models import AssetRules
|
|
7
|
-
from cognite.neat._rules.models._rdfpath import RDFPath
|
|
8
|
-
from cognite.neat._rules.models.asset import AssetClass, AssetProperty
|
|
9
|
-
from cognite.neat._rules.models.entities import (
|
|
10
|
-
AssetEntity,
|
|
11
|
-
AssetFields,
|
|
12
|
-
ClassEntity,
|
|
13
|
-
ReferenceEntity,
|
|
14
|
-
RelationshipEntity,
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
from ._base import BaseAnalysis
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class AssetAnalysis(BaseAnalysis[AssetRules, AssetClass, AssetProperty, ClassEntity, str]):
|
|
21
|
-
"""Assumes analysis over only the complete schema"""
|
|
22
|
-
|
|
23
|
-
def _get_reference(self, class_or_property: AssetClass | AssetProperty) -> ReferenceEntity | None:
|
|
24
|
-
return class_or_property.reference if isinstance(class_or_property.reference, ReferenceEntity) else None
|
|
25
|
-
|
|
26
|
-
def _get_cls_entity(self, class_: AssetClass | AssetProperty) -> ClassEntity:
|
|
27
|
-
return class_.class_
|
|
28
|
-
|
|
29
|
-
def _get_prop_entity(self, property_: AssetProperty) -> str:
|
|
30
|
-
return property_.property_
|
|
31
|
-
|
|
32
|
-
def _get_cls_parents(self, class_: AssetClass) -> list[ClassEntity] | None:
|
|
33
|
-
return list(class_.parent or []) or None
|
|
34
|
-
|
|
35
|
-
def _get_reference_rules(self) -> AssetRules | None:
|
|
36
|
-
return self.rules.reference
|
|
37
|
-
|
|
38
|
-
@classmethod
|
|
39
|
-
def _set_cls_entity(cls, property_: AssetProperty, class_: ClassEntity) -> None:
|
|
40
|
-
property_.class_ = class_
|
|
41
|
-
|
|
42
|
-
def _get_object(self, property_: AssetProperty) -> ClassEntity | None:
|
|
43
|
-
return property_.value_type if isinstance(property_.value_type, ClassEntity) else None
|
|
44
|
-
|
|
45
|
-
def _get_max_occurrence(self, property_: AssetProperty) -> int | float | None:
|
|
46
|
-
return property_.max_count
|
|
47
|
-
|
|
48
|
-
def _get_classes(self) -> list[AssetClass]:
|
|
49
|
-
return list(self.rules.classes)
|
|
50
|
-
|
|
51
|
-
def _get_properties(self) -> list[AssetProperty]:
|
|
52
|
-
return list(self.rules.properties)
|
|
53
|
-
|
|
54
|
-
def subset_rules(self, desired_classes: set[ClassEntity]) -> AssetRules:
|
|
55
|
-
raise NotImplementedError("Method not implemented")
|
|
56
|
-
|
|
57
|
-
def class_property_pairs(
|
|
58
|
-
self,
|
|
59
|
-
only_rdfpath: bool = False,
|
|
60
|
-
consider_inheritance: bool = False,
|
|
61
|
-
implementation_type: EntityTypes = EntityTypes.asset,
|
|
62
|
-
) -> dict[ClassEntity, dict[str, AssetProperty]]:
|
|
63
|
-
class_property_pairs = {}
|
|
64
|
-
|
|
65
|
-
T_implementation = AssetEntity if implementation_type == EntityTypes.asset else RelationshipEntity
|
|
66
|
-
|
|
67
|
-
for class_, properties in self.classes_with_properties(consider_inheritance).items():
|
|
68
|
-
processed_properties = {}
|
|
69
|
-
for property_ in properties:
|
|
70
|
-
if property_.property_ in processed_properties:
|
|
71
|
-
# TODO: use appropriate Warning class from _exceptions.py
|
|
72
|
-
# if missing make one !
|
|
73
|
-
warnings.warn(
|
|
74
|
-
f"Property {property_.property_} for {class_} has been defined more than once!"
|
|
75
|
-
" Only the first definition will be considered, skipping the rest..",
|
|
76
|
-
stacklevel=2,
|
|
77
|
-
)
|
|
78
|
-
continue
|
|
79
|
-
|
|
80
|
-
if (
|
|
81
|
-
property_.implementation
|
|
82
|
-
and any(isinstance(implementation, T_implementation) for implementation in property_.implementation)
|
|
83
|
-
and (not only_rdfpath or (only_rdfpath and isinstance(property_.transformation, RDFPath)))
|
|
84
|
-
):
|
|
85
|
-
implementation = [
|
|
86
|
-
implementation
|
|
87
|
-
for implementation in property_.implementation
|
|
88
|
-
if isinstance(implementation, T_implementation)
|
|
89
|
-
]
|
|
90
|
-
|
|
91
|
-
processed_properties[property_.property_] = property_.model_copy(
|
|
92
|
-
deep=True, update={"implementation": implementation}
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
if processed_properties:
|
|
96
|
-
class_property_pairs[class_] = processed_properties
|
|
97
|
-
|
|
98
|
-
return class_property_pairs
|
|
99
|
-
|
|
100
|
-
def class_topological_sort(self) -> list[ClassEntity]:
|
|
101
|
-
child_parent_asset: dict[ClassEntity, set[ClassEntity]] = {}
|
|
102
|
-
for class_, properties in self.asset_definition().items():
|
|
103
|
-
child_parent_asset[class_] = set()
|
|
104
|
-
for property_ in properties.values():
|
|
105
|
-
if any(
|
|
106
|
-
cast(AssetEntity, implementation).property_ == AssetFields.parentExternalId
|
|
107
|
-
for implementation in property_.implementation
|
|
108
|
-
):
|
|
109
|
-
child_parent_asset[property_.class_].add(cast(ClassEntity, property_.value_type))
|
|
110
|
-
|
|
111
|
-
return list(TopologicalSorter(child_parent_asset).static_order())
|
|
112
|
-
|
|
113
|
-
def asset_definition(
|
|
114
|
-
self, only_rdfpath: bool = False, consider_inheritance: bool = False
|
|
115
|
-
) -> dict[ClassEntity, dict[str, AssetProperty]]:
|
|
116
|
-
return self.class_property_pairs(
|
|
117
|
-
consider_inheritance=consider_inheritance,
|
|
118
|
-
only_rdfpath=only_rdfpath,
|
|
119
|
-
implementation_type=EntityTypes.asset,
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
def relationship_definition(
|
|
123
|
-
self, only_rdfpath: bool = False, consider_inheritance: bool = False
|
|
124
|
-
) -> dict[ClassEntity, dict[str, AssetProperty]]:
|
|
125
|
-
return self.class_property_pairs(
|
|
126
|
-
consider_inheritance=consider_inheritance,
|
|
127
|
-
only_rdfpath=only_rdfpath,
|
|
128
|
-
implementation_type=EntityTypes.relationship,
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
def define_asset_property_renaming_config(self, class_: ClassEntity) -> dict[str, str]:
|
|
132
|
-
property_renaming_configuration = {}
|
|
133
|
-
|
|
134
|
-
if asset_definition := self.asset_definition().get(class_, None):
|
|
135
|
-
for property_, transformation in asset_definition.items():
|
|
136
|
-
asset_property = cast(list[AssetEntity], transformation.implementation)[0].property_
|
|
137
|
-
|
|
138
|
-
if asset_property != "metadata":
|
|
139
|
-
property_renaming_configuration[property_] = str(asset_property)
|
|
140
|
-
else:
|
|
141
|
-
property_renaming_configuration[property_] = f"{asset_property}.{property_}"
|
|
142
|
-
|
|
143
|
-
return property_renaming_configuration
|
|
144
|
-
|
|
145
|
-
def define_relationship_property_renaming_config(self, class_: ClassEntity) -> dict[str, str]:
|
|
146
|
-
property_renaming_configuration = {}
|
|
147
|
-
|
|
148
|
-
if relationship_definition := self.relationship_definition().get(class_, None):
|
|
149
|
-
for property_, transformation in relationship_definition.items():
|
|
150
|
-
relationship = cast(list[RelationshipEntity], transformation.implementation)[0]
|
|
151
|
-
|
|
152
|
-
if relationship.label:
|
|
153
|
-
property_renaming_configuration[property_] = relationship.label
|
|
154
|
-
else:
|
|
155
|
-
property_renaming_configuration[property_] = property_
|
|
156
|
-
|
|
157
|
-
return property_renaming_configuration
|
|
158
|
-
|
|
159
|
-
def define_labels(self) -> set:
|
|
160
|
-
labels = set()
|
|
161
|
-
|
|
162
|
-
for _, properties in AssetAnalysis(self.rules).relationship_definition().items():
|
|
163
|
-
for property_, definition in properties.items():
|
|
164
|
-
labels.add(
|
|
165
|
-
cast(RelationshipEntity, definition.implementation[0]).label
|
|
166
|
-
if cast(RelationshipEntity, definition.implementation[0]).label
|
|
167
|
-
else property_
|
|
168
|
-
)
|
|
169
|
-
|
|
170
|
-
for class_ in AssetAnalysis(self.rules).asset_definition().keys():
|
|
171
|
-
labels.add(class_.suffix)
|
|
172
|
-
|
|
173
|
-
return labels
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
from ._rules import AssetClass, AssetMetadata, AssetProperty, AssetRules
|
|
2
|
-
from ._rules_input import AssetInputClass, AssetInputMetadata, AssetInputProperty, AssetInputRules
|
|
3
|
-
|
|
4
|
-
__all__ = [
|
|
5
|
-
"AssetRules",
|
|
6
|
-
"AssetMetadata",
|
|
7
|
-
"AssetClass",
|
|
8
|
-
"AssetProperty",
|
|
9
|
-
"AssetInputRules",
|
|
10
|
-
"AssetInputMetadata",
|
|
11
|
-
"AssetInputClass",
|
|
12
|
-
"AssetInputProperty",
|
|
13
|
-
]
|