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.
- cognite/neat/_version.py +1 -1
- cognite/neat/constants.py +3 -0
- cognite/neat/graph/__init__.py +0 -3
- cognite/neat/graph/extractors/_mock_graph_generator.py +2 -1
- cognite/neat/graph/loaders/_base.py +3 -3
- cognite/neat/graph/loaders/_rdf2asset.py +24 -25
- cognite/neat/graph/loaders/_rdf2dms.py +20 -15
- cognite/neat/issues/__init__.py +1 -3
- cognite/neat/issues/_base.py +261 -71
- cognite/neat/issues/errors/__init__.py +73 -0
- cognite/neat/issues/errors/_external.py +67 -0
- cognite/neat/issues/errors/_general.py +35 -0
- cognite/neat/issues/errors/_properties.py +62 -0
- cognite/neat/issues/errors/_resources.py +111 -0
- cognite/neat/issues/errors/_workflow.py +36 -0
- cognite/neat/issues/formatters.py +1 -1
- cognite/neat/issues/warnings/__init__.py +66 -0
- cognite/neat/issues/warnings/_external.py +40 -0
- cognite/neat/issues/warnings/_general.py +29 -0
- cognite/neat/issues/warnings/_models.py +92 -0
- cognite/neat/issues/warnings/_properties.py +44 -0
- cognite/neat/issues/warnings/_resources.py +55 -0
- cognite/neat/issues/warnings/user_modeling.py +113 -0
- cognite/neat/rules/_shared.py +53 -2
- cognite/neat/rules/analysis/_base.py +1 -1
- cognite/neat/rules/exporters/_base.py +7 -18
- cognite/neat/rules/exporters/_rules2dms.py +17 -20
- cognite/neat/rules/exporters/_rules2excel.py +9 -16
- cognite/neat/rules/exporters/_rules2ontology.py +77 -64
- cognite/neat/rules/exporters/_rules2yaml.py +6 -9
- cognite/neat/rules/exporters/_validation.py +11 -96
- cognite/neat/rules/importers/_base.py +9 -58
- cognite/neat/rules/importers/_dms2rules.py +188 -135
- cognite/neat/rules/importers/_dtdl2rules/dtdl_converter.py +48 -35
- cognite/neat/rules/importers/_dtdl2rules/dtdl_importer.py +36 -45
- cognite/neat/rules/importers/_dtdl2rules/spec.py +7 -0
- cognite/neat/rules/importers/_rdf/_imf2rules/_imf2classes.py +8 -4
- cognite/neat/rules/importers/_rdf/_imf2rules/_imf2metadata.py +3 -3
- cognite/neat/rules/importers/_rdf/_imf2rules/_imf2properties.py +18 -11
- cognite/neat/rules/importers/_rdf/_imf2rules/_imf2rules.py +12 -19
- cognite/neat/rules/importers/_rdf/_inference2rules.py +14 -37
- cognite/neat/rules/importers/_rdf/_owl2rules/_owl2classes.py +1 -0
- cognite/neat/rules/importers/_rdf/_owl2rules/_owl2properties.py +1 -0
- cognite/neat/rules/importers/_rdf/_owl2rules/_owl2rules.py +9 -20
- cognite/neat/rules/importers/_rdf/_shared.py +4 -4
- cognite/neat/rules/importers/_spreadsheet2rules.py +46 -97
- cognite/neat/rules/importers/_yaml2rules.py +32 -58
- cognite/neat/rules/models/__init__.py +21 -5
- cognite/neat/rules/models/_base_input.py +162 -0
- cognite/neat/rules/models/{_base.py → _base_rules.py} +1 -12
- cognite/neat/rules/models/_rdfpath.py +4 -4
- cognite/neat/rules/models/{_types/_field.py → _types.py} +5 -10
- cognite/neat/rules/models/asset/__init__.py +5 -2
- cognite/neat/rules/models/asset/_rules.py +3 -23
- cognite/neat/rules/models/asset/_rules_input.py +40 -115
- cognite/neat/rules/models/asset/_validation.py +14 -10
- cognite/neat/rules/models/data_types.py +150 -44
- cognite/neat/rules/models/dms/__init__.py +19 -7
- cognite/neat/rules/models/dms/_exporter.py +102 -34
- cognite/neat/rules/models/dms/_rules.py +65 -162
- cognite/neat/rules/models/dms/_rules_input.py +186 -254
- cognite/neat/rules/models/dms/_schema.py +87 -78
- cognite/neat/rules/models/dms/_serializer.py +44 -3
- cognite/neat/rules/models/dms/_validation.py +106 -68
- cognite/neat/rules/models/domain.py +52 -1
- cognite/neat/rules/models/entities/__init__.py +63 -0
- cognite/neat/rules/models/entities/_constants.py +73 -0
- cognite/neat/rules/models/entities/_loaders.py +76 -0
- cognite/neat/rules/models/entities/_multi_value.py +67 -0
- cognite/neat/rules/models/{entities.py → entities/_single_value.py} +74 -232
- cognite/neat/rules/models/entities/_types.py +86 -0
- cognite/neat/rules/models/{wrapped_entities.py → entities/_wrapped.py} +1 -1
- cognite/neat/rules/models/information/__init__.py +10 -2
- cognite/neat/rules/models/information/_rules.py +10 -22
- cognite/neat/rules/models/information/_rules_input.py +57 -204
- cognite/neat/rules/models/information/_validation.py +48 -25
- cognite/neat/rules/transformers/__init__.py +21 -0
- cognite/neat/rules/transformers/_base.py +81 -0
- cognite/neat/rules/{models/information/_converter.py → transformers/_converters.py} +217 -21
- cognite/neat/rules/transformers/_map_onto.py +97 -0
- cognite/neat/rules/transformers/_pipelines.py +61 -0
- cognite/neat/rules/transformers/_verification.py +136 -0
- cognite/neat/{graph/stores → store}/_provenance.py +10 -1
- cognite/neat/utils/auxiliary.py +2 -35
- cognite/neat/utils/cdf/data_classes.py +20 -0
- cognite/neat/utils/regex_patterns.py +6 -0
- cognite/neat/utils/text.py +17 -0
- cognite/neat/workflows/base.py +4 -4
- cognite/neat/workflows/cdf_store.py +3 -3
- cognite/neat/workflows/steps/data_contracts.py +1 -1
- cognite/neat/workflows/steps/lib/current/graph_extractor.py +3 -3
- cognite/neat/workflows/steps/lib/current/graph_loader.py +2 -2
- cognite/neat/workflows/steps/lib/current/graph_store.py +1 -1
- cognite/neat/workflows/steps/lib/current/rules_exporter.py +116 -47
- cognite/neat/workflows/steps/lib/current/rules_importer.py +30 -28
- cognite/neat/workflows/steps/lib/current/rules_validator.py +5 -6
- cognite/neat/workflows/steps/lib/io/io_steps.py +5 -5
- cognite/neat/workflows/steps_registry.py +4 -5
- {cognite_neat-0.88.2.dist-info → cognite_neat-0.89.0.dist-info}/METADATA +1 -1
- {cognite_neat-0.88.2.dist-info → cognite_neat-0.89.0.dist-info}/RECORD +105 -106
- cognite/neat/exceptions.py +0 -145
- cognite/neat/graph/exceptions.py +0 -90
- cognite/neat/issues/errors/external.py +0 -21
- cognite/neat/issues/errors/properties.py +0 -75
- cognite/neat/issues/errors/resources.py +0 -123
- cognite/neat/issues/errors/schema.py +0 -0
- cognite/neat/issues/neat_warnings/__init__.py +0 -2
- cognite/neat/issues/neat_warnings/identifier.py +0 -27
- cognite/neat/issues/neat_warnings/models.py +0 -22
- cognite/neat/issues/neat_warnings/properties.py +0 -77
- cognite/neat/issues/neat_warnings/resources.py +0 -125
- cognite/neat/rules/issues/__init__.py +0 -22
- cognite/neat/rules/issues/base.py +0 -63
- cognite/neat/rules/issues/dms.py +0 -549
- cognite/neat/rules/issues/fileread.py +0 -197
- cognite/neat/rules/issues/ontology.py +0 -298
- cognite/neat/rules/issues/spreadsheet.py +0 -563
- cognite/neat/rules/issues/spreadsheet_file.py +0 -151
- cognite/neat/rules/issues/tables.py +0 -72
- cognite/neat/rules/models/_constants.py +0 -1
- cognite/neat/rules/models/_types/__init__.py +0 -19
- cognite/neat/rules/models/asset/_converter.py +0 -4
- cognite/neat/rules/models/dms/_converter.py +0 -145
- cognite/neat/workflows/_exceptions.py +0 -41
- /cognite/neat/{graph/stores → store}/__init__.py +0 -0
- /cognite/neat/{graph/stores → store}/_base.py +0 -0
- {cognite_neat-0.88.2.dist-info → cognite_neat-0.89.0.dist-info}/LICENSE +0 -0
- {cognite_neat-0.88.2.dist-info → cognite_neat-0.89.0.dist-info}/WHEEL +0 -0
- {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.
|
|
16
|
-
from cognite.neat.
|
|
17
|
-
from cognite.neat.
|
|
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,
|
|
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,
|
|
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(
|
|
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
|
-
|
|
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,
|
|
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.
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
321
|
+
|
|
322
|
+
args: dict[str, Any] = {}
|
|
287
323
|
if issubclass(type_cls, ListablePropertyType):
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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(
|
|
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
|
|
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
|
|
471
|
-
reverse_prop_id
|
|
472
|
-
if isinstance(prop.value_type,
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
15
|
-
from cognite.neat.
|
|
16
|
-
|
|
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
|
-
|
|
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"
|
|
178
|
-
value_type: DataType |
|
|
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:
|
|
200
|
-
) -> DataType |
|
|
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
|
|
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
|
|
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 |
|
|
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
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
-
|
|
324
|
-
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
|
-
|
|
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
|
-
|
|
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(
|
|
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)
|