cognite-neat 0.108.0__py3-none-any.whl → 0.109.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.

Files changed (44) hide show
  1. cognite/neat/_constants.py +1 -1
  2. cognite/neat/_graph/extractors/_classic_cdf/_classic.py +8 -4
  3. cognite/neat/_graph/queries/_base.py +4 -0
  4. cognite/neat/_graph/transformers/__init__.py +3 -3
  5. cognite/neat/_graph/transformers/_base.py +4 -4
  6. cognite/neat/_graph/transformers/_classic_cdf.py +13 -13
  7. cognite/neat/_graph/transformers/_prune_graph.py +3 -3
  8. cognite/neat/_graph/transformers/_rdfpath.py +3 -4
  9. cognite/neat/_graph/transformers/_value_type.py +23 -16
  10. cognite/neat/_issues/errors/__init__.py +2 -0
  11. cognite/neat/_issues/errors/_external.py +8 -0
  12. cognite/neat/_issues/warnings/_resources.py +1 -1
  13. cognite/neat/_rules/exporters/_rules2yaml.py +1 -1
  14. cognite/neat/_rules/importers/_rdf/_inference2rules.py +179 -118
  15. cognite/neat/_rules/models/_base_rules.py +9 -8
  16. cognite/neat/_rules/models/dms/_exporter.py +5 -4
  17. cognite/neat/_rules/transformers/__init__.py +4 -3
  18. cognite/neat/_rules/transformers/_base.py +6 -1
  19. cognite/neat/_rules/transformers/_converters.py +436 -361
  20. cognite/neat/_rules/transformers/_mapping.py +4 -4
  21. cognite/neat/_session/_base.py +71 -69
  22. cognite/neat/_session/_create.py +133 -0
  23. cognite/neat/_session/_drop.py +55 -1
  24. cognite/neat/_session/_fix.py +28 -0
  25. cognite/neat/_session/_inspect.py +19 -5
  26. cognite/neat/_session/_mapping.py +8 -8
  27. cognite/neat/_session/_prepare.py +3 -247
  28. cognite/neat/_session/_read.py +78 -4
  29. cognite/neat/_session/_set.py +34 -12
  30. cognite/neat/_session/_show.py +14 -41
  31. cognite/neat/_session/_state.py +48 -51
  32. cognite/neat/_session/_to.py +7 -3
  33. cognite/neat/_session/exceptions.py +7 -1
  34. cognite/neat/_store/_graph_store.py +14 -13
  35. cognite/neat/_store/_provenance.py +36 -20
  36. cognite/neat/_store/_rules_store.py +172 -293
  37. cognite/neat/_store/exceptions.py +40 -4
  38. cognite/neat/_utils/auth.py +4 -2
  39. cognite/neat/_version.py +1 -1
  40. {cognite_neat-0.108.0.dist-info → cognite_neat-0.109.0.dist-info}/METADATA +1 -1
  41. {cognite_neat-0.108.0.dist-info → cognite_neat-0.109.0.dist-info}/RECORD +44 -42
  42. {cognite_neat-0.108.0.dist-info → cognite_neat-0.109.0.dist-info}/LICENSE +0 -0
  43. {cognite_neat-0.108.0.dist-info → cognite_neat-0.109.0.dist-info}/WHEEL +0 -0
  44. {cognite_neat-0.108.0.dist-info → cognite_neat-0.109.0.dist-info}/entry_points.txt +0 -0
@@ -1,4 +1,4 @@
1
- from dataclasses import dataclass, field
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 RulesTransformer, ToExtensionModel
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__(self, store_type: Literal["memory", "oxigraph"], client: NeatClient | None = None) -> None:
21
- self.instances = InstancesState(store_type)
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: RulesTransformer) -> IssueList:
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
- # This should not be allowed to be done automatically
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
- end = cast(ModelEntity, self.rule_store.provenance[-1].target_entity).display_name
54
- issues.action = f"{start} → {end}"
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.import_(importer)
60
- result = cast(ModelEntity, self.rule_store.provenance[-1].target_entity).display_name
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
- store_type: Literal["memory", "oxigraph"]
80
- issue_lists: list[IssueList] = field(default_factory=list)
81
- outcome: list[UploadResultList] = field(default_factory=list)
82
- _store: NeatGraphStore | None = field(init=False, default=None)
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
- @property
85
- def store(self) -> NeatGraphStore:
86
- if not self.has_store:
87
- if self.store_type == "oxigraph":
88
- self._store = NeatGraphStore.from_oxi_local_store()
89
- else:
90
- self._store = NeatGraphStore.from_memory_store()
91
- return cast(NeatGraphStore, self._store)
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 has_store(self) -> bool:
95
- return self._store is not None
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 minstances."
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])
@@ -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.data_classes.data_modeling import SpaceApply
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(self, space: str | None = None) -> UploadResultList:
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 (CDFMissingClientError, NeatImportError, NeatValueError) as e:
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
- # Adding support for both oxigraph in-memory and file-based storage
211
- for i in range(4):
212
- try:
213
- oxi_store = pyoxigraph.Store(path=str(storage_dir) if storage_dir else None)
214
- break
215
- except OSError as e:
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 Optional
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 = field(default_factory=IssueList)
53
- was_generated_by: Optional["Activity"] = field(default=None, repr=False)
54
- id_: URIRef = DEFAULT_NAMESPACE["graph-store"]
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
- INSTANCES_ENTITY = Entity(was_attributed_to=NEAT_AGENT, id_=CDF_NAMESPACE["instances"])
82
- EMPTY_ENTITY = Entity(was_attributed_to=NEAT_AGENT, id_=DEFAULT_NAMESPACE["empty-entity"])
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: Entity
133
+ target_entity: T_Entity
118
134
  description: str
119
- source_entity: Entity = field(default_factory=Entity.new_unknown_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 cls(
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