cognite-neat 0.75.9__py3-none-any.whl → 0.76.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cognite/neat/_version.py +1 -1
- cognite/neat/rules/exporters/_models.py +3 -0
- cognite/neat/rules/exporters/_rules2dms.py +46 -4
- cognite/neat/rules/exporters/_rules2excel.py +0 -9
- cognite/neat/rules/importers/_base.py +6 -0
- cognite/neat/rules/importers/_dms2rules.py +319 -125
- cognite/neat/rules/importers/_spreadsheet2rules.py +14 -8
- cognite/neat/rules/issues/base.py +3 -0
- cognite/neat/rules/issues/dms.py +142 -54
- cognite/neat/rules/issues/fileread.py +41 -0
- cognite/neat/rules/issues/importing.py +155 -0
- cognite/neat/rules/issues/spreadsheet.py +12 -9
- cognite/neat/rules/models/entities.py +29 -6
- cognite/neat/rules/models/rules/_base.py +5 -6
- cognite/neat/rules/models/rules/_dms_architect_rules.py +492 -332
- cognite/neat/rules/models/rules/_dms_rules_write.py +57 -47
- 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-0.75.9.dist-info → cognite_neat-0.76.1.dist-info}/METADATA +1 -1
- {cognite_neat-0.75.9.dist-info → cognite_neat-0.76.1.dist-info}/RECORD +25 -24
- {cognite_neat-0.75.9.dist-info → cognite_neat-0.76.1.dist-info}/LICENSE +0 -0
- {cognite_neat-0.75.9.dist-info → cognite_neat-0.76.1.dist-info}/WHEEL +0 -0
- {cognite_neat-0.75.9.dist-info → cognite_neat-0.76.1.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
|
|
@@ -118,7 +118,7 @@ class DMSPropertyWrite:
|
|
|
118
118
|
class_=data.get("class_"),
|
|
119
119
|
name=data.get("name"),
|
|
120
120
|
description=data.get("description"),
|
|
121
|
-
|
|
121
|
+
connection=data.get("connection"),
|
|
122
122
|
nullable=data.get("nullable"),
|
|
123
123
|
is_list=data.get("is_list"),
|
|
124
124
|
default=data.get("default"),
|
|
@@ -143,23 +143,25 @@ class DMSPropertyWrite:
|
|
|
143
143
|
|
|
144
144
|
return {
|
|
145
145
|
"View": ViewEntity.load(self.view, space=default_space, version=default_version),
|
|
146
|
-
"
|
|
146
|
+
"View Property": self.view_property,
|
|
147
147
|
"Value Type": value_type,
|
|
148
|
-
"Property": self.property_ or self.view_property,
|
|
149
|
-
"Class":
|
|
150
|
-
|
|
151
|
-
|
|
148
|
+
"Property (linage)": self.property_ or self.view_property,
|
|
149
|
+
"Class (linage)": (
|
|
150
|
+
ClassEntity.load(self.class_, prefix=default_space, version=default_version) if self.class_ else None
|
|
151
|
+
),
|
|
152
152
|
"Name": self.name,
|
|
153
153
|
"Description": self.description,
|
|
154
|
-
"
|
|
154
|
+
"Connection": self.connection,
|
|
155
155
|
"Nullable": self.nullable,
|
|
156
|
-
"
|
|
156
|
+
"Is List": self.is_list,
|
|
157
157
|
"Default": self.default,
|
|
158
158
|
"Reference": self.reference,
|
|
159
|
-
"Container":
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
159
|
+
"Container": (
|
|
160
|
+
ContainerEntity.load(self.container, space=default_space, version=default_version)
|
|
161
|
+
if self.container
|
|
162
|
+
else None
|
|
163
|
+
),
|
|
164
|
+
"Container Property": self.container_property,
|
|
163
165
|
"Index": self.index,
|
|
164
166
|
"Constraint": self.constraint,
|
|
165
167
|
}
|
|
@@ -208,19 +210,23 @@ class DMSContainerWrite:
|
|
|
208
210
|
|
|
209
211
|
def dump(self, default_space: str) -> dict[str, Any]:
|
|
210
212
|
container = ContainerEntity.load(self.container, space=default_space)
|
|
211
|
-
return
|
|
212
|
-
Container
|
|
213
|
-
Class
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
213
|
+
return {
|
|
214
|
+
"Container": container,
|
|
215
|
+
"Class (linage)": (
|
|
216
|
+
ClassEntity.load(self.class_, prefix=default_space) if self.class_ else container.as_class()
|
|
217
|
+
),
|
|
218
|
+
"Name": self.name,
|
|
219
|
+
"Description": self.description,
|
|
220
|
+
"Reference": self.reference,
|
|
221
|
+
"Constraint": (
|
|
222
|
+
[
|
|
223
|
+
ContainerEntity.load(constraint.strip(), space=default_space)
|
|
224
|
+
for constraint in self.constraint.split(",")
|
|
225
|
+
]
|
|
226
|
+
if self.constraint
|
|
227
|
+
else None
|
|
228
|
+
),
|
|
229
|
+
}
|
|
224
230
|
|
|
225
231
|
|
|
226
232
|
@dataclass
|
|
@@ -268,23 +274,27 @@ class DMSViewWrite:
|
|
|
268
274
|
|
|
269
275
|
def dump(self, default_space: str, default_version: str) -> dict[str, Any]:
|
|
270
276
|
view = ViewEntity.load(self.view, space=default_space, version=default_version)
|
|
271
|
-
return
|
|
272
|
-
View
|
|
273
|
-
Class
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
277
|
+
return {
|
|
278
|
+
"View": view,
|
|
279
|
+
"Class (linage)": (
|
|
280
|
+
ClassEntity.load(self.class_, prefix=default_space, version=default_version)
|
|
281
|
+
if self.class_
|
|
282
|
+
else view.as_class()
|
|
283
|
+
),
|
|
284
|
+
"Name": self.name,
|
|
285
|
+
"Description": self.description,
|
|
286
|
+
"Implements": (
|
|
287
|
+
[
|
|
288
|
+
ViewEntity.load(implement, space=default_space, version=default_version)
|
|
289
|
+
for implement in self.implements.split(",")
|
|
290
|
+
]
|
|
291
|
+
if self.implements
|
|
292
|
+
else None
|
|
293
|
+
),
|
|
294
|
+
"Reference": self.reference,
|
|
295
|
+
"Filter": self.filter_,
|
|
296
|
+
"In Model": self.in_model,
|
|
297
|
+
}
|
|
288
298
|
|
|
289
299
|
|
|
290
300
|
@dataclass
|
|
@@ -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,
|