cognite-neat 0.75.7__py3-none-any.whl → 0.75.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of cognite-neat might be problematic. Click here for more details.
- cognite/neat/_version.py +1 -1
- cognite/neat/app/api/configuration.py +4 -9
- cognite/neat/app/api/routers/configuration.py +2 -1
- cognite/neat/app/api/routers/crud.py +5 -5
- cognite/neat/app/api/routers/data_exploration.py +3 -1
- cognite/neat/app/api/routers/rules.py +3 -3
- cognite/neat/app/api/routers/workflows.py +3 -3
- cognite/neat/app/ui/neat-app/build/asset-manifest.json +3 -3
- cognite/neat/app/ui/neat-app/build/index.html +1 -1
- cognite/neat/app/ui/neat-app/build/static/js/{main.4345d42f.js → main.ec7f72e2.js} +3 -3
- cognite/neat/app/ui/neat-app/build/static/js/{main.4345d42f.js.map → main.ec7f72e2.js.map} +1 -1
- cognite/neat/config.py +147 -12
- cognite/neat/constants.py +1 -0
- cognite/neat/graph/exceptions.py +1 -2
- cognite/neat/graph/extractors/_mock_graph_generator.py +6 -5
- cognite/neat/legacy/graph/exceptions.py +1 -2
- cognite/neat/legacy/graph/extractors/_mock_graph_generator.py +1 -2
- cognite/neat/legacy/graph/loaders/_asset_loader.py +8 -13
- cognite/neat/legacy/graph/loaders/_base.py +2 -4
- cognite/neat/legacy/graph/loaders/_exceptions.py +1 -3
- cognite/neat/legacy/graph/loaders/core/rdf_to_assets.py +4 -8
- cognite/neat/legacy/graph/loaders/core/rdf_to_relationships.py +2 -4
- cognite/neat/legacy/graph/loaders/rdf_to_dms.py +2 -4
- cognite/neat/legacy/graph/loaders/validator.py +1 -1
- cognite/neat/legacy/graph/transformations/transformer.py +1 -2
- cognite/neat/legacy/rules/exporters/_rules2dms.py +1 -2
- cognite/neat/legacy/rules/exporters/_validation.py +4 -8
- cognite/neat/legacy/rules/importers/_base.py +0 -4
- cognite/neat/legacy/rules/importers/_dms2rules.py +0 -2
- cognite/neat/legacy/rules/models/rdfpath.py +1 -2
- cognite/neat/legacy/workflows/examples/Export_DMS/workflow.yaml +89 -0
- cognite/neat/legacy/workflows/examples/Export_Rules_to_Ontology/workflow.yaml +152 -0
- cognite/neat/legacy/workflows/examples/Extract_DEXPI_Graph_and_Export_Rules/workflow.yaml +139 -0
- cognite/neat/legacy/workflows/examples/Extract_RDF_Graph_and_Generate_Assets/workflow.yaml +270 -0
- cognite/neat/legacy/workflows/examples/Import_DMS/workflow.yaml +65 -0
- cognite/neat/legacy/workflows/examples/Ontology_to_Data_Model/workflow.yaml +116 -0
- cognite/neat/legacy/workflows/examples/Validate_Rules/workflow.yaml +67 -0
- cognite/neat/legacy/workflows/examples/Validate_Solution_Model/workflow.yaml +64 -0
- cognite/neat/legacy/workflows/examples/Visualize_Data_Model_Using_Mock_Graph/workflow.yaml +95 -0
- cognite/neat/legacy/workflows/examples/Visualize_Semantic_Data_Model/workflow.yaml +111 -0
- cognite/neat/rules/analysis/_base.py +1 -1
- cognite/neat/rules/analysis/_information_rules.py +4 -4
- cognite/neat/rules/exporters/_rules2excel.py +2 -2
- cognite/neat/rules/exporters/_rules2ontology.py +20 -17
- cognite/neat/rules/exporters/_validation.py +8 -10
- cognite/neat/rules/importers/_base.py +2 -4
- cognite/neat/rules/importers/_dms2rules.py +16 -19
- cognite/neat/rules/importers/_dtdl2rules/dtdl_converter.py +21 -19
- cognite/neat/rules/importers/_dtdl2rules/dtdl_importer.py +2 -4
- cognite/neat/rules/importers/_dtdl2rules/spec.py +3 -5
- cognite/neat/rules/importers/_owl2rules/_owl2rules.py +4 -6
- cognite/neat/rules/importers/_spreadsheet2rules.py +10 -9
- cognite/neat/rules/importers/_yaml2rules.py +10 -6
- cognite/neat/rules/issues/dms.py +3 -5
- cognite/neat/rules/issues/formatters.py +3 -1
- cognite/neat/rules/models/data_types.py +54 -31
- cognite/neat/rules/models/entities.py +184 -42
- cognite/neat/rules/models/rdfpath.py +112 -13
- cognite/neat/rules/models/rules/_base.py +2 -2
- cognite/neat/rules/models/rules/_dms_architect_rules.py +119 -189
- cognite/neat/rules/models/rules/_dms_rules_write.py +344 -0
- cognite/neat/rules/models/rules/_dms_schema.py +3 -3
- cognite/neat/rules/models/rules/_domain_rules.py +6 -3
- cognite/neat/rules/models/rules/_information_rules.py +68 -61
- cognite/neat/rules/models/rules/_types/__init__.py +0 -47
- cognite/neat/rules/models/rules/_types/_base.py +1 -309
- cognite/neat/rules/models/rules/_types/_field.py +0 -225
- cognite/neat/utils/cdf_loaders/_data_modeling.py +3 -1
- cognite/neat/utils/cdf_loaders/_ingestion.py +2 -4
- cognite/neat/utils/spreadsheet.py +2 -4
- cognite/neat/utils/utils.py +2 -4
- cognite/neat/workflows/base.py +5 -5
- cognite/neat/workflows/manager.py +32 -22
- cognite/neat/workflows/model.py +3 -3
- cognite/neat/workflows/steps/lib/__init__.py +0 -7
- cognite/neat/workflows/steps/lib/current/__init__.py +6 -0
- cognite/neat/workflows/steps/lib/{rules_exporter.py → current/rules_exporter.py} +8 -8
- cognite/neat/workflows/steps/lib/{rules_importer.py → current/rules_importer.py} +7 -7
- cognite/neat/workflows/steps/lib/io/__init__.py +1 -0
- cognite/neat/workflows/steps/lib/{v1 → legacy}/graph_contextualization.py +2 -2
- cognite/neat/workflows/steps/lib/{v1 → legacy}/graph_extractor.py +9 -9
- cognite/neat/workflows/steps/lib/{v1 → legacy}/graph_loader.py +9 -9
- cognite/neat/workflows/steps/lib/{v1 → legacy}/graph_store.py +4 -4
- cognite/neat/workflows/steps/lib/{v1 → legacy}/graph_transformer.py +2 -2
- cognite/neat/workflows/steps/lib/{v1 → legacy}/rules_exporter.py +15 -17
- cognite/neat/workflows/steps/lib/{v1 → legacy}/rules_importer.py +7 -7
- cognite/neat/workflows/steps/step_model.py +5 -9
- cognite/neat/workflows/steps_registry.py +20 -11
- {cognite_neat-0.75.7.dist-info → cognite_neat-0.75.9.dist-info}/METADATA +1 -1
- {cognite_neat-0.75.7.dist-info → cognite_neat-0.75.9.dist-info}/RECORD +100 -90
- cognite/neat/app/api/data_classes/configuration.py +0 -121
- cognite/neat/rules/models/_entity.py +0 -142
- cognite/neat/rules/models/rules/_types/_value.py +0 -159
- /cognite/neat/app/ui/neat-app/build/static/js/{main.4345d42f.js.LICENSE.txt → main.ec7f72e2.js.LICENSE.txt} +0 -0
- /cognite/neat/workflows/steps/lib/{graph_extractor.py → current/graph_extractor.py} +0 -0
- /cognite/neat/workflows/steps/lib/{graph_loader.py → current/graph_loader.py} +0 -0
- /cognite/neat/workflows/steps/lib/{graph_store.py → current/graph_store.py} +0 -0
- /cognite/neat/workflows/steps/lib/{rules_validator.py → current/rules_validator.py} +0 -0
- /cognite/neat/workflows/steps/lib/{io_steps.py → io/io_steps.py} +0 -0
- /cognite/neat/workflows/steps/lib/{v1 → legacy}/__init__.py +0 -0
- {cognite_neat-0.75.7.dist-info → cognite_neat-0.75.9.dist-info}/LICENSE +0 -0
- {cognite_neat-0.75.7.dist-info → cognite_neat-0.75.9.dist-info}/WHEEL +0 -0
- {cognite_neat-0.75.7.dist-info → cognite_neat-0.75.9.dist-info}/entry_points.txt +0 -0
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import re
|
|
2
2
|
import sys
|
|
3
|
-
import threading
|
|
4
3
|
from abc import ABC, abstractmethod
|
|
5
4
|
from functools import total_ordering
|
|
6
|
-
from typing import Annotated, Any, ClassVar, Generic, TypeVar
|
|
5
|
+
from typing import Annotated, Any, ClassVar, Generic, TypeVar, cast
|
|
7
6
|
|
|
8
7
|
from cognite.client.data_classes.data_modeling.ids import ContainerId, DataModelId, PropertyId, ViewId
|
|
9
|
-
from pydantic import BaseModel, BeforeValidator, Field, PlainSerializer, model_serializer, model_validator
|
|
8
|
+
from pydantic import AnyHttpUrl, BaseModel, BeforeValidator, Field, PlainSerializer, model_serializer, model_validator
|
|
10
9
|
|
|
11
10
|
if sys.version_info >= (3, 11):
|
|
12
11
|
from enum import StrEnum
|
|
@@ -58,45 +57,69 @@ _PROPERTY_ID_REGEX = rf"\((?P<{EntityTypes.property_}>{_ENTITY_ID_REGEX})\)"
|
|
|
58
57
|
_ENTITY_PATTERN = re.compile(r"^(?P<prefix>.*?):?(?P<suffix>[^(:]*)(\((?P<content>[^)]+)\))?$")
|
|
59
58
|
|
|
60
59
|
|
|
61
|
-
class
|
|
62
|
-
...
|
|
60
|
+
class _UndefinedType(BaseModel): ...
|
|
63
61
|
|
|
64
62
|
|
|
65
|
-
class
|
|
63
|
+
class _UnknownType(BaseModel):
|
|
66
64
|
def __str__(self) -> str:
|
|
67
65
|
return "#N/A"
|
|
68
66
|
|
|
69
67
|
|
|
70
68
|
# This is a trick to make Undefined and Unknown singletons
|
|
71
|
-
Undefined =
|
|
72
|
-
Unknown =
|
|
69
|
+
Undefined = _UndefinedType()
|
|
70
|
+
Unknown = _UnknownType()
|
|
71
|
+
_PARSE = object()
|
|
73
72
|
|
|
74
73
|
|
|
75
74
|
@total_ordering
|
|
76
|
-
class Entity(BaseModel):
|
|
75
|
+
class Entity(BaseModel, extra="ignore"):
|
|
77
76
|
"""Entity is a class or property in OWL/RDF sense."""
|
|
78
77
|
|
|
79
78
|
type_: ClassVar[EntityTypes] = EntityTypes.undefined
|
|
80
|
-
prefix: str |
|
|
81
|
-
suffix: str
|
|
79
|
+
prefix: str | _UndefinedType = Undefined
|
|
80
|
+
suffix: str
|
|
82
81
|
|
|
83
82
|
@classmethod
|
|
84
|
-
def load(cls, data: Any) ->
|
|
85
|
-
|
|
83
|
+
def load(cls: "type[T_Entity]", data: Any, **defaults) -> "T_Entity | UnknownEntity":
|
|
84
|
+
if isinstance(data, cls):
|
|
85
|
+
return data
|
|
86
|
+
elif isinstance(data, str) and data == str(Unknown):
|
|
87
|
+
return UnknownEntity(prefix=Undefined, suffix=Unknown)
|
|
88
|
+
if defaults and isinstance(defaults, dict):
|
|
89
|
+
# This is trick to pass in default values
|
|
90
|
+
return cls.model_validate({_PARSE: data, "defaults": defaults})
|
|
91
|
+
else:
|
|
92
|
+
return cls.model_validate(data)
|
|
86
93
|
|
|
87
94
|
@model_validator(mode="before")
|
|
88
|
-
def _load(cls, data: Any) -> dict:
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
95
|
+
def _load(cls, data: Any) -> "dict | Entity":
|
|
96
|
+
defaults = {}
|
|
97
|
+
if isinstance(data, dict) and _PARSE in data:
|
|
98
|
+
defaults = data.get("defaults", {})
|
|
99
|
+
data = data[_PARSE]
|
|
100
|
+
if isinstance(data, dict):
|
|
101
|
+
data.update(defaults)
|
|
92
102
|
return data
|
|
93
103
|
elif hasattr(data, "versioned_id"):
|
|
94
104
|
# Todo: Remove. Is here for backwards compatibility
|
|
95
105
|
data = data.versioned_id
|
|
96
106
|
elif not isinstance(data, str):
|
|
97
107
|
raise ValueError(f"Cannot load {cls.__name__} from {data}")
|
|
98
|
-
|
|
99
|
-
|
|
108
|
+
elif data == str(Unknown) and cls.type_ == EntityTypes.undefined:
|
|
109
|
+
return dict(prefix=Undefined, suffix=Unknown) # type: ignore[arg-type]
|
|
110
|
+
elif data == str(Unknown):
|
|
111
|
+
raise ValueError(f"Unknown is not allowed for {cls.type_} entity")
|
|
112
|
+
|
|
113
|
+
result = cls._parse(data)
|
|
114
|
+
output = defaults.copy()
|
|
115
|
+
# Populate by alias
|
|
116
|
+
for field_name, field_ in cls.model_fields.items():
|
|
117
|
+
name = field_.alias or field_name
|
|
118
|
+
if (field_value := result.get(field_name)) and not (field_value in [Unknown, Undefined] and name in output):
|
|
119
|
+
output[name] = result.pop(field_name)
|
|
120
|
+
elif name not in output and name in result:
|
|
121
|
+
output[name] = result.pop(name)
|
|
122
|
+
return output
|
|
100
123
|
|
|
101
124
|
@model_serializer(when_used="unless-none", return_type=str)
|
|
102
125
|
def as_str(self) -> str:
|
|
@@ -131,7 +154,7 @@ class Entity(BaseModel):
|
|
|
131
154
|
if isinstance(v := getattr(self, field_name), str | None) and field_name not in {"prefix", "suffix"}
|
|
132
155
|
]
|
|
133
156
|
)
|
|
134
|
-
if isinstance(self.prefix,
|
|
157
|
+
if isinstance(self.prefix, _UndefinedType):
|
|
135
158
|
return str(self.suffix), *extra
|
|
136
159
|
else:
|
|
137
160
|
return self.prefix, str(self.suffix), *extra
|
|
@@ -188,10 +211,25 @@ class Entity(BaseModel):
|
|
|
188
211
|
return f"{self.prefix}:{self.suffix!s}"
|
|
189
212
|
|
|
190
213
|
|
|
214
|
+
T_Entity = TypeVar("T_Entity", bound=Entity)
|
|
215
|
+
|
|
216
|
+
|
|
191
217
|
class ClassEntity(Entity):
|
|
192
218
|
type_: ClassVar[EntityTypes] = EntityTypes.class_
|
|
193
219
|
version: str | None = None
|
|
194
220
|
|
|
221
|
+
def as_view_entity(self, default_space: str, default_version) -> "ViewEntity":
|
|
222
|
+
if self.version is None:
|
|
223
|
+
version = default_version
|
|
224
|
+
else:
|
|
225
|
+
version = self.version
|
|
226
|
+
space = default_space if isinstance(self.prefix, _UndefinedType) else self.prefix
|
|
227
|
+
return ViewEntity(space=space, externalId=str(self.suffix), version=version)
|
|
228
|
+
|
|
229
|
+
def as_container_entity(self, default_space: str) -> "ContainerEntity":
|
|
230
|
+
space = default_space if isinstance(self.prefix, _UndefinedType) else self.prefix
|
|
231
|
+
return ContainerEntity(space=space, externalId=str(self.suffix))
|
|
232
|
+
|
|
195
233
|
|
|
196
234
|
class ParentClassEntity(ClassEntity):
|
|
197
235
|
type_: ClassVar[EntityTypes] = EntityTypes.parent_class
|
|
@@ -200,25 +238,34 @@ class ParentClassEntity(ClassEntity):
|
|
|
200
238
|
return ClassEntity(prefix=self.prefix, suffix=self.suffix, version=self.version)
|
|
201
239
|
|
|
202
240
|
|
|
203
|
-
|
|
241
|
+
class UnknownEntity(ClassEntity):
|
|
242
|
+
type_: ClassVar[EntityTypes] = EntityTypes.undefined
|
|
243
|
+
prefix: _UndefinedType = Undefined
|
|
244
|
+
suffix: _UnknownType = Unknown # type: ignore[assignment]
|
|
245
|
+
|
|
246
|
+
@property
|
|
247
|
+
def id(self) -> str:
|
|
248
|
+
return str(Unknown)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
T_ID = TypeVar("T_ID", bound=ContainerId | ViewId | DataModelId | PropertyId | None)
|
|
204
252
|
|
|
205
253
|
|
|
206
254
|
class DMSEntity(Entity, Generic[T_ID], ABC):
|
|
207
255
|
type_: ClassVar[EntityTypes] = EntityTypes.undefined
|
|
208
|
-
|
|
209
|
-
suffix: str
|
|
256
|
+
prefix: str = Field(alias="space")
|
|
257
|
+
suffix: str = Field(alias="externalId")
|
|
210
258
|
|
|
211
259
|
@classmethod
|
|
212
|
-
def
|
|
213
|
-
|
|
260
|
+
def load(cls: "type[T_DMSEntity]", data: Any, **defaults) -> "T_DMSEntity | DMSUnknownEntity": # type: ignore[override]
|
|
261
|
+
if isinstance(data, str) and data == str(Unknown):
|
|
262
|
+
return DMSUnknownEntity.from_id(None)
|
|
263
|
+
return cast(T_DMSEntity, super().load(data, **defaults))
|
|
214
264
|
|
|
215
265
|
@property
|
|
216
266
|
def space(self) -> str:
|
|
217
267
|
"""Returns entity space in CDF."""
|
|
218
|
-
|
|
219
|
-
return self.default_space_by_thread.get(threading.current_thread(), "MISSING")
|
|
220
|
-
else:
|
|
221
|
-
return self.prefix
|
|
268
|
+
return self.prefix
|
|
222
269
|
|
|
223
270
|
@property
|
|
224
271
|
def external_id(self) -> str:
|
|
@@ -229,6 +276,17 @@ class DMSEntity(Entity, Generic[T_ID], ABC):
|
|
|
229
276
|
def as_id(self) -> T_ID:
|
|
230
277
|
raise NotImplementedError("Method as_id must be implemented in subclasses")
|
|
231
278
|
|
|
279
|
+
@classmethod
|
|
280
|
+
@abstractmethod
|
|
281
|
+
def from_id(cls, id: T_ID) -> Self:
|
|
282
|
+
raise NotImplementedError("Method from_id must be implemented in subclasses")
|
|
283
|
+
|
|
284
|
+
def as_class(self) -> ClassEntity:
|
|
285
|
+
return ClassEntity(prefix=self.space, suffix=self.external_id)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
T_DMSEntity = TypeVar("T_DMSEntity", bound=DMSEntity)
|
|
289
|
+
|
|
232
290
|
|
|
233
291
|
class ContainerEntity(DMSEntity[ContainerId]):
|
|
234
292
|
type_: ClassVar[EntityTypes] = EntityTypes.container
|
|
@@ -236,16 +294,16 @@ class ContainerEntity(DMSEntity[ContainerId]):
|
|
|
236
294
|
def as_id(self) -> ContainerId:
|
|
237
295
|
return ContainerId(space=self.space, external_id=self.external_id)
|
|
238
296
|
|
|
297
|
+
@classmethod
|
|
298
|
+
def from_id(cls, id: ContainerId) -> "ContainerEntity":
|
|
299
|
+
return cls(space=id.space, externalId=id.external_id)
|
|
300
|
+
|
|
239
301
|
|
|
240
302
|
class DMSVersionedEntity(DMSEntity[T_ID], ABC):
|
|
241
|
-
version: str
|
|
242
|
-
default_version_by_thread: ClassVar[dict[threading.Thread, str]] = {}
|
|
303
|
+
version: str
|
|
243
304
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if self.version is not None:
|
|
247
|
-
return self.version
|
|
248
|
-
return self.default_version_by_thread.get(threading.current_thread(), "MISSING")
|
|
305
|
+
def as_class(self) -> ClassEntity:
|
|
306
|
+
return ClassEntity(prefix=self.space, suffix=self.external_id, version=self.version)
|
|
249
307
|
|
|
250
308
|
|
|
251
309
|
class ViewEntity(DMSVersionedEntity[ViewId]):
|
|
@@ -254,16 +312,54 @@ class ViewEntity(DMSVersionedEntity[ViewId]):
|
|
|
254
312
|
def as_id(
|
|
255
313
|
self,
|
|
256
314
|
) -> ViewId:
|
|
257
|
-
return ViewId(space=self.space, external_id=self.external_id, version=self.
|
|
315
|
+
return ViewId(space=self.space, external_id=self.external_id, version=self.version)
|
|
316
|
+
|
|
317
|
+
@classmethod
|
|
318
|
+
def from_id(cls, id: ViewId) -> "ViewEntity":
|
|
319
|
+
if id.version is None:
|
|
320
|
+
raise ValueError("Version must be specified")
|
|
321
|
+
return cls(space=id.space, externalId=id.external_id, version=id.version)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
class DMSUnknownEntity(DMSEntity[None]):
|
|
325
|
+
"""This is a special entity that represents an unknown entity.
|
|
326
|
+
|
|
327
|
+
The use case is for direct relations where the source is not known."""
|
|
328
|
+
|
|
329
|
+
type_: ClassVar[EntityTypes] = EntityTypes.undefined
|
|
330
|
+
prefix: _UndefinedType = Field(Undefined, alias="space") # type: ignore[assignment]
|
|
331
|
+
suffix: _UnknownType = Field(Unknown, alias="externalId") # type: ignore[assignment]
|
|
332
|
+
|
|
333
|
+
def as_id(self) -> None:
|
|
334
|
+
return None
|
|
258
335
|
|
|
336
|
+
@classmethod
|
|
337
|
+
def from_id(cls, id: None) -> "DMSUnknownEntity":
|
|
338
|
+
return cls(space=Undefined, externalId=Unknown)
|
|
259
339
|
|
|
260
|
-
|
|
340
|
+
@property
|
|
341
|
+
def id(self) -> str:
|
|
342
|
+
return str(Unknown)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
class ViewPropertyEntity(DMSVersionedEntity[PropertyId]):
|
|
261
346
|
type_: ClassVar[EntityTypes] = EntityTypes.property_
|
|
262
347
|
property_: str = Field(alias="property")
|
|
263
348
|
|
|
264
349
|
def as_id(self) -> PropertyId:
|
|
265
|
-
return PropertyId(
|
|
266
|
-
|
|
350
|
+
return PropertyId(source=ViewId(self.space, self.external_id, self.version), property=self.property_)
|
|
351
|
+
|
|
352
|
+
def as_view_id(self) -> ViewId:
|
|
353
|
+
return ViewId(space=self.space, external_id=self.external_id, version=self.version)
|
|
354
|
+
|
|
355
|
+
@classmethod
|
|
356
|
+
def from_id(cls, id: PropertyId) -> "ViewPropertyEntity":
|
|
357
|
+
if isinstance(id.source, ContainerId):
|
|
358
|
+
raise ValueError("Only view source are supported")
|
|
359
|
+
if id.source.version is None:
|
|
360
|
+
raise ValueError("Version must be specified")
|
|
361
|
+
return cls(
|
|
362
|
+
space=id.source.space, externalId=id.source.external_id, version=id.source.version, property=id.property
|
|
267
363
|
)
|
|
268
364
|
|
|
269
365
|
|
|
@@ -271,11 +367,32 @@ class DataModelEntity(DMSVersionedEntity[DataModelId]):
|
|
|
271
367
|
type_: ClassVar[EntityTypes] = EntityTypes.datamodel
|
|
272
368
|
|
|
273
369
|
def as_id(self) -> DataModelId:
|
|
274
|
-
return DataModelId(space=self.space, external_id=self.external_id, version=self.
|
|
370
|
+
return DataModelId(space=self.space, external_id=self.external_id, version=self.version)
|
|
371
|
+
|
|
372
|
+
@classmethod
|
|
373
|
+
def from_id(cls, id: DataModelId) -> "DataModelEntity":
|
|
374
|
+
if id.version is None:
|
|
375
|
+
raise ValueError("Version must be specified")
|
|
376
|
+
return cls(space=id.space, externalId=id.external_id, version=id.version)
|
|
275
377
|
|
|
276
378
|
|
|
277
|
-
class ReferenceEntity(
|
|
379
|
+
class ReferenceEntity(ClassEntity):
|
|
278
380
|
type_: ClassVar[EntityTypes] = EntityTypes.reference_entity
|
|
381
|
+
prefix: str
|
|
382
|
+
property_: str | None = Field(None, alias="property")
|
|
383
|
+
|
|
384
|
+
def as_view_id(self) -> ViewId:
|
|
385
|
+
if isinstance(self.prefix, _UndefinedType) or isinstance(self.suffix, _UnknownType):
|
|
386
|
+
raise ValueError("Prefix is not defined or suffix is unknown")
|
|
387
|
+
return ViewId(space=self.prefix, external_id=self.suffix, version=self.version)
|
|
388
|
+
|
|
389
|
+
def as_view_property_id(self) -> PropertyId:
|
|
390
|
+
if self.property_ is None or self.prefix is Undefined or self.suffix is Unknown:
|
|
391
|
+
raise ValueError("Property is not defined or prefix is not defined or suffix is unknown")
|
|
392
|
+
return PropertyId(source=self.as_view_id(), property=self.property_)
|
|
393
|
+
|
|
394
|
+
def as_class_entity(self) -> ClassEntity:
|
|
395
|
+
return ClassEntity(prefix=self.prefix, suffix=self.suffix, version=self.version)
|
|
279
396
|
|
|
280
397
|
|
|
281
398
|
def _split_str(v: Any) -> list[str]:
|
|
@@ -297,3 +414,28 @@ ParentEntityList = Annotated[
|
|
|
297
414
|
when_used="unless-none",
|
|
298
415
|
),
|
|
299
416
|
]
|
|
417
|
+
|
|
418
|
+
ContainerEntityList = Annotated[
|
|
419
|
+
list[ContainerEntity],
|
|
420
|
+
BeforeValidator(_split_str),
|
|
421
|
+
PlainSerializer(
|
|
422
|
+
_join_str,
|
|
423
|
+
return_type=str,
|
|
424
|
+
when_used="unless-none",
|
|
425
|
+
),
|
|
426
|
+
]
|
|
427
|
+
|
|
428
|
+
ViewEntityList = Annotated[
|
|
429
|
+
list[ViewEntity],
|
|
430
|
+
BeforeValidator(_split_str),
|
|
431
|
+
PlainSerializer(
|
|
432
|
+
_join_str,
|
|
433
|
+
return_type=str,
|
|
434
|
+
when_used="unless-none",
|
|
435
|
+
),
|
|
436
|
+
]
|
|
437
|
+
|
|
438
|
+
URLEntity = Annotated[
|
|
439
|
+
AnyHttpUrl,
|
|
440
|
+
PlainSerializer(lambda v: str(v), return_type=str, when_used="unless-none"),
|
|
441
|
+
]
|
|
@@ -1,25 +1,15 @@
|
|
|
1
|
-
"""
|
|
2
|
-
"""
|
|
1
|
+
""" """
|
|
3
2
|
|
|
4
3
|
import re
|
|
5
4
|
import sys
|
|
6
5
|
from collections import Counter
|
|
7
|
-
from
|
|
6
|
+
from functools import total_ordering
|
|
7
|
+
from typing import ClassVar, Literal
|
|
8
8
|
|
|
9
9
|
from pydantic import BaseModel, field_validator
|
|
10
10
|
|
|
11
11
|
from cognite.neat.rules import exceptions
|
|
12
12
|
|
|
13
|
-
from ._entity import (
|
|
14
|
-
CLASS_ID_REGEX,
|
|
15
|
-
CLASS_ID_REGEX_COMPILED,
|
|
16
|
-
ENTITY_ID_REGEX,
|
|
17
|
-
PROPERTY_ID_REGEX,
|
|
18
|
-
SUFFIX_REGEX,
|
|
19
|
-
Entity,
|
|
20
|
-
EntityTypes,
|
|
21
|
-
)
|
|
22
|
-
|
|
23
13
|
if sys.version_info >= (3, 11):
|
|
24
14
|
from enum import StrEnum
|
|
25
15
|
from typing import Self
|
|
@@ -40,6 +30,26 @@ class Lookup(StrEnum):
|
|
|
40
30
|
value = "value" # type: ignore
|
|
41
31
|
|
|
42
32
|
|
|
33
|
+
class EntityTypes(StrEnum):
|
|
34
|
+
class_ = "class"
|
|
35
|
+
property_ = "property"
|
|
36
|
+
undefined = "undefined"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# FOR PARSING STRINGS:
|
|
40
|
+
PREFIX_REGEX = r"[a-zA-Z]+[a-zA-Z0-9-_.]*[a-zA-Z0-9]+"
|
|
41
|
+
SUFFIX_REGEX = r"[a-zA-Z0-9-_.]+[a-zA-Z0-9]|[-_.]*[a-zA-Z0-9]+"
|
|
42
|
+
VERSION_REGEX = r"[a-zA-Z0-9]([.a-zA-Z0-9_-]{0,41}[a-zA-Z0-9])?"
|
|
43
|
+
|
|
44
|
+
ENTITY_ID_REGEX = rf"{PREFIX_REGEX}:({SUFFIX_REGEX})"
|
|
45
|
+
ENTITY_ID_REGEX_COMPILED = re.compile(rf"^(?P<prefix>{PREFIX_REGEX}):(?P<suffix>{SUFFIX_REGEX})$")
|
|
46
|
+
VERSIONED_ENTITY_REGEX_COMPILED = re.compile(
|
|
47
|
+
rf"^(?P<prefix>{PREFIX_REGEX}):(?P<suffix>{SUFFIX_REGEX})\(version=(?P<version>{VERSION_REGEX})\)$"
|
|
48
|
+
)
|
|
49
|
+
CLASS_ID_REGEX = rf"(?P<{EntityTypes.class_}>{ENTITY_ID_REGEX})"
|
|
50
|
+
CLASS_ID_REGEX_COMPILED = re.compile(rf"^{CLASS_ID_REGEX}$")
|
|
51
|
+
PROPERTY_ID_REGEX = rf"\((?P<{EntityTypes.property_}>{ENTITY_ID_REGEX})\)"
|
|
52
|
+
|
|
43
53
|
# traversal direction
|
|
44
54
|
DIRECTION_REGEX = r"(?P<direction>(->|<-))"
|
|
45
55
|
|
|
@@ -73,6 +83,95 @@ TABLE_REGEX_COMPILED = re.compile(
|
|
|
73
83
|
StepDirection = Literal["source", "target", "origin"]
|
|
74
84
|
_direction_by_symbol: dict[str, StepDirection] = {"->": "target", "<-": "source"}
|
|
75
85
|
|
|
86
|
+
Undefined = type(object())
|
|
87
|
+
Unknown = type(object())
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# mypy does not like the sentinel value, and it is not possible to ignore only the line with it below.
|
|
91
|
+
# so we ignore all errors beyond this point.
|
|
92
|
+
# mypy: ignore-errors
|
|
93
|
+
@total_ordering
|
|
94
|
+
class Entity(BaseModel, arbitrary_types_allowed=True):
|
|
95
|
+
"""Entity is a class or property in OWL/RDF sense."""
|
|
96
|
+
|
|
97
|
+
type_: ClassVar[EntityTypes] = EntityTypes.undefined
|
|
98
|
+
prefix: str | Undefined = Undefined
|
|
99
|
+
suffix: str | Unknown
|
|
100
|
+
version: str | None = None
|
|
101
|
+
name: str | None = None
|
|
102
|
+
description: str | None = None
|
|
103
|
+
|
|
104
|
+
def __lt__(self, other: object) -> bool:
|
|
105
|
+
if type(self) is not type(other) or not isinstance(other, Entity):
|
|
106
|
+
return NotImplemented
|
|
107
|
+
return self.versioned_id < other.versioned_id
|
|
108
|
+
|
|
109
|
+
def __eq__(self, other: object) -> bool:
|
|
110
|
+
if type(self) is not type(other) or not isinstance(other, Entity):
|
|
111
|
+
return NotImplemented
|
|
112
|
+
return self.versioned_id == other.versioned_id
|
|
113
|
+
|
|
114
|
+
def __hash__(self) -> int:
|
|
115
|
+
return hash(self.versioned_id)
|
|
116
|
+
|
|
117
|
+
def as_non_versioned_entity(self) -> Self:
|
|
118
|
+
return self.from_string(f"{self.prefix}:{self.suffix}")
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def id(self) -> str:
|
|
122
|
+
if self.suffix is Unknown:
|
|
123
|
+
return "#N/A"
|
|
124
|
+
elif self.prefix is Undefined:
|
|
125
|
+
return self.suffix
|
|
126
|
+
else:
|
|
127
|
+
return f"{self.prefix}:{self.suffix}"
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def versioned_id(self) -> str:
|
|
131
|
+
if self.version is None:
|
|
132
|
+
return self.id
|
|
133
|
+
else:
|
|
134
|
+
return f"{self.id}(version={self.version})"
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def space(self) -> str:
|
|
138
|
+
"""Returns entity space in CDF."""
|
|
139
|
+
return self.prefix
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def external_id(self) -> str:
|
|
143
|
+
"""Returns entity external id in CDF."""
|
|
144
|
+
return self.suffix
|
|
145
|
+
|
|
146
|
+
def __repr__(self):
|
|
147
|
+
return self.versioned_id
|
|
148
|
+
|
|
149
|
+
def __str__(self):
|
|
150
|
+
return self.versioned_id
|
|
151
|
+
|
|
152
|
+
@classmethod
|
|
153
|
+
def from_string(cls, entity_string: str, base_prefix: str | None = None) -> Self:
|
|
154
|
+
if entity_string == "#N/A":
|
|
155
|
+
return cls(prefix=Undefined, suffix=Unknown)
|
|
156
|
+
elif result := VERSIONED_ENTITY_REGEX_COMPILED.match(entity_string):
|
|
157
|
+
return cls(
|
|
158
|
+
prefix=result.group("prefix"),
|
|
159
|
+
suffix=result.group("suffix"),
|
|
160
|
+
version=result.group("version"),
|
|
161
|
+
)
|
|
162
|
+
elif result := ENTITY_ID_REGEX_COMPILED.match(entity_string):
|
|
163
|
+
return cls(prefix=result.group("prefix"), suffix=result.group("suffix"))
|
|
164
|
+
elif base_prefix and re.match(SUFFIX_REGEX, entity_string) and re.match(PREFIX_REGEX, base_prefix):
|
|
165
|
+
return cls(prefix=base_prefix, suffix=entity_string)
|
|
166
|
+
else:
|
|
167
|
+
raise ValueError(f"{cls.__name__} is expected to be prefix:suffix, got {entity_string}")
|
|
168
|
+
|
|
169
|
+
@classmethod
|
|
170
|
+
def from_list(cls, entity_strings: list[str], base_prefix: str | None = None) -> list[Self]:
|
|
171
|
+
return [
|
|
172
|
+
cls.from_string(entity_string=entity_string, base_prefix=base_prefix) for entity_string in entity_strings
|
|
173
|
+
]
|
|
174
|
+
|
|
76
175
|
|
|
77
176
|
class Step(BaseModel):
|
|
78
177
|
class_: Entity
|
|
@@ -27,7 +27,7 @@ from pydantic import (
|
|
|
27
27
|
)
|
|
28
28
|
from pydantic.fields import FieldInfo
|
|
29
29
|
|
|
30
|
-
from cognite.neat.rules.models.
|
|
30
|
+
from cognite.neat.rules.models.entities import ClassEntity
|
|
31
31
|
|
|
32
32
|
if sys.version_info >= (3, 11):
|
|
33
33
|
from enum import StrEnum
|
|
@@ -274,7 +274,7 @@ class BaseRules(RuleModel):
|
|
|
274
274
|
|
|
275
275
|
# An sheet entity is either a class or a property.
|
|
276
276
|
class SheetEntity(RuleModel):
|
|
277
|
-
class_:
|
|
277
|
+
class_: ClassEntity = Field(alias="Class")
|
|
278
278
|
name: str | None = Field(alias="Name", default=None)
|
|
279
279
|
description: str | None = Field(alias="Description", default=None)
|
|
280
280
|
|