cognite-neat 0.103.1__py3-none-any.whl → 0.105.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/_client/_api/data_modeling_loaders.py +83 -23
- cognite/neat/_client/_api/schema.py +2 -1
- cognite/neat/_client/data_classes/neat_sequence.py +261 -0
- cognite/neat/_client/data_classes/schema.py +5 -1
- cognite/neat/_client/testing.py +33 -0
- cognite/neat/_constants.py +56 -0
- cognite/neat/_graph/extractors/_classic_cdf/_base.py +6 -5
- cognite/neat/_graph/extractors/_classic_cdf/_sequences.py +225 -11
- cognite/neat/_graph/extractors/_mock_graph_generator.py +2 -2
- cognite/neat/_graph/loaders/_rdf2dms.py +13 -2
- cognite/neat/_graph/transformers/__init__.py +3 -1
- cognite/neat/_graph/transformers/_base.py +109 -1
- cognite/neat/_graph/transformers/_classic_cdf.py +6 -1
- cognite/neat/_graph/transformers/_prune_graph.py +103 -47
- cognite/neat/_graph/transformers/_rdfpath.py +41 -17
- cognite/neat/_graph/transformers/_value_type.py +188 -151
- cognite/neat/_issues/__init__.py +0 -2
- cognite/neat/_issues/_base.py +54 -43
- cognite/neat/_issues/warnings/__init__.py +4 -1
- cognite/neat/_issues/warnings/_general.py +7 -0
- cognite/neat/_issues/warnings/_resources.py +12 -1
- cognite/neat/_rules/_shared.py +18 -34
- cognite/neat/_rules/exporters/_base.py +28 -2
- cognite/neat/_rules/exporters/_rules2dms.py +39 -1
- cognite/neat/_rules/exporters/_rules2excel.py +13 -2
- cognite/neat/_rules/exporters/_rules2instance_template.py +4 -0
- cognite/neat/_rules/exporters/_rules2ontology.py +13 -1
- cognite/neat/_rules/exporters/_rules2yaml.py +4 -0
- cognite/neat/_rules/importers/_base.py +9 -0
- cognite/neat/_rules/importers/_dms2rules.py +80 -57
- cognite/neat/_rules/importers/_dtdl2rules/dtdl_importer.py +5 -2
- cognite/neat/_rules/importers/_rdf/_base.py +10 -8
- cognite/neat/_rules/importers/_rdf/_imf2rules.py +4 -0
- cognite/neat/_rules/importers/_rdf/_inference2rules.py +7 -0
- cognite/neat/_rules/importers/_rdf/_owl2rules.py +4 -0
- cognite/neat/_rules/importers/_spreadsheet2rules.py +17 -8
- cognite/neat/_rules/importers/_yaml2rules.py +21 -7
- cognite/neat/_rules/models/_base_input.py +1 -1
- cognite/neat/_rules/models/_base_rules.py +9 -1
- cognite/neat/_rules/models/dms/_rules.py +4 -0
- cognite/neat/_rules/models/dms/_rules_input.py +9 -0
- cognite/neat/_rules/models/entities/_wrapped.py +10 -5
- cognite/neat/_rules/models/information/_rules.py +4 -0
- cognite/neat/_rules/models/information/_rules_input.py +9 -0
- cognite/neat/_rules/models/mapping/_classic2core.py +2 -5
- cognite/neat/_rules/models/mapping/_classic2core.yaml +239 -38
- cognite/neat/_rules/transformers/__init__.py +13 -6
- cognite/neat/_rules/transformers/_base.py +41 -65
- cognite/neat/_rules/transformers/_converters.py +404 -234
- cognite/neat/_rules/transformers/_mapping.py +93 -72
- cognite/neat/_rules/transformers/_verification.py +50 -38
- cognite/neat/_session/_base.py +32 -121
- cognite/neat/_session/_inspect.py +5 -3
- cognite/neat/_session/_mapping.py +17 -105
- cognite/neat/_session/_prepare.py +138 -268
- cognite/neat/_session/_read.py +39 -195
- cognite/neat/_session/_set.py +6 -30
- cognite/neat/_session/_show.py +40 -21
- cognite/neat/_session/_state.py +49 -107
- cognite/neat/_session/_to.py +44 -33
- cognite/neat/_shared.py +23 -2
- cognite/neat/_store/_provenance.py +3 -82
- cognite/neat/_store/_rules_store.py +368 -10
- cognite/neat/_store/exceptions.py +23 -0
- cognite/neat/_utils/graph_transformations_report.py +36 -0
- cognite/neat/_utils/rdf_.py +8 -0
- cognite/neat/_utils/reader/_base.py +27 -0
- cognite/neat/_utils/spreadsheet.py +5 -4
- cognite/neat/_version.py +1 -1
- {cognite_neat-0.103.1.dist-info → cognite_neat-0.105.0.dist-info}/METADATA +3 -2
- cognite_neat-0.105.0.dist-info/RECORD +179 -0
- {cognite_neat-0.103.1.dist-info → cognite_neat-0.105.0.dist-info}/WHEEL +1 -1
- cognite/neat/_app/api/__init__.py +0 -0
- cognite/neat/_app/api/asgi/metrics.py +0 -4
- cognite/neat/_app/api/configuration.py +0 -98
- cognite/neat/_app/api/context_manager/__init__.py +0 -3
- cognite/neat/_app/api/context_manager/manager.py +0 -16
- cognite/neat/_app/api/data_classes/__init__.py +0 -0
- cognite/neat/_app/api/data_classes/rest.py +0 -59
- cognite/neat/_app/api/explorer.py +0 -66
- cognite/neat/_app/api/routers/configuration.py +0 -25
- cognite/neat/_app/api/routers/crud.py +0 -102
- cognite/neat/_app/api/routers/metrics.py +0 -10
- cognite/neat/_app/api/routers/workflows.py +0 -224
- cognite/neat/_app/api/utils/__init__.py +0 -0
- cognite/neat/_app/api/utils/data_mapping.py +0 -17
- cognite/neat/_app/api/utils/logging.py +0 -26
- cognite/neat/_app/api/utils/query_templates.py +0 -92
- cognite/neat/_app/main.py +0 -17
- cognite/neat/_app/monitoring/__init__.py +0 -0
- cognite/neat/_app/monitoring/metrics.py +0 -69
- cognite/neat/_app/ui/index.html +0 -1
- cognite/neat/_app/ui/neat-app/.gitignore +0 -23
- cognite/neat/_app/ui/neat-app/README.md +0 -70
- cognite/neat/_app/ui/neat-app/build/asset-manifest.json +0 -14
- cognite/neat/_app/ui/neat-app/build/favicon.ico +0 -0
- cognite/neat/_app/ui/neat-app/build/img/architect-icon.svg +0 -116
- cognite/neat/_app/ui/neat-app/build/img/developer-icon.svg +0 -112
- cognite/neat/_app/ui/neat-app/build/img/sme-icon.svg +0 -34
- cognite/neat/_app/ui/neat-app/build/index.html +0 -1
- cognite/neat/_app/ui/neat-app/build/logo192.png +0 -0
- cognite/neat/_app/ui/neat-app/build/manifest.json +0 -25
- cognite/neat/_app/ui/neat-app/build/robots.txt +0 -3
- cognite/neat/_app/ui/neat-app/build/static/css/main.72e3d92e.css +0 -2
- cognite/neat/_app/ui/neat-app/build/static/css/main.72e3d92e.css.map +0 -1
- cognite/neat/_app/ui/neat-app/build/static/js/main.5a52cf09.js +0 -3
- cognite/neat/_app/ui/neat-app/build/static/js/main.5a52cf09.js.LICENSE.txt +0 -88
- cognite/neat/_app/ui/neat-app/build/static/js/main.5a52cf09.js.map +0 -1
- cognite/neat/_app/ui/neat-app/build/static/media/logo.8093b84df9ed36a174c629d6fe0b730d.svg +0 -1
- cognite/neat/_app/ui/neat-app/package-lock.json +0 -18306
- cognite/neat/_app/ui/neat-app/package.json +0 -62
- cognite/neat/_app/ui/neat-app/public/favicon.ico +0 -0
- cognite/neat/_app/ui/neat-app/public/img/architect-icon.svg +0 -116
- cognite/neat/_app/ui/neat-app/public/img/developer-icon.svg +0 -112
- cognite/neat/_app/ui/neat-app/public/img/sme-icon.svg +0 -34
- cognite/neat/_app/ui/neat-app/public/index.html +0 -43
- cognite/neat/_app/ui/neat-app/public/logo192.png +0 -0
- cognite/neat/_app/ui/neat-app/public/manifest.json +0 -25
- cognite/neat/_app/ui/neat-app/public/robots.txt +0 -3
- cognite/neat/_app/ui/neat-app/src/App.css +0 -38
- cognite/neat/_app/ui/neat-app/src/App.js +0 -17
- cognite/neat/_app/ui/neat-app/src/App.test.js +0 -8
- cognite/neat/_app/ui/neat-app/src/MainContainer.tsx +0 -70
- cognite/neat/_app/ui/neat-app/src/components/JsonViewer.tsx +0 -43
- cognite/neat/_app/ui/neat-app/src/components/LocalUploader.tsx +0 -124
- cognite/neat/_app/ui/neat-app/src/components/OverviewComponentEditorDialog.tsx +0 -63
- cognite/neat/_app/ui/neat-app/src/components/StepEditorDialog.tsx +0 -511
- cognite/neat/_app/ui/neat-app/src/components/TabPanel.tsx +0 -36
- cognite/neat/_app/ui/neat-app/src/components/Utils.tsx +0 -56
- cognite/neat/_app/ui/neat-app/src/components/WorkflowDeleteDialog.tsx +0 -60
- cognite/neat/_app/ui/neat-app/src/components/WorkflowExecutionReport.tsx +0 -112
- cognite/neat/_app/ui/neat-app/src/components/WorkflowImportExportDialog.tsx +0 -67
- cognite/neat/_app/ui/neat-app/src/components/WorkflowMetadataDialog.tsx +0 -79
- cognite/neat/_app/ui/neat-app/src/index.css +0 -13
- cognite/neat/_app/ui/neat-app/src/index.js +0 -13
- cognite/neat/_app/ui/neat-app/src/logo.svg +0 -1
- cognite/neat/_app/ui/neat-app/src/reportWebVitals.js +0 -13
- cognite/neat/_app/ui/neat-app/src/setupTests.js +0 -5
- cognite/neat/_app/ui/neat-app/src/types/WorkflowTypes.ts +0 -388
- cognite/neat/_app/ui/neat-app/src/views/AboutView.tsx +0 -61
- cognite/neat/_app/ui/neat-app/src/views/ConfigView.tsx +0 -184
- cognite/neat/_app/ui/neat-app/src/views/GlobalConfigView.tsx +0 -180
- cognite/neat/_app/ui/neat-app/src/views/WorkflowView.tsx +0 -570
- cognite/neat/_app/ui/neat-app/tsconfig.json +0 -27
- cognite/neat/_rules/transformers/_pipelines.py +0 -70
- cognite/neat/_workflows/__init__.py +0 -17
- cognite/neat/_workflows/base.py +0 -590
- cognite/neat/_workflows/cdf_store.py +0 -393
- cognite/neat/_workflows/examples/Export_DMS/workflow.yaml +0 -89
- cognite/neat/_workflows/examples/Export_Semantic_Data_Model/workflow.yaml +0 -66
- cognite/neat/_workflows/examples/Import_DMS/workflow.yaml +0 -65
- cognite/neat/_workflows/examples/Validate_Rules/workflow.yaml +0 -67
- cognite/neat/_workflows/examples/Validate_Solution_Model/workflow.yaml +0 -64
- cognite/neat/_workflows/manager.py +0 -292
- cognite/neat/_workflows/model.py +0 -203
- cognite/neat/_workflows/steps/__init__.py +0 -0
- cognite/neat/_workflows/steps/data_contracts.py +0 -109
- cognite/neat/_workflows/steps/lib/__init__.py +0 -0
- cognite/neat/_workflows/steps/lib/current/__init__.py +0 -6
- cognite/neat/_workflows/steps/lib/current/graph_extractor.py +0 -100
- cognite/neat/_workflows/steps/lib/current/graph_loader.py +0 -51
- cognite/neat/_workflows/steps/lib/current/graph_store.py +0 -48
- cognite/neat/_workflows/steps/lib/current/rules_exporter.py +0 -537
- cognite/neat/_workflows/steps/lib/current/rules_importer.py +0 -398
- cognite/neat/_workflows/steps/lib/current/rules_validator.py +0 -106
- cognite/neat/_workflows/steps/lib/io/__init__.py +0 -1
- cognite/neat/_workflows/steps/lib/io/io_steps.py +0 -393
- cognite/neat/_workflows/steps/step_model.py +0 -79
- cognite/neat/_workflows/steps_registry.py +0 -218
- cognite/neat/_workflows/tasks.py +0 -18
- cognite/neat/_workflows/triggers.py +0 -169
- cognite/neat/_workflows/utils.py +0 -19
- cognite_neat-0.103.1.dist-info/RECORD +0 -275
- {cognite_neat-0.103.1.dist-info → cognite_neat-0.105.0.dist-info}/LICENSE +0 -0
- {cognite_neat-0.103.1.dist-info → cognite_neat-0.105.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import dataclasses
|
|
1
2
|
import re
|
|
2
3
|
import warnings
|
|
3
|
-
from abc import ABC
|
|
4
|
+
from abc import ABC
|
|
4
5
|
from collections import Counter, defaultdict
|
|
5
6
|
from collections.abc import Collection, Mapping
|
|
6
7
|
from datetime import date, datetime
|
|
@@ -9,11 +10,12 @@ from typing import ClassVar, Literal, TypeVar, cast, overload
|
|
|
9
10
|
from cognite.client.data_classes import data_modeling as dms
|
|
10
11
|
from cognite.client.data_classes.data_modeling import DataModelId, DataModelIdentifier, ViewId
|
|
11
12
|
|
|
13
|
+
from cognite.neat._client import NeatClient
|
|
14
|
+
from cognite.neat._client.data_classes.data_modeling import ContainerApplyDict, ViewApplyDict
|
|
12
15
|
from cognite.neat._constants import (
|
|
13
16
|
COGNITE_MODELS,
|
|
14
17
|
DMS_CONTAINER_PROPERTY_SIZE_LIMIT,
|
|
15
18
|
)
|
|
16
|
-
from cognite.neat._issues._base import IssueList
|
|
17
19
|
from cognite.neat._issues.errors import NeatValueError
|
|
18
20
|
from cognite.neat._issues.warnings import NeatValueWarning
|
|
19
21
|
from cognite.neat._issues.warnings._models import (
|
|
@@ -21,13 +23,13 @@ from cognite.neat._issues.warnings._models import (
|
|
|
21
23
|
SolutionModelBuildOnTopOfCDMWarning,
|
|
22
24
|
)
|
|
23
25
|
from cognite.neat._rules._shared import (
|
|
24
|
-
|
|
25
|
-
JustRules,
|
|
26
|
-
OutRules,
|
|
26
|
+
ReadInputRules,
|
|
27
27
|
ReadRules,
|
|
28
|
+
T_InputRules,
|
|
28
29
|
VerifiedRules,
|
|
29
30
|
)
|
|
30
31
|
from cognite.neat._rules.analysis import DMSAnalysis
|
|
32
|
+
from cognite.neat._rules.importers import DMSImporter
|
|
31
33
|
from cognite.neat._rules.models import (
|
|
32
34
|
DMSInputRules,
|
|
33
35
|
DMSRules,
|
|
@@ -36,7 +38,7 @@ from cognite.neat._rules.models import (
|
|
|
36
38
|
data_types,
|
|
37
39
|
)
|
|
38
40
|
from cognite.neat._rules.models.data_types import AnyURI, DataType, String
|
|
39
|
-
from cognite.neat._rules.models.dms import DMSMetadata, DMSProperty, DMSView
|
|
41
|
+
from cognite.neat._rules.models.dms import DMSMetadata, DMSProperty, DMSValidation, DMSView
|
|
40
42
|
from cognite.neat._rules.models.dms._rules import DMSContainer
|
|
41
43
|
from cognite.neat._rules.models.entities import (
|
|
42
44
|
ClassEntity,
|
|
@@ -44,6 +46,7 @@ from cognite.neat._rules.models.entities import (
|
|
|
44
46
|
DMSUnknownEntity,
|
|
45
47
|
EdgeEntity,
|
|
46
48
|
Entity,
|
|
49
|
+
HasDataFilter,
|
|
47
50
|
MultiValueTypeInfo,
|
|
48
51
|
ReverseConnectionEntity,
|
|
49
52
|
T_Entity,
|
|
@@ -56,44 +59,38 @@ from cognite.neat._rules.models.information._rules_input import (
|
|
|
56
59
|
InformationInputProperty,
|
|
57
60
|
InformationInputRules,
|
|
58
61
|
)
|
|
59
|
-
from cognite.neat._utils.collection_ import remove_list_elements
|
|
60
62
|
from cognite.neat._utils.text import to_camel
|
|
61
63
|
|
|
62
64
|
from ._base import RulesTransformer
|
|
65
|
+
from ._verification import VerifyDMSRules
|
|
63
66
|
|
|
64
67
|
T_VerifiedInRules = TypeVar("T_VerifiedInRules", bound=VerifiedRules)
|
|
65
68
|
T_VerifiedOutRules = TypeVar("T_VerifiedOutRules", bound=VerifiedRules)
|
|
66
|
-
T_InputInRules = TypeVar("T_InputInRules", bound=
|
|
67
|
-
T_InputOutRules = TypeVar("T_InputOutRules", bound=
|
|
69
|
+
T_InputInRules = TypeVar("T_InputInRules", bound=ReadInputRules)
|
|
70
|
+
T_InputOutRules = TypeVar("T_InputOutRules", bound=ReadInputRules)
|
|
68
71
|
|
|
69
72
|
|
|
70
73
|
class ConversionTransformer(RulesTransformer[T_VerifiedInRules, T_VerifiedOutRules], ABC):
|
|
71
74
|
"""Base class for all conversion transformers."""
|
|
72
75
|
|
|
73
|
-
|
|
74
|
-
out = self._transform(self._to_rules(rules))
|
|
75
|
-
return JustRules(out)
|
|
76
|
+
...
|
|
76
77
|
|
|
77
|
-
@abstractmethod
|
|
78
|
-
def _transform(self, rules: T_VerifiedInRules) -> T_VerifiedOutRules:
|
|
79
|
-
raise NotImplementedError()
|
|
80
78
|
|
|
81
|
-
|
|
82
|
-
class ToCompliantEntities(RulesTransformer[InformationInputRules, InformationInputRules]): # type: ignore[misc]
|
|
79
|
+
class ToCompliantEntities(RulesTransformer[ReadRules[InformationInputRules], ReadRules[InformationInputRules]]): # type: ignore[misc]
|
|
83
80
|
"""Converts input rules to rules with compliant entity IDs that match regex patters used
|
|
84
81
|
by DMS schema components."""
|
|
85
82
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return ReadRules(self._transform(self._to_rules(rules)), IssueList(), {})
|
|
90
|
-
|
|
91
|
-
def _transform(self, rules: InformationInputRules) -> InformationInputRules:
|
|
92
|
-
rules.classes = self._fix_classes(rules.classes)
|
|
93
|
-
rules.properties = self._fix_properties(rules.properties)
|
|
94
|
-
rules.metadata.version += "_dms_compliant"
|
|
83
|
+
@property
|
|
84
|
+
def description(self) -> str:
|
|
85
|
+
return "Ensures externalIDs are compliant with CDF"
|
|
95
86
|
|
|
96
|
-
|
|
87
|
+
def transform(self, rules: ReadRules[InformationInputRules]) -> ReadRules[InformationInputRules]:
|
|
88
|
+
if rules.rules is None:
|
|
89
|
+
return rules
|
|
90
|
+
copy: InformationInputRules = dataclasses.replace(rules.rules)
|
|
91
|
+
copy.classes = self._fix_classes(copy.classes)
|
|
92
|
+
copy.properties = self._fix_properties(copy.properties)
|
|
93
|
+
return ReadRules(copy, rules.read_context)
|
|
97
94
|
|
|
98
95
|
@classmethod
|
|
99
96
|
def _fix_entity(cls, entity: str) -> str:
|
|
@@ -179,46 +176,47 @@ class ToCompliantEntities(RulesTransformer[InformationInputRules, InformationInp
|
|
|
179
176
|
return fixed_definitions
|
|
180
177
|
|
|
181
178
|
|
|
182
|
-
class PrefixEntities(RulesTransformer[
|
|
179
|
+
class PrefixEntities(RulesTransformer[ReadRules[T_InputRules], ReadRules[T_InputRules]]): # type: ignore[type-var]
|
|
183
180
|
"""Prefixes all entities with a given prefix."""
|
|
184
181
|
|
|
185
182
|
def __init__(self, prefix: str) -> None:
|
|
186
183
|
self._prefix = prefix
|
|
187
184
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
def _transform(self, rules: InputRules) -> InputRules:
|
|
192
|
-
rules.metadata.version += f"_prefixed_{self._prefix}"
|
|
185
|
+
@property
|
|
186
|
+
def description(self) -> str:
|
|
187
|
+
return f"Prefixes all views with {self._prefix!r}"
|
|
193
188
|
|
|
194
|
-
|
|
195
|
-
|
|
189
|
+
def transform(self, rules: ReadRules[T_InputRules]) -> ReadRules[T_InputRules]:
|
|
190
|
+
in_ = rules.rules
|
|
191
|
+
if in_ is None:
|
|
192
|
+
return rules
|
|
193
|
+
copy: T_InputRules = dataclasses.replace(in_)
|
|
194
|
+
if isinstance(copy, InformationInputRules):
|
|
196
195
|
prefixed_by_class: dict[str, str] = {}
|
|
197
|
-
for cls in
|
|
196
|
+
for cls in copy.classes:
|
|
198
197
|
prefixed = str(self._with_prefix(cls.class_))
|
|
199
198
|
prefixed_by_class[str(cls.class_)] = prefixed
|
|
200
199
|
cls.class_ = prefixed
|
|
201
|
-
for prop in
|
|
200
|
+
for prop in copy.properties:
|
|
202
201
|
prop.class_ = self._with_prefix(prop.class_)
|
|
203
202
|
if str(prop.value_type) in prefixed_by_class:
|
|
204
203
|
prop.value_type = prefixed_by_class[str(prop.value_type)]
|
|
205
|
-
return rules
|
|
206
|
-
elif isinstance(
|
|
207
|
-
# Todo not mutate input class new_dms = copy.deepcopy(rules)
|
|
204
|
+
return ReadRules(copy, rules.read_context) # type: ignore[arg-type]
|
|
205
|
+
elif isinstance(copy, DMSInputRules):
|
|
208
206
|
prefixed_by_view: dict[str, str] = {}
|
|
209
|
-
for view in
|
|
207
|
+
for view in copy.views:
|
|
210
208
|
prefixed = str(self._with_prefix(view.view))
|
|
211
209
|
prefixed_by_view[str(view.view)] = prefixed
|
|
212
210
|
view.view = prefixed
|
|
213
|
-
for dms_prop in
|
|
211
|
+
for dms_prop in copy.properties:
|
|
214
212
|
dms_prop.view = self._with_prefix(dms_prop.view)
|
|
215
213
|
if str(dms_prop.value_type) in prefixed_by_view:
|
|
216
214
|
dms_prop.value_type = prefixed_by_view[str(dms_prop.value_type)]
|
|
217
|
-
if
|
|
218
|
-
for container in
|
|
215
|
+
if copy.containers:
|
|
216
|
+
for container in copy.containers:
|
|
219
217
|
container.container = self._with_prefix(container.container)
|
|
220
|
-
return rules
|
|
221
|
-
raise NeatValueError(f"Unsupported rules type: {type(
|
|
218
|
+
return ReadRules(copy, rules.read_context)
|
|
219
|
+
raise NeatValueError(f"Unsupported rules type: {type(copy)}")
|
|
222
220
|
|
|
223
221
|
@overload
|
|
224
222
|
def _with_prefix(self, raw: str) -> str: ...
|
|
@@ -254,14 +252,14 @@ class InformationToDMS(ConversionTransformer[InformationRules, DMSRules]):
|
|
|
254
252
|
self.ignore_undefined_value_types = ignore_undefined_value_types
|
|
255
253
|
self.mode = mode
|
|
256
254
|
|
|
257
|
-
def
|
|
255
|
+
def transform(self, rules: InformationRules) -> DMSRules:
|
|
258
256
|
return _InformationRulesConverter(rules).as_dms_rules(self.ignore_undefined_value_types, self.mode)
|
|
259
257
|
|
|
260
258
|
|
|
261
259
|
class DMSToInformation(ConversionTransformer[DMSRules, InformationRules]):
|
|
262
260
|
"""Converts DMSRules to InformationRules."""
|
|
263
261
|
|
|
264
|
-
def
|
|
262
|
+
def transform(self, rules: DMSRules) -> InformationRules:
|
|
265
263
|
return _DMSRulesConverter(rules).as_information_rules()
|
|
266
264
|
|
|
267
265
|
|
|
@@ -271,13 +269,13 @@ class ConvertToRules(ConversionTransformer[VerifiedRules, VerifiedRules]):
|
|
|
271
269
|
def __init__(self, out_cls: type[VerifiedRules]):
|
|
272
270
|
self._out_cls = out_cls
|
|
273
271
|
|
|
274
|
-
def
|
|
272
|
+
def transform(self, rules: VerifiedRules) -> VerifiedRules:
|
|
275
273
|
if isinstance(rules, self._out_cls):
|
|
276
274
|
return rules
|
|
277
275
|
if isinstance(rules, InformationRules) and self._out_cls is DMSRules:
|
|
278
|
-
return InformationToDMS().transform(rules)
|
|
276
|
+
return InformationToDMS().transform(rules)
|
|
279
277
|
if isinstance(rules, DMSRules) and self._out_cls is InformationRules:
|
|
280
|
-
return DMSToInformation().transform(rules)
|
|
278
|
+
return DMSToInformation().transform(rules)
|
|
281
279
|
raise ValueError(f"Unsupported conversion from {type(rules)} to {self._out_cls}")
|
|
282
280
|
|
|
283
281
|
|
|
@@ -288,119 +286,275 @@ class SetIDDMSModel(RulesTransformer[DMSRules, DMSRules]):
|
|
|
288
286
|
def __init__(self, new_id: DataModelId | tuple[str, str, str]):
|
|
289
287
|
self.new_id = DataModelId.load(new_id)
|
|
290
288
|
|
|
291
|
-
|
|
289
|
+
@property
|
|
290
|
+
def description(self) -> str:
|
|
291
|
+
return f"Sets the Data Model ID to {self.new_id.as_tuple()}"
|
|
292
|
+
|
|
293
|
+
def transform(self, rules: DMSRules) -> DMSRules:
|
|
292
294
|
if self.new_id.version is None:
|
|
293
295
|
raise NeatValueError("Version is required when setting a new Data Model ID")
|
|
294
|
-
dump =
|
|
296
|
+
dump = rules.dump()
|
|
295
297
|
dump["metadata"]["space"] = self.new_id.space
|
|
296
298
|
dump["metadata"]["external_id"] = self.new_id.external_id
|
|
297
299
|
dump["metadata"]["version"] = self.new_id.version
|
|
298
300
|
# Serialize and deserialize to set the new space and external_id
|
|
299
301
|
# as the default values for the new model.
|
|
300
|
-
return
|
|
302
|
+
return DMSRules.model_validate(DMSInputRules.load(dump).dump())
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
class ToExtensionModel(RulesTransformer[DMSRules, DMSRules], ABC):
|
|
306
|
+
type_: ClassVar[str]
|
|
307
|
+
|
|
308
|
+
def __init__(self, new_model_id: DataModelIdentifier, org_name: str, dummy_property: str | None = None) -> None:
|
|
309
|
+
self.new_model_id = DataModelId.load(new_model_id)
|
|
310
|
+
if not self.new_model_id.version:
|
|
311
|
+
raise NeatValueError("Version is required for the new model.")
|
|
312
|
+
self.org_name = org_name
|
|
313
|
+
self.dummy_property = dummy_property
|
|
314
|
+
|
|
315
|
+
@property
|
|
316
|
+
def description(self) -> str:
|
|
317
|
+
return f"Prepared data model {self.new_model_id} to be {self.type_.replace('_', ' ')} data model."
|
|
318
|
+
|
|
319
|
+
def _create_new_views(
|
|
320
|
+
self, rules: DMSRules
|
|
321
|
+
) -> tuple[SheetList[DMSView], SheetList[DMSContainer], SheetList[DMSProperty]]:
|
|
322
|
+
"""Creates new views for the new model.
|
|
323
|
+
|
|
324
|
+
If the dummy property is provided, it will also create a new container for each view
|
|
325
|
+
with a single property that is the dummy property.
|
|
326
|
+
"""
|
|
327
|
+
new_views = SheetList[DMSView]()
|
|
328
|
+
new_containers = SheetList[DMSContainer]()
|
|
329
|
+
new_properties = SheetList[DMSProperty]()
|
|
330
|
+
|
|
331
|
+
for definition in rules.views:
|
|
332
|
+
view_entity = self._remove_cognite_affix(definition.view)
|
|
333
|
+
view_entity.version = cast(str, self.new_model_id.version)
|
|
334
|
+
view_entity.prefix = self.new_model_id.space
|
|
335
|
+
|
|
336
|
+
new_views.append(
|
|
337
|
+
DMSView(
|
|
338
|
+
view=view_entity,
|
|
339
|
+
implements=[definition.view],
|
|
340
|
+
in_model=True,
|
|
341
|
+
name=definition.name,
|
|
342
|
+
)
|
|
343
|
+
)
|
|
344
|
+
if self.dummy_property is None:
|
|
345
|
+
continue
|
|
346
|
+
|
|
347
|
+
container_entity = ContainerEntity(space=view_entity.prefix, externalId=view_entity.external_id)
|
|
301
348
|
|
|
349
|
+
container = DMSContainer(container=container_entity)
|
|
350
|
+
|
|
351
|
+
prefix = to_camel(view_entity.suffix)
|
|
352
|
+
property_ = DMSProperty(
|
|
353
|
+
view=view_entity,
|
|
354
|
+
view_property=f"{prefix}{self.dummy_property}",
|
|
355
|
+
value_type=String(),
|
|
356
|
+
nullable=True,
|
|
357
|
+
immutable=False,
|
|
358
|
+
is_list=False,
|
|
359
|
+
container=container_entity,
|
|
360
|
+
container_property=f"{prefix}{self.dummy_property}",
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
new_properties.append(property_)
|
|
364
|
+
new_containers.append(container)
|
|
365
|
+
|
|
366
|
+
return new_views, new_containers, new_properties
|
|
367
|
+
|
|
368
|
+
def _remove_cognite_affix(self, entity: _T_Entity) -> _T_Entity:
|
|
369
|
+
"""This method removes `Cognite` affix from the entity."""
|
|
370
|
+
new_suffix = entity.suffix.replace("Cognite", self.org_name or "")
|
|
371
|
+
if isinstance(entity, ViewEntity):
|
|
372
|
+
return ViewEntity(space=entity.space, externalId=new_suffix, version=entity.version) # type: ignore[return-value]
|
|
373
|
+
elif isinstance(entity, ClassEntity):
|
|
374
|
+
return ClassEntity(prefix=entity.prefix, suffix=new_suffix, version=entity.version) # type: ignore[return-value]
|
|
375
|
+
raise ValueError(f"Unsupported entity type: {type(entity)}")
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
class ToEnterpriseModel(ToExtensionModel):
|
|
379
|
+
type_: ClassVar[str] = "enterprise"
|
|
302
380
|
|
|
303
|
-
class ToExtension(RulesTransformer[DMSRules, DMSRules]):
|
|
304
381
|
def __init__(
|
|
305
382
|
self,
|
|
306
383
|
new_model_id: DataModelIdentifier,
|
|
307
384
|
org_name: str = "My",
|
|
308
|
-
type_: Literal["enterprise", "solution", "data_product"] = "enterprise",
|
|
309
|
-
mode: Literal["read", "write"] = "read",
|
|
310
385
|
dummy_property: str = "GUID",
|
|
311
386
|
move_connections: bool = False,
|
|
312
|
-
include: Literal["same-space", "all"] = "same-space",
|
|
313
387
|
):
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
raise NeatValueError("Version is required for the new model.")
|
|
388
|
+
super().__init__(new_model_id, org_name, dummy_property)
|
|
389
|
+
self.move_connections = move_connections
|
|
317
390
|
|
|
318
|
-
|
|
391
|
+
def transform(self, rules: DMSRules) -> DMSRules:
|
|
392
|
+
reference_model_id = rules.metadata.as_data_model_id()
|
|
393
|
+
if reference_model_id not in COGNITE_MODELS:
|
|
394
|
+
warnings.warn(
|
|
395
|
+
EnterpriseModelNotBuildOnTopOfCDMWarning(reference_model_id=reference_model_id).as_message(),
|
|
396
|
+
stacklevel=2,
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
return self._to_enterprise(rules)
|
|
400
|
+
|
|
401
|
+
def _to_enterprise(self, reference_model: DMSRules) -> DMSRules:
|
|
402
|
+
dump = reference_model.dump()
|
|
403
|
+
|
|
404
|
+
# This will create reference model components in the enterprise model space
|
|
405
|
+
enterprise_model = DMSRules.model_validate(DMSInputRules.load(dump).dump())
|
|
406
|
+
|
|
407
|
+
# Post validation metadata update:
|
|
408
|
+
enterprise_model.metadata.name = self.type_
|
|
409
|
+
enterprise_model.metadata.name = f"{self.org_name} {self.type_} data model"
|
|
410
|
+
enterprise_model.metadata.space = self.new_model_id.space
|
|
411
|
+
enterprise_model.metadata.external_id = self.new_model_id.external_id
|
|
412
|
+
enterprise_model.metadata.version = cast(str, self.new_model_id.version)
|
|
413
|
+
|
|
414
|
+
# Here we are creating enterprise views with a single container with a dummy property
|
|
415
|
+
# for each view
|
|
416
|
+
enterprise_views, enterprise_containers, enterprise_properties = self._create_new_views(enterprise_model)
|
|
417
|
+
|
|
418
|
+
# We keep the reference views, and adding new enterprise views...
|
|
419
|
+
enterprise_model.views.extend(enterprise_views)
|
|
420
|
+
|
|
421
|
+
if self.move_connections:
|
|
422
|
+
# Move connections from reference model to new enterprise model
|
|
423
|
+
enterprise_properties.extend(self._move_connections(enterprise_model))
|
|
424
|
+
|
|
425
|
+
# ... however, we do not want to keep the reference containers and properties
|
|
426
|
+
enterprise_model.containers = enterprise_containers
|
|
427
|
+
enterprise_model.properties = enterprise_properties
|
|
428
|
+
|
|
429
|
+
return enterprise_model
|
|
430
|
+
|
|
431
|
+
@staticmethod
|
|
432
|
+
def _move_connections(rules: DMSRules) -> SheetList[DMSProperty]:
|
|
433
|
+
implements: dict[ViewEntity, list[ViewEntity]] = defaultdict(list)
|
|
434
|
+
new_properties = SheetList[DMSProperty]()
|
|
435
|
+
|
|
436
|
+
for view in rules.views:
|
|
437
|
+
if view.view.space == rules.metadata.space and view.implements:
|
|
438
|
+
for implemented_view in view.implements:
|
|
439
|
+
implements.setdefault(implemented_view, []).append(view.view)
|
|
440
|
+
|
|
441
|
+
# currently only supporting single implementation of reference view in enterprise view
|
|
442
|
+
# connections that do not have properties
|
|
443
|
+
if all(len(v) == 1 for v in implements.values()):
|
|
444
|
+
for prop_ in rules.properties:
|
|
445
|
+
if (
|
|
446
|
+
prop_.view.space != rules.metadata.space
|
|
447
|
+
and prop_.connection
|
|
448
|
+
and isinstance(prop_.value_type, ViewEntity)
|
|
449
|
+
and implements.get(prop_.view)
|
|
450
|
+
and implements.get(prop_.value_type)
|
|
451
|
+
):
|
|
452
|
+
if isinstance(prop_.connection, EdgeEntity) and prop_.connection.properties:
|
|
453
|
+
continue
|
|
454
|
+
new_property = prop_.model_copy(deep=True)
|
|
455
|
+
new_property.view = implements[prop_.view][0]
|
|
456
|
+
new_property.value_type = implements[prop_.value_type][0]
|
|
457
|
+
new_properties.append(new_property)
|
|
458
|
+
|
|
459
|
+
return new_properties
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
class ToSolutionModel(ToExtensionModel):
|
|
463
|
+
"""Creates a solution data model based on an existing data model.
|
|
464
|
+
|
|
465
|
+
The solution data model will create a new view for each view in the existing model.
|
|
466
|
+
|
|
467
|
+
Args:
|
|
468
|
+
new_model_id: DataData model identifier for the new model.
|
|
469
|
+
org_name: If the existing model is a Cognite Data Model, this will replace the "Cognite" affix.
|
|
470
|
+
mode: The mode of the solution model. Either "read" or "write". A "write" model will create a new
|
|
471
|
+
container for each view with a dummy property. Read mode will only inherit the view filter from the
|
|
472
|
+
original model.
|
|
473
|
+
dummy_property: Only applicable if mode='write'. The identifier of the dummy property in the newly created
|
|
474
|
+
container.
|
|
475
|
+
exclude_views_in_other_spaces: Whether to exclude views that are not in the same space as the existing model,
|
|
476
|
+
when creating the solution model.
|
|
477
|
+
filter_type: If mode="read", this is the type of filter to apply to the new views. The filter is used to
|
|
478
|
+
ensure that the new views will return the same instance as the original views. The view filter is the
|
|
479
|
+
simplest filter, but it has limitation in the fusion UI. The container filter is in essence a more
|
|
480
|
+
verbose version of the view filter, and it has better support in the fusion UI. The default is "container".
|
|
481
|
+
|
|
482
|
+
"""
|
|
483
|
+
|
|
484
|
+
type_: ClassVar[str] = "solution"
|
|
485
|
+
|
|
486
|
+
def __init__(
|
|
487
|
+
self,
|
|
488
|
+
new_model_id: DataModelIdentifier,
|
|
489
|
+
org_name: str = "My",
|
|
490
|
+
mode: Literal["read", "write"] = "read",
|
|
491
|
+
dummy_property: str | None = "GUID",
|
|
492
|
+
exclude_views_in_other_spaces: bool = True,
|
|
493
|
+
filter_type: Literal["container", "view"] = "container",
|
|
494
|
+
):
|
|
495
|
+
super().__init__(new_model_id, org_name, dummy_property if mode == "write" else None)
|
|
319
496
|
self.mode = mode
|
|
320
|
-
self.
|
|
321
|
-
self.
|
|
322
|
-
self.move_connections = move_connections
|
|
323
|
-
self.include = include
|
|
497
|
+
self.exclude_views_in_other_spaces = exclude_views_in_other_spaces
|
|
498
|
+
self.filter_type = filter_type
|
|
324
499
|
|
|
325
|
-
def transform(self, rules: DMSRules
|
|
326
|
-
|
|
327
|
-
reference_model = self._to_rules(rules)
|
|
500
|
+
def transform(self, rules: DMSRules) -> DMSRules:
|
|
501
|
+
reference_model = rules
|
|
328
502
|
reference_model_id = reference_model.metadata.as_data_model_id()
|
|
329
503
|
|
|
330
504
|
# if model is solution then we need to get correct space for views and containers
|
|
331
|
-
if self.
|
|
332
|
-
|
|
333
|
-
raise NeatValueError(f"Unsupported mode: {self.mode}")
|
|
334
|
-
|
|
335
|
-
if reference_model_id in COGNITE_MODELS:
|
|
336
|
-
warnings.warn(
|
|
337
|
-
SolutionModelBuildOnTopOfCDMWarning(reference_model_id=reference_model_id),
|
|
338
|
-
stacklevel=2,
|
|
339
|
-
)
|
|
340
|
-
|
|
341
|
-
return self._to_solution(reference_model)
|
|
505
|
+
if self.mode not in ["read", "write"]:
|
|
506
|
+
raise NeatValueError(f"Unsupported mode: {self.mode}")
|
|
342
507
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
)
|
|
349
|
-
|
|
350
|
-
return self._to_enterprise(reference_model)
|
|
351
|
-
elif self.type_ == "data_product":
|
|
352
|
-
expanded = self._expand_properties(reference_model.model_copy(deep=True))
|
|
353
|
-
if self.include == "same-space":
|
|
354
|
-
expanded.properties = SheetList[DMSProperty](
|
|
355
|
-
[prop for prop in expanded.properties if prop.view.space == expanded.metadata.space]
|
|
356
|
-
)
|
|
357
|
-
expanded.views = SheetList[DMSView](
|
|
358
|
-
[view for view in expanded.views if view.view.space == expanded.metadata.space]
|
|
359
|
-
)
|
|
360
|
-
return self._to_solution(expanded, remove_views_in_other_space=False)
|
|
508
|
+
if reference_model_id in COGNITE_MODELS:
|
|
509
|
+
warnings.warn(
|
|
510
|
+
SolutionModelBuildOnTopOfCDMWarning(reference_model_id=reference_model_id),
|
|
511
|
+
stacklevel=2,
|
|
512
|
+
)
|
|
361
513
|
|
|
362
|
-
|
|
363
|
-
raise NeatValueError(f"Unsupported data model type: {self.type_}")
|
|
514
|
+
return self._to_solution(reference_model)
|
|
364
515
|
|
|
365
|
-
|
|
516
|
+
@staticmethod
|
|
517
|
+
def _has_views_in_multiple_space(rules: DMSRules) -> bool:
|
|
366
518
|
return any(view.view.space != rules.metadata.space for view in rules.views)
|
|
367
519
|
|
|
368
|
-
def _to_solution(self, reference_rules: DMSRules
|
|
520
|
+
def _to_solution(self, reference_rules: DMSRules) -> DMSRules:
|
|
369
521
|
"""For creation of solution data model / rules specifically for mapping over existing containers."""
|
|
370
522
|
|
|
371
|
-
dump = reference_rules.dump()
|
|
523
|
+
dump = reference_rules.dump(entities_exclude_defaults=True)
|
|
372
524
|
|
|
373
525
|
# Prepare new model metadata prior validation
|
|
526
|
+
# Since we dropped the defaults, all entities will update the space and version
|
|
527
|
+
# to the new model space and version
|
|
374
528
|
dump["metadata"]["name"] = f"{self.org_name} {self.type_} data model"
|
|
375
529
|
dump["metadata"]["space"] = self.new_model_id.space
|
|
376
530
|
dump["metadata"]["external_id"] = self.new_model_id.external_id
|
|
377
531
|
dump["metadata"]["version"] = self.new_model_id.version
|
|
378
532
|
|
|
379
|
-
# Set implement to NONE for all views
|
|
380
|
-
for view in dump["views"]:
|
|
381
|
-
view["implements"] = None
|
|
382
|
-
|
|
383
|
-
if remove_views_in_other_space and self._has_views_in_multiple_space(reference_rules):
|
|
384
|
-
views_to_remove = []
|
|
385
|
-
for view in dump["views"]:
|
|
386
|
-
if ":" in view["view"]:
|
|
387
|
-
views_to_remove.append(view)
|
|
388
|
-
|
|
389
|
-
dump["views"] = remove_list_elements(dump["views"], views_to_remove)
|
|
390
|
-
|
|
391
533
|
solution_model = DMSRules.model_validate(DMSInputRules.load(dump).dump())
|
|
392
534
|
|
|
393
|
-
#
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
# We want to map properties to existing containers allowing extension
|
|
535
|
+
# This is not desirable for the containers, so we manually fix that here.
|
|
536
|
+
# It is easier to change the space for all entities and then revert the containers, than
|
|
537
|
+
# to change the space for all entities except the containers.
|
|
397
538
|
for prop in solution_model.properties:
|
|
398
539
|
if prop.container and prop.container.space == self.new_model_id.space:
|
|
540
|
+
# If the container is in the new model space, we want to map it to the reference model space
|
|
541
|
+
# This is reverting the .dump() -> .load() above.
|
|
399
542
|
prop.container = ContainerEntity(
|
|
400
543
|
space=reference_rules.metadata.space,
|
|
401
544
|
externalId=prop.container.suffix,
|
|
402
545
|
)
|
|
403
546
|
|
|
547
|
+
for view in solution_model.views:
|
|
548
|
+
view.implements = None
|
|
549
|
+
|
|
550
|
+
if self.exclude_views_in_other_spaces and self._has_views_in_multiple_space(reference_rules):
|
|
551
|
+
solution_model.views = SheetList[DMSView](
|
|
552
|
+
[view for view in solution_model.views if view.view.space == solution_model.metadata.space]
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
# Dropping containers coming from reference model
|
|
556
|
+
solution_model.containers = None
|
|
557
|
+
|
|
404
558
|
# If reference model on which we are mapping one of Cognite Data Models
|
|
405
559
|
# since we want to affix these with the organization name
|
|
406
560
|
if reference_rules.metadata.as_data_model_id() in COGNITE_MODELS:
|
|
@@ -411,50 +565,73 @@ class ToExtension(RulesTransformer[DMSRules, DMSRules]):
|
|
|
411
565
|
prop.value_type = self._remove_cognite_affix(prop.value_type)
|
|
412
566
|
for view in solution_model.views:
|
|
413
567
|
view.view = self._remove_cognite_affix(view.view)
|
|
568
|
+
if view.implements:
|
|
569
|
+
view.implements = [self._remove_cognite_affix(implemented) for implemented in view.implements]
|
|
414
570
|
|
|
415
571
|
if self.mode == "write":
|
|
416
|
-
_, new_containers, new_properties = self.
|
|
417
|
-
|
|
572
|
+
_, new_containers, new_properties = self._create_new_views(solution_model)
|
|
418
573
|
# Here we add ONLY dummy properties of the solution model and
|
|
419
574
|
# corresponding solution model space containers to hold them
|
|
420
575
|
solution_model.containers = new_containers
|
|
421
576
|
solution_model.properties.extend(new_properties)
|
|
577
|
+
elif self.mode == "read":
|
|
578
|
+
# Inherit view filter from original model to ensure the same instances are returned
|
|
579
|
+
# when querying the new view.
|
|
580
|
+
ref_views_by_external_id = {
|
|
581
|
+
view.view.external_id: view
|
|
582
|
+
for view in reference_rules.views
|
|
583
|
+
if view.view.space == reference_rules.metadata.space
|
|
584
|
+
}
|
|
585
|
+
ref_containers_by_ref_view = defaultdict(set)
|
|
586
|
+
for prop in reference_rules.properties:
|
|
587
|
+
if prop.container:
|
|
588
|
+
ref_containers_by_ref_view[prop.view].add(prop.container)
|
|
589
|
+
for view in solution_model.views:
|
|
590
|
+
if ref_view := ref_views_by_external_id.get(view.view.external_id):
|
|
591
|
+
if self.filter_type == "view":
|
|
592
|
+
view.filter_ = HasDataFilter(inner=[ref_view.view])
|
|
593
|
+
elif self.filter_type == "container" and (
|
|
594
|
+
ref_containers := ref_containers_by_ref_view.get(ref_view.view)
|
|
595
|
+
):
|
|
596
|
+
# Sorting to ensure deterministic order
|
|
597
|
+
view.filter_ = HasDataFilter(inner=sorted(ref_containers))
|
|
422
598
|
|
|
423
|
-
return
|
|
424
|
-
|
|
425
|
-
def _to_enterprise(self, reference_model: DMSRules) -> JustRules[DMSRules]:
|
|
426
|
-
dump = reference_model.dump()
|
|
427
|
-
|
|
428
|
-
# This will create reference model components in the enterprise model space
|
|
429
|
-
enterprise_model = DMSRules.model_validate(DMSInputRules.load(dump).dump())
|
|
430
|
-
|
|
431
|
-
# Post validation metadata update:
|
|
432
|
-
enterprise_model.metadata.name = self.type_
|
|
433
|
-
enterprise_model.metadata.name = f"{self.org_name} {self.type_} data model"
|
|
434
|
-
enterprise_model.metadata.space = self.new_model_id.space
|
|
435
|
-
enterprise_model.metadata.external_id = self.new_model_id.external_id
|
|
436
|
-
enterprise_model.metadata.version = cast(str, self.new_model_id.version)
|
|
437
|
-
|
|
438
|
-
# Here we are creating enterprise specific components
|
|
439
|
-
enterprise_views, enterprise_containers, enterprise_properties = self._get_new_components(enterprise_model)
|
|
599
|
+
return solution_model
|
|
440
600
|
|
|
441
|
-
# And we are adding them to the enterprise model
|
|
442
|
-
# extending reference views with new ones
|
|
443
|
-
enterprise_model.views.extend(enterprise_views)
|
|
444
601
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
enterprise_connections = self._move_connections(enterprise_model)
|
|
448
|
-
else:
|
|
449
|
-
enterprise_connections = SheetList[DMSProperty]()
|
|
602
|
+
class ToDataProductModel(ToSolutionModel):
|
|
603
|
+
type_: ClassVar[str] = "data_product"
|
|
450
604
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
605
|
+
def __init__(
|
|
606
|
+
self,
|
|
607
|
+
new_model_id: DataModelIdentifier,
|
|
608
|
+
org_name: str = "My",
|
|
609
|
+
include: Literal["same-space", "all"] = "same-space",
|
|
610
|
+
):
|
|
611
|
+
super().__init__(new_model_id, org_name, mode="read", dummy_property=None, exclude_views_in_other_spaces=False)
|
|
612
|
+
self.include = include
|
|
454
613
|
|
|
455
|
-
|
|
614
|
+
def transform(self, rules: DMSRules) -> DMSRules:
|
|
615
|
+
# Copy to ensure immutability
|
|
616
|
+
expanded = self._expand_properties(rules.model_copy(deep=True))
|
|
617
|
+
if self.include == "same-space":
|
|
618
|
+
expanded.views = SheetList[DMSView](
|
|
619
|
+
[view for view in expanded.views if view.view.space == expanded.metadata.space]
|
|
620
|
+
)
|
|
621
|
+
used_view_entities = {view.view for view in expanded.views}
|
|
622
|
+
expanded.properties = SheetList[DMSProperty](
|
|
623
|
+
[
|
|
624
|
+
prop
|
|
625
|
+
for prop in expanded.properties
|
|
626
|
+
if prop.view.space == expanded.metadata.space
|
|
627
|
+
and (
|
|
628
|
+
(isinstance(prop.value_type, ViewEntity) and prop.value_type in used_view_entities)
|
|
629
|
+
or not isinstance(prop.value_type, ViewEntity)
|
|
630
|
+
)
|
|
631
|
+
]
|
|
632
|
+
)
|
|
456
633
|
|
|
457
|
-
return
|
|
634
|
+
return self._to_solution(expanded)
|
|
458
635
|
|
|
459
636
|
@staticmethod
|
|
460
637
|
def _expand_properties(rules: DMSRules) -> DMSRules:
|
|
@@ -478,86 +655,6 @@ class ToExtension(RulesTransformer[DMSRules, DMSRules]):
|
|
|
478
655
|
property_ids.add(prop.view_property)
|
|
479
656
|
return rules
|
|
480
657
|
|
|
481
|
-
def _remove_cognite_affix(self, entity: _T_Entity) -> _T_Entity:
|
|
482
|
-
"""This method removes `Cognite` affix from the entity."""
|
|
483
|
-
new_suffix = entity.suffix.replace("Cognite", self.org_name or "")
|
|
484
|
-
if isinstance(entity, ViewEntity):
|
|
485
|
-
return ViewEntity(space=entity.space, externalId=new_suffix, version=entity.version) # type: ignore[return-value]
|
|
486
|
-
elif isinstance(entity, ClassEntity):
|
|
487
|
-
return ClassEntity(prefix=entity.prefix, suffix=new_suffix, version=entity.version) # type: ignore[return-value]
|
|
488
|
-
raise ValueError(f"Unsupported entity type: {type(entity)}")
|
|
489
|
-
|
|
490
|
-
def _get_new_components(
|
|
491
|
-
self, rules: DMSRules
|
|
492
|
-
) -> tuple[SheetList[DMSView], SheetList[DMSContainer], SheetList[DMSProperty]]:
|
|
493
|
-
new_views = SheetList[DMSView]()
|
|
494
|
-
new_containers = SheetList[DMSContainer]()
|
|
495
|
-
new_properties = SheetList[DMSProperty]()
|
|
496
|
-
|
|
497
|
-
for definition in rules.views:
|
|
498
|
-
view_entity = self._remove_cognite_affix(definition.view)
|
|
499
|
-
|
|
500
|
-
view_entity.version = cast(str, self.new_model_id.version)
|
|
501
|
-
view_entity.prefix = self.new_model_id.space
|
|
502
|
-
container_entity = ContainerEntity(space=view_entity.prefix, externalId=view_entity.external_id)
|
|
503
|
-
|
|
504
|
-
view = DMSView(
|
|
505
|
-
view=view_entity,
|
|
506
|
-
implements=[definition.view],
|
|
507
|
-
in_model=True,
|
|
508
|
-
name=definition.name,
|
|
509
|
-
)
|
|
510
|
-
|
|
511
|
-
container = DMSContainer(
|
|
512
|
-
container=container_entity,
|
|
513
|
-
)
|
|
514
|
-
|
|
515
|
-
property_ = DMSProperty(
|
|
516
|
-
view=view_entity,
|
|
517
|
-
view_property=f"{to_camel(view_entity.suffix)}{self.dummy_property}",
|
|
518
|
-
value_type=String(),
|
|
519
|
-
nullable=True,
|
|
520
|
-
immutable=False,
|
|
521
|
-
is_list=False,
|
|
522
|
-
container=container_entity,
|
|
523
|
-
container_property=f"{to_camel(view_entity.suffix)}{self.dummy_property}",
|
|
524
|
-
)
|
|
525
|
-
|
|
526
|
-
new_properties.append(property_)
|
|
527
|
-
new_views.append(view)
|
|
528
|
-
new_containers.append(container)
|
|
529
|
-
|
|
530
|
-
return new_views, new_containers, new_properties
|
|
531
|
-
|
|
532
|
-
def _move_connections(self, rules: DMSRules) -> SheetList[DMSProperty]:
|
|
533
|
-
implements: dict[ViewEntity, list[ViewEntity]] = defaultdict(list)
|
|
534
|
-
new_properties = SheetList[DMSProperty]()
|
|
535
|
-
|
|
536
|
-
for view in rules.views:
|
|
537
|
-
if view.view.space == rules.metadata.space and view.implements:
|
|
538
|
-
for implemented_view in view.implements:
|
|
539
|
-
implements.setdefault(implemented_view, []).append(view.view)
|
|
540
|
-
|
|
541
|
-
# currently only supporting single implementation of reference view in enterprise view
|
|
542
|
-
# connections that do not have properties
|
|
543
|
-
if all(len(v) == 1 for v in implements.values()):
|
|
544
|
-
for prop_ in rules.properties:
|
|
545
|
-
if (
|
|
546
|
-
prop_.view.space != rules.metadata.space
|
|
547
|
-
and prop_.connection
|
|
548
|
-
and isinstance(prop_.value_type, ViewEntity)
|
|
549
|
-
and implements.get(prop_.view)
|
|
550
|
-
and implements.get(prop_.value_type)
|
|
551
|
-
):
|
|
552
|
-
if isinstance(prop_.connection, EdgeEntity) and prop_.connection.properties:
|
|
553
|
-
continue
|
|
554
|
-
new_property = prop_.model_copy(deep=True)
|
|
555
|
-
new_property.view = implements[prop_.view][0]
|
|
556
|
-
new_property.value_type = implements[prop_.value_type][0]
|
|
557
|
-
new_properties.append(new_property)
|
|
558
|
-
|
|
559
|
-
return new_properties
|
|
560
|
-
|
|
561
658
|
|
|
562
659
|
class ReduceCogniteModel(RulesTransformer[DMSRules, DMSRules]):
|
|
563
660
|
_ASSET_VIEW = ViewId("cdf_cdm", "CogniteAsset", "v1")
|
|
@@ -605,8 +702,8 @@ class ReduceCogniteModel(RulesTransformer[DMSRules, DMSRules]):
|
|
|
605
702
|
)
|
|
606
703
|
self.drop_external_ids = {external_id for external_id in drop if external_id not in self._VIEW_BY_COLLECTION}
|
|
607
704
|
|
|
608
|
-
def transform(self, rules: DMSRules
|
|
609
|
-
verified =
|
|
705
|
+
def transform(self, rules: DMSRules) -> DMSRules:
|
|
706
|
+
verified = rules
|
|
610
707
|
if verified.metadata.as_data_model_id() not in COGNITE_MODELS:
|
|
611
708
|
raise NeatValueError(f"Can only reduce Cognite Data Models, not {verified.metadata.as_data_model_id()}")
|
|
612
709
|
|
|
@@ -630,13 +727,86 @@ class ReduceCogniteModel(RulesTransformer[DMSRules, DMSRules]):
|
|
|
630
727
|
|
|
631
728
|
new_model.properties = new_properties
|
|
632
729
|
|
|
633
|
-
return
|
|
730
|
+
return new_model
|
|
634
731
|
|
|
635
732
|
def _is_asset_3D_property(self, prop: DMSProperty) -> bool:
|
|
636
733
|
if "3D" not in self.drop_collection:
|
|
637
734
|
return False
|
|
638
735
|
return prop.view.as_id() == self._ASSET_VIEW and prop.view_property == "object3D"
|
|
639
736
|
|
|
737
|
+
@property
|
|
738
|
+
def description(self) -> str:
|
|
739
|
+
return f"Removed {len(self.drop_external_ids) + len(self.drop_collection)} views from data model"
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
class IncludeReferenced(RulesTransformer[DMSRules, DMSRules]):
|
|
743
|
+
def __init__(self, client: NeatClient, include_properties: bool = False) -> None:
|
|
744
|
+
self._client = client
|
|
745
|
+
self.include_properties = include_properties
|
|
746
|
+
|
|
747
|
+
def transform(self, rules: DMSRules) -> DMSRules:
|
|
748
|
+
dms_rules = rules
|
|
749
|
+
view_ids, container_ids = DMSValidation(dms_rules).imported_views_and_containers_ids()
|
|
750
|
+
if not (view_ids or container_ids):
|
|
751
|
+
warnings.warn(
|
|
752
|
+
NeatValueWarning(
|
|
753
|
+
f"Data model {dms_rules.metadata.as_data_model_id()} does not have any "
|
|
754
|
+
"referenced views or containers."
|
|
755
|
+
"that is not already included in the data model."
|
|
756
|
+
),
|
|
757
|
+
stacklevel=2,
|
|
758
|
+
)
|
|
759
|
+
return dms_rules
|
|
760
|
+
|
|
761
|
+
schema = self._client.schema.retrieve([v.as_id() for v in view_ids], [c.as_id() for c in container_ids])
|
|
762
|
+
copy_ = dms_rules.model_copy(deep=True)
|
|
763
|
+
# Sorting to ensure deterministic order
|
|
764
|
+
schema.containers = ContainerApplyDict(sorted(schema.containers.items(), key=lambda x: x[0].as_tuple()))
|
|
765
|
+
schema.views = ViewApplyDict(sorted(schema.views.items(), key=lambda x: x[0].as_tuple()))
|
|
766
|
+
importer = DMSImporter(schema)
|
|
767
|
+
|
|
768
|
+
imported = importer.to_rules()
|
|
769
|
+
if imported.rules is None:
|
|
770
|
+
raise NeatValueError("Could not import the referenced views and containers.")
|
|
771
|
+
|
|
772
|
+
verified = VerifyDMSRules(validate=False).transform(imported)
|
|
773
|
+
if copy_.containers is None:
|
|
774
|
+
copy_.containers = verified.containers
|
|
775
|
+
else:
|
|
776
|
+
existing_containers = {c.container for c in copy_.containers}
|
|
777
|
+
copy_.containers.extend([c for c in verified.containers or [] if c.container not in existing_containers])
|
|
778
|
+
existing_views = {v.view for v in copy_.views}
|
|
779
|
+
copy_.views.extend([v for v in verified.views if v.view not in existing_views])
|
|
780
|
+
if self.include_properties:
|
|
781
|
+
existing_properties = {(p.view, p.view_property) for p in copy_.properties}
|
|
782
|
+
copy_.properties.extend(
|
|
783
|
+
[p for p in verified.properties if (p.view, p.view_property) not in existing_properties]
|
|
784
|
+
)
|
|
785
|
+
|
|
786
|
+
return copy_
|
|
787
|
+
|
|
788
|
+
@property
|
|
789
|
+
def description(self) -> str:
|
|
790
|
+
return "Included referenced views and containers in the data model."
|
|
791
|
+
|
|
792
|
+
|
|
793
|
+
class AddClassImplements(RulesTransformer[InformationRules, InformationRules]):
|
|
794
|
+
def __init__(self, implements: str, suffix: str):
|
|
795
|
+
self.implements = implements
|
|
796
|
+
self.suffix = suffix
|
|
797
|
+
|
|
798
|
+
def transform(self, rules: InformationRules) -> InformationRules:
|
|
799
|
+
info_rules = rules
|
|
800
|
+
output = info_rules.model_copy(deep=True)
|
|
801
|
+
for class_ in output.classes:
|
|
802
|
+
if class_.class_.suffix.endswith(self.suffix):
|
|
803
|
+
class_.implements = [ClassEntity(prefix=class_.class_.prefix, suffix=self.implements)]
|
|
804
|
+
return output
|
|
805
|
+
|
|
806
|
+
@property
|
|
807
|
+
def description(self) -> str:
|
|
808
|
+
return f"Added implements property to classes with suffix {self.suffix}"
|
|
809
|
+
|
|
640
810
|
|
|
641
811
|
class _InformationRulesConverter:
|
|
642
812
|
_edge_properties: ClassVar[frozenset[str]] = frozenset({"endNode", "end_node", "startNode", "start_node"})
|