cognite-neat 0.75.9__py3-none-any.whl → 0.76.1__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.
- cognite/neat/_version.py +1 -1
- cognite/neat/rules/exporters/_models.py +3 -0
- cognite/neat/rules/exporters/_rules2dms.py +46 -4
- cognite/neat/rules/exporters/_rules2excel.py +0 -9
- cognite/neat/rules/importers/_base.py +6 -0
- cognite/neat/rules/importers/_dms2rules.py +319 -125
- cognite/neat/rules/importers/_spreadsheet2rules.py +14 -8
- cognite/neat/rules/issues/base.py +3 -0
- cognite/neat/rules/issues/dms.py +142 -54
- cognite/neat/rules/issues/fileread.py +41 -0
- cognite/neat/rules/issues/importing.py +155 -0
- cognite/neat/rules/issues/spreadsheet.py +12 -9
- cognite/neat/rules/models/entities.py +29 -6
- cognite/neat/rules/models/rules/_base.py +5 -6
- cognite/neat/rules/models/rules/_dms_architect_rules.py +492 -332
- cognite/neat/rules/models/rules/_dms_rules_write.py +57 -47
- cognite/neat/rules/models/rules/_dms_schema.py +112 -22
- cognite/neat/rules/models/rules/_domain_rules.py +5 -0
- cognite/neat/rules/models/rules/_information_rules.py +13 -6
- cognite/neat/rules/models/wrapped_entities.py +166 -0
- {cognite_neat-0.75.9.dist-info → cognite_neat-0.76.1.dist-info}/METADATA +1 -1
- {cognite_neat-0.75.9.dist-info → cognite_neat-0.76.1.dist-info}/RECORD +25 -24
- {cognite_neat-0.75.9.dist-info → cognite_neat-0.76.1.dist-info}/LICENSE +0 -0
- {cognite_neat-0.75.9.dist-info → cognite_neat-0.76.1.dist-info}/WHEEL +0 -0
- {cognite_neat-0.75.9.dist-info → cognite_neat-0.76.1.dist-info}/entry_points.txt +0 -0
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
from collections import Counter
|
|
2
|
+
from collections.abc import Sequence
|
|
3
|
+
from datetime import datetime
|
|
1
4
|
from pathlib import Path
|
|
2
5
|
from typing import Literal, cast, overload
|
|
3
6
|
|
|
@@ -5,21 +8,29 @@ from cognite.client import CogniteClient
|
|
|
5
8
|
from cognite.client import data_modeling as dm
|
|
6
9
|
from cognite.client.data_classes.data_modeling import DataModelIdentifier
|
|
7
10
|
from cognite.client.data_classes.data_modeling.containers import BTreeIndex, InvertedIndex
|
|
11
|
+
from cognite.client.data_classes.data_modeling.views import (
|
|
12
|
+
MultiEdgeConnectionApply,
|
|
13
|
+
MultiReverseDirectRelationApply,
|
|
14
|
+
SingleEdgeConnectionApply,
|
|
15
|
+
SingleReverseDirectRelationApply,
|
|
16
|
+
ViewPropertyApply,
|
|
17
|
+
)
|
|
8
18
|
from cognite.client.utils import ms_to_datetime
|
|
9
19
|
|
|
10
20
|
from cognite.neat.rules import issues
|
|
11
|
-
from cognite.neat.rules.importers._base import BaseImporter, Rules
|
|
12
|
-
from cognite.neat.rules.issues import IssueList
|
|
21
|
+
from cognite.neat.rules.importers._base import BaseImporter, Rules, _handle_issues
|
|
22
|
+
from cognite.neat.rules.issues import IssueList, ValidationIssue
|
|
13
23
|
from cognite.neat.rules.models.data_types import DataType
|
|
14
24
|
from cognite.neat.rules.models.entities import (
|
|
15
25
|
ClassEntity,
|
|
16
26
|
ContainerEntity,
|
|
27
|
+
DataModelEntity,
|
|
17
28
|
DMSUnknownEntity,
|
|
18
29
|
ViewEntity,
|
|
19
30
|
ViewPropertyEntity,
|
|
20
31
|
)
|
|
21
32
|
from cognite.neat.rules.models.rules import DMSRules, DMSSchema, RoleTypes
|
|
22
|
-
from cognite.neat.rules.models.rules._base import ExtensionCategory, SchemaCompleteness
|
|
33
|
+
from cognite.neat.rules.models.rules._base import DataModelType, ExtensionCategory, SchemaCompleteness
|
|
23
34
|
from cognite.neat.rules.models.rules._dms_architect_rules import (
|
|
24
35
|
DMSContainer,
|
|
25
36
|
DMSMetadata,
|
|
@@ -30,9 +41,16 @@ from cognite.neat.rules.models.rules._dms_architect_rules import (
|
|
|
30
41
|
|
|
31
42
|
|
|
32
43
|
class DMSImporter(BaseImporter):
|
|
33
|
-
def __init__(
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
schema: DMSSchema,
|
|
47
|
+
read_issues: Sequence[ValidationIssue] | None = None,
|
|
48
|
+
metadata: DMSMetadata | None = None,
|
|
49
|
+
):
|
|
34
50
|
self.schema = schema
|
|
35
51
|
self.metadata = metadata
|
|
52
|
+
self.issue_list = IssueList(read_issues)
|
|
53
|
+
self._container_by_id = {container.as_id(): container for container in schema.containers}
|
|
36
54
|
|
|
37
55
|
@classmethod
|
|
38
56
|
def from_data_model_id(cls, client: CogniteClient, data_model_id: DataModelIdentifier) -> "DMSImporter":
|
|
@@ -47,38 +65,59 @@ class DMSImporter(BaseImporter):
|
|
|
47
65
|
"""
|
|
48
66
|
data_models = client.data_modeling.data_models.retrieve(data_model_id, inline_views=True)
|
|
49
67
|
if len(data_models) == 0:
|
|
50
|
-
|
|
68
|
+
return cls(DMSSchema(), [issues.importing.NoDataModelError(f"Data model {data_model_id} not found")])
|
|
51
69
|
data_model = data_models.latest_version()
|
|
52
|
-
|
|
53
|
-
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
schema = DMSSchema.from_data_model(client, data_model)
|
|
73
|
+
except Exception as e:
|
|
74
|
+
return cls(DMSSchema(), [issues.importing.APIError(str(e))])
|
|
54
75
|
|
|
55
76
|
created = ms_to_datetime(data_model.created_time)
|
|
56
77
|
updated = ms_to_datetime(data_model.last_updated_time)
|
|
57
78
|
|
|
58
|
-
metadata =
|
|
79
|
+
metadata = cls._create_metadata_from_model(data_model, created, updated)
|
|
80
|
+
|
|
81
|
+
return cls(schema, [], metadata)
|
|
82
|
+
|
|
83
|
+
@classmethod
|
|
84
|
+
def _create_metadata_from_model(
|
|
85
|
+
cls,
|
|
86
|
+
model: dm.DataModel[dm.View] | dm.DataModelApply,
|
|
87
|
+
created: datetime | None = None,
|
|
88
|
+
updated: datetime | None = None,
|
|
89
|
+
) -> DMSMetadata:
|
|
90
|
+
description, creator = DMSMetadata._get_description_and_creator(model.description)
|
|
91
|
+
now = datetime.now().replace(microsecond=0)
|
|
92
|
+
return DMSMetadata(
|
|
59
93
|
schema_=SchemaCompleteness.complete,
|
|
60
94
|
extension=ExtensionCategory.addition,
|
|
61
|
-
space=
|
|
62
|
-
external_id=
|
|
63
|
-
name=
|
|
64
|
-
version=
|
|
65
|
-
updated=updated,
|
|
66
|
-
created=created,
|
|
95
|
+
space=model.space,
|
|
96
|
+
external_id=model.external_id,
|
|
97
|
+
name=model.name or model.external_id,
|
|
98
|
+
version=model.version or "0.1.0",
|
|
99
|
+
updated=updated or now,
|
|
100
|
+
created=created or now,
|
|
67
101
|
creator=creator,
|
|
68
102
|
description=description,
|
|
69
|
-
default_view_version=data_model.version or "0.1.0",
|
|
70
103
|
)
|
|
71
|
-
return cls(schema, metadata)
|
|
72
104
|
|
|
73
105
|
@classmethod
|
|
74
106
|
def from_directory(cls, directory: str | Path) -> "DMSImporter":
|
|
75
|
-
|
|
107
|
+
issue_list = IssueList()
|
|
108
|
+
with _handle_issues(issue_list) as _:
|
|
109
|
+
schema = DMSSchema.from_directory(directory)
|
|
110
|
+
# If there were errors during the import, the to_rules
|
|
111
|
+
return cls(schema, issue_list)
|
|
76
112
|
|
|
77
113
|
@classmethod
|
|
78
114
|
def from_zip_file(cls, zip_file: str | Path) -> "DMSImporter":
|
|
79
115
|
if Path(zip_file).suffix != ".zip":
|
|
80
|
-
|
|
81
|
-
|
|
116
|
+
return cls(DMSSchema(), [issues.fileread.InvalidFileFormatError(Path(zip_file), [".zip"])])
|
|
117
|
+
issue_list = IssueList()
|
|
118
|
+
with _handle_issues(issue_list) as _:
|
|
119
|
+
schema = DMSSchema.from_zip(zip_file)
|
|
120
|
+
return cls(schema, issue_list)
|
|
82
121
|
|
|
83
122
|
@overload
|
|
84
123
|
def to_rules(self, errors: Literal["raise"], role: RoleTypes | None = None) -> Rules: ...
|
|
@@ -91,126 +130,281 @@ class DMSImporter(BaseImporter):
|
|
|
91
130
|
def to_rules(
|
|
92
131
|
self, errors: Literal["raise", "continue"] = "continue", role: RoleTypes | None = None
|
|
93
132
|
) -> tuple[Rules | None, IssueList] | Rules:
|
|
94
|
-
if
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
data_model = self.schema.data_models[0]
|
|
133
|
+
if self.issue_list.has_errors:
|
|
134
|
+
# In case there were errors during the import, the to_rules method will return None
|
|
135
|
+
return self._return_or_raise(self.issue_list, errors)
|
|
98
136
|
|
|
99
|
-
|
|
137
|
+
if len(self.schema.data_models) == 0:
|
|
138
|
+
self.issue_list.append(issues.importing.NoDataModelError("No data model found."))
|
|
139
|
+
return self._return_or_raise(self.issue_list, errors)
|
|
140
|
+
|
|
141
|
+
if len(self.schema.data_models) > 2:
|
|
142
|
+
# Creating a DataModelEntity to convert the data model id to a string.
|
|
143
|
+
self.issue_list.append(
|
|
144
|
+
issues.importing.MultipleDataModelsWarning(
|
|
145
|
+
[str(DataModelEntity.from_id(model.as_id())) for model in self.schema.data_models]
|
|
146
|
+
)
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
data_model = self.schema.data_models[0]
|
|
100
150
|
|
|
101
151
|
properties = SheetList[DMSProperty]()
|
|
152
|
+
ref_properties = SheetList[DMSProperty]()
|
|
102
153
|
for view in self.schema.views:
|
|
103
|
-
|
|
154
|
+
view_id = view.as_id()
|
|
155
|
+
view_entity = ViewEntity.from_id(view_id)
|
|
156
|
+
class_entity = view_entity.as_class()
|
|
104
157
|
for prop_id, prop in (view.properties or {}).items():
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if prop.container_property_identifier not in container.properties:
|
|
110
|
-
raise ValueError(
|
|
111
|
-
f"Property {prop.container_property_identifier} not found "
|
|
112
|
-
f"in container {container.external_id}"
|
|
113
|
-
)
|
|
114
|
-
container_prop = container.properties[prop.container_property_identifier]
|
|
115
|
-
|
|
116
|
-
index: list[str] = []
|
|
117
|
-
for index_name, index_obj in (container.indexes or {}).items():
|
|
118
|
-
if isinstance(index_obj, BTreeIndex | InvertedIndex) and prop_id in index_obj.properties:
|
|
119
|
-
index.append(index_name)
|
|
120
|
-
unique_constraints: list[str] = []
|
|
121
|
-
for constraint_name, constraint_obj in (container.constraints or {}).items():
|
|
122
|
-
if isinstance(constraint_obj, dm.RequiresConstraint):
|
|
123
|
-
# This is handled in the .from_container method of DMSContainer
|
|
124
|
-
continue
|
|
125
|
-
elif (
|
|
126
|
-
isinstance(constraint_obj, dm.UniquenessConstraint) and prop_id in constraint_obj.properties
|
|
127
|
-
):
|
|
128
|
-
unique_constraints.append(constraint_name)
|
|
129
|
-
elif isinstance(constraint_obj, dm.UniquenessConstraint):
|
|
130
|
-
# This does not apply to this property
|
|
131
|
-
continue
|
|
132
|
-
else:
|
|
133
|
-
raise NotImplementedError(f"Constraint type {type(constraint_obj)} not implemented")
|
|
134
|
-
|
|
135
|
-
if isinstance(container_prop.type, dm.DirectRelation):
|
|
136
|
-
direct_value_type: str | ViewEntity | DataType | DMSUnknownEntity
|
|
137
|
-
if prop.source is None:
|
|
138
|
-
issue_list.append(
|
|
139
|
-
issues.importing.UnknownValueTypeWarning(class_entity.versioned_id, prop_id)
|
|
140
|
-
)
|
|
141
|
-
direct_value_type = DMSUnknownEntity()
|
|
142
|
-
else:
|
|
143
|
-
direct_value_type = ViewEntity.from_id(prop.source)
|
|
144
|
-
|
|
145
|
-
dms_property = DMSProperty(
|
|
146
|
-
class_=class_entity,
|
|
147
|
-
property_=prop_id,
|
|
148
|
-
description=prop.description,
|
|
149
|
-
name=prop.name,
|
|
150
|
-
value_type=direct_value_type,
|
|
151
|
-
relation="direct",
|
|
152
|
-
nullable=container_prop.nullable,
|
|
153
|
-
default=container_prop.default_value,
|
|
154
|
-
is_list=False,
|
|
155
|
-
container=ContainerEntity.from_id(container.as_id()),
|
|
156
|
-
container_property=prop.container_property_identifier,
|
|
157
|
-
view=ViewEntity.from_id(view.as_id()),
|
|
158
|
-
view_property=prop_id,
|
|
159
|
-
index=index or None,
|
|
160
|
-
constraint=unique_constraints or None,
|
|
161
|
-
)
|
|
158
|
+
dms_property = self._create_dms_property(prop_id, prop, view_entity, class_entity)
|
|
159
|
+
if dms_property is not None:
|
|
160
|
+
if view_id in self.schema.frozen_ids:
|
|
161
|
+
ref_properties.append(dms_property)
|
|
162
162
|
else:
|
|
163
|
-
dms_property
|
|
164
|
-
class_=ClassEntity(prefix=view.space, suffix=view.external_id, version=view.version),
|
|
165
|
-
property_=prop_id,
|
|
166
|
-
description=prop.description,
|
|
167
|
-
name=prop.name,
|
|
168
|
-
value_type=cast(ViewPropertyEntity | DataType, container_prop.type._type),
|
|
169
|
-
nullable=container_prop.nullable,
|
|
170
|
-
is_list=container_prop.type.is_list,
|
|
171
|
-
default=container_prop.default_value,
|
|
172
|
-
container=ContainerEntity.from_id(container.as_id()),
|
|
173
|
-
container_property=prop.container_property_identifier,
|
|
174
|
-
view=ViewEntity.from_id(view.as_id()),
|
|
175
|
-
view_property=prop_id,
|
|
176
|
-
index=index or None,
|
|
177
|
-
constraint=unique_constraints or None,
|
|
178
|
-
)
|
|
179
|
-
elif isinstance(prop, dm.MultiEdgeConnectionApply):
|
|
180
|
-
view_entity = ViewEntity.from_id(prop.source)
|
|
181
|
-
dms_property = DMSProperty(
|
|
182
|
-
class_=ClassEntity(prefix=view.space, suffix=view.external_id, version=view.version),
|
|
183
|
-
property_=prop_id,
|
|
184
|
-
relation="multiedge",
|
|
185
|
-
description=prop.description,
|
|
186
|
-
name=prop.name,
|
|
187
|
-
value_type=view_entity,
|
|
188
|
-
view=ViewEntity.from_id(view.as_id()),
|
|
189
|
-
view_property=prop_id,
|
|
190
|
-
)
|
|
191
|
-
else:
|
|
192
|
-
raise NotImplementedError(f"Property type {type(prop)} not implemented")
|
|
193
|
-
|
|
194
|
-
properties.append(dms_property)
|
|
163
|
+
properties.append(dms_property)
|
|
195
164
|
|
|
196
165
|
data_model_view_ids: set[dm.ViewId] = {
|
|
197
166
|
view.as_id() if isinstance(view, dm.View | dm.ViewApply) else view for view in data_model.views or []
|
|
198
167
|
}
|
|
199
168
|
|
|
200
|
-
|
|
201
|
-
|
|
169
|
+
metadata = self.metadata or DMSMetadata.from_data_model(data_model)
|
|
170
|
+
metadata.data_model_type = self._infer_data_model_type(metadata.space)
|
|
171
|
+
if ref_properties:
|
|
172
|
+
metadata.schema_ = SchemaCompleteness.extended
|
|
173
|
+
|
|
174
|
+
with _handle_issues(
|
|
175
|
+
self.issue_list,
|
|
176
|
+
) as future:
|
|
177
|
+
user_rules = DMSRules(
|
|
178
|
+
metadata=metadata,
|
|
179
|
+
properties=properties,
|
|
180
|
+
containers=SheetList[DMSContainer](
|
|
181
|
+
data=[
|
|
182
|
+
DMSContainer.from_container(container)
|
|
183
|
+
for container in self.schema.containers
|
|
184
|
+
if container.as_id() not in self.schema.frozen_ids
|
|
185
|
+
]
|
|
186
|
+
),
|
|
187
|
+
views=SheetList[DMSView](
|
|
188
|
+
data=[
|
|
189
|
+
DMSView.from_view(view, in_model=view.as_id() in data_model_view_ids)
|
|
190
|
+
for view in self.schema.views
|
|
191
|
+
if view.as_id() not in self.schema.frozen_ids
|
|
192
|
+
]
|
|
193
|
+
),
|
|
194
|
+
reference=self._create_reference_rules(ref_properties),
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
if future.result == "failure" or self.issue_list.has_errors:
|
|
198
|
+
return self._return_or_raise(self.issue_list, errors)
|
|
199
|
+
|
|
200
|
+
return self._to_output(user_rules, self.issue_list, errors, role)
|
|
201
|
+
|
|
202
|
+
def _create_reference_rules(self, properties: SheetList[DMSProperty]) -> DMSRules | None:
|
|
203
|
+
if not properties:
|
|
204
|
+
return None
|
|
205
|
+
|
|
206
|
+
if len(self.schema.data_models) == 2:
|
|
207
|
+
data_model = self.schema.data_models[1]
|
|
208
|
+
data_model_view_ids: set[dm.ViewId] = {
|
|
209
|
+
view.as_id() if isinstance(view, dm.View | dm.ViewApply) else view for view in data_model.views or []
|
|
210
|
+
}
|
|
211
|
+
metadata = self._create_metadata_from_model(data_model)
|
|
212
|
+
else:
|
|
213
|
+
data_model_view_ids = set()
|
|
214
|
+
now = datetime.now().replace(microsecond=0)
|
|
215
|
+
space = Counter(prop.view.space for prop in properties).most_common(1)[0][0]
|
|
216
|
+
metadata = DMSMetadata(
|
|
217
|
+
schema_=SchemaCompleteness.complete,
|
|
218
|
+
extension=ExtensionCategory.addition,
|
|
219
|
+
space=space,
|
|
220
|
+
external_id="Unknown",
|
|
221
|
+
version="0.1.0",
|
|
222
|
+
creator=["Unknown"],
|
|
223
|
+
created=now,
|
|
224
|
+
updated=now,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
metadata.data_model_type = DataModelType.enterprise
|
|
228
|
+
return DMSRules(
|
|
229
|
+
metadata=metadata,
|
|
202
230
|
properties=properties,
|
|
231
|
+
views=SheetList[DMSView](
|
|
232
|
+
data=[
|
|
233
|
+
DMSView.from_view(view, in_model=not data_model_view_ids or (view.as_id() in data_model_view_ids))
|
|
234
|
+
for view in self.schema.views
|
|
235
|
+
if view.as_id() in self.schema.frozen_ids
|
|
236
|
+
]
|
|
237
|
+
),
|
|
203
238
|
containers=SheetList[DMSContainer](
|
|
204
|
-
data=[
|
|
239
|
+
data=[
|
|
240
|
+
DMSContainer.from_container(container)
|
|
241
|
+
for container in self.schema.containers
|
|
242
|
+
if container.as_id() in self.schema.frozen_ids
|
|
243
|
+
]
|
|
205
244
|
),
|
|
206
|
-
|
|
245
|
+
reference=None,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
def _infer_data_model_type(self, space: str) -> DataModelType:
|
|
249
|
+
if self.schema.referenced_spaces() - {space}:
|
|
250
|
+
# If the data model has containers, views, node types in another space
|
|
251
|
+
# we assume it is a solution model.
|
|
252
|
+
return DataModelType.solution
|
|
253
|
+
else:
|
|
254
|
+
# All containers, views, node types are in the same space as the data model
|
|
255
|
+
return DataModelType.enterprise
|
|
256
|
+
|
|
257
|
+
def _create_dms_property(
|
|
258
|
+
self, prop_id: str, prop: ViewPropertyApply, view_entity: ViewEntity, class_entity: ClassEntity
|
|
259
|
+
) -> DMSProperty | None:
|
|
260
|
+
if isinstance(prop, dm.MappedPropertyApply) and prop.container not in self._container_by_id:
|
|
261
|
+
self.issue_list.append(
|
|
262
|
+
issues.importing.MissingContainerWarning(
|
|
263
|
+
view_id=str(view_entity),
|
|
264
|
+
property_=prop_id,
|
|
265
|
+
container_id=str(ContainerEntity.from_id(prop.container)),
|
|
266
|
+
)
|
|
267
|
+
)
|
|
268
|
+
return None
|
|
269
|
+
if (
|
|
270
|
+
isinstance(prop, dm.MappedPropertyApply)
|
|
271
|
+
and prop.container_property_identifier not in self._container_by_id[prop.container].properties
|
|
272
|
+
):
|
|
273
|
+
self.issue_list.append(
|
|
274
|
+
issues.importing.MissingContainerPropertyWarning(
|
|
275
|
+
view_id=str(view_entity),
|
|
276
|
+
property_=prop_id,
|
|
277
|
+
container_id=str(ContainerEntity.from_id(prop.container)),
|
|
278
|
+
)
|
|
279
|
+
)
|
|
280
|
+
return None
|
|
281
|
+
if not isinstance(
|
|
282
|
+
prop,
|
|
283
|
+
dm.MappedPropertyApply
|
|
284
|
+
| SingleEdgeConnectionApply
|
|
285
|
+
| MultiEdgeConnectionApply
|
|
286
|
+
| SingleReverseDirectRelationApply
|
|
287
|
+
| MultiReverseDirectRelationApply,
|
|
288
|
+
):
|
|
289
|
+
self.issue_list.append(
|
|
290
|
+
issues.importing.UnknownPropertyTypeWarning(view_entity.versioned_id, prop_id, type(prop).__name__)
|
|
291
|
+
)
|
|
292
|
+
return None
|
|
293
|
+
|
|
294
|
+
value_type = self._get_value_type(prop, view_entity, prop_id)
|
|
295
|
+
if value_type is None:
|
|
296
|
+
return None
|
|
297
|
+
|
|
298
|
+
return DMSProperty(
|
|
299
|
+
class_=class_entity,
|
|
300
|
+
property_=prop_id,
|
|
301
|
+
description=prop.description,
|
|
302
|
+
name=prop.name,
|
|
303
|
+
connection=self._get_relation_type(prop),
|
|
304
|
+
value_type=value_type,
|
|
305
|
+
is_list=self._get_is_list(prop),
|
|
306
|
+
nullable=self._get_nullable(prop),
|
|
307
|
+
default=self._get_default(prop),
|
|
308
|
+
container=ContainerEntity.from_id(prop.container) if isinstance(prop, dm.MappedPropertyApply) else None,
|
|
309
|
+
container_property=prop.container_property_identifier if isinstance(prop, dm.MappedPropertyApply) else None,
|
|
310
|
+
view=view_entity,
|
|
311
|
+
view_property=prop_id,
|
|
312
|
+
index=self._get_index(prop, prop_id),
|
|
313
|
+
constraint=self._get_constraint(prop, prop_id),
|
|
207
314
|
)
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
315
|
+
|
|
316
|
+
def _container_prop_unsafe(self, prop: dm.MappedPropertyApply) -> dm.ContainerProperty:
|
|
317
|
+
"""This method assumes you have already checked that the container with property exists."""
|
|
318
|
+
return self._container_by_id[prop.container].properties[prop.container_property_identifier]
|
|
319
|
+
|
|
320
|
+
def _get_relation_type(self, prop: ViewPropertyApply) -> Literal["edge", "reverse", "direct"] | None:
|
|
321
|
+
if isinstance(prop, SingleEdgeConnectionApply | MultiEdgeConnectionApply) and prop.direction == "outwards":
|
|
322
|
+
return "edge"
|
|
323
|
+
elif isinstance(prop, SingleEdgeConnectionApply | MultiEdgeConnectionApply) and prop.direction == "inwards":
|
|
324
|
+
return "reverse"
|
|
325
|
+
elif isinstance(prop, SingleReverseDirectRelationApply | MultiReverseDirectRelationApply):
|
|
326
|
+
return "reverse"
|
|
327
|
+
elif isinstance(prop, dm.MappedPropertyApply) and isinstance(
|
|
328
|
+
self._container_prop_unsafe(prop).type, dm.DirectRelation
|
|
329
|
+
):
|
|
330
|
+
return "direct"
|
|
211
331
|
else:
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
332
|
+
return None
|
|
333
|
+
|
|
334
|
+
def _get_value_type(
|
|
335
|
+
self, prop: ViewPropertyApply, view_entity: ViewEntity, prop_id
|
|
336
|
+
) -> DataType | ViewEntity | ViewPropertyEntity | DMSUnknownEntity | None:
|
|
337
|
+
if isinstance(prop, SingleEdgeConnectionApply | MultiEdgeConnectionApply) and prop.direction == "outwards":
|
|
338
|
+
return ViewEntity.from_id(prop.source)
|
|
339
|
+
elif isinstance(prop, SingleReverseDirectRelationApply | MultiReverseDirectRelationApply):
|
|
340
|
+
return ViewPropertyEntity.from_id(prop.through)
|
|
341
|
+
elif isinstance(prop, SingleEdgeConnectionApply | MultiEdgeConnectionApply) and prop.direction == "inwards":
|
|
342
|
+
return ViewEntity.from_id(prop.source)
|
|
343
|
+
elif isinstance(prop, dm.MappedPropertyApply):
|
|
344
|
+
container_prop = self._container_prop_unsafe(cast(dm.MappedPropertyApply, prop))
|
|
345
|
+
if isinstance(container_prop.type, dm.DirectRelation):
|
|
346
|
+
if prop.source is None:
|
|
347
|
+
# The warning is issued when the DMS Rules are created.
|
|
348
|
+
return DMSUnknownEntity()
|
|
349
|
+
else:
|
|
350
|
+
return ViewEntity.from_id(prop.source)
|
|
351
|
+
else:
|
|
352
|
+
return DataType.load(container_prop.type._type)
|
|
353
|
+
else:
|
|
354
|
+
self.issue_list.append(issues.importing.FailedToInferValueTypeWarning(str(view_entity), prop_id))
|
|
355
|
+
return None
|
|
356
|
+
|
|
357
|
+
def _get_nullable(self, prop: ViewPropertyApply) -> bool | None:
|
|
358
|
+
if isinstance(prop, dm.MappedPropertyApply):
|
|
359
|
+
return self._container_prop_unsafe(prop).nullable
|
|
360
|
+
else:
|
|
361
|
+
return None
|
|
362
|
+
|
|
363
|
+
def _get_is_list(self, prop: ViewPropertyApply) -> bool | None:
|
|
364
|
+
if isinstance(prop, dm.MappedPropertyApply):
|
|
365
|
+
return self._container_prop_unsafe(prop).type.is_list
|
|
366
|
+
elif isinstance(prop, MultiEdgeConnectionApply | MultiReverseDirectRelationApply):
|
|
367
|
+
return True
|
|
368
|
+
elif isinstance(prop, SingleEdgeConnectionApply | SingleReverseDirectRelationApply):
|
|
369
|
+
return False
|
|
215
370
|
else:
|
|
216
|
-
return
|
|
371
|
+
return None
|
|
372
|
+
|
|
373
|
+
def _get_default(self, prop: ViewPropertyApply) -> str | None:
|
|
374
|
+
if isinstance(prop, dm.MappedPropertyApply):
|
|
375
|
+
default = self._container_prop_unsafe(prop).default_value
|
|
376
|
+
if default is not None:
|
|
377
|
+
return str(default)
|
|
378
|
+
return None
|
|
379
|
+
|
|
380
|
+
def _get_index(self, prop: ViewPropertyApply, prop_id) -> list[str] | None:
|
|
381
|
+
if not isinstance(prop, dm.MappedPropertyApply):
|
|
382
|
+
return None
|
|
383
|
+
container = self._container_by_id[prop.container]
|
|
384
|
+
index: list[str] = []
|
|
385
|
+
for index_name, index_obj in (container.indexes or {}).items():
|
|
386
|
+
if isinstance(index_obj, BTreeIndex | InvertedIndex) and prop_id in index_obj.properties:
|
|
387
|
+
index.append(index_name)
|
|
388
|
+
return index or None
|
|
389
|
+
|
|
390
|
+
def _get_constraint(self, prop: ViewPropertyApply, prop_id: str) -> list[str] | None:
|
|
391
|
+
if not isinstance(prop, dm.MappedPropertyApply):
|
|
392
|
+
return None
|
|
393
|
+
container = self._container_by_id[prop.container]
|
|
394
|
+
unique_constraints: list[str] = []
|
|
395
|
+
for constraint_name, constraint_obj in (container.constraints or {}).items():
|
|
396
|
+
if isinstance(constraint_obj, dm.RequiresConstraint):
|
|
397
|
+
# This is handled in the .from_container method of DMSContainer
|
|
398
|
+
continue
|
|
399
|
+
elif isinstance(constraint_obj, dm.UniquenessConstraint) and prop_id in constraint_obj.properties:
|
|
400
|
+
unique_constraints.append(constraint_name)
|
|
401
|
+
elif isinstance(constraint_obj, dm.UniquenessConstraint):
|
|
402
|
+
# This does not apply to this property
|
|
403
|
+
continue
|
|
404
|
+
else:
|
|
405
|
+
self.issue_list.append(
|
|
406
|
+
issues.importing.UnknownContainerConstraintWarning(
|
|
407
|
+
str(ContainerEntity.from_id(prop.container)), prop_id, type(constraint_obj).__name__
|
|
408
|
+
)
|
|
409
|
+
)
|
|
410
|
+
return unique_constraints or None
|
|
@@ -22,7 +22,15 @@ from cognite.neat.utils.spreadsheet import SpreadsheetRead, read_individual_shee
|
|
|
22
22
|
from ._base import BaseImporter, Rules, _handle_issues
|
|
23
23
|
|
|
24
24
|
SOURCE_SHEET__TARGET_FIELD__HEADERS = [
|
|
25
|
-
(
|
|
25
|
+
(
|
|
26
|
+
"Properties",
|
|
27
|
+
"Properties",
|
|
28
|
+
{
|
|
29
|
+
RoleTypes.domain_expert: "Property",
|
|
30
|
+
RoleTypes.information_architect: "Property",
|
|
31
|
+
RoleTypes.dms_architect: "View Property",
|
|
32
|
+
},
|
|
33
|
+
),
|
|
26
34
|
("Classes", "Classes", "Class"),
|
|
27
35
|
("Containers", "Containers", "Container"),
|
|
28
36
|
("Views", "Views", "View"),
|
|
@@ -131,11 +139,15 @@ class SpreadsheetReader:
|
|
|
131
139
|
)
|
|
132
140
|
return None, read_info_by_sheet
|
|
133
141
|
|
|
134
|
-
for source_sheet_name, target_sheet_name,
|
|
142
|
+
for source_sheet_name, target_sheet_name, headers_input in SOURCE_SHEET__TARGET_FIELD__HEADERS:
|
|
135
143
|
source_sheet_name = self.to_reference_sheet(source_sheet_name) if self._is_reference else source_sheet_name
|
|
136
144
|
|
|
137
145
|
if source_sheet_name not in excel_file.sheet_names:
|
|
138
146
|
continue
|
|
147
|
+
if isinstance(headers_input, dict):
|
|
148
|
+
headers = headers_input[metadata.role]
|
|
149
|
+
else:
|
|
150
|
+
headers = headers_input
|
|
139
151
|
|
|
140
152
|
try:
|
|
141
153
|
sheets[target_sheet_name], read_info_by_sheet[source_sheet_name] = read_individual_sheet(
|
|
@@ -229,12 +241,6 @@ class ExcelImporter(BaseImporter):
|
|
|
229
241
|
role=role,
|
|
230
242
|
)
|
|
231
243
|
|
|
232
|
-
@classmethod
|
|
233
|
-
def _return_or_raise(cls, issue_list: IssueList, errors: Literal["raise", "continue"]) -> tuple[None, IssueList]:
|
|
234
|
-
if errors == "raise":
|
|
235
|
-
raise issue_list.as_errors()
|
|
236
|
-
return None, issue_list
|
|
237
|
-
|
|
238
244
|
|
|
239
245
|
class GoogleSheetImporter(BaseImporter):
|
|
240
246
|
def __init__(self, sheet_id: str, skiprows: int = 1):
|
|
@@ -78,6 +78,9 @@ class NeatValidationError(ValidationIssue, ABC):
|
|
|
78
78
|
all_errors.append(DefaultPydanticError.from_pydantic_error(error))
|
|
79
79
|
return all_errors
|
|
80
80
|
|
|
81
|
+
def as_exception(self) -> Exception:
|
|
82
|
+
return ValueError(self.message())
|
|
83
|
+
|
|
81
84
|
|
|
82
85
|
@dataclass(frozen=True)
|
|
83
86
|
class DefaultPydanticError(NeatValidationError):
|