cognite-neat 0.88.3__py3-none-any.whl → 0.90.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 (84) hide show
  1. cognite/neat/_version.py +1 -1
  2. cognite/neat/constants.py +6 -3
  3. cognite/neat/graph/extractors/__init__.py +2 -0
  4. cognite/neat/graph/extractors/_classic_cdf/_base.py +2 -2
  5. cognite/neat/graph/extractors/_dms.py +158 -0
  6. cognite/neat/graph/extractors/_mock_graph_generator.py +50 -9
  7. cognite/neat/graph/loaders/_rdf2dms.py +16 -13
  8. cognite/neat/graph/models.py +1 -0
  9. cognite/neat/graph/queries/_base.py +4 -2
  10. cognite/neat/issues/_base.py +3 -1
  11. cognite/neat/issues/errors/__init__.py +2 -1
  12. cognite/neat/issues/errors/_general.py +7 -0
  13. cognite/neat/issues/warnings/__init__.py +2 -0
  14. cognite/neat/issues/warnings/_models.py +1 -1
  15. cognite/neat/issues/warnings/_properties.py +12 -0
  16. cognite/neat/issues/warnings/user_modeling.py +1 -1
  17. cognite/neat/rules/_shared.py +49 -6
  18. cognite/neat/rules/analysis/_base.py +1 -1
  19. cognite/neat/rules/exporters/_base.py +7 -18
  20. cognite/neat/rules/exporters/_rules2dms.py +8 -18
  21. cognite/neat/rules/exporters/_rules2excel.py +5 -12
  22. cognite/neat/rules/exporters/_rules2ontology.py +9 -19
  23. cognite/neat/rules/exporters/_rules2yaml.py +3 -6
  24. cognite/neat/rules/importers/_base.py +7 -52
  25. cognite/neat/rules/importers/_dms2rules.py +172 -116
  26. cognite/neat/rules/importers/_dtdl2rules/dtdl_converter.py +26 -18
  27. cognite/neat/rules/importers/_dtdl2rules/dtdl_importer.py +14 -30
  28. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2classes.py +7 -3
  29. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2metadata.py +3 -3
  30. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2properties.py +18 -11
  31. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2rules.py +9 -18
  32. cognite/neat/rules/importers/_rdf/_inference2rules.py +37 -65
  33. cognite/neat/rules/importers/_rdf/_owl2rules/_owl2rules.py +9 -20
  34. cognite/neat/rules/importers/_rdf/_shared.py +1 -1
  35. cognite/neat/rules/importers/_spreadsheet2rules.py +22 -86
  36. cognite/neat/rules/importers/_yaml2rules.py +14 -41
  37. cognite/neat/rules/models/__init__.py +21 -5
  38. cognite/neat/rules/models/_base_input.py +162 -0
  39. cognite/neat/rules/models/{_base.py → _base_rules.py} +1 -12
  40. cognite/neat/rules/models/asset/__init__.py +5 -2
  41. cognite/neat/rules/models/asset/_rules.py +2 -20
  42. cognite/neat/rules/models/asset/_rules_input.py +40 -115
  43. cognite/neat/rules/models/asset/_validation.py +1 -1
  44. cognite/neat/rules/models/data_types.py +150 -44
  45. cognite/neat/rules/models/dms/__init__.py +19 -7
  46. cognite/neat/rules/models/dms/_exporter.py +82 -39
  47. cognite/neat/rules/models/dms/_rules.py +42 -155
  48. cognite/neat/rules/models/dms/_rules_input.py +186 -254
  49. cognite/neat/rules/models/dms/_serializer.py +44 -3
  50. cognite/neat/rules/models/dms/_validation.py +3 -4
  51. cognite/neat/rules/models/domain.py +52 -1
  52. cognite/neat/rules/models/entities/__init__.py +63 -0
  53. cognite/neat/rules/models/entities/_constants.py +73 -0
  54. cognite/neat/rules/models/entities/_loaders.py +76 -0
  55. cognite/neat/rules/models/entities/_multi_value.py +67 -0
  56. cognite/neat/rules/models/{entities.py → entities/_single_value.py} +74 -232
  57. cognite/neat/rules/models/entities/_types.py +86 -0
  58. cognite/neat/rules/models/{wrapped_entities.py → entities/_wrapped.py} +1 -1
  59. cognite/neat/rules/models/information/__init__.py +10 -2
  60. cognite/neat/rules/models/information/_rules.py +3 -14
  61. cognite/neat/rules/models/information/_rules_input.py +57 -204
  62. cognite/neat/rules/models/information/_validation.py +1 -1
  63. cognite/neat/rules/transformers/__init__.py +21 -0
  64. cognite/neat/rules/transformers/_base.py +69 -3
  65. cognite/neat/rules/{models/information/_converter.py → transformers/_converters.py} +226 -21
  66. cognite/neat/rules/transformers/_map_onto.py +97 -0
  67. cognite/neat/rules/transformers/_pipelines.py +61 -0
  68. cognite/neat/rules/transformers/_verification.py +136 -0
  69. cognite/neat/store/_base.py +2 -2
  70. cognite/neat/store/_provenance.py +10 -1
  71. cognite/neat/utils/cdf/data_classes.py +20 -0
  72. cognite/neat/utils/regex_patterns.py +6 -0
  73. cognite/neat/workflows/steps/lib/current/rules_exporter.py +106 -37
  74. cognite/neat/workflows/steps/lib/current/rules_importer.py +24 -22
  75. {cognite_neat-0.88.3.dist-info → cognite_neat-0.90.0.dist-info}/METADATA +1 -1
  76. {cognite_neat-0.88.3.dist-info → cognite_neat-0.90.0.dist-info}/RECORD +80 -74
  77. cognite/neat/rules/models/_constants.py +0 -2
  78. cognite/neat/rules/models/_types/__init__.py +0 -19
  79. cognite/neat/rules/models/asset/_converter.py +0 -4
  80. cognite/neat/rules/models/dms/_converter.py +0 -143
  81. /cognite/neat/rules/models/{_types/_field.py → _types.py} +0 -0
  82. {cognite_neat-0.88.3.dist-info → cognite_neat-0.90.0.dist-info}/LICENSE +0 -0
  83. {cognite_neat-0.88.3.dist-info → cognite_neat-0.90.0.dist-info}/WHEEL +0 -0
  84. {cognite_neat-0.88.3.dist-info → cognite_neat-0.90.0.dist-info}/entry_points.txt +0 -0
@@ -1,10 +1,11 @@
1
1
  import warnings
2
2
  from collections import defaultdict
3
- from collections.abc import Sequence
3
+ from collections.abc import Collection, Sequence
4
4
  from typing import Any, cast
5
5
 
6
6
  from cognite.client.data_classes import data_modeling as dm
7
7
  from cognite.client.data_classes.data_modeling.containers import BTreeIndex
8
+ from cognite.client.data_classes.data_modeling.data_types import EnumValue as DMSEnumValue
8
9
  from cognite.client.data_classes.data_modeling.data_types import ListablePropertyType
9
10
  from cognite.client.data_classes.data_modeling.views import (
10
11
  SingleEdgeConnectionApply,
@@ -12,6 +13,7 @@ from cognite.client.data_classes.data_modeling.views import (
12
13
  ViewPropertyApply,
13
14
  )
14
15
 
16
+ from cognite.neat.issues.errors import NeatTypeError, ResourceNotFoundError
15
17
  from cognite.neat.issues.warnings import NotSupportedWarning, PropertyNotFoundWarning
16
18
  from cognite.neat.issues.warnings.user_modeling import (
17
19
  EmptyContainerWarning,
@@ -19,20 +21,25 @@ from cognite.neat.issues.warnings.user_modeling import (
19
21
  HasDataFilterOnViewWithReferencesWarning,
20
22
  NodeTypeFilterOnParentViewWarning,
21
23
  )
22
- from cognite.neat.rules.models._base import DataModelType, ExtensionCategory, SchemaCompleteness
23
- from cognite.neat.rules.models.data_types import DataType
24
+ from cognite.neat.rules.models._base_rules import DataModelType, ExtensionCategory, SchemaCompleteness
25
+ from cognite.neat.rules.models.data_types import DataType, Double, Enum, Float
24
26
  from cognite.neat.rules.models.entities import (
27
+ ClassEntity,
25
28
  ContainerEntity,
29
+ DMSFilter,
26
30
  DMSNodeEntity,
27
31
  DMSUnknownEntity,
32
+ EdgeEntity,
33
+ HasDataFilter,
34
+ NodeTypeFilter,
28
35
  ReferenceEntity,
36
+ ReverseConnectionEntity,
37
+ UnitEntity,
29
38
  ViewEntity,
30
- ViewPropertyEntity,
31
39
  )
32
- from cognite.neat.rules.models.wrapped_entities import DMSFilter, HasDataFilter, NodeTypeFilter
33
40
  from cognite.neat.utils.cdf.data_classes import ContainerApplyDict, NodeApplyDict, SpaceApplyDict, ViewApplyDict
34
41
 
35
- from ._rules import DMSMetadata, DMSProperty, DMSRules, DMSView
42
+ from ._rules import DMSEnum, DMSMetadata, DMSProperty, DMSRules, DMSView
36
43
  from ._schema import DMSSchema, PipelineSchema
37
44
 
38
45
 
@@ -111,9 +118,16 @@ class _DMSExporter:
111
118
  ]
112
119
  self._update_with_properties(selected_properties, container_properties_by_id, None)
113
120
 
114
- containers = self._create_containers(container_properties_by_id)
121
+ containers = self._create_containers(container_properties_by_id, rules.enum) # type: ignore[arg-type]
115
122
 
116
- views, node_types = self._create_views_with_node_types(view_properties_by_id)
123
+ views, view_node_type_filters = self._create_views_with_node_types(view_properties_by_id)
124
+ if rules.nodes:
125
+ node_types = NodeApplyDict(
126
+ [node.as_node() for node in rules.nodes]
127
+ + [dm.NodeApply(node.space, node.external_id) for node in view_node_type_filters]
128
+ )
129
+ else:
130
+ node_types = NodeApplyDict([dm.NodeApply(node.space, node.external_id) for node in view_node_type_filters])
117
131
 
118
132
  last_schema: DMSSchema | None = None
119
133
  if self.rules.last:
@@ -184,7 +198,7 @@ class _DMSExporter:
184
198
  def _create_views_with_node_types(
185
199
  self,
186
200
  view_properties_by_id: dict[dm.ViewId, list[DMSProperty]],
187
- ) -> tuple[ViewApplyDict, NodeApplyDict]:
201
+ ) -> tuple[ViewApplyDict, set[dm.NodeId]]:
188
202
  input_views = list(self.rules.views)
189
203
  if self.rules.last:
190
204
  existing = {view.view.as_id() for view in input_views}
@@ -245,17 +259,19 @@ class _DMSExporter:
245
259
  # as they are expected for the solution model.
246
260
  unique_node_types.add(dm.NodeId(space=view.space, external_id=view.external_id))
247
261
 
248
- return views, NodeApplyDict(
249
- [dm.NodeApply(space=node.space, external_id=node.external_id) for node in unique_node_types]
250
- )
262
+ return views, unique_node_types
251
263
 
252
264
  @classmethod
253
265
  def _create_edge_type_from_prop(cls, prop: DMSProperty) -> dm.DirectRelationReference:
254
- if isinstance(prop.reference, ReferenceEntity):
266
+ if isinstance(prop.connection, EdgeEntity) and prop.connection.edge_type is not None:
267
+ return prop.connection.edge_type.as_reference()
268
+ elif isinstance(prop.reference, ReferenceEntity):
255
269
  ref_view_prop = prop.reference.as_view_property_id()
256
270
  return cls._create_edge_type_from_view_id(cast(dm.ViewId, ref_view_prop.source), ref_view_prop.property)
257
- else:
271
+ elif isinstance(prop.value_type, ViewEntity):
258
272
  return cls._create_edge_type_from_view_id(prop.view.as_id(), prop.view_property)
273
+ else:
274
+ raise NeatTypeError(f"Invalid valueType {prop.value_type!r}")
259
275
 
260
276
  @staticmethod
261
277
  def _create_edge_type_from_view_id(view_id: dm.ViewId, property_: str) -> dm.DirectRelationReference:
@@ -268,7 +284,12 @@ class _DMSExporter:
268
284
  def _create_containers(
269
285
  self,
270
286
  container_properties_by_id: dict[dm.ContainerId, list[DMSProperty]],
287
+ enum: Collection[DMSEnum] | None,
271
288
  ) -> ContainerApplyDict:
289
+ enum_values_by_collection: dict[ClassEntity, list[DMSEnum]] = defaultdict(list)
290
+ for enum_value in enum or []:
291
+ enum_values_by_collection[enum_value.collection].append(enum_value)
292
+
272
293
  containers = list(self.rules.containers or [])
273
294
  if self.rules.last:
274
295
  existing = {container.container.as_id() for container in containers}
@@ -297,17 +318,34 @@ class _DMSExporter:
297
318
  type_cls = prop.value_type.dms
298
319
  else:
299
320
  type_cls = dm.DirectRelation
300
- type_: dm.PropertyType
321
+
322
+ args: dict[str, Any] = {}
301
323
  if issubclass(type_cls, ListablePropertyType):
302
- type_ = type_cls(is_list=prop.is_list or False)
303
- else:
304
- type_ = type_cls()
324
+ args["is_list"] = prop.is_list or False
325
+ if isinstance(prop.value_type, Double | Float) and isinstance(prop.value_type.unit, UnitEntity):
326
+ args["unit"] = prop.value_type.unit.as_reference()
327
+ if isinstance(prop.value_type, Enum):
328
+ if prop.value_type.collection not in enum_values_by_collection:
329
+ raise ResourceNotFoundError(
330
+ prop.value_type.collection, "enum collection", prop.container, "container"
331
+ )
332
+ args["unknown_value"] = prop.value_type.unknown_value
333
+ args["values"] = {
334
+ value.value: DMSEnumValue(
335
+ name=value.name,
336
+ description=value.description,
337
+ )
338
+ for value in enum_values_by_collection[prop.value_type.collection]
339
+ }
340
+
341
+ type_ = type_cls(**args)
305
342
  container.properties[prop.container_property] = dm.ContainerProperty(
306
343
  type=type_,
307
344
  # If not set, nullable is True and immutable is False
308
345
  nullable=prop.nullable if prop.nullable is not None else True,
309
346
  immutable=prop.immutable if prop.immutable is not None else False,
310
- default_value=prop.default,
347
+ # Guarding against default value being set for connection properties
348
+ default_value=prop.default if not prop.connection else None,
311
349
  name=prop.name,
312
350
  description=prop.description,
313
351
  )
@@ -463,7 +501,7 @@ class _DMSExporter:
463
501
  description=prop.description,
464
502
  **extra_args,
465
503
  )
466
- elif prop.connection == "edge":
504
+ elif isinstance(prop.connection, EdgeEntity):
467
505
  if isinstance(prop.value_type, ViewEntity):
468
506
  source_view_id = prop.value_type.as_id()
469
507
  else:
@@ -472,6 +510,9 @@ class _DMSExporter:
472
510
  "If this error occurs it is a bug in NEAT, please report"
473
511
  f"Debug Info, Invalid valueType edge: {prop.model_dump_json()}"
474
512
  )
513
+ edge_source: dm.ViewId | None = None
514
+ if prop.connection.properties is not None:
515
+ edge_source = prop.connection.properties.as_id()
475
516
  edge_cls: type[dm.EdgeConnectionApply] = dm.MultiEdgeConnectionApply
476
517
  # If is_list is not set, we default to a MultiEdgeConnection
477
518
  if prop.is_list is False:
@@ -483,13 +524,11 @@ class _DMSExporter:
483
524
  direction="outwards",
484
525
  name=prop.name,
485
526
  description=prop.description,
527
+ edge_source=edge_source,
486
528
  )
487
- elif prop.connection == "reverse":
488
- reverse_prop_id: str | None = None
489
- if isinstance(prop.value_type, ViewPropertyEntity):
490
- source_view_id = prop.value_type.as_view_id()
491
- reverse_prop_id = prop.value_type.property_
492
- elif isinstance(prop.value_type, ViewEntity):
529
+ elif isinstance(prop.connection, ReverseConnectionEntity):
530
+ reverse_prop_id = prop.connection.property_
531
+ if isinstance(prop.value_type, ViewEntity):
493
532
  source_view_id = prop.value_type.as_id()
494
533
  else:
495
534
  # Should have been validated.
@@ -497,16 +536,17 @@ class _DMSExporter:
497
536
  "If this error occurs it is a bug in NEAT, please report"
498
537
  f"Debug Info, Invalid valueType reverse connection: {prop.model_dump_json()}"
499
538
  )
500
- reverse_prop: DMSProperty | None = None
501
- if reverse_prop_id is not None:
502
- reverse_prop = next(
503
- (
504
- prop
505
- for prop in view_properties_by_id.get(source_view_id, [])
506
- if prop.property_ == reverse_prop_id
507
- ),
508
- None,
509
- )
539
+ edge_source = None
540
+ reverse_prop = next(
541
+ (prop for prop in view_properties_by_id.get(source_view_id, []) if prop.property_ == reverse_prop_id),
542
+ None,
543
+ )
544
+ if (
545
+ reverse_prop
546
+ and isinstance(reverse_prop.connection, EdgeEntity)
547
+ and reverse_prop.connection.properties is not None
548
+ ):
549
+ edge_source = reverse_prop.connection.properties.as_id()
510
550
 
511
551
  if reverse_prop is None:
512
552
  warnings.warn(
@@ -520,7 +560,7 @@ class _DMSExporter:
520
560
  stacklevel=2,
521
561
  )
522
562
 
523
- if reverse_prop is None or reverse_prop.connection == "edge":
563
+ if reverse_prop is None or isinstance(reverse_prop.connection, EdgeEntity):
524
564
  inwards_edge_cls = (
525
565
  dm.MultiEdgeConnectionApply if prop.is_list in [True, None] else SingleEdgeConnectionApply
526
566
  )
@@ -530,10 +570,13 @@ class _DMSExporter:
530
570
  name=prop.name,
531
571
  description=prop.description,
532
572
  direction="inwards",
573
+ edge_source=edge_source,
533
574
  )
534
- elif reverse_prop_id and reverse_prop and reverse_prop.connection == "direct":
575
+ elif reverse_prop and reverse_prop.connection == "direct":
535
576
  reverse_direct_cls = (
536
- dm.MultiReverseDirectRelationApply if prop.is_list is True else SingleReverseDirectRelationApply
577
+ dm.MultiReverseDirectRelationApply
578
+ if prop.is_list in [True, None]
579
+ else SingleReverseDirectRelationApply
537
580
  )
538
581
  return reverse_direct_cls(
539
582
  source=source_view_id,
@@ -1,8 +1,6 @@
1
1
  import math
2
- import re
3
2
  import sys
4
3
  import warnings
5
- from collections import defaultdict
6
4
  from datetime import datetime
7
5
  from typing import TYPE_CHECKING, Any, ClassVar, Literal
8
6
 
@@ -16,7 +14,7 @@ from cognite.neat.issues.warnings import (
16
14
  PrincipleMatchingSpaceAndVersionWarning,
17
15
  PrincipleSolutionBuildsOnEnterpriseWarning,
18
16
  )
19
- from cognite.neat.rules.models._base import (
17
+ from cognite.neat.rules.models._base_rules import (
20
18
  BaseMetadata,
21
19
  BaseRules,
22
20
  DataModelType,
@@ -33,24 +31,27 @@ from cognite.neat.rules.models._types import (
33
31
  VersionType,
34
32
  )
35
33
  from cognite.neat.rules.models.data_types import DataType
36
- from cognite.neat.rules.models.domain import DomainRules
37
34
  from cognite.neat.rules.models.entities import (
38
35
  ClassEntity,
39
36
  ContainerEntity,
40
37
  ContainerEntityList,
38
+ DMSNodeEntity,
41
39
  DMSUnknownEntity,
40
+ EdgeEntity,
41
+ HasDataFilter,
42
+ NodeTypeFilter,
43
+ RawFilter,
42
44
  ReferenceEntity,
45
+ ReverseConnectionEntity,
43
46
  URLEntity,
44
47
  ViewEntity,
45
48
  ViewEntityList,
46
- ViewPropertyEntity,
47
49
  )
48
- from cognite.neat.rules.models.wrapped_entities import HasDataFilter, NodeTypeFilter, RawFilter
49
50
 
50
51
  from ._schema import DMSSchema
51
52
 
52
53
  if TYPE_CHECKING:
53
- from cognite.neat.rules.models.information._rules import InformationRules
54
+ pass
54
55
 
55
56
  if sys.version_info >= (3, 11):
56
57
  pass
@@ -139,35 +140,6 @@ class DMSMetadata(BaseMetadata):
139
140
  def as_identifier(self) -> str:
140
141
  return repr(self.as_data_model_id())
141
142
 
142
- @classmethod
143
- def _get_description_and_creator(cls, description_raw: str | None) -> tuple[str | None, list[str]]:
144
- if description_raw and (description_match := re.search(r"Creator: (.+)", description_raw)):
145
- creator = description_match.group(1).split(", ")
146
- description = description_raw.replace(description_match.string, "").strip() or None
147
- elif description_raw:
148
- creator = ["MISSING"]
149
- description = description_raw
150
- else:
151
- creator = ["MISSING"]
152
- description = None
153
- return description, creator
154
-
155
- @classmethod
156
- def from_data_model(cls, data_model: dm.DataModelApply, has_reference: bool) -> "DMSMetadata":
157
- description, creator = cls._get_description_and_creator(data_model.description)
158
- return cls(
159
- schema_=SchemaCompleteness.complete,
160
- data_model_type=DataModelType.solution if has_reference else DataModelType.enterprise,
161
- space=data_model.space,
162
- name=data_model.name or None,
163
- description=description,
164
- external_id=data_model.external_id,
165
- version=data_model.version,
166
- creator=creator,
167
- created=datetime.now(),
168
- updated=datetime.now(),
169
- )
170
-
171
143
  def get_prefix(self) -> str:
172
144
  return self.space
173
145
 
@@ -177,8 +149,8 @@ class DMSProperty(SheetEntity):
177
149
  view_property: str = Field(alias="View Property")
178
150
  name: str | None = Field(alias="Name", default=None)
179
151
  description: str | None = Field(alias="Description", default=None)
180
- connection: Literal["direct", "edge", "reverse"] | None = Field(None, alias="Connection")
181
- value_type: DataType | ViewPropertyEntity | ViewEntity | DMSUnknownEntity = Field(alias="Value Type")
152
+ connection: Literal["direct"] | ReverseConnectionEntity | EdgeEntity | None = Field(None, alias="Connection")
153
+ value_type: DataType | ViewEntity | DMSUnknownEntity = Field(alias="Value Type")
182
154
  nullable: bool | None = Field(default=None, alias="Nullable")
183
155
  immutable: bool | None = Field(default=None, alias="Immutable")
184
156
  is_list: bool | None = Field(default=None, alias="Is List")
@@ -199,25 +171,23 @@ class DMSProperty(SheetEntity):
199
171
 
200
172
  @field_validator("value_type", mode="after")
201
173
  def connections_value_type(
202
- cls, value: ViewPropertyEntity | ViewEntity | DMSUnknownEntity, info: ValidationInfo
203
- ) -> DataType | ViewPropertyEntity | ViewEntity | DMSUnknownEntity:
174
+ cls, value: EdgeEntity | ViewEntity | DMSUnknownEntity, info: ValidationInfo
175
+ ) -> DataType | EdgeEntity | ViewEntity | DMSUnknownEntity:
204
176
  if (connection := info.data.get("connection")) is None:
205
177
  return value
206
178
  if connection == "direct" and not isinstance(value, ViewEntity | DMSUnknownEntity):
207
179
  raise ValueError(f"Direct relation must have a value type that points to a view, got {value}")
208
- elif connection == "edge" and not isinstance(value, ViewEntity):
180
+ elif isinstance(connection, EdgeEntity) and not isinstance(value, ViewEntity):
209
181
  raise ValueError(f"Edge connection must have a value type that points to a view, got {value}")
210
- elif connection == "reverse" and not isinstance(value, ViewPropertyEntity | ViewEntity):
211
- raise ValueError(
212
- f"Reverse connection must have a value type that points to a view or view property, got {value}"
213
- )
182
+ elif isinstance(connection, ReverseConnectionEntity) and not isinstance(value, ViewEntity):
183
+ raise ValueError(f"Reverse connection must have a value type that points to a view, got {value}")
214
184
  return value
215
185
 
216
186
  @field_serializer("value_type", when_used="always")
217
187
  @staticmethod
218
- def as_dms_type(value_type: DataType | ViewPropertyEntity | ViewEntity) -> str:
188
+ def as_dms_type(value_type: DataType | EdgeEntity | ViewEntity) -> str:
219
189
  if isinstance(value_type, DataType):
220
- return value_type.dms._type
190
+ return value_type._suffix_extra_args(value_type.dms._type)
221
191
  else:
222
192
  return str(value_type)
223
193
 
@@ -228,6 +198,7 @@ class DMSContainer(SheetEntity):
228
198
  description: str | None = Field(alias="Description", default=None)
229
199
  reference: URLEntity | ReferenceEntity | None = Field(alias="Reference", default=None, union_mode="left_to_right")
230
200
  constraint: ContainerEntityList | None = Field(None, alias="Constraint")
201
+ used_for: Literal["node", "edge", "all"] | None = Field("all", alias="Used For")
231
202
  class_: ClassEntity = Field(alias="Class (linage)")
232
203
 
233
204
  def as_container(self) -> dm.ContainerApply:
@@ -244,22 +215,7 @@ class DMSContainer(SheetEntity):
244
215
  description=self.description,
245
216
  constraints=constraints or None,
246
217
  properties={},
247
- )
248
-
249
- @classmethod
250
- def from_container(cls, container: dm.ContainerApply) -> "DMSContainer":
251
- constraints: list[ContainerEntity] = []
252
- for _, constraint_obj in (container.constraints or {}).items():
253
- if isinstance(constraint_obj, dm.RequiresConstraint):
254
- constraints.append(ContainerEntity.from_id(constraint_obj.require))
255
- # UniquenessConstraint it handled in the properties
256
- container_entity = ContainerEntity.from_id(container.as_id())
257
- return cls(
258
- class_=container_entity.as_class(),
259
- container=container_entity,
260
- name=container.name or None,
261
- description=container.description,
262
- constraint=constraints or None,
218
+ used_for=self.used_for,
263
219
  )
264
220
 
265
221
 
@@ -292,19 +248,27 @@ class DMSView(SheetEntity):
292
248
  properties={},
293
249
  )
294
250
 
295
- @classmethod
296
- def from_view(cls, view: dm.ViewApply, in_model: bool) -> "DMSView":
297
- view_entity = ViewEntity.from_id(view.as_id())
298
- class_entity = view_entity.as_class(skip_version=True)
299
-
300
- return cls(
301
- class_=class_entity,
302
- view=view_entity,
303
- description=view.description,
304
- name=view.name,
305
- implements=[ViewEntity.from_id(parent, _DEFAULT_VERSION) for parent in view.implements] or None,
306
- in_model=in_model,
307
- )
251
+
252
+ class DMSNode(SheetEntity):
253
+ node: DMSNodeEntity = Field(alias="Node")
254
+ usage: Literal["type", "collection"] = Field(alias="Usage")
255
+ name: str | None = Field(alias="Name", default=None)
256
+ description: str | None = Field(alias="Description", default=None)
257
+
258
+ def as_node(self) -> dm.NodeApply:
259
+ if self.usage == "type":
260
+ return dm.NodeApply(space=self.node.space, external_id=self.node.external_id)
261
+ elif self.usage == "collection":
262
+ raise NotImplementedError("Collection nodes are not supported yet")
263
+ else:
264
+ raise ValueError(f"Unknown usage {self.usage}")
265
+
266
+
267
+ class DMSEnum(SheetEntity):
268
+ collection: ClassEntity = Field(alias="Collection")
269
+ value: str = Field(alias="Value")
270
+ name: str | None = Field(alias="Name", default=None)
271
+ description: str | None = Field(alias="Description", default=None)
308
272
 
309
273
 
310
274
  class DMSRules(BaseRules):
@@ -312,6 +276,8 @@ class DMSRules(BaseRules):
312
276
  properties: SheetList[DMSProperty] = Field(alias="Properties")
313
277
  views: SheetList[DMSView] = Field(alias="Views")
314
278
  containers: SheetList[DMSContainer] | None = Field(None, alias="Containers")
279
+ enum: SheetList[DMSEnum] | None = Field(None, alias="Enum")
280
+ nodes: SheetList[DMSNode] | None = Field(None, alias="Nodes")
315
281
  last: "DMSRules | None" = Field(None, alias="Last", description="The previous version of the data model")
316
282
  reference: "DMSRules | None" = Field(None, alias="Reference")
317
283
 
@@ -401,82 +367,3 @@ class DMSRules(BaseRules):
401
367
  from ._exporter import _DMSExporter
402
368
 
403
369
  return _DMSExporter(self, include_pipeline, instance_space).to_schema()
404
-
405
- def as_information_rules(self) -> "InformationRules":
406
- from ._converter import _DMSRulesConverter
407
-
408
- return _DMSRulesConverter(self).as_information_rules()
409
-
410
- def as_domain_expert_rules(self) -> DomainRules:
411
- from ._converter import _DMSRulesConverter
412
-
413
- return _DMSRulesConverter(self).as_domain_rules()
414
-
415
- def create_reference(
416
- self, reference: "DMSRules", view_extension_mapping: dict[str, str], default_extension: str | None = None
417
- ) -> None:
418
- """Takes this data models and makes it into an extension of the reference data model.
419
-
420
- The argument view_extension_mapping is a dictionary where the keys are views of this data model,
421
- and each value is the view of the reference data model that the view should extend. For example:
422
-
423
- ```python
424
- view_extension_mapping = {"Pump": "Asset"}
425
- ```
426
-
427
- This would make the view "Pump" in this data model extend the view "Asset" in the reference data model.
428
- Note that all the keys in the dictionary must be external ids of views in this data model,
429
- and all the values must be external ids of views in the reference data model.
430
-
431
- Args:
432
- reference: The reference data model
433
- view_extension_mapping: A dictionary mapping views in this data model to views in the reference data model
434
- default_extension: The default view in the reference data model that views in this
435
- data model should extend if no mapping is provided.
436
-
437
- """
438
- if self.reference is not None:
439
- raise ValueError("Reference already exists")
440
- self.reference = reference
441
- view_by_external_id = {view.view.external_id: view for view in self.views}
442
- ref_view_by_external_id = {view.view.external_id: view for view in reference.views}
443
-
444
- if invalid_views := set(view_extension_mapping.keys()) - set(view_by_external_id.keys()):
445
- raise ValueError(f"Views are not in this dat model {invalid_views}")
446
- if invalid_views := set(view_extension_mapping.values()) - set(ref_view_by_external_id.keys()):
447
- raise ValueError(f"Views are not in the reference data model {invalid_views}")
448
- if default_extension and default_extension not in ref_view_by_external_id:
449
- raise ValueError(f"Default extension view not in the reference data model {default_extension}")
450
-
451
- properties_by_view_external_id: dict[str, dict[str, DMSProperty]] = defaultdict(dict)
452
- for prop in self.properties:
453
- properties_by_view_external_id[prop.view.external_id][prop.view_property] = prop
454
-
455
- ref_properties_by_view_external_id: dict[str, dict[str, DMSProperty]] = defaultdict(dict)
456
- for prop in reference.properties:
457
- ref_properties_by_view_external_id[prop.view.external_id][prop.view_property] = prop
458
-
459
- for view_external_id, view in view_by_external_id.items():
460
- if view_external_id in view_extension_mapping:
461
- ref_external_id = view_extension_mapping[view_external_id]
462
- elif default_extension:
463
- ref_external_id = default_extension
464
- else:
465
- continue
466
-
467
- ref_view = ref_view_by_external_id[ref_external_id]
468
- shared_properties = set(properties_by_view_external_id[view_external_id].keys()) & set(
469
- ref_properties_by_view_external_id[ref_external_id].keys()
470
- )
471
- if shared_properties:
472
- if view.implements is None:
473
- view.implements = [ref_view.view]
474
- elif isinstance(view.implements, list) and ref_view.view not in view.implements:
475
- view.implements.append(ref_view.view)
476
- for prop_name in shared_properties:
477
- prop = properties_by_view_external_id[view_external_id][prop_name]
478
- ref_prop = ref_properties_by_view_external_id[ref_external_id][prop_name]
479
- if ref_prop.container and ref_prop.container_property:
480
- prop.container = ref_prop.container
481
- prop.container_property = ref_prop.container_property
482
- prop.reference = ReferenceEntity.from_entity(ref_prop.view, ref_prop.view_property)