cognite-neat 0.88.3__py3-none-any.whl → 0.89.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.

Files changed (75) hide show
  1. cognite/neat/_version.py +1 -1
  2. cognite/neat/constants.py +3 -0
  3. cognite/neat/graph/extractors/_mock_graph_generator.py +2 -1
  4. cognite/neat/issues/_base.py +2 -1
  5. cognite/neat/issues/errors/__init__.py +2 -1
  6. cognite/neat/issues/errors/_general.py +7 -0
  7. cognite/neat/issues/warnings/_models.py +1 -1
  8. cognite/neat/issues/warnings/user_modeling.py +1 -1
  9. cognite/neat/rules/_shared.py +49 -6
  10. cognite/neat/rules/analysis/_base.py +1 -1
  11. cognite/neat/rules/exporters/_base.py +7 -18
  12. cognite/neat/rules/exporters/_rules2dms.py +8 -18
  13. cognite/neat/rules/exporters/_rules2excel.py +5 -12
  14. cognite/neat/rules/exporters/_rules2ontology.py +9 -19
  15. cognite/neat/rules/exporters/_rules2yaml.py +3 -6
  16. cognite/neat/rules/importers/_base.py +7 -52
  17. cognite/neat/rules/importers/_dms2rules.py +171 -115
  18. cognite/neat/rules/importers/_dtdl2rules/dtdl_converter.py +26 -18
  19. cognite/neat/rules/importers/_dtdl2rules/dtdl_importer.py +14 -30
  20. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2classes.py +7 -3
  21. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2metadata.py +3 -3
  22. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2properties.py +18 -11
  23. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2rules.py +9 -18
  24. cognite/neat/rules/importers/_rdf/_inference2rules.py +10 -33
  25. cognite/neat/rules/importers/_rdf/_owl2rules/_owl2rules.py +9 -20
  26. cognite/neat/rules/importers/_rdf/_shared.py +1 -1
  27. cognite/neat/rules/importers/_spreadsheet2rules.py +22 -86
  28. cognite/neat/rules/importers/_yaml2rules.py +14 -41
  29. cognite/neat/rules/models/__init__.py +21 -5
  30. cognite/neat/rules/models/_base_input.py +162 -0
  31. cognite/neat/rules/models/{_base.py → _base_rules.py} +1 -12
  32. cognite/neat/rules/models/asset/__init__.py +5 -2
  33. cognite/neat/rules/models/asset/_rules.py +2 -20
  34. cognite/neat/rules/models/asset/_rules_input.py +40 -115
  35. cognite/neat/rules/models/asset/_validation.py +1 -1
  36. cognite/neat/rules/models/data_types.py +150 -44
  37. cognite/neat/rules/models/dms/__init__.py +19 -7
  38. cognite/neat/rules/models/dms/_exporter.py +72 -26
  39. cognite/neat/rules/models/dms/_rules.py +42 -155
  40. cognite/neat/rules/models/dms/_rules_input.py +186 -254
  41. cognite/neat/rules/models/dms/_serializer.py +44 -3
  42. cognite/neat/rules/models/dms/_validation.py +3 -4
  43. cognite/neat/rules/models/domain.py +52 -1
  44. cognite/neat/rules/models/entities/__init__.py +63 -0
  45. cognite/neat/rules/models/entities/_constants.py +73 -0
  46. cognite/neat/rules/models/entities/_loaders.py +76 -0
  47. cognite/neat/rules/models/entities/_multi_value.py +67 -0
  48. cognite/neat/rules/models/{entities.py → entities/_single_value.py} +74 -232
  49. cognite/neat/rules/models/entities/_types.py +86 -0
  50. cognite/neat/rules/models/{wrapped_entities.py → entities/_wrapped.py} +1 -1
  51. cognite/neat/rules/models/information/__init__.py +10 -2
  52. cognite/neat/rules/models/information/_rules.py +3 -14
  53. cognite/neat/rules/models/information/_rules_input.py +57 -204
  54. cognite/neat/rules/models/information/_validation.py +1 -1
  55. cognite/neat/rules/transformers/__init__.py +21 -0
  56. cognite/neat/rules/transformers/_base.py +69 -3
  57. cognite/neat/rules/{models/information/_converter.py → transformers/_converters.py} +216 -20
  58. cognite/neat/rules/transformers/_map_onto.py +97 -0
  59. cognite/neat/rules/transformers/_pipelines.py +61 -0
  60. cognite/neat/rules/transformers/_verification.py +136 -0
  61. cognite/neat/store/_provenance.py +10 -1
  62. cognite/neat/utils/cdf/data_classes.py +20 -0
  63. cognite/neat/utils/regex_patterns.py +6 -0
  64. cognite/neat/workflows/steps/lib/current/rules_exporter.py +106 -37
  65. cognite/neat/workflows/steps/lib/current/rules_importer.py +24 -22
  66. {cognite_neat-0.88.3.dist-info → cognite_neat-0.89.0.dist-info}/METADATA +1 -1
  67. {cognite_neat-0.88.3.dist-info → cognite_neat-0.89.0.dist-info}/RECORD +71 -66
  68. cognite/neat/rules/models/_constants.py +0 -2
  69. cognite/neat/rules/models/_types/__init__.py +0 -19
  70. cognite/neat/rules/models/asset/_converter.py +0 -4
  71. cognite/neat/rules/models/dms/_converter.py +0 -143
  72. /cognite/neat/rules/models/{_types/_field.py → _types.py} +0 -0
  73. {cognite_neat-0.88.3.dist-info → cognite_neat-0.89.0.dist-info}/LICENSE +0 -0
  74. {cognite_neat-0.88.3.dist-info → cognite_neat-0.89.0.dist-info}/WHEEL +0 -0
  75. {cognite_neat-0.88.3.dist-info → cognite_neat-0.89.0.dist-info}/entry_points.txt +0 -0
@@ -1,24 +1,29 @@
1
- from collections.abc import Sequence
1
+ import re
2
2
  from dataclasses import dataclass
3
3
  from datetime import datetime
4
- from typing import Any, Literal, cast, overload
4
+ from typing import Any, Literal
5
5
 
6
- from cognite.neat.rules.models._base import DataModelType, ExtensionCategory, SchemaCompleteness, _add_alias
6
+ from cognite.client import data_modeling as dm
7
+
8
+ from cognite.neat.rules.models._base_input import InputComponent, InputRules
7
9
  from cognite.neat.rules.models.data_types import DataType
8
10
  from cognite.neat.rules.models.entities import (
9
11
  ClassEntity,
10
12
  ContainerEntity,
13
+ DMSNodeEntity,
11
14
  DMSUnknownEntity,
12
- Unknown,
15
+ EdgeEntity,
16
+ ReverseConnectionEntity,
13
17
  ViewEntity,
14
- ViewPropertyEntity,
18
+ load_connection,
19
+ load_dms_value_type,
15
20
  )
16
21
 
17
- from ._rules import DMSContainer, DMSMetadata, DMSProperty, DMSRules, DMSView
22
+ from ._rules import _DEFAULT_VERSION, DMSContainer, DMSEnum, DMSMetadata, DMSNode, DMSProperty, DMSRules, DMSView
18
23
 
19
24
 
20
25
  @dataclass
21
- class DMSMetadataInput:
26
+ class DMSInputMetadata(InputComponent[DMSMetadata]):
22
27
  schema_: Literal["complete", "partial", "extended"]
23
28
  space: str
24
29
  external_id: str
@@ -32,51 +37,57 @@ class DMSMetadataInput:
32
37
  updated: datetime | str | None = None
33
38
 
34
39
  @classmethod
35
- def load(cls, data: dict[str, Any] | None) -> "DMSMetadataInput | None":
36
- if data is None:
37
- return None
38
- _add_alias(data, DMSMetadata)
40
+ def _get_verified_cls(cls) -> type[DMSMetadata]:
41
+ return DMSMetadata
42
+
43
+ def dump(self) -> dict[str, Any]: # type: ignore[override]
44
+ output = super().dump()
45
+ if self.created is None:
46
+ output["created"] = datetime.now()
47
+ if self.updated is None:
48
+ output["updated"] = datetime.now()
49
+ return output
50
+
51
+ @classmethod
52
+ def from_data_model(cls, data_model: dm.DataModelApply, has_reference: bool) -> "DMSInputMetadata":
53
+ description, creator = cls._get_description_and_creator(data_model.description)
39
54
  return cls(
40
- schema_=data.get("schema_"), # type: ignore[arg-type]
41
- space=data.get("space"), # type: ignore[arg-type]
42
- external_id=data.get("external_id"), # type: ignore[arg-type]
43
- creator=data.get("creator"), # type: ignore[arg-type]
44
- version=data.get("version"), # type: ignore[arg-type]
45
- # safeguard from empty cell, i.e. if key provided by value None
46
- extension=data.get("extension", "addition") or "addition",
47
- data_model_type=data.get("data_model_type", "solution") or "solution",
48
- name=data.get("name"),
49
- description=data.get("description"),
50
- created=data.get("created"),
51
- updated=data.get("updated"),
55
+ schema_="complete",
56
+ data_model_type="solution" if has_reference else "enterprise",
57
+ space=data_model.space,
58
+ name=data_model.name or None,
59
+ description=description,
60
+ external_id=data_model.external_id,
61
+ version=data_model.version,
62
+ creator=",".join(creator),
63
+ created=datetime.now(),
64
+ updated=datetime.now(),
52
65
  )
53
66
 
54
- def dump(self) -> dict[str, Any]:
55
- return dict(
56
- schema=SchemaCompleteness(self.schema_),
57
- extension=ExtensionCategory(self.extension),
58
- space=self.space,
59
- externalId=self.external_id,
60
- dataModelType=DataModelType(self.data_model_type),
61
- creator=self.creator,
62
- version=self.version,
63
- name=self.name,
64
- description=self.description,
65
- created=self.created or datetime.now(),
66
- updated=self.updated or datetime.now(),
67
- )
67
+ @classmethod
68
+ def _get_description_and_creator(cls, description_raw: str | None) -> tuple[str | None, list[str]]:
69
+ if description_raw and (description_match := re.search(r"Creator: (.+)", description_raw)):
70
+ creator = description_match.group(1).split(", ")
71
+ description = description_raw.replace(description_match.string, "").strip() or None
72
+ elif description_raw:
73
+ creator = ["MISSING"]
74
+ description = description_raw
75
+ else:
76
+ creator = ["MISSING"]
77
+ description = None
78
+ return description, creator
68
79
 
69
80
 
70
81
  @dataclass
71
- class DMSPropertyInput:
82
+ class DMSInputProperty(InputComponent[DMSProperty]):
72
83
  view: str
73
84
  view_property: str | None
74
- value_type: str
75
- property_: str | None
85
+ value_type: str | DataType | ViewEntity | DMSUnknownEntity
86
+ property_: str | None = None
76
87
  class_: str | None = None
77
88
  name: str | None = None
78
89
  description: str | None = None
79
- connection: Literal["direct", "edge", "reverse"] | None = None
90
+ connection: Literal["direct"] | ReverseConnectionEntity | EdgeEntity | str | None = None
80
91
  nullable: bool | None = None
81
92
  immutable: bool | None = None
82
93
  is_list: bool | None = None
@@ -88,153 +99,76 @@ class DMSPropertyInput:
88
99
  constraint: str | list[str] | None = None
89
100
 
90
101
  @classmethod
91
- @overload
92
- def load(cls, data: None) -> None: ...
93
-
94
- @classmethod
95
- @overload
96
- def load(cls, data: dict[str, Any]) -> "DMSPropertyInput": ...
97
-
98
- @classmethod
99
- @overload
100
- def load(cls, data: list[dict[str, Any]]) -> list["DMSPropertyInput"]: ...
101
-
102
- @classmethod
103
- def load(
104
- cls, data: dict[str, Any] | list[dict[str, Any]] | None
105
- ) -> "DMSPropertyInput | list[DMSPropertyInput] | None":
106
- if data is None:
107
- return None
108
- if isinstance(data, list) or (isinstance(data, dict) and isinstance(data.get("data"), list)):
109
- items = cast(list[dict[str, Any]], data.get("data") if isinstance(data, dict) else data)
110
- return [loaded for item in items if (loaded := cls.load(item)) is not None]
111
-
112
- _add_alias(data, DMSProperty)
113
- return cls(
114
- view=data.get("view"), # type: ignore[arg-type]
115
- view_property=data.get("view_property"), # type: ignore[arg-type]
116
- value_type=data.get("value_type"), # type: ignore[arg-type]
117
- property_=data.get("property_"),
118
- class_=data.get("class_"),
119
- name=data.get("name"),
120
- description=data.get("description"),
121
- connection=data.get("connection"),
122
- nullable=data.get("nullable"),
123
- immutable=data.get("immutable"),
124
- is_list=data.get("is_list"),
125
- default=data.get("default"),
126
- reference=data.get("reference"),
127
- container=data.get("container"),
128
- container_property=data.get("container_property"),
129
- index=data.get("index"),
130
- constraint=data.get("constraint"),
102
+ def _get_verified_cls(cls) -> type[DMSProperty]:
103
+ return DMSProperty
104
+
105
+ def dump(self, default_space: str, default_version: str) -> dict[str, Any]: # type: ignore[override]
106
+ output = super().dump()
107
+ output["View"] = ViewEntity.load(self.view, space=default_space, version=default_version)
108
+ output["Value Type"] = load_dms_value_type(self.value_type, default_space, default_version)
109
+ output["Connection"] = load_connection(self.connection, default_space, default_version)
110
+ output["Property (linage)"] = self.property_ or self.view_property
111
+ output["Class (linage)"] = (
112
+ ClassEntity.load(self.class_ or self.view, prefix=default_space, version=default_version)
113
+ if self.class_ or self.view
114
+ else None
131
115
  )
132
-
133
- def dump(self, default_space: str, default_version: str) -> dict[str, Any]:
134
- value_type: DataType | ViewPropertyEntity | ViewEntity | DMSUnknownEntity
135
- if DataType.is_data_type(self.value_type):
136
- value_type = DataType.load(self.value_type)
137
- elif self.value_type == str(Unknown):
138
- value_type = DMSUnknownEntity()
139
- else:
140
- try:
141
- value_type = ViewPropertyEntity.load(self.value_type, space=default_space, version=default_version)
142
- except ValueError:
143
- value_type = ViewEntity.load(self.value_type, space=default_space, version=default_version)
144
-
145
- return {
146
- "View": ViewEntity.load(self.view, space=default_space, version=default_version),
147
- "View Property": self.view_property,
148
- "Value Type": value_type,
149
- "Property (linage)": self.property_ or self.view_property,
150
- "Class (linage)": (
151
- ClassEntity.load(self.class_ or self.view, prefix=default_space, version=default_version)
152
- if self.class_ or self.view
153
- else None
154
- ),
155
- "Name": self.name,
156
- "Description": self.description,
157
- "Connection": self.connection,
158
- "Nullable": self.nullable,
159
- "Immutable": self.immutable,
160
- "Is List": self.is_list,
161
- "Default": self.default,
162
- "Reference": self.reference,
163
- "Container": (
164
- ContainerEntity.load(self.container, space=default_space, version=default_version)
165
- if self.container
166
- else None
167
- ),
168
- "Container Property": self.container_property,
169
- "Index": self.index,
170
- "Constraint": self.constraint,
171
- }
116
+ output["Container"] = (
117
+ ContainerEntity.load(self.container, space=default_space, version=default_version)
118
+ if self.container
119
+ else None
120
+ )
121
+ return output
172
122
 
173
123
 
174
124
  @dataclass
175
- class DMSContainerInput:
125
+ class DMSInputContainer(InputComponent[DMSContainer]):
176
126
  container: str
177
127
  class_: str | None = None
178
128
  name: str | None = None
179
129
  description: str | None = None
180
130
  reference: str | None = None
181
131
  constraint: str | None = None
132
+ used_for: Literal["node", "edge", "all"] | None = None
182
133
 
183
134
  @classmethod
184
- @overload
185
- def load(cls, data: None) -> None: ...
186
-
187
- @classmethod
188
- @overload
189
- def load(cls, data: dict[str, Any]) -> "DMSContainerInput": ...
135
+ def _get_verified_cls(cls) -> type[DMSContainer]:
136
+ return DMSContainer
190
137
 
191
- @classmethod
192
- @overload
193
- def load(cls, data: list[dict[str, Any]]) -> list["DMSContainerInput"]: ...
138
+ def dump(self, default_space: str) -> dict[str, Any]: # type: ignore[override]
139
+ output = super().dump()
140
+ container = ContainerEntity.load(self.container, space=default_space)
141
+ output["Container"] = container
142
+ output["Class (linage)"] = (
143
+ ClassEntity.load(self.class_, prefix=default_space) if self.class_ else container.as_class()
144
+ )
145
+ output["Constraint"] = (
146
+ [ContainerEntity.load(constraint.strip(), space=default_space) for constraint in self.constraint.split(",")]
147
+ if self.constraint
148
+ else None
149
+ )
150
+ return output
194
151
 
195
152
  @classmethod
196
- def load(
197
- cls, data: dict[str, Any] | list[dict[str, Any]] | None
198
- ) -> "DMSContainerInput | list[DMSContainerInput] | None":
199
- if data is None:
200
- return None
201
- if isinstance(data, list) or (isinstance(data, dict) and isinstance(data.get("data"), list)):
202
- items = cast(list[dict[str, Any]], data.get("data") if isinstance(data, dict) else data)
203
- return [loaded for item in items if (loaded := cls.load(item)) is not None]
204
-
205
- _add_alias(data, DMSContainer)
153
+ def from_container(cls, container: dm.ContainerApply) -> "DMSInputContainer":
154
+ constraints: list[str] = []
155
+ for _, constraint_obj in (container.constraints or {}).items():
156
+ if isinstance(constraint_obj, dm.RequiresConstraint):
157
+ constraints.append(str(ContainerEntity.from_id(constraint_obj.require)))
158
+ # UniquenessConstraint it handled in the properties
159
+ container_entity = ContainerEntity.from_id(container.as_id())
206
160
  return cls(
207
- container=data.get("container"), # type: ignore[arg-type]
208
- class_=data.get("class_"),
209
- name=data.get("name"),
210
- description=data.get("description"),
211
- reference=data.get("reference"),
212
- constraint=data.get("constraint"),
161
+ class_=str(container_entity.as_class()),
162
+ container=str(container_entity),
163
+ name=container.name or None,
164
+ description=container.description,
165
+ constraint=", ".join(constraints) or None,
166
+ used_for=container.used_for,
213
167
  )
214
168
 
215
- def dump(self, default_space: str) -> dict[str, Any]:
216
- container = ContainerEntity.load(self.container, space=default_space)
217
- return {
218
- "Container": container,
219
- "Class (linage)": (
220
- ClassEntity.load(self.class_, prefix=default_space) if self.class_ else container.as_class()
221
- ),
222
- "Name": self.name,
223
- "Description": self.description,
224
- "Reference": self.reference,
225
- "Constraint": (
226
- [
227
- ContainerEntity.load(constraint.strip(), space=default_space)
228
- for constraint in self.constraint.split(",")
229
- ]
230
- if self.constraint
231
- else None
232
- ),
233
- }
234
-
235
169
 
236
170
  @dataclass
237
- class DMSViewInput:
171
+ class DMSInputView(InputComponent[DMSView]):
238
172
  view: str
239
173
  class_: str | None = None
240
174
  name: str | None = None
@@ -245,117 +179,115 @@ class DMSViewInput:
245
179
  in_model: bool = True
246
180
 
247
181
  @classmethod
248
- @overload
249
- def load(cls, data: None) -> None: ...
250
-
251
- @classmethod
252
- @overload
253
- def load(cls, data: dict[str, Any]) -> "DMSViewInput": ...
182
+ def _get_verified_cls(cls) -> type[DMSView]:
183
+ return DMSView
254
184
 
255
- @classmethod
256
- @overload
257
- def load(cls, data: list[dict[str, Any]]) -> list["DMSViewInput"]: ...
185
+ def dump(self, default_space: str, default_version: str) -> dict[str, Any]: # type: ignore[override]
186
+ output = super().dump()
187
+ view = ViewEntity.load(self.view, space=default_space, version=default_version)
188
+ output["View"] = view
189
+ output["Class (linage)"] = (
190
+ ClassEntity.load(self.class_, prefix=default_space, version=default_version)
191
+ if self.class_
192
+ else view.as_class()
193
+ )
194
+ output["Implements"] = (
195
+ [
196
+ ViewEntity.load(implement, space=default_space, version=default_version)
197
+ for implement in self.implements.split(",")
198
+ ]
199
+ if self.implements
200
+ else None
201
+ )
202
+ return output
258
203
 
259
204
  @classmethod
260
- def load(cls, data: dict[str, Any] | list[dict[str, Any]] | None) -> "DMSViewInput | list[DMSViewInput] | None":
261
- if data is None:
262
- return None
263
- if isinstance(data, list) or (isinstance(data, dict) and isinstance(data.get("data"), list)):
264
- items = cast(list[dict[str, Any]], data.get("data") if isinstance(data, dict) else data)
265
- return [loaded for item in items if (loaded := cls.load(item)) is not None]
266
- _add_alias(data, DMSView)
205
+ def from_view(cls, view: dm.ViewApply, in_model: bool) -> "DMSInputView":
206
+ view_entity = ViewEntity.from_id(view.as_id())
207
+ class_entity = view_entity.as_class(skip_version=True)
267
208
 
268
209
  return cls(
269
- view=data.get("view"), # type: ignore[arg-type]
270
- class_=data.get("class"),
271
- name=data.get("name"),
272
- description=data.get("description"),
273
- implements=data.get("implements"),
274
- reference=data.get("reference"),
275
- filter_=data.get("filter_"),
276
- in_model=data.get("in_model", True),
210
+ class_=str(class_entity),
211
+ view=str(view_entity),
212
+ description=view.description,
213
+ name=view.name,
214
+ implements=", ".join([str(ViewEntity.from_id(parent, _DEFAULT_VERSION)) for parent in view.implements])
215
+ or None,
216
+ in_model=in_model,
277
217
  )
278
218
 
279
- def dump(self, default_space: str, default_version: str) -> dict[str, Any]:
280
- view = ViewEntity.load(self.view, space=default_space, version=default_version)
281
- return {
282
- "View": view,
283
- "Class (linage)": (
284
- ClassEntity.load(self.class_, prefix=default_space, version=default_version)
285
- if self.class_
286
- else view.as_class()
287
- ),
288
- "Name": self.name,
289
- "Description": self.description,
290
- "Implements": (
291
- [
292
- ViewEntity.load(implement, space=default_space, version=default_version)
293
- for implement in self.implements.split(",")
294
- ]
295
- if self.implements
296
- else None
297
- ),
298
- "Reference": self.reference,
299
- "Filter": self.filter_,
300
- "In Model": self.in_model,
301
- }
302
-
303
219
 
304
220
  @dataclass
305
- class DMSRulesInput:
306
- metadata: DMSMetadataInput
307
- properties: Sequence[DMSPropertyInput]
308
- views: Sequence[DMSViewInput]
309
- containers: Sequence[DMSContainerInput] | None = None
310
- last: "DMSRulesInput | DMSRules | None" = None
311
- reference: "DMSRulesInput | DMSRules | None" = None
221
+ class DMSInputNode(InputComponent[DMSNode]):
222
+ node: str
223
+ usage: Literal["type", "collocation"]
224
+ name: str | None = None
225
+ description: str | None = None
312
226
 
313
227
  @classmethod
314
- @overload
315
- def load(cls, data: dict[str, Any]) -> "DMSRulesInput": ...
228
+ def _get_verified_cls(cls) -> type[DMSNode]:
229
+ return DMSNode
316
230
 
317
231
  @classmethod
318
- @overload
319
- def load(cls, data: None) -> None: ...
232
+ def from_node_type(cls, node_type: dm.NodeApply) -> "DMSInputNode":
233
+ return cls(node=f"{node_type.space}:{node_type.external_id}", usage="type")
234
+
235
+ def dump(self, default_space: str, **_) -> dict[str, Any]: # type: ignore[override]
236
+ output = super().dump()
237
+ output["Node"] = DMSNodeEntity.load(self.node, space=default_space)
238
+ return output
239
+
240
+
241
+ @dataclass
242
+ class DMSInputEnum(InputComponent[DMSEnum]):
243
+ collection: str
244
+ value: str
245
+ name: str | None = None
246
+ description: str | None = None
320
247
 
321
248
  @classmethod
322
- def load(cls, data: dict | None) -> "DMSRulesInput | None":
323
- if data is None:
324
- return None
325
- _add_alias(data, DMSRules)
326
- return cls(
327
- metadata=DMSMetadataInput.load(data.get("metadata")), # type: ignore[arg-type]
328
- properties=DMSPropertyInput.load(data.get("properties")), # type: ignore[arg-type]
329
- views=DMSViewInput.load(data.get("views")), # type: ignore[arg-type]
330
- containers=DMSContainerInput.load(data.get("containers")) or [],
331
- last=DMSRulesInput.load(data.get("last")),
332
- reference=DMSRulesInput.load(data.get("reference")),
333
- )
249
+ def _get_verified_cls(cls) -> type[DMSEnum]:
250
+ return DMSEnum
251
+
334
252
 
335
- def as_rules(self) -> DMSRules:
336
- return DMSRules.model_validate(self.dump())
253
+ @dataclass
254
+ class DMSInputRules(InputRules[DMSRules]):
255
+ metadata: DMSInputMetadata
256
+ properties: list[DMSInputProperty]
257
+ views: list[DMSInputView]
258
+ containers: list[DMSInputContainer] | None = None
259
+ enum: list[DMSInputEnum] | None = None
260
+ nodes: list[DMSInputNode] | None = None
261
+ last: "DMSInputRules | None" = None
262
+ reference: "DMSInputRules | None" = None
263
+
264
+ @classmethod
265
+ def _get_verified_cls(cls) -> type[DMSRules]:
266
+ return DMSRules
337
267
 
338
268
  def dump(self) -> dict[str, Any]:
339
269
  default_space = self.metadata.space
340
270
  default_version = self.metadata.version
341
271
  reference: dict[str, Any] | None = None
342
- if isinstance(self.reference, DMSRulesInput):
272
+ if isinstance(self.reference, DMSInputRules):
343
273
  reference = self.reference.dump()
344
274
  elif isinstance(self.reference, DMSRules):
345
275
  # We need to load through the DMSRulesInput to set the correct default space and version
346
- reference = DMSRulesInput.load(self.reference.model_dump()).dump()
276
+ reference = DMSInputRules.load(self.reference.model_dump()).dump()
347
277
  last: dict[str, Any] | None = None
348
- if isinstance(self.last, DMSRulesInput):
278
+ if isinstance(self.last, DMSInputRules):
349
279
  last = self.last.dump()
350
280
  elif isinstance(self.last, DMSRules):
351
281
  # We need to load through the DMSRulesInput to set the correct default space and version
352
- last = DMSRulesInput.load(self.last.model_dump()).dump()
353
-
354
- return dict(
355
- Metadata=self.metadata.dump(),
356
- Properties=[prop.dump(default_space, default_version) for prop in self.properties],
357
- Views=[view.dump(default_space, default_version) for view in self.views],
358
- Containers=[container.dump(default_space) for container in self.containers or []] or None,
359
- Last=last,
360
- Reference=reference,
361
- )
282
+ last = DMSInputRules.load(self.last.model_dump()).dump()
283
+
284
+ return {
285
+ "Metadata": self.metadata.dump(),
286
+ "Properties": [prop.dump(default_space, default_version) for prop in self.properties],
287
+ "Views": [view.dump(default_space, default_version) for view in self.views],
288
+ "Containers": [container.dump(default_space) for container in self.containers or []] or None,
289
+ "Enum": [enum.dump() for enum in self.enum or []] or None,
290
+ "Nodes": [node_type.dump(default_space) for node_type in self.nodes or []] or None,
291
+ "Last": last,
292
+ "Reference": reference,
293
+ }
@@ -1,7 +1,7 @@
1
1
  from typing import Any, ClassVar, cast
2
2
 
3
3
  from cognite.neat.rules.models import DMSRules
4
- from cognite.neat.rules.models.dms import DMSContainer, DMSProperty, DMSView
4
+ from cognite.neat.rules.models.dms import DMSContainer, DMSEnum, DMSNode, DMSProperty, DMSView
5
5
  from cognite.neat.rules.models.entities import ReferenceEntity, ViewEntity
6
6
 
7
7
 
@@ -23,14 +23,19 @@ class _DMSRulesSerializer:
23
23
  self.view_name = "views"
24
24
  self.container_name = "containers"
25
25
  self.metadata_name = "metadata"
26
+ self.enum_name = "enum"
27
+ self.nodes_name = "nodes"
26
28
  self.prop_view = "view"
27
29
  self.prop_container = "container"
28
30
  self.prop_view_property = "view_property"
29
31
  self.prop_value_type = "value_type"
32
+ self.prop_connection = "connection"
30
33
  self.view_view = "view"
31
34
  self.view_implements = "implements"
32
35
  self.container_container = "container"
33
36
  self.container_constraint = "constraint"
37
+ self.nodes_node = "node"
38
+ self.enum_collection = "collection"
34
39
  self.reference = "Reference" if by_alias else "reference"
35
40
 
36
41
  if by_alias:
@@ -45,6 +50,7 @@ class _DMSRulesSerializer:
45
50
  self.prop_container = DMSProperty.model_fields[self.prop_container].alias or self.prop_container
46
51
  self.prop_view_property = DMSProperty.model_fields[self.prop_view_property].alias or self.prop_view_property
47
52
  self.prop_value_type = DMSProperty.model_fields[self.prop_value_type].alias or self.prop_value_type
53
+ self.prop_connection = DMSProperty.model_fields[self.prop_connection].alias or self.prop_connection
48
54
  self.view_view = DMSView.model_fields[self.view_view].alias or self.view_view
49
55
  self.view_implements = DMSView.model_fields[self.view_implements].alias or self.view_implements
50
56
  self.container_container = (
@@ -53,10 +59,15 @@ class _DMSRulesSerializer:
53
59
  self.container_constraint = (
54
60
  DMSContainer.model_fields[self.container_constraint].alias or self.container_constraint
55
61
  )
62
+ self.nodes_node = DMSNode.model_fields[self.nodes_node].alias or self.nodes_node
63
+
56
64
  self.prop_name = DMSRules.model_fields[self.prop_name].alias or self.prop_name
57
65
  self.view_name = DMSRules.model_fields[self.view_name].alias or self.view_name
58
66
  self.container_name = DMSRules.model_fields[self.container_name].alias or self.container_name
59
67
  self.metadata_name = DMSRules.model_fields[self.metadata_name].alias or self.metadata_name
68
+ self.nodes_name = DMSRules.model_fields[self.nodes_name].alias or self.nodes_name
69
+ self.enum_name = DMSRules.model_fields[self.enum_name].alias or self.enum_name
70
+ self.enum_collection = DMSEnum.model_fields[self.enum_collection].alias or self.enum_collection
60
71
 
61
72
  def clean(self, dumped: dict[str, Any], as_reference: bool) -> dict[str, Any]:
62
73
  # Sorting to get a deterministic order
@@ -69,6 +80,16 @@ class _DMSRulesSerializer:
69
80
  else:
70
81
  dumped.pop(self.container_name, None)
71
82
 
83
+ if enum_data := dumped.get(self.enum_name):
84
+ dumped[self.enum_name] = sorted(enum_data["data"], key=lambda e: e[self.enum_collection])
85
+ else:
86
+ dumped.pop(self.enum_name, None)
87
+
88
+ if node_types_data := dumped.get(self.nodes_name):
89
+ dumped[self.nodes_name] = sorted(node_types_data["data"], key=lambda n: n[self.nodes_node])
90
+ else:
91
+ dumped.pop(self.nodes_name, None)
92
+
72
93
  for prop in dumped[self.prop_name]:
73
94
  if as_reference:
74
95
  view_entity = cast(ViewEntity, ViewEntity.load(prop[self.prop_view]))
@@ -87,8 +108,25 @@ class _DMSRulesSerializer:
87
108
  continue
88
109
  if value := prop.get(field_name):
89
110
  prop[field_name] = value.removeprefix(self.default_space).removesuffix(self.default_version_wrapped)
90
- # Value type can have a property as well
91
- prop[self.prop_value_type] = prop[self.prop_value_type].replace(self.default_version, "")
111
+ if isinstance(prop.get(self.prop_connection), str):
112
+ # Remove default values from connection (type, direction, properties)
113
+ default_type = f"type={self.default_space}{prop[self.view_view]}.{prop[self.prop_view_property]}"
114
+ default_type_space = f"type={self.default_space}"
115
+ default_properties = f"properties={self.default_space}"
116
+ default_direction = "direction=outwards"
117
+ prop[self.prop_connection] = (
118
+ prop[self.prop_connection]
119
+ .replace(self.default_version, "")
120
+ .replace(default_type, "")
121
+ .replace(default_type_space, "type=")
122
+ .replace(default_properties, "properties=")
123
+ .replace(default_direction, "")
124
+ .replace("()", "")
125
+ .replace("(,)", "")
126
+ .replace("(,,)", "")
127
+ .replace("(,", "(")
128
+ .replace(",)", ")")
129
+ )
92
130
 
93
131
  for view in dumped[self.view_name]:
94
132
  if as_reference:
@@ -113,4 +151,7 @@ class _DMSRulesSerializer:
113
151
  container[self.container_constraint] = ",".join(
114
152
  constraint.strip().removeprefix(self.default_space) for constraint in value.split(",")
115
153
  )
154
+
155
+ for node in dumped.get(self.nodes_name, []):
156
+ node[self.nodes_node] = node[self.nodes_node].removeprefix(self.default_space)
116
157
  return dumped
@@ -3,6 +3,7 @@ from typing import Any, ClassVar
3
3
 
4
4
  from cognite.client import data_modeling as dm
5
5
 
6
+ from cognite.neat.constants import DMS_CONTAINER_PROPERTY_SIZE_LIMIT
6
7
  from cognite.neat.issues import IssueList, NeatError, NeatIssue, NeatIssueList
7
8
  from cognite.neat.issues.errors import (
8
9
  PropertyDefinitionDuplicatedError,
@@ -17,11 +18,9 @@ from cognite.neat.issues.warnings.user_modeling import (
17
18
  NotNeatSupportedFilterWarning,
18
19
  ViewPropertyLimitWarning,
19
20
  )
20
- from cognite.neat.rules.models._base import DataModelType, ExtensionCategory, SchemaCompleteness
21
- from cognite.neat.rules.models._constants import DMS_CONTAINER_PROPERTY_SIZE_LIMIT
21
+ from cognite.neat.rules.models._base_rules import DataModelType, ExtensionCategory, SchemaCompleteness
22
22
  from cognite.neat.rules.models.data_types import DataType
23
- from cognite.neat.rules.models.entities import ContainerEntity
24
- from cognite.neat.rules.models.wrapped_entities import RawFilter
23
+ from cognite.neat.rules.models.entities import ContainerEntity, RawFilter
25
24
 
26
25
  from ._rules import DMSProperty, DMSRules
27
26
  from ._schema import DMSSchema