cognite-neat 1.0.22__py3-none-any.whl → 1.0.24__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/_data_model/exporters/_api_exporter.py +90 -5
- cognite/neat/_data_model/validation/dms/__init__.py +2 -0
- cognite/neat/_data_model/validation/dms/_containers.py +38 -0
- cognite/neat/_version.py +1 -1
- {cognite_neat-1.0.22.dist-info → cognite_neat-1.0.24.dist-info}/METADATA +3 -3
- {cognite_neat-1.0.22.dist-info → cognite_neat-1.0.24.dist-info}/RECORD +7 -7
- {cognite_neat-1.0.22.dist-info → cognite_neat-1.0.24.dist-info}/WHEEL +2 -2
|
@@ -1,9 +1,16 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
import zipfile
|
|
3
|
+
from collections.abc import Iterator
|
|
1
4
|
from pathlib import Path
|
|
2
5
|
|
|
3
6
|
import yaml
|
|
7
|
+
from pydantic import BaseModel
|
|
4
8
|
|
|
5
9
|
from cognite.neat._data_model.exporters._base import DMSExporter, DMSFileExporter
|
|
6
10
|
from cognite.neat._data_model.models.dms import RequestSchema
|
|
11
|
+
from cognite.neat._data_model.models.dms._container import ContainerRequest
|
|
12
|
+
from cognite.neat._data_model.models.dms._references import NodeReference
|
|
13
|
+
from cognite.neat._data_model.models.dms._views import ViewRequest
|
|
7
14
|
|
|
8
15
|
|
|
9
16
|
class DMSAPIExporter(DMSExporter[RequestSchema]):
|
|
@@ -16,12 +23,90 @@ class DMSAPIExporter(DMSExporter[RequestSchema]):
|
|
|
16
23
|
|
|
17
24
|
class DMSAPIYAMLExporter(DMSAPIExporter, DMSFileExporter[RequestSchema]):
|
|
18
25
|
def export_to_file(self, data_model: RequestSchema, file_path: Path) -> None:
|
|
19
|
-
"""Export the data model to a YAML file in API format.
|
|
20
|
-
if file_path.suffix.lower() not in {".yaml", ".yml"}:
|
|
21
|
-
raise ValueError("The file path must have a .yaml or .yml extension.")
|
|
26
|
+
"""Export the data model to a YAML files or zip file in API format.
|
|
22
27
|
|
|
23
|
-
|
|
24
|
-
|
|
28
|
+
Args:
|
|
29
|
+
data_model: Request schema
|
|
30
|
+
file_path: The directory or zip file to save the schema to.
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
if file_path.is_dir():
|
|
35
|
+
self._export_to_directory(data_model, file_path)
|
|
36
|
+
else:
|
|
37
|
+
self._export_to_zip_file(data_model, file_path)
|
|
38
|
+
|
|
39
|
+
def _export_to_directory(self, data_model: RequestSchema, directory: Path) -> None:
|
|
40
|
+
"""Save the schema to a directory as YAML files. This is compatible with the Cognite-Toolkit convention.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
data_model: Request schema
|
|
44
|
+
directory: The directory to save the schema to.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
subdir = directory / "data_models"
|
|
48
|
+
subdir.mkdir(parents=True, exist_ok=True)
|
|
49
|
+
|
|
50
|
+
for file_path, yaml_content in self._generate_yaml_entries(data_model):
|
|
51
|
+
full_path = subdir / file_path
|
|
52
|
+
# Create parent directories if needed (e.g., for views/, containers/, nodes/)
|
|
53
|
+
full_path.parent.mkdir(parents=True, exist_ok=True)
|
|
54
|
+
full_path.write_text(
|
|
55
|
+
yaml_content,
|
|
56
|
+
encoding=self.ENCODING,
|
|
57
|
+
newline=self.NEW_LINE,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def _export_to_zip_file(self, data_model: RequestSchema, zip_file: Path) -> None:
|
|
61
|
+
"""Save the schema as a zip file containing a directory as YAML files.
|
|
62
|
+
This is compatible with the Cognite-Toolkit convention.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
data_model: Request schema
|
|
66
|
+
zip_file: The zip file path to save the schema to.
|
|
67
|
+
"""
|
|
68
|
+
if zip_file.suffix not in {".zip"}:
|
|
69
|
+
warnings.warn("File extension is not .zip, adding it to the file name", stacklevel=2)
|
|
70
|
+
zip_file = zip_file.with_suffix(".zip")
|
|
71
|
+
|
|
72
|
+
with zipfile.ZipFile(zip_file, "w") as zip_ref:
|
|
73
|
+
for file_path, yaml_content in self._generate_yaml_entries(data_model):
|
|
74
|
+
zip_ref.writestr(f"data_models/{file_path}", yaml_content)
|
|
75
|
+
|
|
76
|
+
def _generate_yaml_entries(self, data_model: RequestSchema) -> Iterator[tuple[str, str]]:
|
|
77
|
+
"""Generate file paths and YAML content for all data model components.
|
|
78
|
+
|
|
79
|
+
This helper method eliminates duplication by centralizing the logic for
|
|
80
|
+
iterating through spaces, views, containers, and node types.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
data_model: Request schema
|
|
84
|
+
|
|
85
|
+
Yields:
|
|
86
|
+
Tuples of (file_path, yaml_content) for each component.
|
|
87
|
+
File paths are relative to the data_models directory.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
def _dump(item: BaseModel) -> str:
|
|
91
|
+
return yaml.safe_dump(item.model_dump(mode="json", by_alias=True), sort_keys=False)
|
|
92
|
+
|
|
93
|
+
# Export spaces
|
|
94
|
+
for space in data_model.spaces:
|
|
95
|
+
yield f"{space.space}.space.yaml", _dump(space)
|
|
96
|
+
|
|
97
|
+
# Export data model
|
|
98
|
+
yield f"{data_model.data_model.external_id}.datamodel.yaml", _dump(data_model.data_model)
|
|
99
|
+
|
|
100
|
+
component_configs: list[tuple[str, list[ViewRequest] | list[ContainerRequest] | list[NodeReference]]] = [
|
|
101
|
+
("views", data_model.views),
|
|
102
|
+
("containers", data_model.containers),
|
|
103
|
+
("nodes", data_model.node_types),
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
for dir_prefix, components in component_configs:
|
|
107
|
+
file_suffix = dir_prefix.removesuffix("s")
|
|
108
|
+
for component in components:
|
|
109
|
+
yield f"{dir_prefix}/{component.external_id}.{file_suffix}.yaml", _dump(component)
|
|
25
110
|
|
|
26
111
|
|
|
27
112
|
class DMSAPIJSONExporter(DMSAPIExporter, DMSFileExporter[RequestSchema]):
|
|
@@ -26,6 +26,7 @@ from ._containers import (
|
|
|
26
26
|
ExternalContainerDoesNotExist,
|
|
27
27
|
ExternalContainerPropertyDoesNotExist,
|
|
28
28
|
RequiredContainerDoesNotExist,
|
|
29
|
+
RequiresConstraintCycle,
|
|
29
30
|
)
|
|
30
31
|
from ._limits import (
|
|
31
32
|
ContainerPropertyCountIsOutOfLimits,
|
|
@@ -54,6 +55,7 @@ __all__ = [
|
|
|
54
55
|
"ExternalContainerPropertyDoesNotExist",
|
|
55
56
|
"ImplementedViewNotExisting",
|
|
56
57
|
"RequiredContainerDoesNotExist",
|
|
58
|
+
"RequiresConstraintCycle",
|
|
57
59
|
"ReverseConnectionContainerMissing",
|
|
58
60
|
"ReverseConnectionContainerPropertyMissing",
|
|
59
61
|
"ReverseConnectionContainerPropertyWrongType",
|
|
@@ -197,3 +197,41 @@ class RequiredContainerDoesNotExist(DataModelValidator):
|
|
|
197
197
|
)
|
|
198
198
|
|
|
199
199
|
return errors
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class RequiresConstraintCycle(DataModelValidator):
|
|
203
|
+
"""
|
|
204
|
+
Validates that requires constraints between containers do not form cycles.
|
|
205
|
+
|
|
206
|
+
## What it does
|
|
207
|
+
This validator checks if the requires constraints between containers form a cycle.
|
|
208
|
+
For example, if container A requires B, B requires C, and C requires A, this forms
|
|
209
|
+
a cycle.
|
|
210
|
+
|
|
211
|
+
## Why is this bad?
|
|
212
|
+
Cycles in requires constraints will be rejected by the CDF API. The deployment
|
|
213
|
+
of the data model will fail if any such cycle exists.
|
|
214
|
+
|
|
215
|
+
## Example
|
|
216
|
+
Container `my_space:OrderContainer` requires `my_space:CustomerContainer`, which
|
|
217
|
+
requires `my_space:OrderContainer`. This creates a cycle and will be rejected.
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
code = f"{BASE_CODE}-005"
|
|
221
|
+
issue_type = ConsistencyError
|
|
222
|
+
alpha = True # Still in development
|
|
223
|
+
|
|
224
|
+
def run(self) -> list[ConsistencyError]:
|
|
225
|
+
errors: list[ConsistencyError] = []
|
|
226
|
+
|
|
227
|
+
for cycle in self.validation_resources.requires_constraint_cycles:
|
|
228
|
+
cycle_str = " -> ".join(str(c) for c in cycle) + f" -> {cycle[0]!s}"
|
|
229
|
+
errors.append(
|
|
230
|
+
ConsistencyError(
|
|
231
|
+
message=f"Requires constraints form a cycle: {cycle_str}",
|
|
232
|
+
fix="Remove one of the requires constraints to break the cycle",
|
|
233
|
+
code=self.code,
|
|
234
|
+
)
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
return errors
|
cognite/neat/_version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
__version__ = "1.0.
|
|
1
|
+
__version__ = "1.0.24"
|
|
2
2
|
__engine__ = "^2.0.4"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cognite-neat
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.24
|
|
4
4
|
Summary: Knowledge graph transformation
|
|
5
5
|
Author: Nikola Vasiljevic, Anders Albert
|
|
6
6
|
Author-email: Nikola Vasiljevic <nikola.vasiljevic@cognite.com>, Anders Albert <anders.albert@cognite.com>
|
|
@@ -55,10 +55,10 @@ Requires-Dist: lxml>=5.3.0,<6.0.0 ; extra == 'lxml'
|
|
|
55
55
|
Requires-Dist: oxrdflib>=0.4.0,<0.5.0 ; extra == 'oxi'
|
|
56
56
|
Requires-Dist: pyoxigraph>=0.4.3,<0.5.0 ; extra == 'oxi'
|
|
57
57
|
Requires-Python: >=3.10
|
|
58
|
-
Project-URL: Changelog, https://github.com/cognitedata/neat/releases
|
|
59
58
|
Project-URL: Documentation, https://cognite-neat.readthedocs-hosted.com/
|
|
60
|
-
Project-URL: GitHub, https://github.com/cognitedata/neat
|
|
61
59
|
Project-URL: Homepage, https://cognite-neat.readthedocs-hosted.com/
|
|
60
|
+
Project-URL: GitHub, https://github.com/cognitedata/neat
|
|
61
|
+
Project-URL: Changelog, https://github.com/cognitedata/neat/releases
|
|
62
62
|
Provides-Extra: docs
|
|
63
63
|
Provides-Extra: google
|
|
64
64
|
Provides-Extra: lxml
|
|
@@ -29,7 +29,7 @@ cognite/neat/_data_model/deployer/_differ_view.py,sha256=g1xHwsoxFUaTOTtQa19nntK
|
|
|
29
29
|
cognite/neat/_data_model/deployer/data_classes.py,sha256=cq86u7wuKCcvH-A_cGI_gWzlQCTIG6mrXG2MahdiGco,27299
|
|
30
30
|
cognite/neat/_data_model/deployer/deployer.py,sha256=lEdTh4jOwTxLkSEx-SlcnXUZyPZCUtIzop1zhAe2s44,19681
|
|
31
31
|
cognite/neat/_data_model/exporters/__init__.py,sha256=AskjmB_0Vqib4kN84bWt8-M8nO42QypFf-l-E8oA5W8,482
|
|
32
|
-
cognite/neat/_data_model/exporters/_api_exporter.py,sha256=
|
|
32
|
+
cognite/neat/_data_model/exporters/_api_exporter.py,sha256=nBDHx9dGbaje0T4IEQv0Kulk2Yu7FkPXgXK_MgLbE50,4948
|
|
33
33
|
cognite/neat/_data_model/exporters/_base.py,sha256=rG_qAU5i5Hh5hUMep2UmDFFZID4x3LEenL6Z5C6N8GQ,646
|
|
34
34
|
cognite/neat/_data_model/exporters/_table_exporter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
35
35
|
cognite/neat/_data_model/exporters/_table_exporter/exporter.py,sha256=4BPu_Chtjh1EyOaKbThXYohsqllVOkCbSoNekNZuBXc,5159
|
|
@@ -74,12 +74,12 @@ cognite/neat/_data_model/models/entities/_data_types.py,sha256=DfdEWGek7gODro-_0
|
|
|
74
74
|
cognite/neat/_data_model/models/entities/_identifiers.py,sha256=a7ojJKY1ErZgUANHscEwkctX4RJ7bWEEWOQt5g5Tsdk,1915
|
|
75
75
|
cognite/neat/_data_model/models/entities/_parser.py,sha256=zef_pSDZYMZrJl4IKreFDR577KutfhtN1xpH3Ayjt2o,7669
|
|
76
76
|
cognite/neat/_data_model/validation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
77
|
-
cognite/neat/_data_model/validation/dms/__init__.py,sha256=
|
|
77
|
+
cognite/neat/_data_model/validation/dms/__init__.py,sha256=STb0mYmyD-E6CfhjaXNOgWM1LvYSm59m6-Khu9cEhNI,2702
|
|
78
78
|
cognite/neat/_data_model/validation/dms/_ai_readiness.py,sha256=bffMQJ5pqumU5P3KaEdQP67OO5eMKqzN2BAWbUjG6KE,16143
|
|
79
79
|
cognite/neat/_data_model/validation/dms/_base.py,sha256=G_gMPTgKwyBW62UcCkKIBVHWp9ufAZPJ2p7o69_dJI0,820
|
|
80
80
|
cognite/neat/_data_model/validation/dms/_connections.py,sha256=-kUXf2_3V50ckxwXRwJoTHsKkS5zxiBKkkkHg8Dm4WI,30353
|
|
81
81
|
cognite/neat/_data_model/validation/dms/_consistency.py,sha256=IKSUoRQfQQcsymviESW9VuTFX7jsZMXfsObeZHPdov4,2435
|
|
82
|
-
cognite/neat/_data_model/validation/dms/_containers.py,sha256=
|
|
82
|
+
cognite/neat/_data_model/validation/dms/_containers.py,sha256=8pVnmeX6G9tQaGzzwRB_40y7TYUm4guaNbRiFgoGILU,9895
|
|
83
83
|
cognite/neat/_data_model/validation/dms/_limits.py,sha256=U7z8sN-kAyJsF5hYHPNBBg25Fvz1F8njhzYVSQOIiOU,14779
|
|
84
84
|
cognite/neat/_data_model/validation/dms/_orchestrator.py,sha256=qiuUSUmNhekFyBARUUO2yhG-X9AeU_LL49UrJ65JXFA,2964
|
|
85
85
|
cognite/neat/_data_model/validation/dms/_views.py,sha256=pRdnj5ZBnHNnbLKleXGbipteGma8_l5AYsDIfqgAil4,6345
|
|
@@ -321,9 +321,9 @@ cognite/neat/_v0/session/_template.py,sha256=BNcvrW5y7LWzRM1XFxZkfR1Nc7e8UgjBClH
|
|
|
321
321
|
cognite/neat/_v0/session/_to.py,sha256=AnsRSDDdfFyYwSgi0Z-904X7WdLtPfLlR0x1xsu_jAo,19447
|
|
322
322
|
cognite/neat/_v0/session/_wizard.py,sha256=baPJgXAAF3d1bn4nbIzon1gWfJOeS5T43UXRDJEnD3c,1490
|
|
323
323
|
cognite/neat/_v0/session/exceptions.py,sha256=jv52D-SjxGfgqaHR8vnpzo0SOJETIuwbyffSWAxSDJw,3495
|
|
324
|
-
cognite/neat/_version.py,sha256=
|
|
324
|
+
cognite/neat/_version.py,sha256=PO9Xwbm-V67g819NDS3RNL4T-HEXrVj1Uh15fuaKXMw,45
|
|
325
325
|
cognite/neat/legacy.py,sha256=eI2ecxOV8ilGHyLZlN54ve_abtoK34oXognkFv3yvF0,219
|
|
326
326
|
cognite/neat/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
327
|
-
cognite_neat-1.0.
|
|
328
|
-
cognite_neat-1.0.
|
|
329
|
-
cognite_neat-1.0.
|
|
327
|
+
cognite_neat-1.0.24.dist-info/WHEEL,sha256=XV0cjMrO7zXhVAIyyc8aFf1VjZ33Fen4IiJk5zFlC3g,80
|
|
328
|
+
cognite_neat-1.0.24.dist-info/METADATA,sha256=2vPtDebNUHh5VM6_xSvyvtiDYEegtjFKofJk2siB6Iw,6689
|
|
329
|
+
cognite_neat-1.0.24.dist-info/RECORD,,
|