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,14 +1,15 @@
1
1
  from collections import Counter
2
- from collections.abc import Sequence
2
+ from collections.abc import Collection, Sequence
3
3
  from datetime import datetime
4
4
  from pathlib import Path
5
- from typing import Any, Literal, cast, overload
5
+ from typing import Literal, cast
6
6
 
7
7
  from cognite.client import CogniteClient
8
8
  from cognite.client import data_modeling as dm
9
9
  from cognite.client.data_classes.data_modeling import DataModelId, DataModelIdentifier
10
10
  from cognite.client.data_classes.data_modeling.containers import BTreeIndex, InvertedIndex
11
- from cognite.client.data_classes.data_modeling.data_types import ListablePropertyType
11
+ from cognite.client.data_classes.data_modeling.data_types import Enum as DMSEnum
12
+ from cognite.client.data_classes.data_modeling.data_types import ListablePropertyType, PropertyTypeWithUnit
12
13
  from cognite.client.data_classes.data_modeling.views import (
13
14
  MultiEdgeConnectionApply,
14
15
  MultiReverseDirectRelationApply,
@@ -19,40 +20,42 @@ from cognite.client.data_classes.data_modeling.views import (
19
20
  from cognite.client.utils import ms_to_datetime
20
21
 
21
22
  from cognite.neat.issues import IssueList, NeatIssue
22
- from cognite.neat.issues.errors.resources import ResourceNotFoundError
23
- from cognite.neat.issues.neat_warnings.properties import (
23
+ from cognite.neat.issues.errors import FileTypeUnexpectedError, ResourceMissingIdentifierError, ResourceRetrievalError
24
+ from cognite.neat.issues.warnings import (
25
+ PropertyNotFoundWarning,
24
26
  PropertyTypeNotSupportedWarning,
25
- ReferredPropertyNotFoundWarning,
27
+ ResourceNotFoundWarning,
28
+ ResourcesDuplicatedWarning,
26
29
  )
27
- from cognite.neat.issues.neat_warnings.resources import ReferredResourceNotFoundWarning
28
- from cognite.neat.rules import issues
29
- from cognite.neat.rules.importers._base import BaseImporter, Rules, _handle_issues
30
+ from cognite.neat.rules._shared import ReadRules
31
+ from cognite.neat.rules.importers._base import BaseImporter, _handle_issues
30
32
  from cognite.neat.rules.models import (
31
33
  DataModelType,
32
- DMSRules,
34
+ DMSInputRules,
33
35
  DMSSchema,
34
- ExtensionCategory,
35
- RoleTypes,
36
36
  SchemaCompleteness,
37
- SheetList,
38
37
  )
39
- from cognite.neat.rules.models.data_types import DataType
38
+ from cognite.neat.rules.models.data_types import DataType, Enum
40
39
  from cognite.neat.rules.models.dms import (
41
- DMSContainer,
42
- DMSMetadata,
43
- DMSProperty,
44
- DMSView,
40
+ DMSInputContainer,
41
+ DMSInputEnum,
42
+ DMSInputMetadata,
43
+ DMSInputNode,
44
+ DMSInputProperty,
45
+ DMSInputView,
45
46
  )
46
47
  from cognite.neat.rules.models.entities import (
47
48
  ClassEntity,
48
49
  ContainerEntity,
50
+ DMSNodeEntity,
49
51
  DMSUnknownEntity,
52
+ EdgeEntity,
53
+ ReverseConnectionEntity,
50
54
  ViewEntity,
51
- ViewPropertyEntity,
52
55
  )
53
56
 
54
57
 
55
- class DMSImporter(BaseImporter):
58
+ class DMSImporter(BaseImporter[DMSInputRules]):
56
59
  """Imports a Data Model from Cognite Data Fusion.
57
60
 
58
61
  Args:
@@ -67,8 +70,8 @@ class DMSImporter(BaseImporter):
67
70
  self,
68
71
  schema: DMSSchema,
69
72
  read_issues: Sequence[NeatIssue] | None = None,
70
- metadata: DMSMetadata | None = None,
71
- ref_metadata: DMSMetadata | None = None,
73
+ metadata: DMSInputMetadata | None = None,
74
+ ref_metadata: DMSInputMetadata | None = None,
72
75
  ):
73
76
  # Calling this root schema to distinguish it from
74
77
  # * User Schema
@@ -78,10 +81,10 @@ class DMSImporter(BaseImporter):
78
81
  self.ref_metadata = ref_metadata
79
82
  self.issue_list = IssueList(read_issues)
80
83
  self._all_containers_by_id = schema.containers.copy()
81
- self._all_view_ids = set(self.root_schema.views.keys())
82
- if self.root_schema.reference:
83
- self._all_containers_by_id.update(self.root_schema.reference.containers)
84
- self._all_view_ids.update(self.root_schema.reference.views.keys())
84
+ self._all_views_by_id = schema.views.copy()
85
+ if schema.reference:
86
+ self._all_containers_by_id.update(schema.reference.containers.items())
87
+ self._all_views_by_id.update(schema.reference.views.items())
85
88
 
86
89
  @classmethod
87
90
  def from_data_model_id(
@@ -109,9 +112,9 @@ class DMSImporter(BaseImporter):
109
112
  return cls(
110
113
  DMSSchema(),
111
114
  [
112
- ResourceNotFoundError[dm.DataModelId](
115
+ ResourceRetrievalError(
113
116
  dm.DataModelId.load(reference_model_id), # type: ignore[arg-type]
114
- "DataModel",
117
+ "data model",
115
118
  "Data Model is missing in CDF",
116
119
  )
117
120
  ],
@@ -124,8 +127,8 @@ class DMSImporter(BaseImporter):
124
127
  return cls(
125
128
  DMSSchema(),
126
129
  [
127
- ResourceNotFoundError[dm.DataModelId](
128
- dm.DataModelId.load(reference_model_id), "DataModel", "Data Model is missing in CDF"
130
+ ResourceRetrievalError(
131
+ dm.DataModelId.load(reference_model_id), "data model", "Data Model is missing in CDF"
129
132
  )
130
133
  ],
131
134
  )
@@ -163,8 +166,8 @@ class DMSImporter(BaseImporter):
163
166
  cls,
164
167
  model: dm.DataModel[dm.View] | dm.DataModelApply,
165
168
  has_reference: bool = False,
166
- ) -> DMSMetadata:
167
- description, creator = DMSMetadata._get_description_and_creator(model.description)
169
+ ) -> DMSInputMetadata:
170
+ description, creator = DMSInputMetadata._get_description_and_creator(model.description)
168
171
 
169
172
  if isinstance(model, dm.DataModel):
170
173
  created = ms_to_datetime(model.created_time)
@@ -173,17 +176,17 @@ class DMSImporter(BaseImporter):
173
176
  now = datetime.now().replace(microsecond=0)
174
177
  created = now
175
178
  updated = now
176
- return DMSMetadata(
177
- schema_=SchemaCompleteness.complete,
178
- data_model_type=DataModelType.solution if has_reference else DataModelType.enterprise,
179
- extension=ExtensionCategory.addition,
179
+ return DMSInputMetadata(
180
+ schema_="complete",
181
+ data_model_type="solution" if has_reference else "enterprise",
182
+ extension="addition",
180
183
  space=model.space,
181
184
  external_id=model.external_id,
182
185
  name=model.name or model.external_id,
183
186
  version=model.version or "0.1.0",
184
187
  updated=updated,
185
188
  created=created,
186
- creator=creator,
189
+ creator=",".join(creator),
187
190
  description=description,
188
191
  )
189
192
 
@@ -198,77 +201,58 @@ class DMSImporter(BaseImporter):
198
201
  @classmethod
199
202
  def from_zip_file(cls, zip_file: str | Path) -> "DMSImporter":
200
203
  if Path(zip_file).suffix != ".zip":
201
- return cls(DMSSchema(), [issues.fileread.InvalidFileFormatError(Path(zip_file), [".zip"])])
204
+ return cls(DMSSchema(), [FileTypeUnexpectedError(Path(zip_file), frozenset([".zip"]))])
202
205
  issue_list = IssueList()
203
206
  with _handle_issues(issue_list) as _:
204
207
  schema = DMSSchema.from_zip(zip_file)
205
208
  return cls(schema, issue_list)
206
209
 
207
- @overload
208
- def to_rules(self, errors: Literal["raise"], role: RoleTypes | None = None) -> Rules: ...
209
-
210
- @overload
211
- def to_rules(
212
- self, errors: Literal["continue"] = "continue", role: RoleTypes | None = None
213
- ) -> tuple[Rules | None, IssueList]: ...
214
-
215
- def to_rules(
216
- self, errors: Literal["raise", "continue"] = "continue", role: RoleTypes | None = None
217
- ) -> tuple[Rules | None, IssueList] | Rules:
210
+ def to_rules(self) -> ReadRules[DMSInputRules]:
218
211
  if self.issue_list.has_errors:
219
212
  # In case there were errors during the import, the to_rules method will return None
220
- return self._return_or_raise(self.issue_list, errors)
213
+ return ReadRules(None, self.issue_list, {})
221
214
 
222
215
  if not self.root_schema.data_model:
223
- self.issue_list.append(ResourceNotFoundError[str]("Unknown", "DataModel", "Identifier is missing"))
224
- return self._return_or_raise(self.issue_list, errors)
216
+ self.issue_list.append(ResourceMissingIdentifierError("data model", type(self.root_schema).__name__))
217
+ return ReadRules(None, self.issue_list, {})
218
+
225
219
  model = self.root_schema.data_model
226
- with _handle_issues(
227
- self.issue_list,
228
- ) as future:
229
- schema_completeness = SchemaCompleteness.complete
230
- data_model_type = DataModelType.enterprise
231
- reference: DMSRules | None = None
232
- if (ref_schema := self.root_schema.reference) and (ref_model := ref_schema.data_model):
233
- # Reference should always be an enterprise model.
234
- reference = DMSRules(
235
- **self._create_rule_components(
236
- ref_model,
237
- ref_schema,
238
- self.ref_metadata
239
- or self._create_default_metadata(list(ref_schema.views.values()), is_ref=True),
240
- DataModelType.enterprise,
241
- )
242
- )
243
- data_model_type = DataModelType.solution
244
-
245
- user_rules = DMSRules(
246
- **self._create_rule_components(
247
- model,
248
- self.root_schema,
249
- self.metadata,
250
- data_model_type,
251
- schema_completeness,
252
- has_reference=reference is not None,
253
- ),
254
- reference=reference,
255
- )
256
220
 
257
- if future.result == "failure" or self.issue_list.has_errors:
258
- return self._return_or_raise(self.issue_list, errors)
221
+ schema_completeness = SchemaCompleteness.complete
222
+ data_model_type = DataModelType.enterprise
223
+ reference: DMSInputRules | None = None
224
+ if (ref_schema := self.root_schema.reference) and (ref_model := ref_schema.data_model):
225
+ # Reference should always be an enterprise model.
226
+ reference = self._create_rule_components(
227
+ ref_model,
228
+ ref_schema,
229
+ self.ref_metadata or self._create_default_metadata(list(ref_schema.views.values()), is_ref=True),
230
+ DataModelType.enterprise,
231
+ )
232
+ data_model_type = DataModelType.solution
233
+
234
+ user_rules = self._create_rule_components(
235
+ model,
236
+ self.root_schema,
237
+ self.metadata,
238
+ data_model_type,
239
+ schema_completeness,
240
+ has_reference=reference is not None,
241
+ )
242
+ user_rules.reference = reference
259
243
 
260
- return self._to_output(user_rules, self.issue_list, errors, role)
244
+ return ReadRules(user_rules, self.issue_list, {})
261
245
 
262
246
  def _create_rule_components(
263
247
  self,
264
248
  data_model: dm.DataModelApply,
265
249
  schema: DMSSchema,
266
- metadata: DMSMetadata | None = None,
250
+ metadata: DMSInputMetadata | None = None,
267
251
  data_model_type: DataModelType | None = None,
268
252
  schema_completeness: SchemaCompleteness | None = None,
269
253
  has_reference: bool = False,
270
- ) -> dict[str, Any]:
271
- properties = SheetList[DMSProperty]()
254
+ ) -> DMSInputRules:
255
+ properties: list[DMSInputProperty] = []
272
256
  for view_id, view in schema.views.items():
273
257
  view_entity = ViewEntity.from_id(view_id)
274
258
  class_entity = view_entity.as_class()
@@ -281,51 +265,54 @@ class DMSImporter(BaseImporter):
281
265
  view.as_id() if isinstance(view, dm.View | dm.ViewApply) else view for view in data_model.views or []
282
266
  }
283
267
 
284
- metadata = metadata or DMSMetadata.from_data_model(data_model, has_reference)
268
+ metadata = metadata or DMSInputMetadata.from_data_model(data_model, has_reference)
285
269
  if data_model_type is not None:
286
- metadata.data_model_type = data_model_type
270
+ metadata.data_model_type = str(data_model_type) # type: ignore[assignment]
287
271
  if schema_completeness is not None:
288
- metadata.schema_ = schema_completeness
289
- return dict(
272
+ metadata.schema_ = str(schema_completeness) # type: ignore[assignment]
273
+
274
+ enum = self._create_enum_collections(schema.containers.values())
275
+
276
+ return DMSInputRules(
290
277
  metadata=metadata,
291
278
  properties=properties,
292
- containers=SheetList[DMSContainer](
293
- data=[DMSContainer.from_container(container) for container in schema.containers.values()]
294
- ),
295
- views=SheetList[DMSView](
296
- data=[
297
- DMSView.from_view(view, in_model=view_id in data_model_view_ids)
298
- for view_id, view in schema.views.items()
299
- ]
300
- ),
279
+ containers=[DMSInputContainer.from_container(container) for container in schema.containers.values()],
280
+ views=[
281
+ DMSInputView.from_view(view, in_model=view_id in data_model_view_ids)
282
+ for view_id, view in schema.views.items()
283
+ ],
284
+ nodes=[DMSInputNode.from_node_type(node_type) for node_type in schema.node_types.values()],
285
+ enum=enum,
301
286
  )
302
287
 
303
288
  @classmethod
304
- def _create_default_metadata(cls, views: Sequence[dm.View | dm.ViewApply], is_ref: bool = False) -> DMSMetadata:
289
+ def _create_default_metadata(
290
+ cls, views: Sequence[dm.View | dm.ViewApply], is_ref: bool = False
291
+ ) -> DMSInputMetadata:
305
292
  now = datetime.now().replace(microsecond=0)
306
293
  space = Counter(view.space for view in views).most_common(1)[0][0]
307
- return DMSMetadata(
308
- schema_=SchemaCompleteness.complete,
309
- extension=ExtensionCategory.addition,
310
- data_model_type=DataModelType.enterprise if is_ref else DataModelType.solution,
294
+ return DMSInputMetadata(
295
+ schema_="complete",
296
+ extension="addition",
297
+ data_model_type="enterprise" if is_ref else "solution",
311
298
  space=space,
312
299
  external_id="Unknown",
313
300
  version="0.1.0",
314
- creator=["Unknown"],
301
+ creator="Unknown",
315
302
  created=now,
316
303
  updated=now,
317
304
  )
318
305
 
319
306
  def _create_dms_property(
320
307
  self, prop_id: str, prop: ViewPropertyApply, view_entity: ViewEntity, class_entity: ClassEntity
321
- ) -> DMSProperty | None:
308
+ ) -> DMSInputProperty | None:
322
309
  if isinstance(prop, dm.MappedPropertyApply) and prop.container not in self._all_containers_by_id:
323
310
  self.issue_list.append(
324
- ReferredResourceNotFoundWarning[dm.ContainerId, dm.PropertyId](
311
+ ResourceNotFoundWarning[dm.ContainerId, dm.PropertyId](
325
312
  dm.ContainerId.load(prop.container),
326
- "Container",
313
+ "container",
327
314
  view_entity.to_property_id(prop_id),
328
- "View Property",
315
+ "view property",
329
316
  )
330
317
  )
331
318
  return None
@@ -334,9 +321,7 @@ class DMSImporter(BaseImporter):
334
321
  and prop.container_property_identifier not in self._all_containers_by_id[prop.container].properties
335
322
  ):
336
323
  self.issue_list.append(
337
- ReferredPropertyNotFoundWarning[dm.ContainerId, dm.ViewId](
338
- prop.container, "Container", view_entity.as_id(), "View", prop_id
339
- ),
324
+ PropertyNotFoundWarning(prop.container, "container", prop_id, view_entity.as_id(), "view"),
340
325
  )
341
326
  return None
342
327
  if not isinstance(
@@ -348,7 +333,7 @@ class DMSImporter(BaseImporter):
348
333
  | MultiReverseDirectRelationApply,
349
334
  ):
350
335
  self.issue_list.append(
351
- PropertyTypeNotSupportedWarning[dm.ViewId](view_entity.as_id(), "View", prop_id, type(prop).__name__)
336
+ PropertyTypeNotSupportedWarning[dm.ViewId](view_entity.as_id(), "view", prop_id, type(prop).__name__)
352
337
  )
353
338
  return None
354
339
 
@@ -356,20 +341,22 @@ class DMSImporter(BaseImporter):
356
341
  if value_type is None:
357
342
  return None
358
343
 
359
- return DMSProperty(
360
- class_=class_entity,
344
+ return DMSInputProperty(
345
+ class_=str(class_entity),
361
346
  property_=prop_id,
362
347
  description=prop.description,
363
348
  name=prop.name,
364
- connection=self._get_relation_type(prop),
365
- value_type=value_type,
349
+ connection=self._get_connection_type(prop_id, prop, view_entity.as_id()),
350
+ value_type=str(value_type),
366
351
  is_list=self._get_is_list(prop),
367
352
  nullable=self._get_nullable(prop),
368
353
  immutable=self._get_immutable(prop),
369
354
  default=self._get_default(prop),
370
- container=ContainerEntity.from_id(prop.container) if isinstance(prop, dm.MappedPropertyApply) else None,
355
+ container=str(ContainerEntity.from_id(prop.container))
356
+ if isinstance(prop, dm.MappedPropertyApply)
357
+ else None,
371
358
  container_property=prop.container_property_identifier if isinstance(prop, dm.MappedPropertyApply) else None,
372
- view=view_entity,
359
+ view=str(view_entity),
373
360
  view_property=prop_id,
374
361
  index=self._get_index(prop, prop_id),
375
362
  constraint=self._get_constraint(prop, prop_id),
@@ -379,13 +366,22 @@ class DMSImporter(BaseImporter):
379
366
  """This method assumes you have already checked that the container with property exists."""
380
367
  return self._all_containers_by_id[prop.container].properties[prop.container_property_identifier]
381
368
 
382
- def _get_relation_type(self, prop: ViewPropertyApply) -> Literal["edge", "reverse", "direct"] | None:
369
+ def _get_connection_type(
370
+ self, prop_id: str, prop: ViewPropertyApply, view_id: dm.ViewId
371
+ ) -> Literal["direct"] | ReverseConnectionEntity | EdgeEntity | None:
383
372
  if isinstance(prop, SingleEdgeConnectionApply | MultiEdgeConnectionApply) and prop.direction == "outwards":
384
- return "edge"
373
+ properties = ViewEntity.from_id(prop.edge_source) if prop.edge_source is not None else None
374
+ return EdgeEntity(properties=properties, type=DMSNodeEntity.from_reference(prop.type), direction="outwards")
385
375
  elif isinstance(prop, SingleEdgeConnectionApply | MultiEdgeConnectionApply) and prop.direction == "inwards":
386
- return "reverse"
376
+ if reverse_prop := self._find_reverse_edge(prop_id, prop, view_id):
377
+ return ReverseConnectionEntity(property=reverse_prop)
378
+ else:
379
+ properties = ViewEntity.from_id(prop.source) if prop.edge_source is not None else None
380
+ return EdgeEntity(
381
+ properties=properties, type=DMSNodeEntity.from_reference(prop.type), direction="inwards"
382
+ )
387
383
  elif isinstance(prop, SingleReverseDirectRelationApply | MultiReverseDirectRelationApply):
388
- return "reverse"
384
+ return ReverseConnectionEntity(property=prop.through.property)
389
385
  elif isinstance(prop, dm.MappedPropertyApply) and isinstance(
390
386
  self._container_prop_unsafe(prop).type, dm.DirectRelation
391
387
  ):
@@ -395,26 +391,31 @@ class DMSImporter(BaseImporter):
395
391
 
396
392
  def _get_value_type(
397
393
  self, prop: ViewPropertyApply, view_entity: ViewEntity, prop_id
398
- ) -> DataType | ViewEntity | ViewPropertyEntity | DMSUnknownEntity | None:
399
- if isinstance(prop, SingleEdgeConnectionApply | MultiEdgeConnectionApply) and prop.direction == "outwards":
400
- return ViewEntity.from_id(prop.source)
401
- elif isinstance(prop, SingleReverseDirectRelationApply | MultiReverseDirectRelationApply):
402
- return ViewPropertyEntity.from_id(prop.through)
403
- elif isinstance(prop, SingleEdgeConnectionApply | MultiEdgeConnectionApply) and prop.direction == "inwards":
394
+ ) -> DataType | ViewEntity | DMSUnknownEntity | None:
395
+ if isinstance(
396
+ prop,
397
+ SingleEdgeConnectionApply
398
+ | MultiEdgeConnectionApply
399
+ | SingleReverseDirectRelationApply
400
+ | MultiReverseDirectRelationApply,
401
+ ):
404
402
  return ViewEntity.from_id(prop.source)
405
403
  elif isinstance(prop, dm.MappedPropertyApply):
406
404
  container_prop = self._container_prop_unsafe(cast(dm.MappedPropertyApply, prop))
407
405
  if isinstance(container_prop.type, dm.DirectRelation):
408
- if prop.source is None or prop.source not in self._all_view_ids:
409
- # The warning is issued when the DMS Rules are created.
406
+ if prop.source is None or prop.source not in self._all_views_by_id:
410
407
  return DMSUnknownEntity()
411
408
  else:
412
409
  return ViewEntity.from_id(prop.source)
410
+ elif isinstance(container_prop.type, PropertyTypeWithUnit) and container_prop.type.unit:
411
+ return DataType.load(f"{container_prop.type._type}(unit={container_prop.type.unit.external_id})")
412
+ elif isinstance(container_prop.type, DMSEnum):
413
+ return Enum(collection=ClassEntity(suffix=prop_id), unknownValue=container_prop.type.unknown_value)
413
414
  else:
414
415
  return DataType.load(container_prop.type._type)
415
416
  else:
416
417
  self.issue_list.append(
417
- PropertyTypeNotSupportedWarning[dm.ViewId](view_entity.as_id(), "View", prop_id, type(prop).__name__)
418
+ PropertyTypeNotSupportedWarning[dm.ViewId](view_entity.as_id(), "view", prop_id, type(prop).__name__)
418
419
  )
419
420
  return None
420
421
 
@@ -475,7 +476,59 @@ class DMSImporter(BaseImporter):
475
476
  else:
476
477
  self.issue_list.append(
477
478
  PropertyTypeNotSupportedWarning[dm.ContainerId](
478
- prop.container, "Container", prop_id, type(constraint_obj).__name__
479
+ prop.container, "container", prop_id, type(constraint_obj).__name__
479
480
  )
480
481
  )
481
482
  return unique_constraints or None
483
+
484
+ def _find_reverse_edge(
485
+ self, prop_id: str, prop: SingleEdgeConnectionApply | MultiEdgeConnectionApply, view_id: dm.ViewId
486
+ ) -> str | None:
487
+ if prop.source not in self._all_views_by_id:
488
+ return None
489
+ view = self._all_views_by_id[prop.source]
490
+ candidates = []
491
+ for prop_name, reverse_prop in (view.properties or {}).items():
492
+ if isinstance(reverse_prop, SingleEdgeConnectionApply | MultiEdgeConnectionApply):
493
+ if (
494
+ reverse_prop.type == prop.type
495
+ and reverse_prop.source == view_id
496
+ and reverse_prop.direction != prop.direction
497
+ ):
498
+ candidates.append(prop_name)
499
+ if len(candidates) == 0:
500
+ self.issue_list.append(
501
+ PropertyNotFoundWarning(
502
+ prop.source,
503
+ "view property",
504
+ f"reverse edge of {prop_id}",
505
+ dm.PropertyId(view_id, prop_id),
506
+ "view property",
507
+ )
508
+ )
509
+ return None
510
+ if len(candidates) > 1:
511
+ self.issue_list.append(
512
+ ResourcesDuplicatedWarning(
513
+ frozenset(dm.PropertyId(view.as_id(), candidate) for candidate in candidates),
514
+ "view property",
515
+ default_action="Multiple reverse edges found for "
516
+ f"{dm.PropertyId(view_id, prop_id)!r}. Will use {candidates[0]}",
517
+ )
518
+ )
519
+
520
+ return candidates[0]
521
+
522
+ @staticmethod
523
+ def _create_enum_collections(containers: Collection[dm.ContainerApply]) -> list[DMSInputEnum] | None:
524
+ enum_collections: list[DMSInputEnum] = []
525
+ for container in containers:
526
+ for prop_id, prop in container.properties.items():
527
+ if isinstance(prop.type, DMSEnum):
528
+ for identifier, value in prop.type.values.items():
529
+ enum_collections.append(
530
+ DMSInputEnum(
531
+ collection=prop_id, value=identifier, name=value.name, description=value.description
532
+ )
533
+ )
534
+ return enum_collections