cognite-neat 0.108.0__py3-none-any.whl → 0.109.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of cognite-neat might be problematic. Click here for more details.
- cognite/neat/_constants.py +1 -1
- cognite/neat/_graph/extractors/_classic_cdf/_classic.py +8 -4
- cognite/neat/_graph/queries/_base.py +4 -0
- cognite/neat/_graph/transformers/__init__.py +3 -3
- cognite/neat/_graph/transformers/_base.py +4 -4
- cognite/neat/_graph/transformers/_classic_cdf.py +13 -13
- cognite/neat/_graph/transformers/_prune_graph.py +3 -3
- cognite/neat/_graph/transformers/_rdfpath.py +3 -4
- cognite/neat/_graph/transformers/_value_type.py +23 -16
- cognite/neat/_issues/errors/__init__.py +2 -0
- cognite/neat/_issues/errors/_external.py +8 -0
- cognite/neat/_issues/warnings/_resources.py +1 -1
- cognite/neat/_rules/exporters/_rules2yaml.py +1 -1
- cognite/neat/_rules/importers/_rdf/_inference2rules.py +179 -118
- cognite/neat/_rules/models/_base_rules.py +9 -8
- cognite/neat/_rules/models/dms/_exporter.py +5 -4
- cognite/neat/_rules/transformers/__init__.py +4 -3
- cognite/neat/_rules/transformers/_base.py +6 -1
- cognite/neat/_rules/transformers/_converters.py +436 -361
- cognite/neat/_rules/transformers/_mapping.py +4 -4
- cognite/neat/_session/_base.py +71 -69
- cognite/neat/_session/_create.py +133 -0
- cognite/neat/_session/_drop.py +55 -1
- cognite/neat/_session/_fix.py +28 -0
- cognite/neat/_session/_inspect.py +20 -6
- cognite/neat/_session/_mapping.py +8 -8
- cognite/neat/_session/_prepare.py +3 -247
- cognite/neat/_session/_read.py +78 -4
- cognite/neat/_session/_set.py +34 -12
- cognite/neat/_session/_show.py +14 -41
- cognite/neat/_session/_state.py +48 -51
- cognite/neat/_session/_to.py +7 -3
- cognite/neat/_session/exceptions.py +7 -1
- cognite/neat/_store/_graph_store.py +14 -13
- cognite/neat/_store/_provenance.py +36 -20
- cognite/neat/_store/_rules_store.py +172 -293
- cognite/neat/_store/exceptions.py +40 -4
- cognite/neat/_utils/auth.py +4 -2
- cognite/neat/_version.py +1 -1
- {cognite_neat-0.108.0.dist-info → cognite_neat-0.109.1.dist-info}/METADATA +1 -1
- {cognite_neat-0.108.0.dist-info → cognite_neat-0.109.1.dist-info}/RECORD +44 -42
- {cognite_neat-0.108.0.dist-info → cognite_neat-0.109.1.dist-info}/LICENSE +0 -0
- {cognite_neat-0.108.0.dist-info → cognite_neat-0.109.1.dist-info}/WHEEL +0 -0
- {cognite_neat-0.108.0.dist-info → cognite_neat-0.109.1.dist-info}/entry_points.txt +0 -0
cognite/neat/_session/_state.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from
|
|
1
|
+
from pathlib import Path
|
|
2
2
|
from typing import Literal, cast
|
|
3
3
|
|
|
4
4
|
from cognite.neat._client import NeatClient
|
|
@@ -6,58 +6,46 @@ from cognite.neat._graph.extractors import KnowledgeGraphExtractor
|
|
|
6
6
|
from cognite.neat._issues import IssueList
|
|
7
7
|
from cognite.neat._rules.importers import BaseImporter, InferenceImporter
|
|
8
8
|
from cognite.neat._rules.models import DMSRules, InformationRules
|
|
9
|
-
from cognite.neat._rules.transformers import
|
|
9
|
+
from cognite.neat._rules.transformers import (
|
|
10
|
+
VerifiedRulesTransformer,
|
|
11
|
+
)
|
|
10
12
|
from cognite.neat._store import NeatGraphStore, NeatRulesStore
|
|
11
|
-
from cognite.neat._store._rules_store import ModelEntity
|
|
12
|
-
from cognite.neat._utils.rdf_ import uri_display_name
|
|
13
|
-
from cognite.neat._utils.text import humanize_collection
|
|
14
13
|
from cognite.neat._utils.upload import UploadResultList
|
|
15
14
|
|
|
16
|
-
from .exceptions import NeatSessionError
|
|
15
|
+
from .exceptions import NeatSessionError, _session_method_wrapper
|
|
17
16
|
|
|
18
17
|
|
|
19
18
|
class SessionState:
|
|
20
|
-
def __init__(
|
|
21
|
-
self
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
store_type: Literal["memory", "oxigraph"],
|
|
22
|
+
storage_path: Path | None = None,
|
|
23
|
+
client: NeatClient | None = None,
|
|
24
|
+
) -> None:
|
|
25
|
+
self.instances = InstancesState(store_type, storage_path=storage_path)
|
|
22
26
|
self.rule_store = NeatRulesStore()
|
|
23
27
|
self.last_reference: DMSRules | InformationRules | None = None
|
|
24
28
|
self.client = client
|
|
25
29
|
self.quoted_source_identifiers = False
|
|
26
30
|
|
|
27
|
-
def rule_transform(self, *transformer:
|
|
31
|
+
def rule_transform(self, *transformer: VerifiedRulesTransformer) -> IssueList:
|
|
28
32
|
if not transformer:
|
|
29
33
|
raise NeatSessionError("No transformers provided.")
|
|
30
|
-
first_transformer = transformer[0]
|
|
31
34
|
|
|
32
|
-
|
|
33
|
-
pruned = self.rule_store.prune_until_compatible(first_transformer)
|
|
34
|
-
if pruned:
|
|
35
|
-
type_hint = first_transformer.transform_type_hint()
|
|
36
|
-
action = uri_display_name(first_transformer.agent.id_)
|
|
37
|
-
location = cast(ModelEntity, self.rule_store.provenance[-1].target_entity).display_name
|
|
38
|
-
expected = humanize_collection([hint.display_type_name() for hint in type_hint]) # type: ignore[attr-defined]
|
|
39
|
-
step_str = "step" if len(pruned) == 1 else "steps"
|
|
40
|
-
print(
|
|
41
|
-
f"The {action} actions expects a {expected}. "
|
|
42
|
-
f"Moving back {len(pruned)} {step_str} to the last {location}."
|
|
43
|
-
)
|
|
44
|
-
if (
|
|
45
|
-
any(isinstance(t, ToExtensionModel) for t in transformer)
|
|
46
|
-
and isinstance(self.rule_store.provenance[-1].target_entity, ModelEntity)
|
|
47
|
-
and isinstance(self.rule_store.provenance[-1].target_entity.result, DMSRules | InformationRules)
|
|
48
|
-
):
|
|
49
|
-
self.last_reference = self.rule_store.provenance[-1].target_entity.result
|
|
50
|
-
|
|
51
|
-
start = cast(ModelEntity, self.rule_store.provenance[-1].target_entity).display_name
|
|
35
|
+
start = self.rule_store.provenance[-1].target_entity.display_name
|
|
52
36
|
issues = self.rule_store.transform(*transformer)
|
|
53
|
-
|
|
54
|
-
issues.action = f"{start} → {
|
|
37
|
+
last_entity = self.rule_store.provenance[-1].target_entity
|
|
38
|
+
issues.action = f"{start} → {last_entity.display_name}"
|
|
55
39
|
issues.hint = "Use the .inspect.issues() for more details."
|
|
40
|
+
self.instances.store.add_rules(last_entity.information)
|
|
56
41
|
return issues
|
|
57
42
|
|
|
58
43
|
def rule_import(self, importer: BaseImporter) -> IssueList:
|
|
59
|
-
issues = self.rule_store.
|
|
60
|
-
|
|
44
|
+
issues = self.rule_store.import_rules(importer, client=self.client)
|
|
45
|
+
if self.rule_store.empty:
|
|
46
|
+
result = "failed"
|
|
47
|
+
else:
|
|
48
|
+
result = self.rule_store.provenance[-1].target_entity.display_name
|
|
61
49
|
if isinstance(importer, InferenceImporter):
|
|
62
50
|
issues.action = f"Inferred {result}"
|
|
63
51
|
else:
|
|
@@ -74,30 +62,39 @@ class SessionState:
|
|
|
74
62
|
return issues
|
|
75
63
|
|
|
76
64
|
|
|
77
|
-
@dataclass
|
|
78
65
|
class InstancesState:
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
66
|
+
def __init__(
|
|
67
|
+
self,
|
|
68
|
+
store_type: Literal["memory", "oxigraph"],
|
|
69
|
+
storage_path: Path | None = None,
|
|
70
|
+
) -> None:
|
|
71
|
+
self.store_type = store_type
|
|
72
|
+
self.storage_path = storage_path
|
|
73
|
+
self.issue_lists = IssueList()
|
|
74
|
+
self.outcome = UploadResultList()
|
|
83
75
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
76
|
+
# Ensure that error handling is done in the constructor
|
|
77
|
+
self.store = _session_method_wrapper(self._create_store, "NeatSession")()
|
|
78
|
+
|
|
79
|
+
if self.storage_path:
|
|
80
|
+
print("Remember to close neat session .close() once you are done to avoid oxigraph lock.")
|
|
81
|
+
|
|
82
|
+
def _create_store(self) -> NeatGraphStore:
|
|
83
|
+
if self.store_type == "oxigraph":
|
|
84
|
+
if self.storage_path:
|
|
85
|
+
self.storage_path.mkdir(parents=True, exist_ok=True)
|
|
86
|
+
return NeatGraphStore.from_oxi_local_store(storage_dir=self.storage_path)
|
|
87
|
+
else:
|
|
88
|
+
return NeatGraphStore.from_memory_store()
|
|
92
89
|
|
|
93
90
|
@property
|
|
94
|
-
def
|
|
95
|
-
return self.
|
|
91
|
+
def empty(self) -> bool:
|
|
92
|
+
return self.store.empty
|
|
96
93
|
|
|
97
94
|
@property
|
|
98
95
|
def last_outcome(self) -> UploadResultList:
|
|
99
96
|
if not self.outcome:
|
|
100
97
|
raise NeatSessionError(
|
|
101
|
-
"No outcome available. Try using [bold].to.cdf.instances[/bold] to upload a data
|
|
98
|
+
"No outcome available. Try using [bold].to.cdf.instances[/bold] to upload a data instance."
|
|
102
99
|
)
|
|
103
|
-
return self.outcome[-1]
|
|
100
|
+
return cast(UploadResultList, self.outcome[-1])
|
cognite/neat/_session/_to.py
CHANGED
|
@@ -4,7 +4,7 @@ from collections.abc import Collection
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from typing import Any, Literal, overload
|
|
6
6
|
|
|
7
|
-
from cognite.client
|
|
7
|
+
from cognite.client import data_modeling as dm
|
|
8
8
|
|
|
9
9
|
from cognite.neat._constants import COGNITE_MODELS
|
|
10
10
|
from cognite.neat._graph import loaders
|
|
@@ -194,7 +194,10 @@ class CDFToAPI:
|
|
|
194
194
|
self._state = state
|
|
195
195
|
self._verbose = verbose
|
|
196
196
|
|
|
197
|
-
def instances(
|
|
197
|
+
def instances(
|
|
198
|
+
self,
|
|
199
|
+
space: str | None = None,
|
|
200
|
+
) -> UploadResultList:
|
|
198
201
|
"""Export the verified DMS instances to CDF.
|
|
199
202
|
|
|
200
203
|
Args:
|
|
@@ -213,7 +216,7 @@ class CDFToAPI:
|
|
|
213
216
|
raise NeatSessionError("Please provide a valid space name. {PATTERNS.space_compliance.pattern}")
|
|
214
217
|
|
|
215
218
|
if not client.data_modeling.spaces.retrieve(space):
|
|
216
|
-
client.data_modeling.spaces.apply(SpaceApply(space=space))
|
|
219
|
+
client.data_modeling.spaces.apply(dm.SpaceApply(space=space))
|
|
217
220
|
|
|
218
221
|
loader = loaders.DMSLoader.from_rules(
|
|
219
222
|
self._state.rule_store.last_verified_dms_rules,
|
|
@@ -224,6 +227,7 @@ class CDFToAPI:
|
|
|
224
227
|
# urllib.parse.unquote() on the load.
|
|
225
228
|
unquote_external_ids=self._state.quoted_source_identifiers,
|
|
226
229
|
)
|
|
230
|
+
|
|
227
231
|
result = loader.load_into_cdf(client)
|
|
228
232
|
self._state.instances.outcome.append(result)
|
|
229
233
|
print("You can inspect the details with the .inspect.outcome.instances(...) method.")
|
|
@@ -3,6 +3,7 @@ from collections.abc import Callable
|
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
5
|
from cognite.neat._issues.errors import CDFMissingClientError, NeatImportError
|
|
6
|
+
from cognite.neat._issues.errors._external import OxigraphStorageLockedError
|
|
6
7
|
from cognite.neat._issues.errors._general import NeatValueError
|
|
7
8
|
|
|
8
9
|
from ._collector import _COLLECTOR
|
|
@@ -34,7 +35,12 @@ def _session_method_wrapper(func: Callable, cls_name: str):
|
|
|
34
35
|
except NeatSessionError as e:
|
|
35
36
|
action = _get_action()
|
|
36
37
|
print(f"{_PREFIX} Cannot {action}: {e}")
|
|
37
|
-
except (
|
|
38
|
+
except (
|
|
39
|
+
CDFMissingClientError,
|
|
40
|
+
NeatImportError,
|
|
41
|
+
NeatValueError,
|
|
42
|
+
OxigraphStorageLockedError,
|
|
43
|
+
) as e:
|
|
38
44
|
print(f"{_PREFIX} {escape(e.as_message())}")
|
|
39
45
|
except ModuleNotFoundError as e:
|
|
40
46
|
if e.name == "neatengine":
|
|
@@ -17,6 +17,7 @@ from cognite.neat._graph.extractors import RdfFileExtractor, TripleExtractors
|
|
|
17
17
|
from cognite.neat._graph.queries import Queries
|
|
18
18
|
from cognite.neat._graph.transformers import Transformers
|
|
19
19
|
from cognite.neat._issues import IssueList, catch_issues
|
|
20
|
+
from cognite.neat._issues.errors import OxigraphStorageLockedError
|
|
20
21
|
from cognite.neat._rules.analysis import InformationAnalysis
|
|
21
22
|
from cognite.neat._rules.models import InformationRules
|
|
22
23
|
from cognite.neat._rules.models.entities import ClassEntity
|
|
@@ -24,7 +25,7 @@ from cognite.neat._shared import InstanceType, Triple
|
|
|
24
25
|
from cognite.neat._utils.auxiliary import local_import
|
|
25
26
|
from cognite.neat._utils.rdf_ import add_triples_in_batch, remove_namespace_from_uri
|
|
26
27
|
|
|
27
|
-
from ._provenance import Change, Provenance
|
|
28
|
+
from ._provenance import Change, Entity, Provenance
|
|
28
29
|
|
|
29
30
|
if sys.version_info < (3, 11):
|
|
30
31
|
from typing_extensions import Self
|
|
@@ -59,7 +60,7 @@ class NeatGraphStore:
|
|
|
59
60
|
|
|
60
61
|
_start = datetime.now(timezone.utc)
|
|
61
62
|
self.dataset = dataset
|
|
62
|
-
self.provenance = Provenance(
|
|
63
|
+
self.provenance = Provenance[Entity](
|
|
63
64
|
[
|
|
64
65
|
Change.record(
|
|
65
66
|
activity=f"{type(self).__name__}.__init__",
|
|
@@ -207,17 +208,12 @@ class NeatGraphStore:
|
|
|
207
208
|
import oxrdflib
|
|
208
209
|
import pyoxigraph
|
|
209
210
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if "lock" in str(e) and i < 3:
|
|
217
|
-
continue
|
|
218
|
-
raise e
|
|
219
|
-
else:
|
|
220
|
-
raise Exception("Error initializing Oxigraph store")
|
|
211
|
+
try:
|
|
212
|
+
oxi_store = pyoxigraph.Store(path=str(storage_dir) if storage_dir else None)
|
|
213
|
+
except OSError as e:
|
|
214
|
+
if "lock" in str(e):
|
|
215
|
+
raise OxigraphStorageLockedError(filepath=cast(Path, storage_dir)) from e
|
|
216
|
+
raise e
|
|
221
217
|
|
|
222
218
|
return cls(
|
|
223
219
|
dataset=Dataset(
|
|
@@ -685,3 +681,8 @@ class NeatGraphStore:
|
|
|
685
681
|
@property
|
|
686
682
|
def named_graphs(self) -> list[URIRef]:
|
|
687
683
|
return [cast(URIRef, context.identifier) for context in self.dataset.contexts()]
|
|
684
|
+
|
|
685
|
+
@property
|
|
686
|
+
def empty(self) -> bool:
|
|
687
|
+
"""Cheap way to check if the graph store is empty."""
|
|
688
|
+
return not self.queries.has_data()
|
|
@@ -20,7 +20,7 @@ import uuid
|
|
|
20
20
|
from collections.abc import Iterable, Sequence
|
|
21
21
|
from dataclasses import dataclass, field
|
|
22
22
|
from datetime import datetime
|
|
23
|
-
from typing import
|
|
23
|
+
from typing import Generic, TypeVar
|
|
24
24
|
|
|
25
25
|
from rdflib import PROV, RDF, Literal, URIRef
|
|
26
26
|
|
|
@@ -49,9 +49,31 @@ UNKNOWN_AGENT = Agent(acted_on_behalf_of="UNKNOWN", id_=DEFAULT_NAMESPACE["unkno
|
|
|
49
49
|
@dataclass(frozen=True)
|
|
50
50
|
class Entity:
|
|
51
51
|
was_attributed_to: Agent
|
|
52
|
-
issues: IssueList
|
|
53
|
-
was_generated_by:
|
|
54
|
-
id_: URIRef
|
|
52
|
+
issues: IssueList
|
|
53
|
+
was_generated_by: "Activity | None" = field(repr=False)
|
|
54
|
+
id_: URIRef
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def create_with_defaults(
|
|
58
|
+
cls,
|
|
59
|
+
was_attributed_to: Agent,
|
|
60
|
+
issues: IssueList | None = None,
|
|
61
|
+
was_generated_by: "Activity | None" = None,
|
|
62
|
+
id_: URIRef = DEFAULT_NAMESPACE["graph-store"],
|
|
63
|
+
) -> "Entity":
|
|
64
|
+
return cls(
|
|
65
|
+
was_attributed_to=was_attributed_to,
|
|
66
|
+
issues=issues or IssueList(),
|
|
67
|
+
was_generated_by=was_generated_by,
|
|
68
|
+
id_=id_,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def create_new_unknown_entity(cls) -> "Entity":
|
|
73
|
+
return cls.create_with_defaults(
|
|
74
|
+
was_attributed_to=UNKNOWN_AGENT,
|
|
75
|
+
id_=DEFAULT_NAMESPACE[f"unknown-entity/{uuid.uuid4()}"],
|
|
76
|
+
)
|
|
55
77
|
|
|
56
78
|
def as_triples(self) -> list[Triple]:
|
|
57
79
|
output: list[tuple[URIRef, URIRef, Literal | URIRef]] = [
|
|
@@ -70,16 +92,10 @@ class Entity:
|
|
|
70
92
|
|
|
71
93
|
return output
|
|
72
94
|
|
|
73
|
-
@classmethod
|
|
74
|
-
def new_unknown_entity(cls) -> "Entity":
|
|
75
|
-
return cls(
|
|
76
|
-
was_attributed_to=UNKNOWN_AGENT,
|
|
77
|
-
id_=DEFAULT_NAMESPACE[f"unknown-entity/{uuid.uuid4()}"],
|
|
78
|
-
)
|
|
79
|
-
|
|
80
95
|
|
|
81
|
-
|
|
82
|
-
|
|
96
|
+
T_Entity = TypeVar("T_Entity", bound=Entity)
|
|
97
|
+
INSTANCES_ENTITY = Entity.create_with_defaults(was_attributed_to=NEAT_AGENT, id_=CDF_NAMESPACE["instances"])
|
|
98
|
+
EMPTY_ENTITY = Entity.create_with_defaults(was_attributed_to=NEAT_AGENT, id_=DEFAULT_NAMESPACE["empty-entity"])
|
|
83
99
|
|
|
84
100
|
|
|
85
101
|
@dataclass(frozen=True)
|
|
@@ -111,12 +127,12 @@ class Activity:
|
|
|
111
127
|
|
|
112
128
|
|
|
113
129
|
@dataclass(frozen=True)
|
|
114
|
-
class Change(FrozenNeatObject):
|
|
130
|
+
class Change(FrozenNeatObject, Generic[T_Entity]):
|
|
115
131
|
agent: Agent
|
|
116
132
|
activity: Activity
|
|
117
|
-
target_entity:
|
|
133
|
+
target_entity: T_Entity
|
|
118
134
|
description: str
|
|
119
|
-
source_entity: Entity = field(default_factory=Entity.
|
|
135
|
+
source_entity: Entity = field(default_factory=Entity.create_new_unknown_entity)
|
|
120
136
|
|
|
121
137
|
def as_triples(self) -> list[Triple]:
|
|
122
138
|
return (
|
|
@@ -127,7 +143,7 @@ class Change(FrozenNeatObject):
|
|
|
127
143
|
)
|
|
128
144
|
|
|
129
145
|
@classmethod
|
|
130
|
-
def record(cls, activity: str, start: datetime, end: datetime, description: str) -> "Change":
|
|
146
|
+
def record(cls, activity: str, start: datetime, end: datetime, description: str) -> "Change[Entity]":
|
|
131
147
|
"""User friendly method to record a change that occurred in the graph store."""
|
|
132
148
|
agent = Agent()
|
|
133
149
|
activity = Activity(
|
|
@@ -136,8 +152,8 @@ class Change(FrozenNeatObject):
|
|
|
136
152
|
started_at_time=start,
|
|
137
153
|
ended_at_time=end,
|
|
138
154
|
)
|
|
139
|
-
target_entity = Entity(was_generated_by=activity, was_attributed_to=agent)
|
|
140
|
-
return
|
|
155
|
+
target_entity = Entity.create_with_defaults(was_generated_by=activity, was_attributed_to=agent)
|
|
156
|
+
return Change(
|
|
141
157
|
agent=agent,
|
|
142
158
|
activity=activity,
|
|
143
159
|
target_entity=target_entity,
|
|
@@ -154,7 +170,7 @@ class Change(FrozenNeatObject):
|
|
|
154
170
|
}
|
|
155
171
|
|
|
156
172
|
|
|
157
|
-
class Provenance(NeatList[Change]):
|
|
173
|
+
class Provenance(NeatList[Change[T_Entity]]):
|
|
158
174
|
def __init__(self, changes: Sequence[Change] | None = None):
|
|
159
175
|
super().__init__(changes or [])
|
|
160
176
|
|