cognite-neat 0.75.8__py3-none-any.whl → 0.76.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/app/api/configuration.py +4 -9
- cognite/neat/app/api/routers/configuration.py +2 -1
- cognite/neat/app/api/routers/crud.py +5 -5
- cognite/neat/app/api/routers/data_exploration.py +3 -1
- cognite/neat/app/api/routers/rules.py +3 -3
- cognite/neat/app/api/routers/workflows.py +3 -3
- cognite/neat/app/ui/neat-app/build/asset-manifest.json +3 -3
- cognite/neat/app/ui/neat-app/build/index.html +1 -1
- cognite/neat/app/ui/neat-app/build/static/js/{main.4345d42f.js → main.ec7f72e2.js} +3 -3
- cognite/neat/app/ui/neat-app/build/static/js/{main.4345d42f.js.map → main.ec7f72e2.js.map} +1 -1
- cognite/neat/config.py +147 -12
- cognite/neat/constants.py +1 -0
- cognite/neat/graph/exceptions.py +1 -2
- cognite/neat/legacy/graph/exceptions.py +1 -2
- cognite/neat/legacy/graph/extractors/_mock_graph_generator.py +1 -2
- cognite/neat/legacy/graph/loaders/_asset_loader.py +8 -13
- cognite/neat/legacy/graph/loaders/_base.py +2 -4
- cognite/neat/legacy/graph/loaders/_exceptions.py +1 -3
- cognite/neat/legacy/graph/loaders/core/rdf_to_assets.py +4 -8
- cognite/neat/legacy/graph/loaders/core/rdf_to_relationships.py +2 -4
- cognite/neat/legacy/graph/loaders/rdf_to_dms.py +2 -4
- cognite/neat/legacy/graph/loaders/validator.py +1 -1
- cognite/neat/legacy/graph/transformations/transformer.py +1 -2
- cognite/neat/legacy/rules/exporters/_rules2dms.py +1 -2
- cognite/neat/legacy/rules/exporters/_validation.py +4 -8
- cognite/neat/legacy/rules/importers/_base.py +0 -4
- cognite/neat/legacy/rules/importers/_dms2rules.py +0 -2
- cognite/neat/legacy/rules/models/rdfpath.py +1 -2
- cognite/neat/legacy/workflows/examples/Export_DMS/workflow.yaml +89 -0
- cognite/neat/legacy/workflows/examples/Export_Rules_to_Ontology/workflow.yaml +152 -0
- cognite/neat/legacy/workflows/examples/Extract_DEXPI_Graph_and_Export_Rules/workflow.yaml +139 -0
- cognite/neat/legacy/workflows/examples/Extract_RDF_Graph_and_Generate_Assets/workflow.yaml +270 -0
- cognite/neat/legacy/workflows/examples/Import_DMS/workflow.yaml +65 -0
- cognite/neat/legacy/workflows/examples/Ontology_to_Data_Model/workflow.yaml +116 -0
- cognite/neat/legacy/workflows/examples/Validate_Rules/workflow.yaml +67 -0
- cognite/neat/legacy/workflows/examples/Validate_Solution_Model/workflow.yaml +64 -0
- cognite/neat/legacy/workflows/examples/Visualize_Data_Model_Using_Mock_Graph/workflow.yaml +95 -0
- cognite/neat/legacy/workflows/examples/Visualize_Semantic_Data_Model/workflow.yaml +111 -0
- cognite/neat/rules/exporters/_models.py +3 -0
- cognite/neat/rules/exporters/_rules2dms.py +46 -4
- cognite/neat/rules/exporters/_rules2excel.py +2 -11
- cognite/neat/rules/exporters/_validation.py +6 -8
- cognite/neat/rules/importers/_base.py +8 -4
- cognite/neat/rules/importers/_dms2rules.py +321 -129
- cognite/neat/rules/importers/_dtdl2rules/dtdl_importer.py +2 -4
- cognite/neat/rules/importers/_dtdl2rules/spec.py +2 -4
- cognite/neat/rules/importers/_owl2rules/_owl2rules.py +2 -4
- cognite/neat/rules/importers/_spreadsheet2rules.py +18 -16
- cognite/neat/rules/importers/_yaml2rules.py +2 -4
- cognite/neat/rules/issues/base.py +3 -0
- cognite/neat/rules/issues/dms.py +144 -58
- cognite/neat/rules/issues/fileread.py +41 -0
- cognite/neat/rules/issues/formatters.py +3 -1
- cognite/neat/rules/issues/importing.py +155 -0
- cognite/neat/rules/issues/spreadsheet.py +12 -9
- cognite/neat/rules/models/entities.py +30 -8
- cognite/neat/rules/models/rdfpath.py +1 -2
- cognite/neat/rules/models/rules/_base.py +5 -6
- cognite/neat/rules/models/rules/_dms_architect_rules.py +494 -333
- cognite/neat/rules/models/rules/_dms_rules_write.py +43 -52
- cognite/neat/rules/models/rules/_dms_schema.py +112 -22
- cognite/neat/rules/models/rules/_domain_rules.py +5 -0
- cognite/neat/rules/models/rules/_information_rules.py +13 -6
- cognite/neat/rules/models/wrapped_entities.py +166 -0
- cognite/neat/utils/cdf_loaders/_data_modeling.py +3 -1
- cognite/neat/utils/cdf_loaders/_ingestion.py +2 -4
- cognite/neat/utils/spreadsheet.py +2 -4
- cognite/neat/utils/utils.py +2 -4
- cognite/neat/workflows/base.py +5 -5
- cognite/neat/workflows/manager.py +32 -22
- cognite/neat/workflows/model.py +3 -3
- cognite/neat/workflows/steps/lib/__init__.py +0 -7
- cognite/neat/workflows/steps/lib/current/__init__.py +6 -0
- cognite/neat/workflows/steps/lib/{rules_exporter.py → current/rules_exporter.py} +8 -8
- cognite/neat/workflows/steps/lib/{rules_importer.py → current/rules_importer.py} +4 -4
- cognite/neat/workflows/steps/lib/io/__init__.py +1 -0
- cognite/neat/workflows/steps/lib/{v1 → legacy}/graph_contextualization.py +2 -2
- cognite/neat/workflows/steps/lib/{v1 → legacy}/graph_extractor.py +9 -9
- cognite/neat/workflows/steps/lib/{v1 → legacy}/graph_loader.py +9 -9
- cognite/neat/workflows/steps/lib/{v1 → legacy}/graph_store.py +4 -4
- cognite/neat/workflows/steps/lib/{v1 → legacy}/graph_transformer.py +2 -2
- cognite/neat/workflows/steps/lib/{v1 → legacy}/rules_exporter.py +15 -17
- cognite/neat/workflows/steps/lib/{v1 → legacy}/rules_importer.py +7 -7
- cognite/neat/workflows/steps/step_model.py +5 -9
- cognite/neat/workflows/steps_registry.py +20 -11
- {cognite_neat-0.75.8.dist-info → cognite_neat-0.76.0.dist-info}/METADATA +1 -1
- {cognite_neat-0.75.8.dist-info → cognite_neat-0.76.0.dist-info}/RECORD +98 -86
- cognite/neat/app/api/data_classes/configuration.py +0 -121
- /cognite/neat/app/ui/neat-app/build/static/js/{main.4345d42f.js.LICENSE.txt → main.ec7f72e2.js.LICENSE.txt} +0 -0
- /cognite/neat/workflows/steps/lib/{graph_extractor.py → current/graph_extractor.py} +0 -0
- /cognite/neat/workflows/steps/lib/{graph_loader.py → current/graph_loader.py} +0 -0
- /cognite/neat/workflows/steps/lib/{graph_store.py → current/graph_store.py} +0 -0
- /cognite/neat/workflows/steps/lib/{rules_validator.py → current/rules_validator.py} +0 -0
- /cognite/neat/workflows/steps/lib/{io_steps.py → io/io_steps.py} +0 -0
- /cognite/neat/workflows/steps/lib/{v1 → legacy}/__init__.py +0 -0
- {cognite_neat-0.75.8.dist-info → cognite_neat-0.76.0.dist-info}/LICENSE +0 -0
- {cognite_neat-0.75.8.dist-info → cognite_neat-0.76.0.dist-info}/WHEEL +0 -0
- {cognite_neat-0.75.8.dist-info → cognite_neat-0.76.0.dist-info}/entry_points.txt +0 -0
|
@@ -15,7 +15,7 @@ from cognite.neat.rules.models.entities import (
|
|
|
15
15
|
ViewPropertyEntity,
|
|
16
16
|
)
|
|
17
17
|
|
|
18
|
-
from ._base import ExtensionCategory, SchemaCompleteness
|
|
18
|
+
from ._base import DataModelType, ExtensionCategory, SchemaCompleteness
|
|
19
19
|
from ._dms_architect_rules import DMSContainer, DMSMetadata, DMSProperty, DMSRules, DMSView
|
|
20
20
|
|
|
21
21
|
|
|
@@ -27,11 +27,11 @@ class DMSMetadataWrite:
|
|
|
27
27
|
creator: str
|
|
28
28
|
version: str
|
|
29
29
|
extension: Literal["addition", "reshape", "rebuild"] = "addition"
|
|
30
|
+
data_model_type: Literal["solution", "enterprise"] = "solution"
|
|
30
31
|
name: str | None = None
|
|
31
32
|
description: str | None = None
|
|
32
33
|
created: datetime | str | None = None
|
|
33
34
|
updated: datetime | str | None = None
|
|
34
|
-
default_view_version: str | None = None
|
|
35
35
|
|
|
36
36
|
@classmethod
|
|
37
37
|
def load(cls, data: dict[str, Any] | None) -> "DMSMetadataWrite | None":
|
|
@@ -45,11 +45,11 @@ class DMSMetadataWrite:
|
|
|
45
45
|
creator=data.get("creator"), # type: ignore[arg-type]
|
|
46
46
|
version=data.get("version"), # type: ignore[arg-type]
|
|
47
47
|
extension=data.get("extension", "addition"),
|
|
48
|
+
data_model_type=data.get("data_model_type", "solution"),
|
|
48
49
|
name=data.get("name"),
|
|
49
50
|
description=data.get("description"),
|
|
50
51
|
created=data.get("created"),
|
|
51
52
|
updated=data.get("updated"),
|
|
52
|
-
default_view_version=data.get("default_view_version"),
|
|
53
53
|
)
|
|
54
54
|
|
|
55
55
|
def dump(self) -> dict[str, Any]:
|
|
@@ -58,13 +58,13 @@ class DMSMetadataWrite:
|
|
|
58
58
|
extension=ExtensionCategory(self.extension),
|
|
59
59
|
space=self.space,
|
|
60
60
|
externalId=self.external_id,
|
|
61
|
+
dataModelType=DataModelType(self.data_model_type),
|
|
61
62
|
creator=self.creator,
|
|
62
63
|
version=self.version,
|
|
63
64
|
name=self.name,
|
|
64
65
|
description=self.description,
|
|
65
66
|
created=self.created or datetime.now(),
|
|
66
67
|
updated=self.updated or datetime.now(),
|
|
67
|
-
default_view_version=self.default_view_version or self.version,
|
|
68
68
|
)
|
|
69
69
|
|
|
70
70
|
|
|
@@ -77,7 +77,7 @@ class DMSPropertyWrite:
|
|
|
77
77
|
class_: str | None = None
|
|
78
78
|
name: str | None = None
|
|
79
79
|
description: str | None = None
|
|
80
|
-
|
|
80
|
+
connection: Literal["direct", "edge", "reverse"] | None = None
|
|
81
81
|
nullable: bool | None = None
|
|
82
82
|
is_list: bool | None = None
|
|
83
83
|
default: str | int | dict | None = None
|
|
@@ -89,18 +89,15 @@ class DMSPropertyWrite:
|
|
|
89
89
|
|
|
90
90
|
@classmethod
|
|
91
91
|
@overload
|
|
92
|
-
def load(cls, data: None) -> None:
|
|
93
|
-
...
|
|
92
|
+
def load(cls, data: None) -> None: ...
|
|
94
93
|
|
|
95
94
|
@classmethod
|
|
96
95
|
@overload
|
|
97
|
-
def load(cls, data: dict[str, Any]) -> "DMSPropertyWrite":
|
|
98
|
-
...
|
|
96
|
+
def load(cls, data: dict[str, Any]) -> "DMSPropertyWrite": ...
|
|
99
97
|
|
|
100
98
|
@classmethod
|
|
101
99
|
@overload
|
|
102
|
-
def load(cls, data: list[dict[str, Any]]) -> list["DMSPropertyWrite"]:
|
|
103
|
-
...
|
|
100
|
+
def load(cls, data: list[dict[str, Any]]) -> list["DMSPropertyWrite"]: ...
|
|
104
101
|
|
|
105
102
|
@classmethod
|
|
106
103
|
def load(
|
|
@@ -121,7 +118,7 @@ class DMSPropertyWrite:
|
|
|
121
118
|
class_=data.get("class_"),
|
|
122
119
|
name=data.get("name"),
|
|
123
120
|
description=data.get("description"),
|
|
124
|
-
|
|
121
|
+
connection=data.get("connection"),
|
|
125
122
|
nullable=data.get("nullable"),
|
|
126
123
|
is_list=data.get("is_list"),
|
|
127
124
|
default=data.get("default"),
|
|
@@ -146,23 +143,23 @@ class DMSPropertyWrite:
|
|
|
146
143
|
|
|
147
144
|
return {
|
|
148
145
|
"View": ViewEntity.load(self.view, space=default_space, version=default_version),
|
|
149
|
-
"
|
|
146
|
+
"View Property": self.view_property,
|
|
150
147
|
"Value Type": value_type,
|
|
151
|
-
"Property": self.property_ or self.view_property,
|
|
152
|
-
"Class": ClassEntity.load(self.class_, prefix=default_space, version=default_version)
|
|
148
|
+
"Property (linage)": self.property_ or self.view_property,
|
|
149
|
+
"Class (linage)": ClassEntity.load(self.class_, prefix=default_space, version=default_version)
|
|
153
150
|
if self.class_
|
|
154
151
|
else None,
|
|
155
152
|
"Name": self.name,
|
|
156
153
|
"Description": self.description,
|
|
157
|
-
"
|
|
154
|
+
"Connection": self.connection,
|
|
158
155
|
"Nullable": self.nullable,
|
|
159
|
-
"
|
|
156
|
+
"Is List": self.is_list,
|
|
160
157
|
"Default": self.default,
|
|
161
158
|
"Reference": self.reference,
|
|
162
159
|
"Container": ContainerEntity.load(self.container, space=default_space, version=default_version)
|
|
163
160
|
if self.container
|
|
164
161
|
else None,
|
|
165
|
-
"
|
|
162
|
+
"Container Property": self.container_property,
|
|
166
163
|
"Index": self.index,
|
|
167
164
|
"Constraint": self.constraint,
|
|
168
165
|
}
|
|
@@ -179,18 +176,15 @@ class DMSContainerWrite:
|
|
|
179
176
|
|
|
180
177
|
@classmethod
|
|
181
178
|
@overload
|
|
182
|
-
def load(cls, data: None) -> None:
|
|
183
|
-
...
|
|
179
|
+
def load(cls, data: None) -> None: ...
|
|
184
180
|
|
|
185
181
|
@classmethod
|
|
186
182
|
@overload
|
|
187
|
-
def load(cls, data: dict[str, Any]) -> "DMSContainerWrite":
|
|
188
|
-
...
|
|
183
|
+
def load(cls, data: dict[str, Any]) -> "DMSContainerWrite": ...
|
|
189
184
|
|
|
190
185
|
@classmethod
|
|
191
186
|
@overload
|
|
192
|
-
def load(cls, data: list[dict[str, Any]]) -> list["DMSContainerWrite"]:
|
|
193
|
-
...
|
|
187
|
+
def load(cls, data: list[dict[str, Any]]) -> list["DMSContainerWrite"]: ...
|
|
194
188
|
|
|
195
189
|
@classmethod
|
|
196
190
|
def load(
|
|
@@ -214,19 +208,21 @@ class DMSContainerWrite:
|
|
|
214
208
|
|
|
215
209
|
def dump(self, default_space: str) -> dict[str, Any]:
|
|
216
210
|
container = ContainerEntity.load(self.container, space=default_space)
|
|
217
|
-
return
|
|
218
|
-
Container
|
|
219
|
-
Class
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
211
|
+
return {
|
|
212
|
+
"Container": container,
|
|
213
|
+
"Class (linage)": ClassEntity.load(self.class_, prefix=default_space)
|
|
214
|
+
if self.class_
|
|
215
|
+
else container.as_class(),
|
|
216
|
+
"Name": self.name,
|
|
217
|
+
"Description": self.description,
|
|
218
|
+
"Reference": self.reference,
|
|
219
|
+
"Constraint": [
|
|
224
220
|
ContainerEntity.load(constraint.strip(), space=default_space)
|
|
225
221
|
for constraint in self.constraint.split(",")
|
|
226
222
|
]
|
|
227
223
|
if self.constraint
|
|
228
224
|
else None,
|
|
229
|
-
|
|
225
|
+
}
|
|
230
226
|
|
|
231
227
|
|
|
232
228
|
@dataclass
|
|
@@ -242,18 +238,15 @@ class DMSViewWrite:
|
|
|
242
238
|
|
|
243
239
|
@classmethod
|
|
244
240
|
@overload
|
|
245
|
-
def load(cls, data: None) -> None:
|
|
246
|
-
...
|
|
241
|
+
def load(cls, data: None) -> None: ...
|
|
247
242
|
|
|
248
243
|
@classmethod
|
|
249
244
|
@overload
|
|
250
|
-
def load(cls, data: dict[str, Any]) -> "DMSViewWrite":
|
|
251
|
-
...
|
|
245
|
+
def load(cls, data: dict[str, Any]) -> "DMSViewWrite": ...
|
|
252
246
|
|
|
253
247
|
@classmethod
|
|
254
248
|
@overload
|
|
255
|
-
def load(cls, data: list[dict[str, Any]]) -> list["DMSViewWrite"]:
|
|
256
|
-
...
|
|
249
|
+
def load(cls, data: list[dict[str, Any]]) -> list["DMSViewWrite"]: ...
|
|
257
250
|
|
|
258
251
|
@classmethod
|
|
259
252
|
def load(cls, data: dict[str, Any] | list[dict[str, Any]] | None) -> "DMSViewWrite | list[DMSViewWrite] | None":
|
|
@@ -277,23 +270,23 @@ class DMSViewWrite:
|
|
|
277
270
|
|
|
278
271
|
def dump(self, default_space: str, default_version: str) -> dict[str, Any]:
|
|
279
272
|
view = ViewEntity.load(self.view, space=default_space, version=default_version)
|
|
280
|
-
return
|
|
281
|
-
View
|
|
282
|
-
Class
|
|
273
|
+
return {
|
|
274
|
+
"View": view,
|
|
275
|
+
"Class (linage)": ClassEntity.load(self.class_, prefix=default_space, version=default_version)
|
|
283
276
|
if self.class_
|
|
284
277
|
else view.as_class(),
|
|
285
|
-
Name
|
|
286
|
-
Description
|
|
287
|
-
Implements
|
|
278
|
+
"Name": self.name,
|
|
279
|
+
"Description": self.description,
|
|
280
|
+
"Implements": [
|
|
288
281
|
ViewEntity.load(implement, space=default_space, version=default_version)
|
|
289
282
|
for implement in self.implements.split(",")
|
|
290
283
|
]
|
|
291
284
|
if self.implements
|
|
292
285
|
else None,
|
|
293
|
-
Reference
|
|
294
|
-
Filter
|
|
295
|
-
|
|
296
|
-
|
|
286
|
+
"Reference": self.reference,
|
|
287
|
+
"Filter": self.filter_,
|
|
288
|
+
"In Model": self.in_model,
|
|
289
|
+
}
|
|
297
290
|
|
|
298
291
|
|
|
299
292
|
@dataclass
|
|
@@ -306,13 +299,11 @@ class DMSRulesWrite:
|
|
|
306
299
|
|
|
307
300
|
@classmethod
|
|
308
301
|
@overload
|
|
309
|
-
def load(cls, data: dict[str, Any]) -> "DMSRulesWrite":
|
|
310
|
-
...
|
|
302
|
+
def load(cls, data: dict[str, Any]) -> "DMSRulesWrite": ...
|
|
311
303
|
|
|
312
304
|
@classmethod
|
|
313
305
|
@overload
|
|
314
|
-
def load(cls, data: None) -> None:
|
|
315
|
-
...
|
|
306
|
+
def load(cls, data: None) -> None: ...
|
|
316
307
|
|
|
317
308
|
@classmethod
|
|
318
309
|
def load(cls, data: dict | None) -> "DMSRulesWrite | None":
|
|
@@ -3,7 +3,7 @@ import sys
|
|
|
3
3
|
import warnings
|
|
4
4
|
import zipfile
|
|
5
5
|
from collections import Counter, defaultdict
|
|
6
|
-
from dataclasses import dataclass, field, fields
|
|
6
|
+
from dataclasses import Field, dataclass, field, fields
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import Any, ClassVar, cast
|
|
9
9
|
|
|
@@ -14,6 +14,7 @@ from cognite.client.data_classes import DatabaseWrite, DatabaseWriteList, Transf
|
|
|
14
14
|
from cognite.client.data_classes.data_modeling import ViewApply
|
|
15
15
|
from cognite.client.data_classes.transformations.common import Edges, EdgeType, Nodes, ViewInfo
|
|
16
16
|
|
|
17
|
+
from cognite.neat.rules import issues
|
|
17
18
|
from cognite.neat.rules.issues.dms import (
|
|
18
19
|
ContainerPropertyUsedMultipleTimesError,
|
|
19
20
|
DirectRelationMissingSourceWarning,
|
|
@@ -106,23 +107,33 @@ class DMSSchema:
|
|
|
106
107
|
The directory is expected to follow the Cognite-Toolkit convention
|
|
107
108
|
where each file is named as `resource_type.resource_name.yaml`.
|
|
108
109
|
"""
|
|
109
|
-
data = cls._read_directory(Path(directory))
|
|
110
|
-
return cls.load(data)
|
|
110
|
+
data, context = cls._read_directory(Path(directory))
|
|
111
|
+
return cls.load(data, context)
|
|
111
112
|
|
|
112
113
|
@classmethod
|
|
113
|
-
def _read_directory(cls, directory: Path) -> dict[str, list[Any]]:
|
|
114
|
+
def _read_directory(cls, directory: Path) -> tuple[dict[str, list[Any]], dict[str, list[Path]]]:
|
|
114
115
|
data: dict[str, Any] = {}
|
|
116
|
+
context: dict[str, list[Path]] = {}
|
|
115
117
|
for yaml_file in directory.rglob("*.yaml"):
|
|
116
118
|
if "." in yaml_file.stem:
|
|
117
119
|
resource_type = yaml_file.stem.rsplit(".", 1)[-1]
|
|
118
120
|
if attr_name := cls._FIELD_NAME_BY_RESOURCE_TYPE.get(resource_type):
|
|
119
121
|
data.setdefault(attr_name, [])
|
|
120
|
-
|
|
122
|
+
context.setdefault(attr_name, [])
|
|
123
|
+
try:
|
|
124
|
+
# Using CSafeLoader over safe_load for ~10x speedup
|
|
125
|
+
loaded = yaml.safe_load(yaml_file.read_text())
|
|
126
|
+
except Exception as e:
|
|
127
|
+
warnings.warn(issues.fileread.InvalidFileFormatWarning(yaml_file, str(e)), stacklevel=2)
|
|
128
|
+
continue
|
|
129
|
+
|
|
121
130
|
if isinstance(loaded, list):
|
|
122
131
|
data[attr_name].extend(loaded)
|
|
132
|
+
context[attr_name].extend([yaml_file] * len(loaded))
|
|
123
133
|
else:
|
|
124
134
|
data[attr_name].append(loaded)
|
|
125
|
-
|
|
135
|
+
context[attr_name].append(yaml_file)
|
|
136
|
+
return data, context
|
|
126
137
|
|
|
127
138
|
def to_directory(
|
|
128
139
|
self,
|
|
@@ -182,12 +193,13 @@ class DMSSchema:
|
|
|
182
193
|
The ZIP file is expected to follow the Cognite-Toolkit convention
|
|
183
194
|
where each file is named as `resource_type.resource_name.yaml`.
|
|
184
195
|
"""
|
|
185
|
-
data = cls._read_zip(Path(zip_file))
|
|
186
|
-
return cls.load(data)
|
|
196
|
+
data, context = cls._read_zip(Path(zip_file))
|
|
197
|
+
return cls.load(data, context)
|
|
187
198
|
|
|
188
199
|
@classmethod
|
|
189
|
-
def _read_zip(cls, zip_file: Path) -> dict[str, list[Any]]:
|
|
200
|
+
def _read_zip(cls, zip_file: Path) -> tuple[dict[str, list[Any]], dict[str, list[Path]]]:
|
|
190
201
|
data: dict[str, list[Any]] = {}
|
|
202
|
+
context: dict[str, list[Path]] = {}
|
|
191
203
|
with zipfile.ZipFile(zip_file, "r") as zip_ref:
|
|
192
204
|
for file_info in zip_ref.infolist():
|
|
193
205
|
if file_info.filename.endswith(".yaml"):
|
|
@@ -199,12 +211,19 @@ class DMSSchema:
|
|
|
199
211
|
resource_type = filename.stem.rsplit(".", 1)[-1]
|
|
200
212
|
if attr_name := cls._FIELD_NAME_BY_RESOURCE_TYPE.get(resource_type):
|
|
201
213
|
data.setdefault(attr_name, [])
|
|
202
|
-
|
|
214
|
+
context.setdefault(attr_name, [])
|
|
215
|
+
try:
|
|
216
|
+
loaded = yaml.safe_load(zip_ref.read(file_info).decode())
|
|
217
|
+
except Exception as e:
|
|
218
|
+
warnings.warn(issues.fileread.InvalidFileFormatWarning(filename, str(e)), stacklevel=2)
|
|
219
|
+
continue
|
|
203
220
|
if isinstance(loaded, list):
|
|
204
221
|
data[attr_name].extend(loaded)
|
|
222
|
+
context[attr_name].extend([filename] * len(loaded))
|
|
205
223
|
else:
|
|
206
224
|
data[attr_name].append(loaded)
|
|
207
|
-
|
|
225
|
+
context[attr_name].append(filename)
|
|
226
|
+
return data, context
|
|
208
227
|
|
|
209
228
|
def to_zip(self, zip_file: str | Path, exclude: set[str] | None = None) -> None:
|
|
210
229
|
"""Save the schema to a ZIP file as YAML files. This is compatible with the Cognite-Toolkit convention.
|
|
@@ -234,18 +253,63 @@ class DMSSchema:
|
|
|
234
253
|
zip_ref.writestr(f"data_models/nodes/{node.external_id}.node.yaml", node.dump_yaml())
|
|
235
254
|
|
|
236
255
|
@classmethod
|
|
237
|
-
def load(cls, data: str | dict[str, Any]) -> Self:
|
|
256
|
+
def load(cls, data: str | dict[str, list[Any]], context: dict[str, list[Path]] | None = None) -> Self:
|
|
257
|
+
"""Loads a schema from a dictionary or a YAML or JSON formatted string.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
data: The data to load the schema from. This can be a dictionary, a YAML or JSON formatted string.
|
|
261
|
+
context: This provides linage for where the data was loaded from. This is used in Warnings
|
|
262
|
+
if a single item fails to load.
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
DMSSchema: The loaded schema.
|
|
266
|
+
"""
|
|
267
|
+
context = context or {}
|
|
238
268
|
if isinstance(data, str):
|
|
239
269
|
# YAML is a superset of JSON, so we can use the same parser
|
|
240
|
-
|
|
270
|
+
try:
|
|
271
|
+
data_dict = yaml.safe_load(data)
|
|
272
|
+
except Exception as e:
|
|
273
|
+
raise issues.fileread.FailedStringLoadError(".yaml", str(e)).as_exception() from None
|
|
274
|
+
if not isinstance(data_dict, dict) and all(isinstance(v, list) for v in data_dict.values()):
|
|
275
|
+
raise issues.fileread.FailedStringLoadError(
|
|
276
|
+
"dict[str, list[Any]]", f"Invalid data structure: {type(data)}"
|
|
277
|
+
).as_exception() from None
|
|
241
278
|
else:
|
|
242
279
|
data_dict = data
|
|
243
280
|
loaded: dict[str, Any] = {}
|
|
244
281
|
for attr in fields(cls):
|
|
245
282
|
if items := data_dict.get(attr.name) or data_dict.get(to_camel(attr.name)):
|
|
246
|
-
|
|
283
|
+
try:
|
|
284
|
+
loaded[attr.name] = attr.type.load(items)
|
|
285
|
+
except Exception as e:
|
|
286
|
+
loaded[attr.name] = cls._load_individual_resources(items, attr, str(e), context.get(attr.name, []))
|
|
247
287
|
return cls(**loaded)
|
|
248
288
|
|
|
289
|
+
@classmethod
|
|
290
|
+
def _load_individual_resources(cls, items: list, attr: Field, trigger_error: str, resource_context) -> list[Any]:
|
|
291
|
+
resources = attr.type([])
|
|
292
|
+
if not hasattr(attr.type, "_RESOURCE"):
|
|
293
|
+
warnings.warn(
|
|
294
|
+
issues.fileread.FailedLoadWarning(Path("UNKNOWN"), attr.type.__name__, trigger_error), stacklevel=2
|
|
295
|
+
)
|
|
296
|
+
return resources
|
|
297
|
+
# Fallback to load individual resources.
|
|
298
|
+
single_cls = attr.type._RESOURCE
|
|
299
|
+
for no, item in enumerate(items):
|
|
300
|
+
try:
|
|
301
|
+
loaded_instance = single_cls.load(item)
|
|
302
|
+
except Exception as e:
|
|
303
|
+
try:
|
|
304
|
+
filepath = resource_context[no]
|
|
305
|
+
except IndexError:
|
|
306
|
+
filepath = Path("UNKNOWN")
|
|
307
|
+
# We use repr(e) instead of str(e) to include the exception type in the warning message
|
|
308
|
+
warnings.warn(issues.fileread.FailedLoadWarning(filepath, single_cls.__name__, repr(e)), stacklevel=2)
|
|
309
|
+
else:
|
|
310
|
+
resources.append(loaded_instance)
|
|
311
|
+
return resources
|
|
312
|
+
|
|
249
313
|
def dump(self, camel_case: bool = True, sort: bool = True) -> dict[str, Any]:
|
|
250
314
|
"""Dump the schema to a dictionary that can be serialized to JSON.
|
|
251
315
|
|
|
@@ -410,6 +474,18 @@ class DMSSchema:
|
|
|
410
474
|
)
|
|
411
475
|
return None
|
|
412
476
|
|
|
477
|
+
def referenced_spaces(self) -> set[str]:
|
|
478
|
+
referenced_spaces = {container.space for container in self.containers}
|
|
479
|
+
referenced_spaces |= {view.space for view in self.views}
|
|
480
|
+
referenced_spaces |= {container.space for view in self.views for container in view.referenced_containers()}
|
|
481
|
+
referenced_spaces |= {parent.space for view in self.views for parent in view.implements or []}
|
|
482
|
+
referenced_spaces |= {node.space for node in self.node_types}
|
|
483
|
+
referenced_spaces |= {model.space for model in self.data_models}
|
|
484
|
+
referenced_spaces |= {view.space for model in self.data_models for view in model.views or []}
|
|
485
|
+
referenced_spaces |= {s.space for s in self.spaces}
|
|
486
|
+
|
|
487
|
+
return referenced_spaces
|
|
488
|
+
|
|
413
489
|
|
|
414
490
|
@dataclass
|
|
415
491
|
class PipelineSchema(DMSSchema):
|
|
@@ -429,18 +505,25 @@ class PipelineSchema(DMSSchema):
|
|
|
429
505
|
self.databases.extend([DatabaseWrite(name=database) for database in missing])
|
|
430
506
|
|
|
431
507
|
@classmethod
|
|
432
|
-
def _read_directory(cls, directory: Path) -> dict[str, list[Any]]:
|
|
433
|
-
data = super()._read_directory(directory)
|
|
508
|
+
def _read_directory(cls, directory: Path) -> tuple[dict[str, list[Any]], dict[str, list[Path]]]:
|
|
509
|
+
data, context = super()._read_directory(directory)
|
|
434
510
|
for yaml_file in directory.rglob("*.yaml"):
|
|
435
511
|
if yaml_file.parent.name in ("transformations", "raw"):
|
|
436
512
|
attr_name = cls._FIELD_NAME_BY_RESOURCE_TYPE.get(yaml_file.parent.name, yaml_file.parent.name)
|
|
437
513
|
data.setdefault(attr_name, [])
|
|
438
|
-
|
|
514
|
+
context.setdefault(attr_name, [])
|
|
515
|
+
try:
|
|
516
|
+
loaded = yaml.safe_load(yaml_file.read_text())
|
|
517
|
+
except Exception as e:
|
|
518
|
+
warnings.warn(issues.fileread.InvalidFileFormatWarning(yaml_file, str(e)), stacklevel=2)
|
|
519
|
+
continue
|
|
439
520
|
if isinstance(loaded, list):
|
|
440
521
|
data[attr_name].extend(loaded)
|
|
522
|
+
context[attr_name].extend([yaml_file] * len(loaded))
|
|
441
523
|
else:
|
|
442
524
|
data[attr_name].append(loaded)
|
|
443
|
-
|
|
525
|
+
context[attr_name].append(yaml_file)
|
|
526
|
+
return data, context
|
|
444
527
|
|
|
445
528
|
def to_directory(
|
|
446
529
|
self,
|
|
@@ -483,8 +566,8 @@ class PipelineSchema(DMSSchema):
|
|
|
483
566
|
zip_ref.writestr(f"raw/{raw_table.name}.yaml", raw_table.dump_yaml())
|
|
484
567
|
|
|
485
568
|
@classmethod
|
|
486
|
-
def _read_zip(cls, zip_file: Path) -> dict[str, list[Any]]:
|
|
487
|
-
data = super()._read_zip(zip_file)
|
|
569
|
+
def _read_zip(cls, zip_file: Path) -> tuple[dict[str, list[Any]], dict[str, list[Path]]]:
|
|
570
|
+
data, context = super()._read_zip(zip_file)
|
|
488
571
|
with zipfile.ZipFile(zip_file, "r") as zip_ref:
|
|
489
572
|
for file_info in zip_ref.infolist():
|
|
490
573
|
if file_info.filename.endswith(".yaml"):
|
|
@@ -494,12 +577,19 @@ class PipelineSchema(DMSSchema):
|
|
|
494
577
|
if (parent := filepath.parent.name) in ("transformations", "raw"):
|
|
495
578
|
attr_name = cls._FIELD_NAME_BY_RESOURCE_TYPE.get(parent, parent)
|
|
496
579
|
data.setdefault(attr_name, [])
|
|
497
|
-
|
|
580
|
+
context.setdefault(attr_name, [])
|
|
581
|
+
try:
|
|
582
|
+
loaded = yaml.safe_load(zip_ref.read(file_info).decode())
|
|
583
|
+
except Exception as e:
|
|
584
|
+
warnings.warn(issues.fileread.InvalidFileFormatWarning(filepath, str(e)), stacklevel=2)
|
|
585
|
+
continue
|
|
498
586
|
if isinstance(loaded, list):
|
|
499
587
|
data[attr_name].extend(loaded)
|
|
588
|
+
context[attr_name].extend([filepath] * len(loaded))
|
|
500
589
|
else:
|
|
501
590
|
data[attr_name].append(loaded)
|
|
502
|
-
|
|
591
|
+
context[attr_name].append(filepath)
|
|
592
|
+
return data, context
|
|
503
593
|
|
|
504
594
|
@classmethod
|
|
505
595
|
def from_dms(cls, schema: DMSSchema, instance_space: str | None = None) -> "PipelineSchema":
|
|
@@ -23,7 +23,10 @@ class DomainMetadata(BaseMetadata):
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
class DomainProperty(SheetEntity):
|
|
26
|
+
class_: ClassEntity = Field(alias="Class")
|
|
26
27
|
property_: PropertyType = Field(alias="Property")
|
|
28
|
+
name: str | None = Field(alias="Name", default=None)
|
|
29
|
+
description: str | None = Field(alias="Description", default=None)
|
|
27
30
|
value_type: DataType | ClassEntity = Field(alias="Value Type")
|
|
28
31
|
min_count: int | None = Field(alias="Min Count", default=None)
|
|
29
32
|
max_count: int | float | None = Field(alias="Max Count", default=None)
|
|
@@ -42,6 +45,8 @@ class DomainProperty(SheetEntity):
|
|
|
42
45
|
|
|
43
46
|
|
|
44
47
|
class DomainClass(SheetEntity):
|
|
48
|
+
class_: ClassEntity = Field(alias="Class")
|
|
49
|
+
name: str | None = Field(alias="Name", default=None)
|
|
45
50
|
description: str | None = Field(None, alias="Description")
|
|
46
51
|
parent: ParentEntityList | None = Field(alias="Parent Class")
|
|
47
52
|
|
|
@@ -45,6 +45,7 @@ from cognite.neat.rules.models.rdfpath import (
|
|
|
45
45
|
|
|
46
46
|
from ._base import (
|
|
47
47
|
BaseMetadata,
|
|
48
|
+
DataModelType,
|
|
48
49
|
ExtensionCategory,
|
|
49
50
|
ExtensionCategoryType,
|
|
50
51
|
MatchType,
|
|
@@ -75,6 +76,7 @@ else:
|
|
|
75
76
|
|
|
76
77
|
class InformationMetadata(BaseMetadata):
|
|
77
78
|
role: ClassVar[RoleTypes] = RoleTypes.information_architect
|
|
79
|
+
data_model_type: DataModelType = Field(DataModelType.solution, alias="dataModelType")
|
|
78
80
|
schema_: SchemaCompleteness = Field(alias="schema")
|
|
79
81
|
extension: ExtensionCategoryType | None = ExtensionCategory.addition
|
|
80
82
|
prefix: PrefixType
|
|
@@ -123,6 +125,9 @@ class InformationClass(SheetEntity):
|
|
|
123
125
|
match_type: The match type of the resource being described and the source entity.
|
|
124
126
|
"""
|
|
125
127
|
|
|
128
|
+
class_: ClassEntity = Field(alias="Class")
|
|
129
|
+
name: str | None = Field(alias="Name", default=None)
|
|
130
|
+
description: str | None = Field(alias="Description", default=None)
|
|
126
131
|
parent: ParentEntityList | None = Field(alias="Parent Class", default=None)
|
|
127
132
|
reference: URLEntity | ReferenceEntity | None = Field(alias="Reference", default=None, union_mode="left_to_right")
|
|
128
133
|
match_type: MatchType | None = Field(alias="Match Type", default=None)
|
|
@@ -150,7 +155,10 @@ class InformationProperty(SheetEntity):
|
|
|
150
155
|
knowledge graph. Defaults to None (no transformation)
|
|
151
156
|
"""
|
|
152
157
|
|
|
158
|
+
class_: ClassEntity = Field(alias="Class")
|
|
153
159
|
property_: PropertyType = Field(alias="Property")
|
|
160
|
+
name: str | None = Field(alias="Name", default=None)
|
|
161
|
+
description: str | None = Field(alias="Description", default=None)
|
|
154
162
|
value_type: DataType | ClassEntity | UnknownEntity = Field(alias="Value Type", union_mode="left_to_right")
|
|
155
163
|
min_count: int | None = Field(alias="Min Count", default=None)
|
|
156
164
|
max_count: int | float | None = Field(alias="Max Count", default=None)
|
|
@@ -430,7 +438,7 @@ class _InformationRulesConverter:
|
|
|
430
438
|
for class_ in self.information.classes:
|
|
431
439
|
properties: list[DMSProperty] = properties_by_class.get(class_.class_.versioned_id, [])
|
|
432
440
|
if not properties or all(
|
|
433
|
-
isinstance(prop.value_type, ViewPropertyEntity) and prop.
|
|
441
|
+
isinstance(prop.value_type, ViewPropertyEntity) and prop.connection != "direct" for prop in properties
|
|
434
442
|
):
|
|
435
443
|
classes_without_properties.add(class_.class_.versioned_id)
|
|
436
444
|
|
|
@@ -480,16 +488,15 @@ class _InformationRulesConverter:
|
|
|
480
488
|
else:
|
|
481
489
|
raise ValueError(f"Unsupported value type: {prop.value_type.type_}")
|
|
482
490
|
|
|
483
|
-
relation: Literal["direct", "
|
|
491
|
+
relation: Literal["direct", "edge", "reverse"] | None = None
|
|
484
492
|
if isinstance(value_type, ViewEntity | ViewPropertyEntity):
|
|
485
|
-
relation = "
|
|
493
|
+
relation = "edge" if prop.is_list else "direct"
|
|
486
494
|
|
|
487
495
|
container: ContainerEntity | None = None
|
|
488
496
|
container_property: str | None = None
|
|
489
497
|
is_list: bool | None = prop.is_list
|
|
490
498
|
nullable: bool | None = not prop.is_mandatory
|
|
491
|
-
if relation == "
|
|
492
|
-
is_list = None
|
|
499
|
+
if relation == "edge":
|
|
493
500
|
nullable = None
|
|
494
501
|
elif relation == "direct":
|
|
495
502
|
nullable = True
|
|
@@ -504,7 +511,7 @@ class _InformationRulesConverter:
|
|
|
504
511
|
value_type=value_type,
|
|
505
512
|
nullable=nullable,
|
|
506
513
|
is_list=is_list,
|
|
507
|
-
|
|
514
|
+
connection=relation,
|
|
508
515
|
default=prop.default,
|
|
509
516
|
reference=prop.reference,
|
|
510
517
|
container=container,
|