cognite-neat 0.76.1__py3-none-any.whl → 0.76.2__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/app/api/routers/core.py +1 -1
- cognite/neat/app/api/routers/rules.py +1 -1
- cognite/neat/graph/extractors/_mock_graph_generator.py +2 -2
- cognite/neat/rules/_shared.py +1 -1
- cognite/neat/rules/analysis/_information_rules.py +3 -3
- cognite/neat/rules/exporters/_base.py +1 -1
- cognite/neat/rules/exporters/_rules2dms.py +8 -49
- cognite/neat/rules/exporters/_rules2excel.py +9 -3
- cognite/neat/rules/exporters/_rules2ontology.py +2 -2
- cognite/neat/rules/exporters/_rules2yaml.py +1 -1
- cognite/neat/rules/exporters/_validation.py +2 -2
- cognite/neat/rules/importers/_base.py +1 -1
- cognite/neat/rules/importers/_dms2rules.py +93 -108
- cognite/neat/rules/importers/_dtdl2rules/dtdl_converter.py +1 -1
- cognite/neat/rules/importers/_dtdl2rules/dtdl_importer.py +2 -3
- cognite/neat/rules/importers/_owl2rules/_owl2classes.py +1 -1
- cognite/neat/rules/importers/_owl2rules/_owl2metadata.py +2 -2
- cognite/neat/rules/importers/_owl2rules/_owl2properties.py +1 -1
- cognite/neat/rules/importers/_owl2rules/_owl2rules.py +1 -1
- cognite/neat/rules/importers/_spreadsheet2rules.py +10 -4
- cognite/neat/rules/importers/_yaml2rules.py +3 -3
- cognite/neat/rules/issues/base.py +5 -0
- cognite/neat/rules/models/__init__.py +27 -0
- cognite/neat/rules/models/dms/__init__.py +18 -0
- cognite/neat/rules/models/dms/_converter.py +140 -0
- cognite/neat/rules/models/dms/_exporter.py +405 -0
- cognite/neat/rules/models/dms/_rules.py +379 -0
- cognite/neat/rules/models/{rules/_dms_rules_write.py → dms/_rules_input.py} +33 -33
- cognite/neat/rules/models/{rules/_dms_schema.py → dms/_schema.py} +10 -4
- cognite/neat/rules/models/dms/_serializer.py +126 -0
- cognite/neat/rules/models/dms/_validation.py +255 -0
- cognite/neat/rules/models/information/__init__.py +3 -0
- cognite/neat/rules/models/information/_converter.py +193 -0
- cognite/neat/rules/models/{rules/_information_rules.py → information/_rules.py} +35 -202
- cognite/neat/workflows/steps/data_contracts.py +1 -1
- cognite/neat/workflows/steps/lib/current/rules_exporter.py +9 -3
- cognite/neat/workflows/steps/lib/current/rules_importer.py +1 -1
- cognite/neat/workflows/steps/lib/current/rules_validator.py +1 -2
- {cognite_neat-0.76.1.dist-info → cognite_neat-0.76.2.dist-info}/METADATA +1 -1
- {cognite_neat-0.76.1.dist-info → cognite_neat-0.76.2.dist-info}/RECORD +50 -43
- cognite/neat/rules/models/rules/__init__.py +0 -14
- cognite/neat/rules/models/rules/_dms_architect_rules.py +0 -1255
- /cognite/neat/rules/models/{rules/_base.py → _base.py} +0 -0
- /cognite/neat/rules/models/{rdfpath.py → _rdfpath.py} +0 -0
- /cognite/neat/rules/models/{rules/_types → _types}/__init__.py +0 -0
- /cognite/neat/rules/models/{rules/_types → _types}/_base.py +0 -0
- /cognite/neat/rules/models/{rules/_types → _types}/_field.py +0 -0
- /cognite/neat/rules/models/{rules/_domain_rules.py → domain.py} +0 -0
- {cognite_neat-0.76.1.dist-info → cognite_neat-0.76.2.dist-info}/LICENSE +0 -0
- {cognite_neat-0.76.1.dist-info → cognite_neat-0.76.2.dist-info}/WHEEL +0 -0
- {cognite_neat-0.76.1.dist-info → cognite_neat-0.76.2.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import re
|
|
3
|
+
import sys
|
|
4
|
+
import warnings
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Literal, cast
|
|
8
|
+
|
|
9
|
+
from cognite.client import data_modeling as dm
|
|
10
|
+
from pydantic import Field, field_serializer, field_validator, model_serializer, model_validator
|
|
11
|
+
from pydantic_core.core_schema import SerializationInfo, ValidationInfo
|
|
12
|
+
|
|
13
|
+
from cognite.neat.rules import issues
|
|
14
|
+
from cognite.neat.rules.issues import MultiValueError
|
|
15
|
+
from cognite.neat.rules.models._base import (
|
|
16
|
+
BaseMetadata,
|
|
17
|
+
BaseRules,
|
|
18
|
+
DataModelType,
|
|
19
|
+
ExtensionCategory,
|
|
20
|
+
RoleTypes,
|
|
21
|
+
SchemaCompleteness,
|
|
22
|
+
SheetEntity,
|
|
23
|
+
SheetList,
|
|
24
|
+
)
|
|
25
|
+
from cognite.neat.rules.models._types import (
|
|
26
|
+
ExternalIdType,
|
|
27
|
+
PropertyType,
|
|
28
|
+
StrListType,
|
|
29
|
+
VersionType,
|
|
30
|
+
)
|
|
31
|
+
from cognite.neat.rules.models.data_types import DataType
|
|
32
|
+
from cognite.neat.rules.models.domain import DomainRules
|
|
33
|
+
from cognite.neat.rules.models.entities import (
|
|
34
|
+
ClassEntity,
|
|
35
|
+
ContainerEntity,
|
|
36
|
+
ContainerEntityList,
|
|
37
|
+
DMSUnknownEntity,
|
|
38
|
+
ReferenceEntity,
|
|
39
|
+
URLEntity,
|
|
40
|
+
ViewEntity,
|
|
41
|
+
ViewEntityList,
|
|
42
|
+
ViewPropertyEntity,
|
|
43
|
+
)
|
|
44
|
+
from cognite.neat.rules.models.wrapped_entities import HasDataFilter, NodeTypeFilter
|
|
45
|
+
|
|
46
|
+
from ._schema import DMSSchema
|
|
47
|
+
|
|
48
|
+
if TYPE_CHECKING:
|
|
49
|
+
from cognite.neat.rules.models.information._rules import InformationRules
|
|
50
|
+
|
|
51
|
+
if sys.version_info >= (3, 11):
|
|
52
|
+
from typing import Self
|
|
53
|
+
else:
|
|
54
|
+
from typing_extensions import Self
|
|
55
|
+
|
|
56
|
+
_DEFAULT_VERSION = "1"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class DMSMetadata(BaseMetadata):
|
|
60
|
+
role: ClassVar[RoleTypes] = RoleTypes.dms_architect
|
|
61
|
+
data_model_type: DataModelType = Field(DataModelType.solution, alias="dataModelType")
|
|
62
|
+
schema_: SchemaCompleteness = Field(alias="schema")
|
|
63
|
+
extension: ExtensionCategory = ExtensionCategory.addition
|
|
64
|
+
space: ExternalIdType
|
|
65
|
+
name: str | None = Field(
|
|
66
|
+
None,
|
|
67
|
+
description="Human readable name of the data model",
|
|
68
|
+
min_length=1,
|
|
69
|
+
max_length=255,
|
|
70
|
+
)
|
|
71
|
+
description: str | None = Field(None, min_length=1, max_length=1024)
|
|
72
|
+
external_id: ExternalIdType = Field(alias="externalId")
|
|
73
|
+
version: VersionType
|
|
74
|
+
creator: StrListType
|
|
75
|
+
created: datetime = Field(
|
|
76
|
+
description=("Date of the data model creation"),
|
|
77
|
+
)
|
|
78
|
+
updated: datetime = Field(
|
|
79
|
+
description=("Date of the data model update"),
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
@field_validator("*", mode="before")
|
|
83
|
+
def strip_string(cls, value: Any) -> Any:
|
|
84
|
+
if isinstance(value, str):
|
|
85
|
+
return value.strip()
|
|
86
|
+
return value
|
|
87
|
+
|
|
88
|
+
@field_serializer("schema_", "extension", "data_model_type", when_used="always")
|
|
89
|
+
@staticmethod
|
|
90
|
+
def as_string(value: SchemaCompleteness | ExtensionCategory | DataModelType) -> str:
|
|
91
|
+
return str(value)
|
|
92
|
+
|
|
93
|
+
@field_validator("schema_", mode="plain")
|
|
94
|
+
def as_enum_schema(cls, value: str) -> SchemaCompleteness:
|
|
95
|
+
return SchemaCompleteness(value)
|
|
96
|
+
|
|
97
|
+
@field_validator("extension", mode="plain")
|
|
98
|
+
def as_enum_extension(cls, value: str) -> ExtensionCategory:
|
|
99
|
+
return ExtensionCategory(value)
|
|
100
|
+
|
|
101
|
+
@field_validator("data_model_type", mode="plain")
|
|
102
|
+
def as_enum_model_type(cls, value: str) -> DataModelType:
|
|
103
|
+
return DataModelType(value)
|
|
104
|
+
|
|
105
|
+
@field_validator("description", mode="before")
|
|
106
|
+
def nan_as_none(cls, value):
|
|
107
|
+
if isinstance(value, float) and math.isnan(value):
|
|
108
|
+
return None
|
|
109
|
+
return value
|
|
110
|
+
|
|
111
|
+
def as_space(self) -> dm.SpaceApply:
|
|
112
|
+
return dm.SpaceApply(
|
|
113
|
+
space=self.space,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
def as_data_model_id(self) -> dm.DataModelId:
|
|
117
|
+
return dm.DataModelId(space=self.space, external_id=self.external_id, version=self.version)
|
|
118
|
+
|
|
119
|
+
def as_data_model(self) -> dm.DataModelApply:
|
|
120
|
+
suffix = f"Creator: {', '.join(self.creator)}"
|
|
121
|
+
if self.description:
|
|
122
|
+
description = f"{self.description} Creator: {', '.join(self.creator)}"
|
|
123
|
+
else:
|
|
124
|
+
description = suffix
|
|
125
|
+
|
|
126
|
+
return dm.DataModelApply(
|
|
127
|
+
space=self.space,
|
|
128
|
+
external_id=self.external_id,
|
|
129
|
+
name=self.name or None,
|
|
130
|
+
version=self.version or "missing",
|
|
131
|
+
description=description,
|
|
132
|
+
views=[],
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
@classmethod
|
|
136
|
+
def _get_description_and_creator(cls, description_raw: str | None) -> tuple[str | None, list[str]]:
|
|
137
|
+
if description_raw and (description_match := re.search(r"Creator: (.+)", description_raw)):
|
|
138
|
+
creator = description_match.group(1).split(", ")
|
|
139
|
+
description = description_raw.replace(description_match.string, "").strip() or None
|
|
140
|
+
elif description_raw:
|
|
141
|
+
creator = ["MISSING"]
|
|
142
|
+
description = description_raw
|
|
143
|
+
else:
|
|
144
|
+
creator = ["MISSING"]
|
|
145
|
+
description = None
|
|
146
|
+
return description, creator
|
|
147
|
+
|
|
148
|
+
@classmethod
|
|
149
|
+
def from_data_model(cls, data_model: dm.DataModelApply) -> "DMSMetadata":
|
|
150
|
+
description, creator = cls._get_description_and_creator(data_model.description)
|
|
151
|
+
return cls(
|
|
152
|
+
schema_=SchemaCompleteness.complete,
|
|
153
|
+
space=data_model.space,
|
|
154
|
+
name=data_model.name or None,
|
|
155
|
+
description=description,
|
|
156
|
+
external_id=data_model.external_id,
|
|
157
|
+
version=data_model.version,
|
|
158
|
+
creator=creator,
|
|
159
|
+
created=datetime.now(),
|
|
160
|
+
updated=datetime.now(),
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class DMSProperty(SheetEntity):
|
|
165
|
+
view: ViewEntity = Field(alias="View")
|
|
166
|
+
view_property: str = Field(alias="View Property")
|
|
167
|
+
name: str | None = Field(alias="Name", default=None)
|
|
168
|
+
description: str | None = Field(alias="Description", default=None)
|
|
169
|
+
connection: Literal["direct", "edge", "reverse"] | None = Field(None, alias="Connection")
|
|
170
|
+
value_type: DataType | ViewPropertyEntity | ViewEntity | DMSUnknownEntity = Field(alias="Value Type")
|
|
171
|
+
nullable: bool | None = Field(default=None, alias="Nullable")
|
|
172
|
+
is_list: bool | None = Field(default=None, alias="Is List")
|
|
173
|
+
default: str | int | dict | None = Field(None, alias="Default")
|
|
174
|
+
reference: URLEntity | ReferenceEntity | None = Field(default=None, alias="Reference", union_mode="left_to_right")
|
|
175
|
+
container: ContainerEntity | None = Field(None, alias="Container")
|
|
176
|
+
container_property: str | None = Field(None, alias="Container Property")
|
|
177
|
+
index: StrListType | None = Field(None, alias="Index")
|
|
178
|
+
constraint: StrListType | None = Field(None, alias="Constraint")
|
|
179
|
+
class_: ClassEntity = Field(alias="Class (linage)")
|
|
180
|
+
property_: PropertyType = Field(alias="Property (linage)")
|
|
181
|
+
|
|
182
|
+
@field_validator("nullable")
|
|
183
|
+
def direct_relation_must_be_nullable(cls, value: Any, info: ValidationInfo) -> None:
|
|
184
|
+
if info.data.get("connection") == "direct" and value is False:
|
|
185
|
+
raise ValueError("Direct relation must be nullable")
|
|
186
|
+
return value
|
|
187
|
+
|
|
188
|
+
@field_validator("value_type", mode="after")
|
|
189
|
+
def connections_value_type(
|
|
190
|
+
cls, value: ViewPropertyEntity | ViewEntity | DMSUnknownEntity, info: ValidationInfo
|
|
191
|
+
) -> DataType | ViewPropertyEntity | ViewEntity | DMSUnknownEntity:
|
|
192
|
+
if (connection := info.data.get("connection")) is None:
|
|
193
|
+
return value
|
|
194
|
+
if connection == "direct" and not isinstance(value, ViewEntity | DMSUnknownEntity):
|
|
195
|
+
raise ValueError(f"Direct relation must have a value type that points to a view, got {value}")
|
|
196
|
+
elif connection == "edge" and not isinstance(value, ViewEntity):
|
|
197
|
+
raise ValueError(f"Edge connection must have a value type that points to a view, got {value}")
|
|
198
|
+
elif connection == "reverse" and not isinstance(value, ViewPropertyEntity | ViewEntity):
|
|
199
|
+
raise ValueError(
|
|
200
|
+
f"Reverse connection must have a value type that points to a view or view property, got {value}"
|
|
201
|
+
)
|
|
202
|
+
return value
|
|
203
|
+
|
|
204
|
+
@field_serializer("value_type", when_used="always")
|
|
205
|
+
@staticmethod
|
|
206
|
+
def as_dms_type(value_type: DataType | ViewPropertyEntity | ViewEntity) -> str:
|
|
207
|
+
if isinstance(value_type, DataType):
|
|
208
|
+
return value_type.dms._type
|
|
209
|
+
else:
|
|
210
|
+
return str(value_type)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class DMSContainer(SheetEntity):
|
|
214
|
+
container: ContainerEntity = Field(alias="Container")
|
|
215
|
+
name: str | None = Field(alias="Name", default=None)
|
|
216
|
+
description: str | None = Field(alias="Description", default=None)
|
|
217
|
+
reference: URLEntity | ReferenceEntity | None = Field(alias="Reference", default=None, union_mode="left_to_right")
|
|
218
|
+
constraint: ContainerEntityList | None = Field(None, alias="Constraint")
|
|
219
|
+
class_: ClassEntity = Field(alias="Class (linage)")
|
|
220
|
+
|
|
221
|
+
def as_container(self) -> dm.ContainerApply:
|
|
222
|
+
container_id = self.container.as_id()
|
|
223
|
+
constraints: dict[str, dm.Constraint] = {}
|
|
224
|
+
for constraint in self.constraint or []:
|
|
225
|
+
requires = dm.RequiresConstraint(constraint.as_id())
|
|
226
|
+
constraints[f"{constraint.space}_{constraint.external_id}"] = requires
|
|
227
|
+
|
|
228
|
+
return dm.ContainerApply(
|
|
229
|
+
space=container_id.space,
|
|
230
|
+
external_id=container_id.external_id,
|
|
231
|
+
name=self.name or None,
|
|
232
|
+
description=self.description,
|
|
233
|
+
constraints=constraints or None,
|
|
234
|
+
properties={},
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
@classmethod
|
|
238
|
+
def from_container(cls, container: dm.ContainerApply) -> "DMSContainer":
|
|
239
|
+
constraints: list[ContainerEntity] = []
|
|
240
|
+
for _, constraint_obj in (container.constraints or {}).items():
|
|
241
|
+
if isinstance(constraint_obj, dm.RequiresConstraint):
|
|
242
|
+
constraints.append(ContainerEntity.from_id(constraint_obj.require))
|
|
243
|
+
# UniquenessConstraint it handled in the properties
|
|
244
|
+
container_entity = ContainerEntity.from_id(container.as_id())
|
|
245
|
+
return cls(
|
|
246
|
+
class_=container_entity.as_class(),
|
|
247
|
+
container=container_entity,
|
|
248
|
+
name=container.name or None,
|
|
249
|
+
description=container.description,
|
|
250
|
+
constraint=constraints or None,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class DMSView(SheetEntity):
|
|
255
|
+
view: ViewEntity = Field(alias="View")
|
|
256
|
+
name: str | None = Field(alias="Name", default=None)
|
|
257
|
+
description: str | None = Field(alias="Description", default=None)
|
|
258
|
+
implements: ViewEntityList | None = Field(None, alias="Implements")
|
|
259
|
+
reference: URLEntity | ReferenceEntity | None = Field(alias="Reference", default=None, union_mode="left_to_right")
|
|
260
|
+
filter_: HasDataFilter | NodeTypeFilter | None = Field(None, alias="Filter")
|
|
261
|
+
in_model: bool = Field(True, alias="In Model")
|
|
262
|
+
class_: ClassEntity = Field(alias="Class (linage)")
|
|
263
|
+
|
|
264
|
+
def as_view(self) -> dm.ViewApply:
|
|
265
|
+
view_id = self.view.as_id()
|
|
266
|
+
return dm.ViewApply(
|
|
267
|
+
space=view_id.space,
|
|
268
|
+
external_id=view_id.external_id,
|
|
269
|
+
version=view_id.version or _DEFAULT_VERSION,
|
|
270
|
+
name=self.name or None,
|
|
271
|
+
description=self.description,
|
|
272
|
+
implements=[parent.as_id() for parent in self.implements or []] or None,
|
|
273
|
+
properties={},
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
@classmethod
|
|
277
|
+
def from_view(cls, view: dm.ViewApply, in_model: bool) -> "DMSView":
|
|
278
|
+
view_entity = ViewEntity.from_id(view.as_id())
|
|
279
|
+
class_entity = view_entity.as_class(skip_version=True)
|
|
280
|
+
|
|
281
|
+
return cls(
|
|
282
|
+
class_=class_entity,
|
|
283
|
+
view=view_entity,
|
|
284
|
+
description=view.description,
|
|
285
|
+
name=view.name,
|
|
286
|
+
implements=[ViewEntity.from_id(parent, _DEFAULT_VERSION) for parent in view.implements] or None,
|
|
287
|
+
in_model=in_model,
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
class DMSRules(BaseRules):
|
|
292
|
+
metadata: DMSMetadata = Field(alias="Metadata")
|
|
293
|
+
properties: SheetList[DMSProperty] = Field(alias="Properties")
|
|
294
|
+
views: SheetList[DMSView] = Field(alias="Views")
|
|
295
|
+
containers: SheetList[DMSContainer] | None = Field(None, alias="Containers")
|
|
296
|
+
last: "DMSRules | None" = Field(None, alias="Last", description="The previous version of the data model")
|
|
297
|
+
reference: "DMSRules | None" = Field(None, alias="Reference")
|
|
298
|
+
|
|
299
|
+
@field_validator("reference")
|
|
300
|
+
def check_reference_of_reference(cls, value: "DMSRules | None", info: ValidationInfo) -> "DMSRules | None":
|
|
301
|
+
if value is None:
|
|
302
|
+
return None
|
|
303
|
+
if value.reference is not None:
|
|
304
|
+
raise ValueError("Reference rules cannot have a reference")
|
|
305
|
+
if value.metadata.data_model_type == DataModelType.solution and (metadata := info.data.get("metadata")):
|
|
306
|
+
warnings.warn(
|
|
307
|
+
issues.dms.SolutionOnTopOfSolutionModelWarning(
|
|
308
|
+
metadata.as_data_model_id(), value.metadata.as_data_model_id()
|
|
309
|
+
),
|
|
310
|
+
stacklevel=2,
|
|
311
|
+
)
|
|
312
|
+
return value
|
|
313
|
+
|
|
314
|
+
@field_validator("views")
|
|
315
|
+
def matching_version_and_space(cls, value: SheetList[DMSView], info: ValidationInfo) -> SheetList[DMSView]:
|
|
316
|
+
if not (metadata := info.data.get("metadata")):
|
|
317
|
+
return value
|
|
318
|
+
model_version = metadata.version
|
|
319
|
+
if different_version := [view.view.as_id() for view in value if view.view.version != model_version]:
|
|
320
|
+
warnings.warn(issues.dms.ViewModelVersionNotMatchingWarning(different_version, model_version), stacklevel=2)
|
|
321
|
+
if different_space := [view.view.as_id() for view in value if view.view.space != metadata.space]:
|
|
322
|
+
warnings.warn(issues.dms.ViewModelSpaceNotMatchingWarning(different_space, metadata.space), stacklevel=2)
|
|
323
|
+
return value
|
|
324
|
+
|
|
325
|
+
@model_validator(mode="after")
|
|
326
|
+
def post_validation(self) -> "DMSRules":
|
|
327
|
+
from ._validation import DMSPostValidation
|
|
328
|
+
|
|
329
|
+
issue_list = DMSPostValidation(self).validate()
|
|
330
|
+
if issue_list.warnings:
|
|
331
|
+
issue_list.trigger_warnings()
|
|
332
|
+
if issue_list.has_errors:
|
|
333
|
+
raise MultiValueError([error for error in issue_list if isinstance(error, issues.NeatValidationError)])
|
|
334
|
+
return self
|
|
335
|
+
|
|
336
|
+
@model_serializer(mode="wrap", when_used="always")
|
|
337
|
+
def dms_rules_serialization(
|
|
338
|
+
self,
|
|
339
|
+
handler: Callable,
|
|
340
|
+
info: SerializationInfo,
|
|
341
|
+
) -> dict[str, Any]:
|
|
342
|
+
from ._serializer import _DMSRulesSerializer
|
|
343
|
+
|
|
344
|
+
dumped = cast(dict[str, Any], handler(self, info))
|
|
345
|
+
space, version = self.metadata.space, self.metadata.version
|
|
346
|
+
return _DMSRulesSerializer(info, space, version).clean(dumped)
|
|
347
|
+
|
|
348
|
+
def as_schema(
|
|
349
|
+
self, include_ref: bool = False, include_pipeline: bool = False, instance_space: str | None = None
|
|
350
|
+
) -> DMSSchema:
|
|
351
|
+
from ._exporter import _DMSExporter
|
|
352
|
+
|
|
353
|
+
return _DMSExporter(self, include_ref, include_pipeline, instance_space).to_schema()
|
|
354
|
+
|
|
355
|
+
def as_information_architect_rules(self) -> "InformationRules":
|
|
356
|
+
from ._converter import _DMSRulesConverter
|
|
357
|
+
|
|
358
|
+
return _DMSRulesConverter(self).as_information_architect_rules()
|
|
359
|
+
|
|
360
|
+
def as_domain_expert_rules(self) -> DomainRules:
|
|
361
|
+
from ._converter import _DMSRulesConverter
|
|
362
|
+
|
|
363
|
+
return _DMSRulesConverter(self).as_domain_rules()
|
|
364
|
+
|
|
365
|
+
def reference_self(self) -> Self:
|
|
366
|
+
new_rules = self.model_copy(deep=True)
|
|
367
|
+
for prop in new_rules.properties:
|
|
368
|
+
prop.reference = ReferenceEntity(
|
|
369
|
+
prefix=prop.view.prefix, suffix=prop.view.suffix, version=prop.view.version, property=prop.property_
|
|
370
|
+
)
|
|
371
|
+
view: DMSView
|
|
372
|
+
for view in new_rules.views:
|
|
373
|
+
view.reference = ReferenceEntity(
|
|
374
|
+
prefix=view.view.prefix, suffix=view.view.suffix, version=view.view.version
|
|
375
|
+
)
|
|
376
|
+
container: DMSContainer
|
|
377
|
+
for container in new_rules.containers or []:
|
|
378
|
+
container.reference = ReferenceEntity(prefix=container.container.prefix, suffix=container.container.suffix)
|
|
379
|
+
return new_rules
|
|
@@ -5,6 +5,7 @@ from typing import Any, Literal, cast, overload
|
|
|
5
5
|
|
|
6
6
|
from pydantic import BaseModel
|
|
7
7
|
|
|
8
|
+
from cognite.neat.rules.models._base import DataModelType, ExtensionCategory, SchemaCompleteness
|
|
8
9
|
from cognite.neat.rules.models.data_types import DataType
|
|
9
10
|
from cognite.neat.rules.models.entities import (
|
|
10
11
|
ClassEntity,
|
|
@@ -15,12 +16,11 @@ from cognite.neat.rules.models.entities import (
|
|
|
15
16
|
ViewPropertyEntity,
|
|
16
17
|
)
|
|
17
18
|
|
|
18
|
-
from .
|
|
19
|
-
from ._dms_architect_rules import DMSContainer, DMSMetadata, DMSProperty, DMSRules, DMSView
|
|
19
|
+
from ._rules import DMSContainer, DMSMetadata, DMSProperty, DMSRules, DMSView
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
@dataclass
|
|
23
|
-
class
|
|
23
|
+
class DMSMetadataInput:
|
|
24
24
|
schema_: Literal["complete", "partial", "extended"]
|
|
25
25
|
space: str
|
|
26
26
|
external_id: str
|
|
@@ -34,7 +34,7 @@ class DMSMetadataWrite:
|
|
|
34
34
|
updated: datetime | str | None = None
|
|
35
35
|
|
|
36
36
|
@classmethod
|
|
37
|
-
def load(cls, data: dict[str, Any] | None) -> "
|
|
37
|
+
def load(cls, data: dict[str, Any] | None) -> "DMSMetadataInput | None":
|
|
38
38
|
if data is None:
|
|
39
39
|
return None
|
|
40
40
|
_add_alias(data, DMSMetadata)
|
|
@@ -69,7 +69,7 @@ class DMSMetadataWrite:
|
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
@dataclass
|
|
72
|
-
class
|
|
72
|
+
class DMSPropertyInput:
|
|
73
73
|
view: str
|
|
74
74
|
view_property: str | None
|
|
75
75
|
value_type: str
|
|
@@ -93,16 +93,16 @@ class DMSPropertyWrite:
|
|
|
93
93
|
|
|
94
94
|
@classmethod
|
|
95
95
|
@overload
|
|
96
|
-
def load(cls, data: dict[str, Any]) -> "
|
|
96
|
+
def load(cls, data: dict[str, Any]) -> "DMSPropertyInput": ...
|
|
97
97
|
|
|
98
98
|
@classmethod
|
|
99
99
|
@overload
|
|
100
|
-
def load(cls, data: list[dict[str, Any]]) -> list["
|
|
100
|
+
def load(cls, data: list[dict[str, Any]]) -> list["DMSPropertyInput"]: ...
|
|
101
101
|
|
|
102
102
|
@classmethod
|
|
103
103
|
def load(
|
|
104
104
|
cls, data: dict[str, Any] | list[dict[str, Any]] | None
|
|
105
|
-
) -> "
|
|
105
|
+
) -> "DMSPropertyInput | list[DMSPropertyInput] | None":
|
|
106
106
|
if data is None:
|
|
107
107
|
return None
|
|
108
108
|
if isinstance(data, list) or (isinstance(data, dict) and isinstance(data.get("data"), list)):
|
|
@@ -168,7 +168,7 @@ class DMSPropertyWrite:
|
|
|
168
168
|
|
|
169
169
|
|
|
170
170
|
@dataclass
|
|
171
|
-
class
|
|
171
|
+
class DMSContainerInput:
|
|
172
172
|
container: str
|
|
173
173
|
class_: str | None = None
|
|
174
174
|
name: str | None = None
|
|
@@ -182,16 +182,16 @@ class DMSContainerWrite:
|
|
|
182
182
|
|
|
183
183
|
@classmethod
|
|
184
184
|
@overload
|
|
185
|
-
def load(cls, data: dict[str, Any]) -> "
|
|
185
|
+
def load(cls, data: dict[str, Any]) -> "DMSContainerInput": ...
|
|
186
186
|
|
|
187
187
|
@classmethod
|
|
188
188
|
@overload
|
|
189
|
-
def load(cls, data: list[dict[str, Any]]) -> list["
|
|
189
|
+
def load(cls, data: list[dict[str, Any]]) -> list["DMSContainerInput"]: ...
|
|
190
190
|
|
|
191
191
|
@classmethod
|
|
192
192
|
def load(
|
|
193
193
|
cls, data: dict[str, Any] | list[dict[str, Any]] | None
|
|
194
|
-
) -> "
|
|
194
|
+
) -> "DMSContainerInput | list[DMSContainerInput] | None":
|
|
195
195
|
if data is None:
|
|
196
196
|
return None
|
|
197
197
|
if isinstance(data, list) or (isinstance(data, dict) and isinstance(data.get("data"), list)):
|
|
@@ -230,7 +230,7 @@ class DMSContainerWrite:
|
|
|
230
230
|
|
|
231
231
|
|
|
232
232
|
@dataclass
|
|
233
|
-
class
|
|
233
|
+
class DMSViewInput:
|
|
234
234
|
view: str
|
|
235
235
|
class_: str | None = None
|
|
236
236
|
name: str | None = None
|
|
@@ -246,14 +246,14 @@ class DMSViewWrite:
|
|
|
246
246
|
|
|
247
247
|
@classmethod
|
|
248
248
|
@overload
|
|
249
|
-
def load(cls, data: dict[str, Any]) -> "
|
|
249
|
+
def load(cls, data: dict[str, Any]) -> "DMSViewInput": ...
|
|
250
250
|
|
|
251
251
|
@classmethod
|
|
252
252
|
@overload
|
|
253
|
-
def load(cls, data: list[dict[str, Any]]) -> list["
|
|
253
|
+
def load(cls, data: list[dict[str, Any]]) -> list["DMSViewInput"]: ...
|
|
254
254
|
|
|
255
255
|
@classmethod
|
|
256
|
-
def load(cls, data: dict[str, Any] | list[dict[str, Any]] | None) -> "
|
|
256
|
+
def load(cls, data: dict[str, Any] | list[dict[str, Any]] | None) -> "DMSViewInput | list[DMSViewInput] | None":
|
|
257
257
|
if data is None:
|
|
258
258
|
return None
|
|
259
259
|
if isinstance(data, list) or (isinstance(data, dict) and isinstance(data.get("data"), list)):
|
|
@@ -298,46 +298,46 @@ class DMSViewWrite:
|
|
|
298
298
|
|
|
299
299
|
|
|
300
300
|
@dataclass
|
|
301
|
-
class
|
|
302
|
-
metadata:
|
|
303
|
-
properties: Sequence[
|
|
304
|
-
views: Sequence[
|
|
305
|
-
containers: Sequence[
|
|
306
|
-
reference: "
|
|
301
|
+
class DMSRulesInput:
|
|
302
|
+
metadata: DMSMetadataInput
|
|
303
|
+
properties: Sequence[DMSPropertyInput]
|
|
304
|
+
views: Sequence[DMSViewInput]
|
|
305
|
+
containers: Sequence[DMSContainerInput] | None = None
|
|
306
|
+
reference: "DMSRulesInput | DMSRules | None" = None
|
|
307
307
|
|
|
308
308
|
@classmethod
|
|
309
309
|
@overload
|
|
310
|
-
def load(cls, data: dict[str, Any]) -> "
|
|
310
|
+
def load(cls, data: dict[str, Any]) -> "DMSRulesInput": ...
|
|
311
311
|
|
|
312
312
|
@classmethod
|
|
313
313
|
@overload
|
|
314
314
|
def load(cls, data: None) -> None: ...
|
|
315
315
|
|
|
316
316
|
@classmethod
|
|
317
|
-
def load(cls, data: dict | None) -> "
|
|
317
|
+
def load(cls, data: dict | None) -> "DMSRulesInput | None":
|
|
318
318
|
if data is None:
|
|
319
319
|
return None
|
|
320
320
|
_add_alias(data, DMSRules)
|
|
321
321
|
return cls(
|
|
322
|
-
metadata=
|
|
323
|
-
properties=
|
|
324
|
-
views=
|
|
325
|
-
containers=
|
|
326
|
-
reference=
|
|
322
|
+
metadata=DMSMetadataInput.load(data.get("metadata")), # type: ignore[arg-type]
|
|
323
|
+
properties=DMSPropertyInput.load(data.get("properties")), # type: ignore[arg-type]
|
|
324
|
+
views=DMSViewInput.load(data.get("views")), # type: ignore[arg-type]
|
|
325
|
+
containers=DMSContainerInput.load(data.get("containers")) or [],
|
|
326
|
+
reference=DMSRulesInput.load(data.get("reference")),
|
|
327
327
|
)
|
|
328
328
|
|
|
329
|
-
def
|
|
329
|
+
def as_rules(self) -> DMSRules:
|
|
330
330
|
return DMSRules.model_validate(self.dump())
|
|
331
331
|
|
|
332
332
|
def dump(self) -> dict[str, Any]:
|
|
333
333
|
default_space = self.metadata.space
|
|
334
334
|
default_version = self.metadata.version
|
|
335
335
|
reference: dict[str, Any] | None = None
|
|
336
|
-
if isinstance(self.reference,
|
|
336
|
+
if isinstance(self.reference, DMSRulesInput):
|
|
337
337
|
reference = self.reference.dump()
|
|
338
338
|
elif isinstance(self.reference, DMSRules):
|
|
339
|
-
# We need to load through the
|
|
340
|
-
reference =
|
|
339
|
+
# We need to load through the DMSRulesInput to set the correct default space and version
|
|
340
|
+
reference = DMSRulesInput.load(self.reference.model_dump()).dump()
|
|
341
341
|
|
|
342
342
|
return dict(
|
|
343
343
|
Metadata=self.metadata.dump(),
|
|
@@ -41,14 +41,16 @@ else:
|
|
|
41
41
|
|
|
42
42
|
@dataclass
|
|
43
43
|
class DMSSchema:
|
|
44
|
-
spaces: dm.SpaceApplyList = field(default_factory=lambda: dm.SpaceApplyList([]))
|
|
45
44
|
data_models: dm.DataModelApplyList = field(default_factory=lambda: dm.DataModelApplyList([]))
|
|
45
|
+
spaces: dm.SpaceApplyList = field(default_factory=lambda: dm.SpaceApplyList([]))
|
|
46
46
|
views: dm.ViewApplyList = field(default_factory=lambda: dm.ViewApplyList([]))
|
|
47
47
|
containers: dm.ContainerApplyList = field(default_factory=lambda: dm.ContainerApplyList([]))
|
|
48
48
|
node_types: dm.NodeApplyList = field(default_factory=lambda: dm.NodeApplyList([]))
|
|
49
|
-
# The
|
|
50
|
-
#
|
|
51
|
-
|
|
49
|
+
# The last schema is the previous version of the data model. In the case, extension=additio, this
|
|
50
|
+
# should not be modified.
|
|
51
|
+
last: "DMSSchema | None" = None
|
|
52
|
+
# Reference is typically the Enterprise model, while this is the solution model.
|
|
53
|
+
reference: "DMSSchema | None" = None
|
|
52
54
|
|
|
53
55
|
_FIELD_NAME_BY_RESOURCE_TYPE: ClassVar[dict[str, str]] = {
|
|
54
56
|
"container": "containers",
|
|
@@ -351,6 +353,10 @@ class DMSSchema:
|
|
|
351
353
|
defined_spaces = {space.space for space in self.spaces}
|
|
352
354
|
defined_containers = {container.as_id(): container for container in self.containers}
|
|
353
355
|
defined_views = {view.as_id() for view in self.views}
|
|
356
|
+
if self.reference:
|
|
357
|
+
defined_spaces |= {space.space for space in self.reference.spaces}
|
|
358
|
+
defined_containers |= {container.as_id(): container for container in self.reference.containers}
|
|
359
|
+
defined_views |= {view.as_id() for view in self.reference.views}
|
|
354
360
|
|
|
355
361
|
for container in self.containers:
|
|
356
362
|
if container.space not in defined_spaces:
|