cognite-neat 0.88.2__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 (129) hide show
  1. cognite/neat/_version.py +1 -1
  2. cognite/neat/constants.py +3 -0
  3. cognite/neat/graph/__init__.py +0 -3
  4. cognite/neat/graph/extractors/_mock_graph_generator.py +2 -1
  5. cognite/neat/graph/loaders/_base.py +3 -3
  6. cognite/neat/graph/loaders/_rdf2asset.py +24 -25
  7. cognite/neat/graph/loaders/_rdf2dms.py +20 -15
  8. cognite/neat/issues/__init__.py +1 -3
  9. cognite/neat/issues/_base.py +261 -71
  10. cognite/neat/issues/errors/__init__.py +73 -0
  11. cognite/neat/issues/errors/_external.py +67 -0
  12. cognite/neat/issues/errors/_general.py +35 -0
  13. cognite/neat/issues/errors/_properties.py +62 -0
  14. cognite/neat/issues/errors/_resources.py +111 -0
  15. cognite/neat/issues/errors/_workflow.py +36 -0
  16. cognite/neat/issues/formatters.py +1 -1
  17. cognite/neat/issues/warnings/__init__.py +66 -0
  18. cognite/neat/issues/warnings/_external.py +40 -0
  19. cognite/neat/issues/warnings/_general.py +29 -0
  20. cognite/neat/issues/warnings/_models.py +92 -0
  21. cognite/neat/issues/warnings/_properties.py +44 -0
  22. cognite/neat/issues/warnings/_resources.py +55 -0
  23. cognite/neat/issues/warnings/user_modeling.py +113 -0
  24. cognite/neat/rules/_shared.py +53 -2
  25. cognite/neat/rules/analysis/_base.py +1 -1
  26. cognite/neat/rules/exporters/_base.py +7 -18
  27. cognite/neat/rules/exporters/_rules2dms.py +17 -20
  28. cognite/neat/rules/exporters/_rules2excel.py +9 -16
  29. cognite/neat/rules/exporters/_rules2ontology.py +77 -64
  30. cognite/neat/rules/exporters/_rules2yaml.py +6 -9
  31. cognite/neat/rules/exporters/_validation.py +11 -96
  32. cognite/neat/rules/importers/_base.py +9 -58
  33. cognite/neat/rules/importers/_dms2rules.py +188 -135
  34. cognite/neat/rules/importers/_dtdl2rules/dtdl_converter.py +48 -35
  35. cognite/neat/rules/importers/_dtdl2rules/dtdl_importer.py +36 -45
  36. cognite/neat/rules/importers/_dtdl2rules/spec.py +7 -0
  37. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2classes.py +8 -4
  38. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2metadata.py +3 -3
  39. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2properties.py +18 -11
  40. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2rules.py +12 -19
  41. cognite/neat/rules/importers/_rdf/_inference2rules.py +14 -37
  42. cognite/neat/rules/importers/_rdf/_owl2rules/_owl2classes.py +1 -0
  43. cognite/neat/rules/importers/_rdf/_owl2rules/_owl2properties.py +1 -0
  44. cognite/neat/rules/importers/_rdf/_owl2rules/_owl2rules.py +9 -20
  45. cognite/neat/rules/importers/_rdf/_shared.py +4 -4
  46. cognite/neat/rules/importers/_spreadsheet2rules.py +46 -97
  47. cognite/neat/rules/importers/_yaml2rules.py +32 -58
  48. cognite/neat/rules/models/__init__.py +21 -5
  49. cognite/neat/rules/models/_base_input.py +162 -0
  50. cognite/neat/rules/models/{_base.py → _base_rules.py} +1 -12
  51. cognite/neat/rules/models/_rdfpath.py +4 -4
  52. cognite/neat/rules/models/{_types/_field.py → _types.py} +5 -10
  53. cognite/neat/rules/models/asset/__init__.py +5 -2
  54. cognite/neat/rules/models/asset/_rules.py +3 -23
  55. cognite/neat/rules/models/asset/_rules_input.py +40 -115
  56. cognite/neat/rules/models/asset/_validation.py +14 -10
  57. cognite/neat/rules/models/data_types.py +150 -44
  58. cognite/neat/rules/models/dms/__init__.py +19 -7
  59. cognite/neat/rules/models/dms/_exporter.py +102 -34
  60. cognite/neat/rules/models/dms/_rules.py +65 -162
  61. cognite/neat/rules/models/dms/_rules_input.py +186 -254
  62. cognite/neat/rules/models/dms/_schema.py +87 -78
  63. cognite/neat/rules/models/dms/_serializer.py +44 -3
  64. cognite/neat/rules/models/dms/_validation.py +106 -68
  65. cognite/neat/rules/models/domain.py +52 -1
  66. cognite/neat/rules/models/entities/__init__.py +63 -0
  67. cognite/neat/rules/models/entities/_constants.py +73 -0
  68. cognite/neat/rules/models/entities/_loaders.py +76 -0
  69. cognite/neat/rules/models/entities/_multi_value.py +67 -0
  70. cognite/neat/rules/models/{entities.py → entities/_single_value.py} +74 -232
  71. cognite/neat/rules/models/entities/_types.py +86 -0
  72. cognite/neat/rules/models/{wrapped_entities.py → entities/_wrapped.py} +1 -1
  73. cognite/neat/rules/models/information/__init__.py +10 -2
  74. cognite/neat/rules/models/information/_rules.py +10 -22
  75. cognite/neat/rules/models/information/_rules_input.py +57 -204
  76. cognite/neat/rules/models/information/_validation.py +48 -25
  77. cognite/neat/rules/transformers/__init__.py +21 -0
  78. cognite/neat/rules/transformers/_base.py +81 -0
  79. cognite/neat/rules/{models/information/_converter.py → transformers/_converters.py} +217 -21
  80. cognite/neat/rules/transformers/_map_onto.py +97 -0
  81. cognite/neat/rules/transformers/_pipelines.py +61 -0
  82. cognite/neat/rules/transformers/_verification.py +136 -0
  83. cognite/neat/{graph/stores → store}/_provenance.py +10 -1
  84. cognite/neat/utils/auxiliary.py +2 -35
  85. cognite/neat/utils/cdf/data_classes.py +20 -0
  86. cognite/neat/utils/regex_patterns.py +6 -0
  87. cognite/neat/utils/text.py +17 -0
  88. cognite/neat/workflows/base.py +4 -4
  89. cognite/neat/workflows/cdf_store.py +3 -3
  90. cognite/neat/workflows/steps/data_contracts.py +1 -1
  91. cognite/neat/workflows/steps/lib/current/graph_extractor.py +3 -3
  92. cognite/neat/workflows/steps/lib/current/graph_loader.py +2 -2
  93. cognite/neat/workflows/steps/lib/current/graph_store.py +1 -1
  94. cognite/neat/workflows/steps/lib/current/rules_exporter.py +116 -47
  95. cognite/neat/workflows/steps/lib/current/rules_importer.py +30 -28
  96. cognite/neat/workflows/steps/lib/current/rules_validator.py +5 -6
  97. cognite/neat/workflows/steps/lib/io/io_steps.py +5 -5
  98. cognite/neat/workflows/steps_registry.py +4 -5
  99. {cognite_neat-0.88.2.dist-info → cognite_neat-0.89.0.dist-info}/METADATA +1 -1
  100. {cognite_neat-0.88.2.dist-info → cognite_neat-0.89.0.dist-info}/RECORD +105 -106
  101. cognite/neat/exceptions.py +0 -145
  102. cognite/neat/graph/exceptions.py +0 -90
  103. cognite/neat/issues/errors/external.py +0 -21
  104. cognite/neat/issues/errors/properties.py +0 -75
  105. cognite/neat/issues/errors/resources.py +0 -123
  106. cognite/neat/issues/errors/schema.py +0 -0
  107. cognite/neat/issues/neat_warnings/__init__.py +0 -2
  108. cognite/neat/issues/neat_warnings/identifier.py +0 -27
  109. cognite/neat/issues/neat_warnings/models.py +0 -22
  110. cognite/neat/issues/neat_warnings/properties.py +0 -77
  111. cognite/neat/issues/neat_warnings/resources.py +0 -125
  112. cognite/neat/rules/issues/__init__.py +0 -22
  113. cognite/neat/rules/issues/base.py +0 -63
  114. cognite/neat/rules/issues/dms.py +0 -549
  115. cognite/neat/rules/issues/fileread.py +0 -197
  116. cognite/neat/rules/issues/ontology.py +0 -298
  117. cognite/neat/rules/issues/spreadsheet.py +0 -563
  118. cognite/neat/rules/issues/spreadsheet_file.py +0 -151
  119. cognite/neat/rules/issues/tables.py +0 -72
  120. cognite/neat/rules/models/_constants.py +0 -1
  121. cognite/neat/rules/models/_types/__init__.py +0 -19
  122. cognite/neat/rules/models/asset/_converter.py +0 -4
  123. cognite/neat/rules/models/dms/_converter.py +0 -145
  124. cognite/neat/workflows/_exceptions.py +0 -41
  125. /cognite/neat/{graph/stores → store}/__init__.py +0 -0
  126. /cognite/neat/{graph/stores → store}/_base.py +0 -0
  127. {cognite_neat-0.88.2.dist-info → cognite_neat-0.89.0.dist-info}/LICENSE +0 -0
  128. {cognite_neat-0.88.2.dist-info → cognite_neat-0.89.0.dist-info}/WHEEL +0 -0
  129. {cognite_neat-0.88.2.dist-info → cognite_neat-0.89.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,21 +13,33 @@ from cognite.client.data_classes.data_modeling.views import (
12
13
  ViewPropertyApply,
13
14
  )
14
15
 
15
- from cognite.neat.rules import issues
16
- from cognite.neat.rules.models._base import DataModelType, ExtensionCategory, SchemaCompleteness
17
- from cognite.neat.rules.models.data_types import DataType
16
+ from cognite.neat.issues.errors import NeatTypeError, ResourceNotFoundError
17
+ from cognite.neat.issues.warnings import NotSupportedWarning, PropertyNotFoundWarning
18
+ from cognite.neat.issues.warnings.user_modeling import (
19
+ EmptyContainerWarning,
20
+ HasDataFilterOnNoPropertiesViewWarning,
21
+ HasDataFilterOnViewWithReferencesWarning,
22
+ NodeTypeFilterOnParentViewWarning,
23
+ )
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
18
26
  from cognite.neat.rules.models.entities import (
27
+ ClassEntity,
19
28
  ContainerEntity,
29
+ DMSFilter,
20
30
  DMSNodeEntity,
21
31
  DMSUnknownEntity,
32
+ EdgeEntity,
33
+ HasDataFilter,
34
+ NodeTypeFilter,
22
35
  ReferenceEntity,
36
+ ReverseConnectionEntity,
37
+ UnitEntity,
23
38
  ViewEntity,
24
- ViewPropertyEntity,
25
39
  )
26
- from cognite.neat.rules.models.wrapped_entities import DMSFilter, HasDataFilter, NodeTypeFilter
27
40
  from cognite.neat.utils.cdf.data_classes import ContainerApplyDict, NodeApplyDict, SpaceApplyDict, ViewApplyDict
28
41
 
29
- from ._rules import DMSMetadata, DMSProperty, DMSRules, DMSView
42
+ from ._rules import DMSEnum, DMSMetadata, DMSProperty, DMSRules, DMSView
30
43
  from ._schema import DMSSchema, PipelineSchema
31
44
 
32
45
 
@@ -105,9 +118,16 @@ class _DMSExporter:
105
118
  ]
106
119
  self._update_with_properties(selected_properties, container_properties_by_id, None)
107
120
 
108
- containers = self._create_containers(container_properties_by_id)
121
+ containers = self._create_containers(container_properties_by_id, rules.enum) # type: ignore[arg-type]
109
122
 
110
- 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])
111
131
 
112
132
  last_schema: DMSSchema | None = None
113
133
  if self.rules.last:
@@ -178,7 +198,7 @@ class _DMSExporter:
178
198
  def _create_views_with_node_types(
179
199
  self,
180
200
  view_properties_by_id: dict[dm.ViewId, list[DMSProperty]],
181
- ) -> tuple[ViewApplyDict, NodeApplyDict]:
201
+ ) -> tuple[ViewApplyDict, set[dm.NodeId]]:
182
202
  input_views = list(self.rules.views)
183
203
  if self.rules.last:
184
204
  existing = {view.view.as_id() for view in input_views}
@@ -213,7 +233,11 @@ class _DMSExporter:
213
233
  if isinstance(view_filter, NodeTypeFilter):
214
234
  unique_node_types.update(view_filter.nodes)
215
235
  if view.as_id() in parent_views:
216
- warnings.warn(issues.dms.NodeTypeFilterOnParentViewWarning(view.as_id()), stacklevel=2)
236
+ warnings.warn(
237
+ NodeTypeFilterOnParentViewWarning(view.as_id()),
238
+ stacklevel=2,
239
+ )
240
+
217
241
  elif isinstance(view_filter, HasDataFilter) and data_model_type == DataModelType.solution:
218
242
  if dms_view and isinstance(dms_view.reference, ReferenceEntity):
219
243
  references = {dms_view.reference.as_view_id()}
@@ -226,7 +250,8 @@ class _DMSExporter:
226
250
  else:
227
251
  continue
228
252
  warnings.warn(
229
- issues.dms.HasDataFilterOnViewWithReferencesWarning(view.as_id(), list(references)), stacklevel=2
253
+ HasDataFilterOnViewWithReferencesWarning(view.as_id(), frozenset(references)),
254
+ stacklevel=2,
230
255
  )
231
256
 
232
257
  if data_model_type == DataModelType.enterprise:
@@ -234,17 +259,19 @@ class _DMSExporter:
234
259
  # as they are expected for the solution model.
235
260
  unique_node_types.add(dm.NodeId(space=view.space, external_id=view.external_id))
236
261
 
237
- return views, NodeApplyDict(
238
- [dm.NodeApply(space=node.space, external_id=node.external_id) for node in unique_node_types]
239
- )
262
+ return views, unique_node_types
240
263
 
241
264
  @classmethod
242
265
  def _create_edge_type_from_prop(cls, prop: DMSProperty) -> dm.DirectRelationReference:
243
- 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):
244
269
  ref_view_prop = prop.reference.as_view_property_id()
245
270
  return cls._create_edge_type_from_view_id(cast(dm.ViewId, ref_view_prop.source), ref_view_prop.property)
246
- else:
271
+ elif isinstance(prop.value_type, ViewEntity):
247
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}")
248
275
 
249
276
  @staticmethod
250
277
  def _create_edge_type_from_view_id(view_id: dm.ViewId, property_: str) -> dm.DirectRelationReference:
@@ -257,7 +284,12 @@ class _DMSExporter:
257
284
  def _create_containers(
258
285
  self,
259
286
  container_properties_by_id: dict[dm.ContainerId, list[DMSProperty]],
287
+ enum: Collection[DMSEnum] | None,
260
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
+
261
293
  containers = list(self.rules.containers or [])
262
294
  if self.rules.last:
263
295
  existing = {container.container.as_id() for container in containers}
@@ -273,7 +305,10 @@ class _DMSExporter:
273
305
  for container in containers:
274
306
  container_id = container.as_id()
275
307
  if not (container_properties := container_properties_by_id.get(container_id)):
276
- warnings.warn(issues.dms.EmptyContainerWarning(container_id=container_id), stacklevel=2)
308
+ warnings.warn(
309
+ EmptyContainerWarning(container_id),
310
+ stacklevel=2,
311
+ )
277
312
  container_to_drop.add(container_id)
278
313
  continue
279
314
  for prop in container_properties:
@@ -283,11 +318,27 @@ class _DMSExporter:
283
318
  type_cls = prop.value_type.dms
284
319
  else:
285
320
  type_cls = dm.DirectRelation
286
- type_: dm.PropertyType
321
+
322
+ args: dict[str, Any] = {}
287
323
  if issubclass(type_cls, ListablePropertyType):
288
- type_ = type_cls(is_list=prop.is_list or False)
289
- else:
290
- 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)
291
342
  container.properties[prop.container_property] = dm.ContainerProperty(
292
343
  type=type_,
293
344
  # If not set, nullable is True and immutable is False
@@ -409,7 +460,10 @@ class _DMSExporter:
409
460
  if not ref_containers or selected_filter_name == HasDataFilter.name:
410
461
  # Child filter without container properties
411
462
  if selected_filter_name == HasDataFilter.name:
412
- warnings.warn(issues.dms.HasDataFilterOnNoPropertiesViewWarning(view.as_id()), stacklevel=2)
463
+ warnings.warn(
464
+ HasDataFilterOnNoPropertiesViewWarning(view.as_id()),
465
+ stacklevel=2,
466
+ )
413
467
  return NodeTypeFilter(inner=[DMSNodeEntity(space=view.space, externalId=view.external_id)])
414
468
  else:
415
469
  # HasData or not provided (this is the default)
@@ -446,7 +500,7 @@ class _DMSExporter:
446
500
  description=prop.description,
447
501
  **extra_args,
448
502
  )
449
- elif prop.connection == "edge":
503
+ elif isinstance(prop.connection, EdgeEntity):
450
504
  if isinstance(prop.value_type, ViewEntity):
451
505
  source_view_id = prop.value_type.as_id()
452
506
  else:
@@ -455,6 +509,9 @@ class _DMSExporter:
455
509
  "If this error occurs it is a bug in NEAT, please report"
456
510
  f"Debug Info, Invalid valueType edge: {prop.model_dump_json()}"
457
511
  )
512
+ edge_source: dm.ViewId | None = None
513
+ if prop.connection.properties is not None:
514
+ edge_source = prop.connection.properties.as_id()
458
515
  edge_cls: type[dm.EdgeConnectionApply] = dm.MultiEdgeConnectionApply
459
516
  # If is_list is not set, we default to a MultiEdgeConnection
460
517
  if prop.is_list is False:
@@ -466,13 +523,11 @@ class _DMSExporter:
466
523
  direction="outwards",
467
524
  name=prop.name,
468
525
  description=prop.description,
526
+ edge_source=edge_source,
469
527
  )
470
- elif prop.connection == "reverse":
471
- reverse_prop_id: str | None = None
472
- if isinstance(prop.value_type, ViewPropertyEntity):
473
- source_view_id = prop.value_type.as_view_id()
474
- reverse_prop_id = prop.value_type.property_
475
- elif isinstance(prop.value_type, ViewEntity):
528
+ elif isinstance(prop.connection, ReverseConnectionEntity):
529
+ reverse_prop_id = prop.connection.property_
530
+ if isinstance(prop.value_type, ViewEntity):
476
531
  source_view_id = prop.value_type.as_id()
477
532
  else:
478
533
  # Should have been validated.
@@ -481,6 +536,7 @@ class _DMSExporter:
481
536
  f"Debug Info, Invalid valueType reverse connection: {prop.model_dump_json()}"
482
537
  )
483
538
  reverse_prop: DMSProperty | None = None
539
+ edge_source = None
484
540
  if reverse_prop_id is not None:
485
541
  reverse_prop = next(
486
542
  (
@@ -490,14 +546,26 @@ class _DMSExporter:
490
546
  ),
491
547
  None,
492
548
  )
549
+ if (
550
+ reverse_prop
551
+ and isinstance(reverse_prop.connection, EdgeEntity)
552
+ and reverse_prop.connection.properties is not None
553
+ ):
554
+ edge_source = reverse_prop.connection.properties.as_id()
493
555
 
494
556
  if reverse_prop is None:
495
557
  warnings.warn(
496
- issues.dms.ReverseRelationMissingOtherSideWarning(source_view_id, prop.view_property),
558
+ PropertyNotFoundWarning(
559
+ source_view_id,
560
+ "view",
561
+ reverse_prop_id or "MISSING",
562
+ dm.PropertyId(prop.view.as_id(), prop.view_property),
563
+ "view property",
564
+ ),
497
565
  stacklevel=2,
498
566
  )
499
567
 
500
- if reverse_prop is None or reverse_prop.connection == "edge":
568
+ if reverse_prop is None or isinstance(reverse_prop.connection, EdgeEntity):
501
569
  inwards_edge_cls = (
502
570
  dm.MultiEdgeConnectionApply if prop.is_list in [True, None] else SingleEdgeConnectionApply
503
571
  )
@@ -507,6 +575,7 @@ class _DMSExporter:
507
575
  name=prop.name,
508
576
  description=prop.description,
509
577
  direction="inwards",
578
+ edge_source=edge_source,
510
579
  )
511
580
  elif reverse_prop_id and reverse_prop and reverse_prop.connection == "direct":
512
581
  reverse_direct_cls = (
@@ -523,7 +592,6 @@ class _DMSExporter:
523
592
 
524
593
  elif prop.view and prop.view_property and prop.connection:
525
594
  warnings.warn(
526
- issues.dms.UnsupportedConnectionWarning(prop.view.as_id(), prop.view_property, prop.connection or ""),
527
- stacklevel=2,
595
+ NotSupportedWarning(f"{prop.connection} in {prop.view.as_id()!r}.{prop.view_property}"), stacklevel=2
528
596
  )
529
597
  return None
@@ -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
 
@@ -11,9 +9,12 @@ from pydantic import Field, field_serializer, field_validator, model_validator
11
9
  from pydantic.main import IncEx
12
10
  from pydantic_core.core_schema import ValidationInfo
13
11
 
14
- from cognite.neat.rules import issues
15
- from cognite.neat.rules.issues import MultiValueError
16
- from cognite.neat.rules.models._base import (
12
+ from cognite.neat.issues import MultiValueError
13
+ from cognite.neat.issues.warnings import (
14
+ PrincipleMatchingSpaceAndVersionWarning,
15
+ PrincipleSolutionBuildsOnEnterpriseWarning,
16
+ )
17
+ from cognite.neat.rules.models._base_rules import (
17
18
  BaseMetadata,
18
19
  BaseRules,
19
20
  DataModelType,
@@ -30,24 +31,27 @@ from cognite.neat.rules.models._types import (
30
31
  VersionType,
31
32
  )
32
33
  from cognite.neat.rules.models.data_types import DataType
33
- from cognite.neat.rules.models.domain import DomainRules
34
34
  from cognite.neat.rules.models.entities import (
35
35
  ClassEntity,
36
36
  ContainerEntity,
37
37
  ContainerEntityList,
38
+ DMSNodeEntity,
38
39
  DMSUnknownEntity,
40
+ EdgeEntity,
41
+ HasDataFilter,
42
+ NodeTypeFilter,
43
+ RawFilter,
39
44
  ReferenceEntity,
45
+ ReverseConnectionEntity,
40
46
  URLEntity,
41
47
  ViewEntity,
42
48
  ViewEntityList,
43
- ViewPropertyEntity,
44
49
  )
45
- from cognite.neat.rules.models.wrapped_entities import HasDataFilter, NodeTypeFilter, RawFilter
46
50
 
47
51
  from ._schema import DMSSchema
48
52
 
49
53
  if TYPE_CHECKING:
50
- from cognite.neat.rules.models.information._rules import InformationRules
54
+ pass
51
55
 
52
56
  if sys.version_info >= (3, 11):
53
57
  pass
@@ -136,35 +140,6 @@ class DMSMetadata(BaseMetadata):
136
140
  def as_identifier(self) -> str:
137
141
  return repr(self.as_data_model_id())
138
142
 
139
- @classmethod
140
- def _get_description_and_creator(cls, description_raw: str | None) -> tuple[str | None, list[str]]:
141
- if description_raw and (description_match := re.search(r"Creator: (.+)", description_raw)):
142
- creator = description_match.group(1).split(", ")
143
- description = description_raw.replace(description_match.string, "").strip() or None
144
- elif description_raw:
145
- creator = ["MISSING"]
146
- description = description_raw
147
- else:
148
- creator = ["MISSING"]
149
- description = None
150
- return description, creator
151
-
152
- @classmethod
153
- def from_data_model(cls, data_model: dm.DataModelApply, has_reference: bool) -> "DMSMetadata":
154
- description, creator = cls._get_description_and_creator(data_model.description)
155
- return cls(
156
- schema_=SchemaCompleteness.complete,
157
- data_model_type=DataModelType.solution if has_reference else DataModelType.enterprise,
158
- space=data_model.space,
159
- name=data_model.name or None,
160
- description=description,
161
- external_id=data_model.external_id,
162
- version=data_model.version,
163
- creator=creator,
164
- created=datetime.now(),
165
- updated=datetime.now(),
166
- )
167
-
168
143
  def get_prefix(self) -> str:
169
144
  return self.space
170
145
 
@@ -174,8 +149,8 @@ class DMSProperty(SheetEntity):
174
149
  view_property: str = Field(alias="View Property")
175
150
  name: str | None = Field(alias="Name", default=None)
176
151
  description: str | None = Field(alias="Description", default=None)
177
- connection: Literal["direct", "edge", "reverse"] | None = Field(None, alias="Connection")
178
- 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")
179
154
  nullable: bool | None = Field(default=None, alias="Nullable")
180
155
  immutable: bool | None = Field(default=None, alias="Immutable")
181
156
  is_list: bool | None = Field(default=None, alias="Is List")
@@ -196,25 +171,23 @@ class DMSProperty(SheetEntity):
196
171
 
197
172
  @field_validator("value_type", mode="after")
198
173
  def connections_value_type(
199
- cls, value: ViewPropertyEntity | ViewEntity | DMSUnknownEntity, info: ValidationInfo
200
- ) -> DataType | ViewPropertyEntity | ViewEntity | DMSUnknownEntity:
174
+ cls, value: EdgeEntity | ViewEntity | DMSUnknownEntity, info: ValidationInfo
175
+ ) -> DataType | EdgeEntity | ViewEntity | DMSUnknownEntity:
201
176
  if (connection := info.data.get("connection")) is None:
202
177
  return value
203
178
  if connection == "direct" and not isinstance(value, ViewEntity | DMSUnknownEntity):
204
179
  raise ValueError(f"Direct relation must have a value type that points to a view, got {value}")
205
- elif connection == "edge" and not isinstance(value, ViewEntity):
180
+ elif isinstance(connection, EdgeEntity) and not isinstance(value, ViewEntity):
206
181
  raise ValueError(f"Edge connection must have a value type that points to a view, got {value}")
207
- elif connection == "reverse" and not isinstance(value, ViewPropertyEntity | ViewEntity):
208
- raise ValueError(
209
- f"Reverse connection must have a value type that points to a view or view property, got {value}"
210
- )
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}")
211
184
  return value
212
185
 
213
186
  @field_serializer("value_type", when_used="always")
214
187
  @staticmethod
215
- def as_dms_type(value_type: DataType | ViewPropertyEntity | ViewEntity) -> str:
188
+ def as_dms_type(value_type: DataType | EdgeEntity | ViewEntity) -> str:
216
189
  if isinstance(value_type, DataType):
217
- return value_type.dms._type
190
+ return value_type._suffix_extra_args(value_type.dms._type)
218
191
  else:
219
192
  return str(value_type)
220
193
 
@@ -225,6 +198,7 @@ class DMSContainer(SheetEntity):
225
198
  description: str | None = Field(alias="Description", default=None)
226
199
  reference: URLEntity | ReferenceEntity | None = Field(alias="Reference", default=None, union_mode="left_to_right")
227
200
  constraint: ContainerEntityList | None = Field(None, alias="Constraint")
201
+ used_for: Literal["node", "edge", "all"] | None = Field("all", alias="Used For")
228
202
  class_: ClassEntity = Field(alias="Class (linage)")
229
203
 
230
204
  def as_container(self) -> dm.ContainerApply:
@@ -241,22 +215,7 @@ class DMSContainer(SheetEntity):
241
215
  description=self.description,
242
216
  constraints=constraints or None,
243
217
  properties={},
244
- )
245
-
246
- @classmethod
247
- def from_container(cls, container: dm.ContainerApply) -> "DMSContainer":
248
- constraints: list[ContainerEntity] = []
249
- for _, constraint_obj in (container.constraints or {}).items():
250
- if isinstance(constraint_obj, dm.RequiresConstraint):
251
- constraints.append(ContainerEntity.from_id(constraint_obj.require))
252
- # UniquenessConstraint it handled in the properties
253
- container_entity = ContainerEntity.from_id(container.as_id())
254
- return cls(
255
- class_=container_entity.as_class(),
256
- container=container_entity,
257
- name=container.name or None,
258
- description=container.description,
259
- constraint=constraints or None,
218
+ used_for=self.used_for,
260
219
  )
261
220
 
262
221
 
@@ -289,19 +248,27 @@ class DMSView(SheetEntity):
289
248
  properties={},
290
249
  )
291
250
 
292
- @classmethod
293
- def from_view(cls, view: dm.ViewApply, in_model: bool) -> "DMSView":
294
- view_entity = ViewEntity.from_id(view.as_id())
295
- class_entity = view_entity.as_class(skip_version=True)
296
-
297
- return cls(
298
- class_=class_entity,
299
- view=view_entity,
300
- description=view.description,
301
- name=view.name,
302
- implements=[ViewEntity.from_id(parent, _DEFAULT_VERSION) for parent in view.implements] or None,
303
- in_model=in_model,
304
- )
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)
305
272
 
306
273
 
307
274
  class DMSRules(BaseRules):
@@ -309,6 +276,8 @@ class DMSRules(BaseRules):
309
276
  properties: SheetList[DMSProperty] = Field(alias="Properties")
310
277
  views: SheetList[DMSView] = Field(alias="Views")
311
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")
312
281
  last: "DMSRules | None" = Field(None, alias="Last", description="The previous version of the data model")
313
282
  reference: "DMSRules | None" = Field(None, alias="Reference")
314
283
 
@@ -320,8 +289,9 @@ class DMSRules(BaseRules):
320
289
  raise ValueError("Reference rules cannot have a reference")
321
290
  if value.metadata.data_model_type == DataModelType.solution and (metadata := info.data.get("metadata")):
322
291
  warnings.warn(
323
- issues.dms.SolutionOnTopOfSolutionModelWarning(
324
- metadata.as_data_model_id(), value.metadata.as_data_model_id()
292
+ PrincipleSolutionBuildsOnEnterpriseWarning(
293
+ f"The solution model {metadata.as_data_model_id()} is referencing another "
294
+ f"solution model {value.metadata.as_data_model_id()}",
325
295
  ),
326
296
  stacklevel=2,
327
297
  )
@@ -333,9 +303,21 @@ class DMSRules(BaseRules):
333
303
  return value
334
304
  model_version = metadata.version
335
305
  if different_version := [view.view.as_id() for view in value if view.view.version != model_version]:
336
- warnings.warn(issues.dms.ViewModelVersionNotMatchingWarning(different_version, model_version), stacklevel=2)
306
+ for view_id in different_version:
307
+ warnings.warn(
308
+ PrincipleMatchingSpaceAndVersionWarning(
309
+ f"The view {view_id!r} has a different version than the data model version, {model_version}",
310
+ ),
311
+ stacklevel=2,
312
+ )
337
313
  if different_space := [view.view.as_id() for view in value if view.view.space != metadata.space]:
338
- warnings.warn(issues.dms.ViewModelSpaceNotMatchingWarning(different_space, metadata.space), stacklevel=2)
314
+ for view_id in different_space:
315
+ warnings.warn(
316
+ PrincipleMatchingSpaceAndVersionWarning(
317
+ f"The view {view_id!r} is in a different space than the data model space, {metadata.space}",
318
+ ),
319
+ stacklevel=2,
320
+ )
339
321
  return value
340
322
 
341
323
  @model_validator(mode="after")
@@ -346,7 +328,7 @@ class DMSRules(BaseRules):
346
328
  if issue_list.warnings:
347
329
  issue_list.trigger_warnings()
348
330
  if issue_list.has_errors:
349
- raise MultiValueError([error for error in issue_list if isinstance(error, issues.NeatValidationError)])
331
+ raise MultiValueError(issue_list.errors)
350
332
  return self
351
333
 
352
334
  def dump(
@@ -385,82 +367,3 @@ class DMSRules(BaseRules):
385
367
  from ._exporter import _DMSExporter
386
368
 
387
369
  return _DMSExporter(self, include_pipeline, instance_space).to_schema()
388
-
389
- def as_information_rules(self) -> "InformationRules":
390
- from ._converter import _DMSRulesConverter
391
-
392
- return _DMSRulesConverter(self).as_information_rules()
393
-
394
- def as_domain_expert_rules(self) -> DomainRules:
395
- from ._converter import _DMSRulesConverter
396
-
397
- return _DMSRulesConverter(self).as_domain_rules()
398
-
399
- def create_reference(
400
- self, reference: "DMSRules", view_extension_mapping: dict[str, str], default_extension: str | None = None
401
- ) -> None:
402
- """Takes this data models and makes it into an extension of the reference data model.
403
-
404
- The argument view_extension_mapping is a dictionary where the keys are views of this data model,
405
- and each value is the view of the reference data model that the view should extend. For example:
406
-
407
- ```python
408
- view_extension_mapping = {"Pump": "Asset"}
409
- ```
410
-
411
- This would make the view "Pump" in this data model extend the view "Asset" in the reference data model.
412
- Note that all the keys in the dictionary must be external ids of views in this data model,
413
- and all the values must be external ids of views in the reference data model.
414
-
415
- Args:
416
- reference: The reference data model
417
- view_extension_mapping: A dictionary mapping views in this data model to views in the reference data model
418
- default_extension: The default view in the reference data model that views in this
419
- data model should extend if no mapping is provided.
420
-
421
- """
422
- if self.reference is not None:
423
- raise ValueError("Reference already exists")
424
- self.reference = reference
425
- view_by_external_id = {view.view.external_id: view for view in self.views}
426
- ref_view_by_external_id = {view.view.external_id: view for view in reference.views}
427
-
428
- if invalid_views := set(view_extension_mapping.keys()) - set(view_by_external_id.keys()):
429
- raise ValueError(f"Views are not in this dat model {invalid_views}")
430
- if invalid_views := set(view_extension_mapping.values()) - set(ref_view_by_external_id.keys()):
431
- raise ValueError(f"Views are not in the reference data model {invalid_views}")
432
- if default_extension and default_extension not in ref_view_by_external_id:
433
- raise ValueError(f"Default extension view not in the reference data model {default_extension}")
434
-
435
- properties_by_view_external_id: dict[str, dict[str, DMSProperty]] = defaultdict(dict)
436
- for prop in self.properties:
437
- properties_by_view_external_id[prop.view.external_id][prop.view_property] = prop
438
-
439
- ref_properties_by_view_external_id: dict[str, dict[str, DMSProperty]] = defaultdict(dict)
440
- for prop in reference.properties:
441
- ref_properties_by_view_external_id[prop.view.external_id][prop.view_property] = prop
442
-
443
- for view_external_id, view in view_by_external_id.items():
444
- if view_external_id in view_extension_mapping:
445
- ref_external_id = view_extension_mapping[view_external_id]
446
- elif default_extension:
447
- ref_external_id = default_extension
448
- else:
449
- continue
450
-
451
- ref_view = ref_view_by_external_id[ref_external_id]
452
- shared_properties = set(properties_by_view_external_id[view_external_id].keys()) & set(
453
- ref_properties_by_view_external_id[ref_external_id].keys()
454
- )
455
- if shared_properties:
456
- if view.implements is None:
457
- view.implements = [ref_view.view]
458
- elif isinstance(view.implements, list) and ref_view.view not in view.implements:
459
- view.implements.append(ref_view.view)
460
- for prop_name in shared_properties:
461
- prop = properties_by_view_external_id[view_external_id][prop_name]
462
- ref_prop = ref_properties_by_view_external_id[ref_external_id][prop_name]
463
- if ref_prop.container and ref_prop.container_property:
464
- prop.container = ref_prop.container
465
- prop.container_property = ref_prop.container_property
466
- prop.reference = ReferenceEntity.from_entity(ref_prop.view, ref_prop.view_property)