cognite-neat 0.96.5__py3-none-any.whl → 0.97.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/_constants.py +4 -1
- cognite/neat/_graph/extractors/__init__.py +3 -0
- cognite/neat/_graph/extractors/_base.py +1 -1
- cognite/neat/_graph/extractors/_classic_cdf/_assets.py +1 -1
- cognite/neat/_graph/extractors/_classic_cdf/_base.py +1 -1
- cognite/neat/_graph/extractors/_classic_cdf/_classic.py +1 -1
- cognite/neat/_graph/extractors/_classic_cdf/_data_sets.py +1 -1
- cognite/neat/_graph/extractors/_classic_cdf/_events.py +1 -1
- cognite/neat/_graph/extractors/_classic_cdf/_files.py +1 -1
- cognite/neat/_graph/extractors/_classic_cdf/_labels.py +1 -1
- cognite/neat/_graph/extractors/_classic_cdf/_relationships.py +1 -1
- cognite/neat/_graph/extractors/_classic_cdf/_sequences.py +1 -1
- cognite/neat/_graph/extractors/_classic_cdf/_timeseries.py +1 -1
- cognite/neat/_graph/extractors/_dexpi.py +1 -1
- cognite/neat/_graph/extractors/_dms.py +1 -1
- cognite/neat/_graph/extractors/_iodd.py +1 -1
- cognite/neat/_graph/extractors/_mock_graph_generator.py +1 -1
- cognite/neat/_graph/extractors/_rdf_file.py +1 -1
- cognite/neat/_graph/loaders/_rdf2dms.py +1 -1
- cognite/neat/_graph/queries/_base.py +1 -1
- cognite/neat/_graph/transformers/__init__.py +3 -1
- cognite/neat/_graph/transformers/_rdfpath.py +60 -1
- cognite/neat/_issues/errors/__init__.py +2 -0
- cognite/neat/_issues/errors/_properties.py +12 -0
- cognite/neat/_issues/warnings/__init__.py +2 -0
- cognite/neat/_issues/warnings/_models.py +11 -0
- cognite/neat/_rules/importers/__init__.py +11 -0
- cognite/neat/_rules/importers/_base.py +7 -0
- cognite/neat/_rules/importers/_dms2rules.py +12 -3
- cognite/neat/_rules/importers/_rdf/_inference2rules.py +17 -2
- cognite/neat/_rules/importers/_spreadsheet2rules.py +5 -1
- cognite/neat/_rules/models/asset/_rules.py +6 -2
- cognite/neat/_rules/models/asset/_rules_input.py +6 -1
- cognite/neat/_rules/models/data_types.py +6 -0
- cognite/neat/_rules/models/dms/_exporter.py +16 -3
- cognite/neat/_rules/models/dms/_rules.py +37 -12
- cognite/neat/_rules/models/dms/_rules_input.py +8 -0
- cognite/neat/_rules/models/dms/_validation.py +64 -2
- cognite/neat/_rules/models/domain.py +10 -0
- cognite/neat/_rules/models/entities/_loaders.py +3 -5
- cognite/neat/_rules/models/information/_rules.py +6 -2
- cognite/neat/_rules/models/information/_rules_input.py +6 -1
- cognite/neat/_rules/transformers/_base.py +7 -0
- cognite/neat/_rules/transformers/_converters.py +56 -4
- cognite/neat/_session/_base.py +94 -23
- cognite/neat/_session/_inspect.py +12 -4
- cognite/neat/_session/_prepare.py +144 -21
- cognite/neat/_session/_read.py +137 -30
- cognite/neat/_session/_set.py +22 -3
- cognite/neat/_session/_show.py +171 -45
- cognite/neat/_session/_state.py +79 -30
- cognite/neat/_session/_to.py +16 -17
- cognite/neat/_session/engine/__init__.py +4 -0
- cognite/neat/_session/engine/_import.py +7 -0
- cognite/neat/_session/engine/_interface.py +24 -0
- cognite/neat/_session/engine/_load.py +129 -0
- cognite/neat/_session/exceptions.py +13 -3
- cognite/neat/_shared.py +6 -1
- cognite/neat/_store/_base.py +3 -24
- cognite/neat/_store/_provenance.py +185 -42
- cognite/neat/_utils/rdf_.py +34 -1
- cognite/neat/_utils/reader/__init__.py +3 -0
- cognite/neat/_utils/reader/_base.py +162 -0
- cognite/neat/_version.py +2 -1
- {cognite_neat-0.96.5.dist-info → cognite_neat-0.97.0.dist-info}/METADATA +5 -3
- {cognite_neat-0.96.5.dist-info → cognite_neat-0.97.0.dist-info}/RECORD +69 -64
- cognite/neat/_graph/models.py +0 -7
- {cognite_neat-0.96.5.dist-info → cognite_neat-0.97.0.dist-info}/LICENSE +0 -0
- {cognite_neat-0.96.5.dist-info → cognite_neat-0.97.0.dist-info}/WHEEL +0 -0
- {cognite_neat-0.96.5.dist-info → cognite_neat-0.97.0.dist-info}/entry_points.txt +0 -0
|
@@ -402,17 +402,30 @@ class _DMSExporter:
|
|
|
402
402
|
|
|
403
403
|
return container_properties_by_id, view_properties_by_id
|
|
404
404
|
|
|
405
|
-
@staticmethod
|
|
406
405
|
def _gather_properties_with_ancestors(
|
|
406
|
+
self,
|
|
407
407
|
view_properties_by_id: dict[dm.ViewId, list[DMSProperty]],
|
|
408
408
|
views: Sequence[DMSView],
|
|
409
409
|
) -> dict[dm.ViewId, list[DMSProperty]]:
|
|
410
|
+
all_view_properties_by_id = view_properties_by_id.copy()
|
|
411
|
+
if self.rules.reference:
|
|
412
|
+
# We need to include t
|
|
413
|
+
ref_view_properties_by_id = self._gather_properties(self.rules.reference.properties)[1]
|
|
414
|
+
for view_id, properties in ref_view_properties_by_id.items():
|
|
415
|
+
if view_id not in all_view_properties_by_id:
|
|
416
|
+
all_view_properties_by_id[view_id] = properties
|
|
417
|
+
else:
|
|
418
|
+
existing_properties = {prop._identifier() for prop in all_view_properties_by_id[view_id]}
|
|
419
|
+
for prop in properties:
|
|
420
|
+
if prop._identifier() not in existing_properties:
|
|
421
|
+
all_view_properties_by_id[view_id].append(prop)
|
|
422
|
+
|
|
410
423
|
view_properties_with_parents_by_id: dict[dm.ViewId, list[DMSProperty]] = defaultdict(list)
|
|
411
424
|
view_by_view_id = {view.view.as_id(): view for view in views}
|
|
412
425
|
for view in views:
|
|
413
426
|
view_id = view.view.as_id()
|
|
414
427
|
seen: set[Hashable] = set()
|
|
415
|
-
if view_properties :=
|
|
428
|
+
if view_properties := all_view_properties_by_id.get(view_id):
|
|
416
429
|
view_properties_with_parents_by_id[view_id].extend(view_properties)
|
|
417
430
|
seen.update(prop._identifier() for prop in view_properties)
|
|
418
431
|
if not view.implements:
|
|
@@ -428,7 +441,7 @@ class _DMSExporter:
|
|
|
428
441
|
parents.append(grandparent)
|
|
429
442
|
seen_parents.add(grandparent)
|
|
430
443
|
|
|
431
|
-
if not (parent_view_properties :=
|
|
444
|
+
if not (parent_view_properties := all_view_properties_by_id.get(parent_view_id)):
|
|
432
445
|
continue
|
|
433
446
|
for prop in parent_view_properties:
|
|
434
447
|
new_prop = prop.model_copy(update={"view": view.view})
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import math
|
|
2
|
-
import sys
|
|
3
2
|
import warnings
|
|
4
3
|
from collections.abc import Hashable
|
|
5
4
|
from datetime import datetime
|
|
6
|
-
from typing import
|
|
5
|
+
from typing import Any, ClassVar, Literal
|
|
7
6
|
|
|
8
7
|
import pandas as pd
|
|
9
8
|
from cognite.client import data_modeling as dm
|
|
10
9
|
from pydantic import Field, field_serializer, field_validator, model_validator
|
|
11
10
|
from pydantic_core.core_schema import SerializationInfo, ValidationInfo
|
|
11
|
+
from rdflib import URIRef
|
|
12
12
|
|
|
13
|
+
from cognite.neat._constants import COGNITE_SPACES, DEFAULT_NAMESPACE
|
|
13
14
|
from cognite.neat._issues import MultiValueError
|
|
14
15
|
from cognite.neat._issues.warnings import (
|
|
15
16
|
PrincipleMatchingSpaceAndVersionWarning,
|
|
@@ -56,14 +57,6 @@ from cognite.neat._rules.models.entities import (
|
|
|
56
57
|
|
|
57
58
|
from ._schema import DMSSchema
|
|
58
59
|
|
|
59
|
-
if TYPE_CHECKING:
|
|
60
|
-
pass
|
|
61
|
-
|
|
62
|
-
if sys.version_info >= (3, 11):
|
|
63
|
-
pass
|
|
64
|
-
else:
|
|
65
|
-
pass
|
|
66
|
-
|
|
67
60
|
_DEFAULT_VERSION = "1"
|
|
68
61
|
|
|
69
62
|
|
|
@@ -197,6 +190,24 @@ class DMSProperty(SheetRow):
|
|
|
197
190
|
raise ValueError(f"Reverse connection must have a value type that points to a view, got {value}")
|
|
198
191
|
return value
|
|
199
192
|
|
|
193
|
+
@field_validator("container", "container_property", mode="after")
|
|
194
|
+
def container_set_correctly(cls, value: Any, info: ValidationInfo) -> Any:
|
|
195
|
+
if (connection := info.data.get("connection")) is None:
|
|
196
|
+
return value
|
|
197
|
+
if connection == "direct" and value is None:
|
|
198
|
+
raise ValueError(
|
|
199
|
+
"You must provide a container and container property for where to store direct connections"
|
|
200
|
+
)
|
|
201
|
+
elif isinstance(connection, EdgeEntity) and value is not None:
|
|
202
|
+
raise ValueError(
|
|
203
|
+
"Edge connections are not stored in a container, please remove the container and container property"
|
|
204
|
+
)
|
|
205
|
+
elif isinstance(connection, ReverseConnectionEntity) and value is not None:
|
|
206
|
+
raise ValueError(
|
|
207
|
+
"Reverse connection are not stored in a container, please remove the container and container property"
|
|
208
|
+
)
|
|
209
|
+
return value
|
|
210
|
+
|
|
200
211
|
@field_serializer("reference", when_used="always")
|
|
201
212
|
def set_reference(self, value: Any, info: SerializationInfo) -> str | None:
|
|
202
213
|
if isinstance(info.context, dict) and info.context.get("as_reference") is True:
|
|
@@ -421,7 +432,11 @@ class DMSRules(BaseRules):
|
|
|
421
432
|
if not (metadata := info.data.get("metadata")):
|
|
422
433
|
return value
|
|
423
434
|
model_version = metadata.version
|
|
424
|
-
if different_version := [
|
|
435
|
+
if different_version := [
|
|
436
|
+
view.view.as_id()
|
|
437
|
+
for view in value
|
|
438
|
+
if view.view.version != model_version and view.view.space not in COGNITE_SPACES
|
|
439
|
+
]:
|
|
425
440
|
for view_id in different_version:
|
|
426
441
|
warnings.warn(
|
|
427
442
|
PrincipleMatchingSpaceAndVersionWarning(
|
|
@@ -429,7 +444,11 @@ class DMSRules(BaseRules):
|
|
|
429
444
|
),
|
|
430
445
|
stacklevel=2,
|
|
431
446
|
)
|
|
432
|
-
if different_space := [
|
|
447
|
+
if different_space := [
|
|
448
|
+
view.view.as_id()
|
|
449
|
+
for view in value
|
|
450
|
+
if view.view.space != metadata.space and view.view.space not in COGNITE_SPACES
|
|
451
|
+
]:
|
|
433
452
|
for view_id in different_space:
|
|
434
453
|
warnings.warn(
|
|
435
454
|
PrincipleMatchingSpaceAndVersionWarning(
|
|
@@ -469,3 +488,9 @@ class DMSRules(BaseRules):
|
|
|
469
488
|
}
|
|
470
489
|
|
|
471
490
|
return pd.DataFrame([summary]).T.rename(columns={0: ""})._repr_html_() # type: ignore
|
|
491
|
+
|
|
492
|
+
@property
|
|
493
|
+
def id_(self) -> URIRef:
|
|
494
|
+
return DEFAULT_NAMESPACE[
|
|
495
|
+
f"data-model/verified/dms/{self.metadata.space}/{self.metadata.external_id}/{self.metadata.version}"
|
|
496
|
+
]
|
|
@@ -5,7 +5,9 @@ from typing import Any, Literal
|
|
|
5
5
|
|
|
6
6
|
import pandas as pd
|
|
7
7
|
from cognite.client import data_modeling as dm
|
|
8
|
+
from rdflib import URIRef
|
|
8
9
|
|
|
10
|
+
from cognite.neat._constants import DEFAULT_NAMESPACE
|
|
9
11
|
from cognite.neat._rules.models._base_input import InputComponent, InputRules
|
|
10
12
|
from cognite.neat._rules.models.data_types import DataType
|
|
11
13
|
from cognite.neat._rules.models.entities import (
|
|
@@ -307,3 +309,9 @@ class DMSInputRules(InputRules[DMSRules]):
|
|
|
307
309
|
}
|
|
308
310
|
|
|
309
311
|
return pd.DataFrame([summary]).T.rename(columns={0: ""})._repr_html_() # type: ignore
|
|
312
|
+
|
|
313
|
+
@property
|
|
314
|
+
def id_(self) -> URIRef:
|
|
315
|
+
return DEFAULT_NAMESPACE[
|
|
316
|
+
f"data-model/unverified/dms/{self.metadata.space}/{self.metadata.external_id}/{self.metadata.version}"
|
|
317
|
+
]
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
from collections import defaultdict
|
|
2
|
-
from typing import Any, ClassVar
|
|
2
|
+
from typing import Any, ClassVar, cast
|
|
3
3
|
|
|
4
4
|
from cognite.client import data_modeling as dm
|
|
5
5
|
|
|
6
|
-
from cognite.neat._constants import DMS_CONTAINER_PROPERTY_SIZE_LIMIT
|
|
6
|
+
from cognite.neat._constants import COGNITE_MODELS, DMS_CONTAINER_PROPERTY_SIZE_LIMIT
|
|
7
7
|
from cognite.neat._issues import IssueList, NeatError, NeatIssue, NeatIssueList
|
|
8
8
|
from cognite.neat._issues.errors import (
|
|
9
9
|
PropertyDefinitionDuplicatedError,
|
|
10
10
|
ResourceChangedError,
|
|
11
11
|
ResourceNotDefinedError,
|
|
12
12
|
)
|
|
13
|
+
from cognite.neat._issues.errors._properties import ReversedConnectionNotFeasibleError
|
|
13
14
|
from cognite.neat._issues.warnings import (
|
|
14
15
|
NotSupportedHasDataFilterLimitWarning,
|
|
15
16
|
NotSupportedViewContainerLimitWarning,
|
|
17
|
+
UndefinedViewWarning,
|
|
16
18
|
)
|
|
17
19
|
from cognite.neat._issues.warnings.user_modeling import (
|
|
18
20
|
NotNeatSupportedFilterWarning,
|
|
@@ -21,6 +23,10 @@ from cognite.neat._issues.warnings.user_modeling import (
|
|
|
21
23
|
from cognite.neat._rules.models._base_rules import DataModelType, ExtensionCategory, SchemaCompleteness
|
|
22
24
|
from cognite.neat._rules.models.data_types import DataType
|
|
23
25
|
from cognite.neat._rules.models.entities import ContainerEntity, RawFilter
|
|
26
|
+
from cognite.neat._rules.models.entities._single_value import (
|
|
27
|
+
ReverseConnectionEntity,
|
|
28
|
+
ViewEntity,
|
|
29
|
+
)
|
|
24
30
|
|
|
25
31
|
from ._rules import DMSProperty, DMSRules
|
|
26
32
|
from ._schema import DMSSchema
|
|
@@ -45,6 +51,8 @@ class DMSPostValidation:
|
|
|
45
51
|
def validate(self) -> NeatIssueList:
|
|
46
52
|
self._validate_raw_filter()
|
|
47
53
|
self._consistent_container_properties()
|
|
54
|
+
self._validate_value_type_existence()
|
|
55
|
+
self._validate_reverse_connections()
|
|
48
56
|
|
|
49
57
|
self._referenced_views_and_containers_are_existing_and_proper_size()
|
|
50
58
|
if self.metadata.schema_ is SchemaCompleteness.extended:
|
|
@@ -318,6 +326,60 @@ class DMSPostValidation:
|
|
|
318
326
|
NotNeatSupportedFilterWarning(view.view.as_id()),
|
|
319
327
|
)
|
|
320
328
|
|
|
329
|
+
def _validate_value_type_existence(self) -> None:
|
|
330
|
+
views = {prop_.view for prop_ in self.properties}.union({view_.view for view_ in self.views})
|
|
331
|
+
|
|
332
|
+
for prop_ in self.properties:
|
|
333
|
+
if isinstance(prop_.value_type, ViewEntity) and prop_.value_type not in views:
|
|
334
|
+
self.issue_list.append(
|
|
335
|
+
UndefinedViewWarning(
|
|
336
|
+
str(prop_.view),
|
|
337
|
+
str(prop_.value_type),
|
|
338
|
+
prop_.property_,
|
|
339
|
+
)
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
def _validate_reverse_connections(self) -> None:
|
|
343
|
+
# do not check for reverse connections in Cognite models
|
|
344
|
+
if self.metadata.as_data_model_id() in COGNITE_MODELS:
|
|
345
|
+
return None
|
|
346
|
+
|
|
347
|
+
properties_by_ids = {f"{prop_.view!s}.{prop_.property_}": prop_ for prop_ in self.properties}
|
|
348
|
+
reversed_by_ids = {
|
|
349
|
+
id_: prop_
|
|
350
|
+
for id_, prop_ in properties_by_ids.items()
|
|
351
|
+
if prop_.connection and isinstance(prop_.connection, ReverseConnectionEntity)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
for id_, prop_ in reversed_by_ids.items():
|
|
355
|
+
source_id = f"{prop_.value_type!s}." f"{cast(ReverseConnectionEntity, prop_.connection).property_}"
|
|
356
|
+
if source_id not in properties_by_ids:
|
|
357
|
+
self.issue_list.append(
|
|
358
|
+
ReversedConnectionNotFeasibleError(
|
|
359
|
+
id_,
|
|
360
|
+
"reversed connection",
|
|
361
|
+
prop_.property_,
|
|
362
|
+
str(prop_.view),
|
|
363
|
+
str(prop_.value_type),
|
|
364
|
+
cast(ReverseConnectionEntity, prop_.connection).property_,
|
|
365
|
+
)
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
elif source_id in properties_by_ids and properties_by_ids[source_id].value_type != prop_.view:
|
|
369
|
+
self.issue_list.append(
|
|
370
|
+
ReversedConnectionNotFeasibleError(
|
|
371
|
+
id_,
|
|
372
|
+
"view property",
|
|
373
|
+
prop_.property_,
|
|
374
|
+
str(prop_.view),
|
|
375
|
+
str(prop_.value_type),
|
|
376
|
+
cast(ReverseConnectionEntity, prop_.connection).property_,
|
|
377
|
+
)
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
else:
|
|
381
|
+
continue
|
|
382
|
+
|
|
321
383
|
@staticmethod
|
|
322
384
|
def _changed_attributes_and_properties(
|
|
323
385
|
new_dumped: dict[str, Any], existing_dumped: dict[str, Any]
|
|
@@ -4,7 +4,9 @@ from dataclasses import dataclass, field
|
|
|
4
4
|
from typing import ClassVar
|
|
5
5
|
|
|
6
6
|
from pydantic import Field, field_serializer, field_validator
|
|
7
|
+
from rdflib import URIRef
|
|
7
8
|
|
|
9
|
+
from cognite.neat._constants import DEFAULT_NAMESPACE
|
|
8
10
|
from cognite.neat._rules.models.data_types import DataType
|
|
9
11
|
from cognite.neat._rules.models.entities import ClassEntity, ClassEntityList
|
|
10
12
|
|
|
@@ -76,6 +78,10 @@ class DomainRules(BaseRules):
|
|
|
76
78
|
last: "DomainRules | None" = Field(None, alias="Last")
|
|
77
79
|
reference: "DomainRules | None" = Field(None, alias="Reference")
|
|
78
80
|
|
|
81
|
+
@property
|
|
82
|
+
def id_(self) -> URIRef:
|
|
83
|
+
return DEFAULT_NAMESPACE["data-model/verified/domain"]
|
|
84
|
+
|
|
79
85
|
|
|
80
86
|
@dataclass
|
|
81
87
|
class DomainInputMetadata(InputComponent[DomainMetadata]):
|
|
@@ -124,3 +130,7 @@ class DomainInputRules(InputRules[DomainRules]):
|
|
|
124
130
|
@classmethod
|
|
125
131
|
def _get_verified_cls(cls) -> type[DomainRules]:
|
|
126
132
|
return DomainRules
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def id_(self) -> URIRef:
|
|
136
|
+
return DEFAULT_NAMESPACE["data-model/unverified/domain"]
|
|
@@ -63,11 +63,9 @@ def load_connection(
|
|
|
63
63
|
default_space: str,
|
|
64
64
|
default_version: str,
|
|
65
65
|
) -> Literal["direct"] | ReverseConnectionEntity | EdgeEntity | None:
|
|
66
|
-
if (
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
or (isinstance(raw, str) and raw == "direct")
|
|
70
|
-
):
|
|
66
|
+
if isinstance(raw, str) and raw.lower() == "direct":
|
|
67
|
+
return "direct" # type: ignore[return-value]
|
|
68
|
+
elif isinstance(raw, EdgeEntity | ReverseConnectionEntity) or raw is None:
|
|
71
69
|
return raw # type: ignore[return-value]
|
|
72
70
|
elif isinstance(raw, str) and raw.startswith("edge"):
|
|
73
71
|
return EdgeEntity.load(raw, space=default_space, version=default_version) # type: ignore[return-value]
|
|
@@ -7,9 +7,9 @@ from typing import TYPE_CHECKING, Any, ClassVar
|
|
|
7
7
|
import pandas as pd
|
|
8
8
|
from pydantic import Field, field_serializer, field_validator, model_validator
|
|
9
9
|
from pydantic_core.core_schema import SerializationInfo
|
|
10
|
-
from rdflib import Namespace
|
|
10
|
+
from rdflib import Namespace, URIRef
|
|
11
11
|
|
|
12
|
-
from cognite.neat._constants import get_default_prefixes
|
|
12
|
+
from cognite.neat._constants import DEFAULT_NAMESPACE, get_default_prefixes
|
|
13
13
|
from cognite.neat._issues.errors import NeatValueError, PropertyDefinitionError
|
|
14
14
|
from cognite.neat._rules._constants import EntityTypes
|
|
15
15
|
from cognite.neat._rules.models._base_rules import (
|
|
@@ -394,3 +394,7 @@ class InformationRules(BaseRules):
|
|
|
394
394
|
}
|
|
395
395
|
|
|
396
396
|
return pd.DataFrame([summary]).T.rename(columns={0: ""})._repr_html_() # type: ignore
|
|
397
|
+
|
|
398
|
+
@property
|
|
399
|
+
def id_(self) -> URIRef:
|
|
400
|
+
return DEFAULT_NAMESPACE[f"data-model/verified/info/{self.metadata.prefix}/{self.metadata.version}"]
|
|
@@ -3,8 +3,9 @@ from datetime import datetime
|
|
|
3
3
|
from typing import Any, Literal
|
|
4
4
|
|
|
5
5
|
import pandas as pd
|
|
6
|
-
from rdflib import Namespace
|
|
6
|
+
from rdflib import Namespace, URIRef
|
|
7
7
|
|
|
8
|
+
from cognite.neat._constants import DEFAULT_NAMESPACE
|
|
8
9
|
from cognite.neat._rules.models._base_input import InputComponent, InputRules
|
|
9
10
|
from cognite.neat._rules.models.data_types import DataType
|
|
10
11
|
from cognite.neat._rules.models.entities import (
|
|
@@ -158,3 +159,7 @@ class InformationInputRules(InputRules[InformationRules]):
|
|
|
158
159
|
}
|
|
159
160
|
|
|
160
161
|
return pd.DataFrame([summary]).T.rename(columns={0: ""})._repr_html_() # type: ignore
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def id_(self) -> URIRef:
|
|
165
|
+
return DEFAULT_NAMESPACE[f"data-model/unverified/info/{self.metadata.prefix}/{self.metadata.version}"]
|
|
@@ -2,6 +2,7 @@ from abc import ABC, abstractmethod
|
|
|
2
2
|
from collections.abc import MutableSequence
|
|
3
3
|
from typing import Generic, TypeVar
|
|
4
4
|
|
|
5
|
+
from cognite.neat._constants import DEFAULT_NAMESPACE
|
|
5
6
|
from cognite.neat._issues import IssueList, NeatError
|
|
6
7
|
from cognite.neat._issues.errors import NeatTypeError, NeatValueError
|
|
7
8
|
from cognite.neat._rules._shared import (
|
|
@@ -12,6 +13,7 @@ from cognite.neat._rules._shared import (
|
|
|
12
13
|
Rules,
|
|
13
14
|
VerifiedRules,
|
|
14
15
|
)
|
|
16
|
+
from cognite.neat._store._provenance import Agent as ProvenanceAgent
|
|
15
17
|
|
|
16
18
|
T_RulesIn = TypeVar("T_RulesIn", bound=Rules)
|
|
17
19
|
T_RulesOut = TypeVar("T_RulesOut", bound=Rules)
|
|
@@ -54,6 +56,11 @@ class RulesTransformer(ABC, Generic[T_RulesIn, T_RulesOut]):
|
|
|
54
56
|
else:
|
|
55
57
|
raise NeatTypeError(f"Unsupported type: {type(rules)}")
|
|
56
58
|
|
|
59
|
+
@property
|
|
60
|
+
def agent(self) -> ProvenanceAgent:
|
|
61
|
+
"""Provenance agent for the importer."""
|
|
62
|
+
return ProvenanceAgent(id_=DEFAULT_NAMESPACE[f"agent/{type(self).__name__}"])
|
|
63
|
+
|
|
57
64
|
|
|
58
65
|
class RulesPipeline(list, MutableSequence[RulesTransformer], Generic[T_RulesIn, T_RulesOut]):
|
|
59
66
|
def transform(self, rules: T_RulesIn | OutRules[T_RulesIn]) -> OutRules[T_RulesOut]:
|
|
@@ -10,7 +10,11 @@ from cognite.client.data_classes import data_modeling as dms
|
|
|
10
10
|
from cognite.client.data_classes.data_modeling import DataModelId, DataModelIdentifier, ViewId
|
|
11
11
|
from rdflib import Namespace
|
|
12
12
|
|
|
13
|
-
from cognite.neat._constants import
|
|
13
|
+
from cognite.neat._constants import (
|
|
14
|
+
COGNITE_MODELS,
|
|
15
|
+
DMS_CONTAINER_PROPERTY_SIZE_LIMIT,
|
|
16
|
+
)
|
|
17
|
+
from cognite.neat._issues._base import IssueList
|
|
14
18
|
from cognite.neat._issues.errors import NeatValueError
|
|
15
19
|
from cognite.neat._issues.warnings._models import (
|
|
16
20
|
EnterpriseModelNotBuildOnTopOfCDMWarning,
|
|
@@ -18,7 +22,13 @@ from cognite.neat._issues.warnings._models import (
|
|
|
18
22
|
)
|
|
19
23
|
from cognite.neat._issues.warnings.user_modeling import ParentInDifferentSpaceWarning
|
|
20
24
|
from cognite.neat._rules._constants import EntityTypes
|
|
21
|
-
from cognite.neat._rules._shared import
|
|
25
|
+
from cognite.neat._rules._shared import (
|
|
26
|
+
InputRules,
|
|
27
|
+
JustRules,
|
|
28
|
+
OutRules,
|
|
29
|
+
ReadRules,
|
|
30
|
+
VerifiedRules,
|
|
31
|
+
)
|
|
22
32
|
from cognite.neat._rules.analysis import DMSAnalysis
|
|
23
33
|
from cognite.neat._rules.models import (
|
|
24
34
|
AssetRules,
|
|
@@ -83,13 +93,16 @@ class ToCompliantEntities(RulesTransformer[InformationInputRules, InformationInp
|
|
|
83
93
|
|
|
84
94
|
def transform(
|
|
85
95
|
self, rules: InformationInputRules | OutRules[InformationInputRules]
|
|
86
|
-
) ->
|
|
87
|
-
return
|
|
96
|
+
) -> ReadRules[InformationInputRules]:
|
|
97
|
+
return ReadRules(self._transform(self._to_rules(rules)), IssueList(), {})
|
|
88
98
|
|
|
89
99
|
def _transform(self, rules: InformationInputRules) -> InformationInputRules:
|
|
90
100
|
rules.metadata.prefix = self._fix_entity(rules.metadata.prefix)
|
|
91
101
|
rules.classes = self._fix_classes(rules.classes)
|
|
92
102
|
rules.properties = self._fix_properties(rules.properties)
|
|
103
|
+
|
|
104
|
+
rules.metadata.version += "_dms_compliant"
|
|
105
|
+
|
|
93
106
|
return rules
|
|
94
107
|
|
|
95
108
|
@classmethod
|
|
@@ -258,6 +271,7 @@ class ToExtension(RulesTransformer[DMSRules, DMSRules]):
|
|
|
258
271
|
type_: Literal["enterprise", "solution"] = "enterprise",
|
|
259
272
|
mode: Literal["read", "write"] = "read",
|
|
260
273
|
dummy_property: str = "GUID",
|
|
274
|
+
move_connections: bool = False,
|
|
261
275
|
):
|
|
262
276
|
self.new_model_id = DataModelId.load(new_model_id)
|
|
263
277
|
if not self.new_model_id.version:
|
|
@@ -267,6 +281,7 @@ class ToExtension(RulesTransformer[DMSRules, DMSRules]):
|
|
|
267
281
|
self.mode = mode
|
|
268
282
|
self.type_ = type_
|
|
269
283
|
self.dummy_property = dummy_property
|
|
284
|
+
self.move_connections = move_connections
|
|
270
285
|
|
|
271
286
|
def transform(self, rules: DMSRules | OutRules[DMSRules]) -> JustRules[DMSRules]:
|
|
272
287
|
# Copy to ensure immutability
|
|
@@ -393,10 +408,18 @@ class ToExtension(RulesTransformer[DMSRules, DMSRules]):
|
|
|
393
408
|
# extending reference views with new ones
|
|
394
409
|
enterprise_model.views.extend(enterprise_views)
|
|
395
410
|
|
|
411
|
+
# Move connections from reference model to enterprise model
|
|
412
|
+
if self.move_connections:
|
|
413
|
+
enterprise_connections = self._move_connections(enterprise_model)
|
|
414
|
+
else:
|
|
415
|
+
enterprise_connections = SheetList[DMSProperty]()
|
|
416
|
+
|
|
396
417
|
# while overwriting containers and properties with new ones
|
|
397
418
|
enterprise_model.containers = enterprise_containers
|
|
398
419
|
enterprise_model.properties = enterprise_properties
|
|
399
420
|
|
|
421
|
+
enterprise_properties.extend(enterprise_connections)
|
|
422
|
+
|
|
400
423
|
return JustRules(enterprise_model)
|
|
401
424
|
|
|
402
425
|
def _remove_cognite_affix(self, entity: _T_Entity) -> _T_Entity:
|
|
@@ -455,6 +478,35 @@ class ToExtension(RulesTransformer[DMSRules, DMSRules]):
|
|
|
455
478
|
|
|
456
479
|
return new_views, new_containers, new_properties
|
|
457
480
|
|
|
481
|
+
def _move_connections(self, rules: DMSRules) -> SheetList[DMSProperty]:
|
|
482
|
+
implements: dict[ViewEntity, list[ViewEntity]] = defaultdict(list)
|
|
483
|
+
new_properties = SheetList[DMSProperty]()
|
|
484
|
+
|
|
485
|
+
for view in rules.views:
|
|
486
|
+
if view.view.space == rules.metadata.space and view.implements:
|
|
487
|
+
for implemented_view in view.implements:
|
|
488
|
+
implements.setdefault(implemented_view, []).append(view.view)
|
|
489
|
+
|
|
490
|
+
# currently only supporting single implementation of reference view in enterprise view
|
|
491
|
+
# connections that do not have properties
|
|
492
|
+
if all(len(v) == 1 for v in implements.values()):
|
|
493
|
+
for prop_ in rules.properties:
|
|
494
|
+
if (
|
|
495
|
+
prop_.view.space != rules.metadata.space
|
|
496
|
+
and prop_.connection
|
|
497
|
+
and isinstance(prop_.value_type, ViewEntity)
|
|
498
|
+
and implements.get(prop_.view)
|
|
499
|
+
and implements.get(prop_.value_type)
|
|
500
|
+
):
|
|
501
|
+
if isinstance(prop_.connection, EdgeEntity) and prop_.connection.properties:
|
|
502
|
+
continue
|
|
503
|
+
new_property = prop_.model_copy(deep=True)
|
|
504
|
+
new_property.view = implements[prop_.view][0]
|
|
505
|
+
new_property.value_type = implements[prop_.value_type][0]
|
|
506
|
+
new_properties.append(new_property)
|
|
507
|
+
|
|
508
|
+
return new_properties
|
|
509
|
+
|
|
458
510
|
|
|
459
511
|
class ReduceCogniteModel(RulesTransformer[DMSRules, DMSRules]):
|
|
460
512
|
_ASSET_VIEW = ViewId("cdf_cdm", "CogniteAsset", "v1")
|
cognite/neat/_session/_base.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from datetime import datetime, timezone
|
|
1
2
|
from typing import Literal, cast
|
|
2
3
|
|
|
3
4
|
from cognite.client import CogniteClient
|
|
@@ -14,6 +15,10 @@ from cognite.neat._rules.models.entities._single_value import UnknownEntity
|
|
|
14
15
|
from cognite.neat._rules.models.information._rules import InformationRules
|
|
15
16
|
from cognite.neat._rules.models.information._rules_input import InformationInputRules
|
|
16
17
|
from cognite.neat._rules.transformers import ConvertToRules, VerifyAnyRules
|
|
18
|
+
from cognite.neat._store._provenance import (
|
|
19
|
+
INSTANCES_ENTITY,
|
|
20
|
+
Change,
|
|
21
|
+
)
|
|
17
22
|
|
|
18
23
|
from ._inspect import InspectAPI
|
|
19
24
|
from ._prepare import PrepareAPI
|
|
@@ -22,7 +27,8 @@ from ._set import SetAPI
|
|
|
22
27
|
from ._show import ShowAPI
|
|
23
28
|
from ._state import SessionState
|
|
24
29
|
from ._to import ToAPI
|
|
25
|
-
from .
|
|
30
|
+
from .engine import load_neat_engine
|
|
31
|
+
from .exceptions import NeatSessionError, intercept_session_exceptions
|
|
26
32
|
|
|
27
33
|
|
|
28
34
|
@intercept_session_exceptions
|
|
@@ -32,6 +38,7 @@ class NeatSession:
|
|
|
32
38
|
client: CogniteClient | None = None,
|
|
33
39
|
storage: Literal["memory", "oxigraph"] = "memory",
|
|
34
40
|
verbose: bool = True,
|
|
41
|
+
load_engine: Literal["newest", "cache", "skip"] = "cache",
|
|
35
42
|
) -> None:
|
|
36
43
|
self._client = client
|
|
37
44
|
self._verbose = verbose
|
|
@@ -42,26 +49,69 @@ class NeatSession:
|
|
|
42
49
|
self.show = ShowAPI(self._state)
|
|
43
50
|
self.set = SetAPI(self._state, verbose)
|
|
44
51
|
self.inspect = InspectAPI(self._state)
|
|
52
|
+
if load_engine != "skip" and (engine_version := load_neat_engine(client, load_engine)):
|
|
53
|
+
print(f"Neat Engine {engine_version} loaded.")
|
|
45
54
|
|
|
46
55
|
@property
|
|
47
56
|
def version(self) -> str:
|
|
48
57
|
return _version.__version__
|
|
49
58
|
|
|
50
59
|
def verify(self) -> IssueList:
|
|
51
|
-
|
|
60
|
+
source_id, last_unverified_rule = self._state.data_model.last_unverified_rule
|
|
61
|
+
transformer = VerifyAnyRules("continue")
|
|
62
|
+
start = datetime.now(timezone.utc)
|
|
63
|
+
output = transformer.try_transform(last_unverified_rule)
|
|
64
|
+
end = datetime.now(timezone.utc)
|
|
65
|
+
|
|
52
66
|
if output.rules:
|
|
53
|
-
|
|
67
|
+
change = Change.from_rules_activity(
|
|
68
|
+
output.rules,
|
|
69
|
+
transformer.agent,
|
|
70
|
+
start,
|
|
71
|
+
end,
|
|
72
|
+
f"Verified data model {source_id} as {output.rules.id_}",
|
|
73
|
+
self._state.data_model.provenance.source_entity(source_id)
|
|
74
|
+
or self._state.data_model.provenance.target_entity(source_id),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
self._state.data_model.write(output.rules, change)
|
|
78
|
+
|
|
54
79
|
if isinstance(output.rules, InformationRules):
|
|
55
|
-
self._state.store.add_rules(output.rules)
|
|
80
|
+
self._state.instances.store.add_rules(output.rules)
|
|
81
|
+
|
|
56
82
|
output.issues.action = "verify"
|
|
57
|
-
self._state.issue_lists.append(output.issues)
|
|
83
|
+
self._state.data_model.issue_lists.append(output.issues)
|
|
58
84
|
if output.issues:
|
|
59
85
|
print("You can inspect the issues with the .inspect.issues(...) method.")
|
|
60
86
|
return output.issues
|
|
61
87
|
|
|
62
|
-
def convert(self, target: Literal["dms"]) -> None:
|
|
63
|
-
|
|
64
|
-
|
|
88
|
+
def convert(self, target: Literal["dms", "information"]) -> None:
|
|
89
|
+
start = datetime.now(timezone.utc)
|
|
90
|
+
if target == "dms":
|
|
91
|
+
source_id, info_rules = self._state.data_model.last_verified_information_rules
|
|
92
|
+
converter = ConvertToRules(DMSRules)
|
|
93
|
+
converted_rules = converter.transform(info_rules).rules
|
|
94
|
+
elif target == "information":
|
|
95
|
+
source_id, dms_rules = self._state.data_model.last_verified_dms_rules
|
|
96
|
+
converter = ConvertToRules(InformationRules)
|
|
97
|
+
converted_rules = converter.transform(dms_rules).rules
|
|
98
|
+
else:
|
|
99
|
+
raise NeatSessionError(f"Target {target} not supported.")
|
|
100
|
+
end = datetime.now(timezone.utc)
|
|
101
|
+
|
|
102
|
+
# Provenance
|
|
103
|
+
change = Change.from_rules_activity(
|
|
104
|
+
converted_rules,
|
|
105
|
+
converter.agent,
|
|
106
|
+
start,
|
|
107
|
+
end,
|
|
108
|
+
f"Converted data model {source_id} to {converted_rules.id_}",
|
|
109
|
+
self._state.data_model.provenance.source_entity(source_id)
|
|
110
|
+
or self._state.data_model.provenance.target_entity(source_id),
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
self._state.data_model.write(converted_rules, change)
|
|
114
|
+
|
|
65
115
|
if self._verbose:
|
|
66
116
|
print(f"Rules converted to {target}")
|
|
67
117
|
|
|
@@ -73,6 +123,7 @@ class NeatSession:
|
|
|
73
123
|
"v1",
|
|
74
124
|
),
|
|
75
125
|
non_existing_node_type: UnknownEntity | AnyURI = DEFAULT_NON_EXISTING_NODE_TYPE,
|
|
126
|
+
max_number_of_instance: int = 100,
|
|
76
127
|
) -> IssueList:
|
|
77
128
|
"""Data model inference from instances.
|
|
78
129
|
|
|
@@ -83,35 +134,55 @@ class NeatSession:
|
|
|
83
134
|
|
|
84
135
|
model_id = dm.DataModelId.load(model_id)
|
|
85
136
|
|
|
86
|
-
|
|
87
|
-
|
|
137
|
+
start = datetime.now(timezone.utc)
|
|
138
|
+
importer = importers.InferenceImporter.from_graph_store(
|
|
139
|
+
store=self._state.instances.store,
|
|
88
140
|
non_existing_node_type=non_existing_node_type,
|
|
89
|
-
|
|
141
|
+
max_number_of_instance=max_number_of_instance,
|
|
142
|
+
)
|
|
143
|
+
inferred_rules: ReadRules = importer.to_rules()
|
|
144
|
+
end = datetime.now(timezone.utc)
|
|
90
145
|
|
|
91
146
|
if model_id.space:
|
|
92
|
-
cast(InformationInputRules,
|
|
147
|
+
cast(InformationInputRules, inferred_rules.rules).metadata.prefix = model_id.space
|
|
93
148
|
if model_id.external_id:
|
|
94
|
-
cast(InformationInputRules,
|
|
149
|
+
cast(InformationInputRules, inferred_rules.rules).metadata.name = model_id.external_id
|
|
95
150
|
|
|
96
151
|
if model_id.version:
|
|
97
|
-
cast(InformationInputRules,
|
|
152
|
+
cast(InformationInputRules, inferred_rules.rules).metadata.version = model_id.version
|
|
153
|
+
|
|
154
|
+
# Provenance
|
|
155
|
+
change = Change.from_rules_activity(
|
|
156
|
+
inferred_rules,
|
|
157
|
+
importer.agent,
|
|
158
|
+
start,
|
|
159
|
+
end,
|
|
160
|
+
"Inferred data model",
|
|
161
|
+
INSTANCES_ENTITY,
|
|
162
|
+
)
|
|
98
163
|
|
|
99
|
-
self.
|
|
100
|
-
return
|
|
164
|
+
self._state.data_model.write(inferred_rules, change)
|
|
165
|
+
return inferred_rules.issues
|
|
101
166
|
|
|
102
167
|
def _repr_html_(self) -> str:
|
|
103
168
|
state = self._state
|
|
104
|
-
if
|
|
169
|
+
if (
|
|
170
|
+
not state.instances.has_store
|
|
171
|
+
and not state.data_model.has_unverified_rules
|
|
172
|
+
and not state.data_model.has_verified_rules
|
|
173
|
+
):
|
|
105
174
|
return "<strong>Empty session</strong>. Get started by reading something with the <em>.read</em> attribute."
|
|
106
175
|
|
|
107
176
|
output = []
|
|
108
|
-
if state.input_rules and not state.verified_rules:
|
|
109
|
-
output.append(f"<H2>Unverified Data Model</H2><br />{state.input_rule.rules._repr_html_()}") # type: ignore
|
|
110
177
|
|
|
111
|
-
if state.
|
|
112
|
-
|
|
178
|
+
if state.data_model.has_unverified_rules and not state.data_model.has_verified_rules:
|
|
179
|
+
rules: ReadRules = state.data_model.last_unverified_rule[1]
|
|
180
|
+
output.append(f"<H2>Unverified Data Model</H2><br />{rules.rules._repr_html_()}") # type: ignore
|
|
181
|
+
|
|
182
|
+
if state.data_model.has_verified_rules:
|
|
183
|
+
output.append(f"<H2>Verified Data Model</H2><br />{state.data_model.last_verified_rule[1]._repr_html_()}") # type: ignore
|
|
113
184
|
|
|
114
|
-
if state.has_store:
|
|
115
|
-
output.append(f"<H2>Instances</H2> {state.store._repr_html_()}")
|
|
185
|
+
if state.instances.has_store:
|
|
186
|
+
output.append(f"<H2>Instances</H2> {state.instances.store._repr_html_()}")
|
|
116
187
|
|
|
117
188
|
return "<br />".join(output)
|