cognite-neat 0.96.6__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 +3 -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/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/_rules.py +8 -1
- 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.6.dist-info → cognite_neat-0.97.0.dist-info}/METADATA +5 -3
- {cognite_neat-0.96.6.dist-info → cognite_neat-0.97.0.dist-info}/RECORD +67 -62
- cognite/neat/_graph/models.py +0 -7
- {cognite_neat-0.96.6.dist-info → cognite_neat-0.97.0.dist-info}/LICENSE +0 -0
- {cognite_neat-0.96.6.dist-info → cognite_neat-0.97.0.dist-info}/WHEEL +0 -0
- {cognite_neat-0.96.6.dist-info → cognite_neat-0.97.0.dist-info}/entry_points.txt +0 -0
|
@@ -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)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import difflib
|
|
2
|
+
from collections.abc import Callable
|
|
2
3
|
from typing import Literal, overload
|
|
3
4
|
|
|
4
5
|
import pandas as pd
|
|
@@ -21,7 +22,7 @@ class InspectAPI:
|
|
|
21
22
|
@property
|
|
22
23
|
def properties(self) -> pd.DataFrame:
|
|
23
24
|
"""Returns the properties of the current data model."""
|
|
24
|
-
return self._state.last_verified_rule.properties.to_pandas()
|
|
25
|
+
return self._state.data_model.last_verified_rule[1].properties.to_pandas()
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
@intercept_session_exceptions
|
|
@@ -51,7 +52,7 @@ class InspectIssues:
|
|
|
51
52
|
return_dataframe: bool = (False if IN_NOTEBOOK else True), # type: ignore[assignment]
|
|
52
53
|
) -> pd.DataFrame | None:
|
|
53
54
|
"""Returns the issues of the current data model."""
|
|
54
|
-
issues = self._state.last_issues
|
|
55
|
+
issues = self._state.data_model.last_issues
|
|
55
56
|
if not issues:
|
|
56
57
|
self._print("No issues found.")
|
|
57
58
|
|
|
@@ -94,7 +95,14 @@ class InspectIssues:
|
|
|
94
95
|
@intercept_session_exceptions
|
|
95
96
|
class InspectOutcome:
|
|
96
97
|
def __init__(self, state: SessionState) -> None:
|
|
97
|
-
self.
|
|
98
|
+
self.data_model = InspectUploadOutcome(lambda: state.data_model.last_outcome)
|
|
99
|
+
self.instances = InspectUploadOutcome(lambda: state.instances.last_outcome)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@intercept_session_exceptions
|
|
103
|
+
class InspectUploadOutcome:
|
|
104
|
+
def __init__(self, get_last_outcome: Callable[[], UploadResultList]) -> None:
|
|
105
|
+
self._get_last_outcome = get_last_outcome
|
|
98
106
|
|
|
99
107
|
@staticmethod
|
|
100
108
|
def _as_set(value: str | list[str] | None) -> set[str] | None:
|
|
@@ -130,7 +138,7 @@ class InspectOutcome:
|
|
|
130
138
|
return_dataframe: bool = (False if IN_NOTEBOOK else True), # type: ignore[assignment]
|
|
131
139
|
) -> pd.DataFrame | None:
|
|
132
140
|
"""Returns the outcome of the last upload."""
|
|
133
|
-
outcome = self.
|
|
141
|
+
outcome = self._get_last_outcome()
|
|
134
142
|
name_set = self._as_set(name)
|
|
135
143
|
|
|
136
144
|
def outcome_filter(item: UploadResultCore) -> bool:
|