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.
@@ -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
- api_format = data_model.model_dump(mode="json", by_alias=True)
24
- file_path.write_text(yaml.safe_dump(api_format, sort_keys=False), encoding=self.ENCODING, newline=self.NEW_LINE)
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.22"
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.22
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=G9cqezy_SH8VdhW4o862qBHh_QcbzfP6O1Yyjvdpeog,1579
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=kKD18-Bg_G-w11Cs7Wv_TKV0C_q62Pm2RKLpOz27ar4,2642
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=5Lka1Cg-SaP9Ued0cku0leG1Sjx76JQ9XcBZK_dtfuM,8520
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=Hfg3XAZ55nMEf14PYaan3dSoGcksNgatAnU-8OnQxPQ,45
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.22.dist-info/WHEEL,sha256=XjEbIc5-wIORjWaafhI6vBtlxDBp7S9KiujWF1EM7Ak,79
328
- cognite_neat-1.0.22.dist-info/METADATA,sha256=mctTUqJ2MXTzVNKQPCDhhCFzHim1f7W5tTHhVtdytac,6689
329
- cognite_neat-1.0.22.dist-info/RECORD,,
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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.25
2
+ Generator: uv 0.9.26
3
3
  Root-Is-Purelib: true
4
- Tag: py3-none-any
4
+ Tag: py3-none-any