cognite-neat 0.107.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 (69) hide show
  1. cognite/neat/_constants.py +35 -1
  2. cognite/neat/_graph/_shared.py +4 -0
  3. cognite/neat/_graph/extractors/_classic_cdf/_base.py +115 -14
  4. cognite/neat/_graph/extractors/_classic_cdf/_classic.py +87 -6
  5. cognite/neat/_graph/extractors/_classic_cdf/_relationships.py +48 -12
  6. cognite/neat/_graph/extractors/_classic_cdf/_sequences.py +19 -1
  7. cognite/neat/_graph/extractors/_dms.py +162 -47
  8. cognite/neat/_graph/extractors/_dms_graph.py +54 -4
  9. cognite/neat/_graph/extractors/_mock_graph_generator.py +1 -1
  10. cognite/neat/_graph/extractors/_rdf_file.py +3 -2
  11. cognite/neat/_graph/loaders/__init__.py +1 -3
  12. cognite/neat/_graph/loaders/_rdf2dms.py +20 -10
  13. cognite/neat/_graph/queries/_base.py +144 -84
  14. cognite/neat/_graph/queries/_construct.py +1 -1
  15. cognite/neat/_graph/transformers/__init__.py +3 -1
  16. cognite/neat/_graph/transformers/_base.py +4 -4
  17. cognite/neat/_graph/transformers/_classic_cdf.py +13 -13
  18. cognite/neat/_graph/transformers/_prune_graph.py +3 -3
  19. cognite/neat/_graph/transformers/_rdfpath.py +3 -4
  20. cognite/neat/_graph/transformers/_value_type.py +71 -13
  21. cognite/neat/_issues/errors/__init__.py +2 -0
  22. cognite/neat/_issues/errors/_external.py +8 -0
  23. cognite/neat/_issues/errors/_resources.py +1 -1
  24. cognite/neat/_issues/warnings/__init__.py +0 -2
  25. cognite/neat/_issues/warnings/_models.py +1 -1
  26. cognite/neat/_issues/warnings/_properties.py +0 -8
  27. cognite/neat/_issues/warnings/_resources.py +1 -1
  28. cognite/neat/_rules/catalog/classic_model.xlsx +0 -0
  29. cognite/neat/_rules/exporters/_rules2instance_template.py +3 -3
  30. cognite/neat/_rules/exporters/_rules2yaml.py +1 -1
  31. cognite/neat/_rules/importers/__init__.py +3 -1
  32. cognite/neat/_rules/importers/_dtdl2rules/spec.py +1 -2
  33. cognite/neat/_rules/importers/_rdf/__init__.py +2 -2
  34. cognite/neat/_rules/importers/_rdf/_base.py +2 -2
  35. cognite/neat/_rules/importers/_rdf/_inference2rules.py +310 -26
  36. cognite/neat/_rules/models/_base_rules.py +22 -11
  37. cognite/neat/_rules/models/dms/_exporter.py +5 -4
  38. cognite/neat/_rules/models/dms/_rules.py +1 -8
  39. cognite/neat/_rules/models/dms/_rules_input.py +4 -0
  40. cognite/neat/_rules/models/information/_rules_input.py +5 -0
  41. cognite/neat/_rules/transformers/__init__.py +10 -3
  42. cognite/neat/_rules/transformers/_base.py +6 -1
  43. cognite/neat/_rules/transformers/_converters.py +530 -364
  44. cognite/neat/_rules/transformers/_mapping.py +4 -4
  45. cognite/neat/_session/_base.py +100 -47
  46. cognite/neat/_session/_create.py +133 -0
  47. cognite/neat/_session/_drop.py +60 -2
  48. cognite/neat/_session/_fix.py +28 -0
  49. cognite/neat/_session/_inspect.py +22 -7
  50. cognite/neat/_session/_mapping.py +8 -8
  51. cognite/neat/_session/_prepare.py +3 -247
  52. cognite/neat/_session/_read.py +138 -17
  53. cognite/neat/_session/_set.py +50 -1
  54. cognite/neat/_session/_show.py +16 -43
  55. cognite/neat/_session/_state.py +53 -52
  56. cognite/neat/_session/_to.py +11 -4
  57. cognite/neat/_session/_wizard.py +1 -1
  58. cognite/neat/_session/exceptions.py +8 -1
  59. cognite/neat/_store/_graph_store.py +301 -146
  60. cognite/neat/_store/_provenance.py +36 -20
  61. cognite/neat/_store/_rules_store.py +253 -267
  62. cognite/neat/_store/exceptions.py +40 -4
  63. cognite/neat/_utils/auth.py +5 -3
  64. cognite/neat/_version.py +1 -1
  65. {cognite_neat-0.107.0.dist-info → cognite_neat-0.109.0.dist-info}/METADATA +1 -1
  66. {cognite_neat-0.107.0.dist-info → cognite_neat-0.109.0.dist-info}/RECORD +69 -67
  67. {cognite_neat-0.107.0.dist-info → cognite_neat-0.109.0.dist-info}/LICENSE +0 -0
  68. {cognite_neat-0.107.0.dist-info → cognite_neat-0.109.0.dist-info}/WHEEL +0 -0
  69. {cognite_neat-0.107.0.dist-info → cognite_neat-0.109.0.dist-info}/entry_points.txt +0 -0
@@ -98,22 +98,15 @@ class ShowDataModelAPI(ShowBaseAPI):
98
98
  self.implements = ShowDataModelImplementsAPI(self._state)
99
99
 
100
100
  def __call__(self) -> Any:
101
- if not self._state.rule_store.has_verified_rules:
102
- raise NeatSessionError(
103
- "No verified data model available. Try using [bold].verify()[/bold] to verify data model."
104
- )
105
-
106
- rules = self._state.rule_store.last_verified_rule
101
+ if self._state.rule_store.empty:
102
+ raise NeatSessionError("No data model available. Try using [bold].read[/bold] to read a new data model.")
103
+ last_target = self._state.rule_store.provenance[-1].target_entity
104
+ rules = last_target.dms or last_target.information
107
105
 
108
- if isinstance(rules, DMSRules):
109
- di_graph = self._generate_dms_di_graph(rules)
110
- elif isinstance(rules, InformationRules):
111
- di_graph = self._generate_info_di_graph(rules)
106
+ if last_target.dms is not None:
107
+ di_graph = self._generate_dms_di_graph(last_target.dms)
112
108
  else:
113
- # This should never happen, but we need to handle it to satisfy mypy
114
- raise NeatSessionError(
115
- f"Unsupported type {type(rules) }. Make sure you have either information or DMS rules."
116
- )
109
+ di_graph = self._generate_info_di_graph(last_target.information)
117
110
  identifier = to_directory_compatible(str(rules.metadata.identifier))
118
111
  name = f"{identifier}.html"
119
112
 
@@ -187,22 +180,16 @@ class ShowDataModelImplementsAPI(ShowBaseAPI):
187
180
  self._state = state
188
181
 
189
182
  def __call__(self) -> Any:
190
- if not self._state.rule_store.has_verified_rules:
191
- raise NeatSessionError(
192
- "No verified data model available. Try using [bold].verify()[/bold] to verify data model."
193
- )
183
+ if self._state.rule_store.empty:
184
+ raise NeatSessionError("No data model available. Try using [bold].read[/bold] to read a data model.")
194
185
 
195
- rules = self._state.rule_store.last_verified_rule
186
+ last_target = self._state.rule_store.provenance[-1].target_entity
187
+ rules = last_target.dms or last_target.information
196
188
 
197
- if isinstance(rules, DMSRules):
198
- di_graph = self._generate_dms_di_graph(rules)
199
- elif isinstance(rules, InformationRules):
200
- di_graph = self._generate_info_di_graph(rules)
189
+ if last_target.dms is not None:
190
+ di_graph = self._generate_dms_di_graph(last_target.dms)
201
191
  else:
202
- # This should never happen, but we need to handle it to satisfy mypy
203
- raise NeatSessionError(
204
- f"Unsupported type {type(rules) }. Make sure you have either information or DMS rules."
205
- )
192
+ di_graph = self._generate_info_di_graph(last_target.information)
206
193
  identifier = to_directory_compatible(str(rules.metadata.identifier))
207
194
  name = f"{identifier}_implements.html"
208
195
  return self._generate_visualization(di_graph, name)
@@ -325,20 +312,6 @@ class ShowDataModelProvenanceAPI(ShowBaseAPI):
325
312
  )
326
313
  di_graph.add_edge(source_shorten, export_id, label="exported", color="grey")
327
314
 
328
- for pruned_lists in self._state.rule_store.pruned_by_source_entity_id.values():
329
- for prune_path in pruned_lists:
330
- for change in prune_path:
331
- source = uri_display_name(change.source_entity.id_)
332
- target = uri_display_name(change.target_entity.id_)
333
- di_graph.add_node(
334
- target,
335
- label=target,
336
- type="Pruned",
337
- title="Pruned",
338
- color=hex_colored_types["Pruned"],
339
- )
340
- di_graph.add_edge(source, target, label="pruned", color="grey")
341
-
342
315
  return di_graph
343
316
 
344
317
 
@@ -364,7 +337,7 @@ class ShowInstanceAPI(ShowBaseAPI):
364
337
  'Try setting [bold]NeatSession(storage="oxigraph")[/bold] enable Oxigraph store.'
365
338
  )
366
339
 
367
- if not self._state.instances.store.graph:
340
+ if not self._state.instances.store.dataset:
368
341
  raise NeatSessionError("No instances available. Try using [bold].read[/bold] to load instances.")
369
342
 
370
343
  di_graph = self._generate_instance_di_graph_and_types()
@@ -395,7 +368,7 @@ class ShowInstanceAPI(ShowBaseAPI):
395
368
  object,
396
369
  subject_type,
397
370
  object_type,
398
- ) in self._state.instances.store.graph.query(query):
371
+ ) in self._state.instances.store.dataset.query(query):
399
372
  subject = remove_namespace_from_uri(subject)
400
373
  property_ = remove_namespace_from_uri(property_)
401
374
  object = remove_namespace_from_uri(object)
@@ -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,55 +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
29
+ self.quoted_source_identifiers = False
25
30
 
26
- def rule_transform(self, *transformer: RulesTransformer) -> IssueList:
31
+ def rule_transform(self, *transformer: VerifiedRulesTransformer) -> IssueList:
27
32
  if not transformer:
28
33
  raise NeatSessionError("No transformers provided.")
29
- first_transformer = transformer[0]
30
- pruned = self.rule_store.prune_until_compatible(first_transformer)
31
- if pruned:
32
- type_hint = first_transformer.transform_type_hint()
33
- action = uri_display_name(first_transformer.agent.id_)
34
- location = cast(ModelEntity, self.rule_store.provenance[-1].target_entity).display_name
35
- expected = humanize_collection([hint.display_type_name() for hint in type_hint]) # type: ignore[attr-defined]
36
- step_str = "step" if len(pruned) == 1 else "steps"
37
- print(
38
- f"The {action} actions expects a {expected}. "
39
- f"Moving back {len(pruned)} {step_str} to the last {location}."
40
- )
41
- if (
42
- any(isinstance(t, ToExtensionModel) for t in transformer)
43
- and isinstance(self.rule_store.provenance[-1].target_entity, ModelEntity)
44
- and isinstance(self.rule_store.provenance[-1].target_entity.result, DMSRules | InformationRules)
45
- ):
46
- self.last_reference = self.rule_store.provenance[-1].target_entity.result
47
34
 
48
- start = cast(ModelEntity, self.rule_store.provenance[-1].target_entity).display_name
35
+ start = self.rule_store.provenance[-1].target_entity.display_name
49
36
  issues = self.rule_store.transform(*transformer)
50
- end = cast(ModelEntity, self.rule_store.provenance[-1].target_entity).display_name
51
- issues.action = f"{start} → {end}"
37
+ last_entity = self.rule_store.provenance[-1].target_entity
38
+ issues.action = f"{start} → {last_entity.display_name}"
52
39
  issues.hint = "Use the .inspect.issues() for more details."
40
+ self.instances.store.add_rules(last_entity.information)
53
41
  return issues
54
42
 
55
43
  def rule_import(self, importer: BaseImporter) -> IssueList:
56
- issues = self.rule_store.import_(importer)
57
- 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
58
49
  if isinstance(importer, InferenceImporter):
59
50
  issues.action = f"Inferred {result}"
60
51
  else:
@@ -64,36 +55,46 @@ class SessionState:
64
55
  return issues
65
56
 
66
57
  def write_graph(self, extractor: KnowledgeGraphExtractor) -> IssueList:
67
- self.instances.store.write(extractor)
68
- issue_list = self.rule_store.import_graph(extractor)
58
+ extract_issues = self.instances.store.write(extractor)
59
+ issues = self.rule_store.import_graph(extractor)
69
60
  self.instances.store.add_rules(self.rule_store.last_verified_information_rules)
70
- return issue_list
61
+ issues.extend(extract_issues)
62
+ return issues
71
63
 
72
64
 
73
- @dataclass
74
65
  class InstancesState:
75
- store_type: Literal["memory", "oxigraph"]
76
- issue_lists: list[IssueList] = field(default_factory=list)
77
- outcome: list[UploadResultList] = field(default_factory=list)
78
- _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()
79
75
 
80
- @property
81
- def store(self) -> NeatGraphStore:
82
- if not self.has_store:
83
- if self.store_type == "oxigraph":
84
- self._store = NeatGraphStore.from_oxi_store()
85
- else:
86
- self._store = NeatGraphStore.from_memory_store()
87
- 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()
88
89
 
89
90
  @property
90
- def has_store(self) -> bool:
91
- return self._store is not None
91
+ def empty(self) -> bool:
92
+ return self.store.empty
92
93
 
93
94
  @property
94
95
  def last_outcome(self) -> UploadResultList:
95
96
  if not self.outcome:
96
97
  raise NeatSessionError(
97
- "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."
98
99
  )
99
- 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
@@ -104,7 +104,7 @@ class ToAPI:
104
104
 
105
105
  with zipfile.ZipFile(filepath, "w") as zip_ref:
106
106
  zip_ref.writestr(
107
- "neat-session/instances/instances.ttl",
107
+ "neat-session/instances/instances.trig",
108
108
  self._state.instances.store.serialize(),
109
109
  )
110
110
 
@@ -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,14 +216,18 @@ 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,
220
223
  self._state.instances.store,
221
224
  instance_space=space,
222
225
  client=client,
226
+ # In case urllib.parse.quote() was run on the extraction, we need to run
227
+ # urllib.parse.unquote() on the load.
228
+ unquote_external_ids=self._state.quoted_source_identifiers,
223
229
  )
230
+
224
231
  result = loader.load_into_cdf(client)
225
232
  self._state.instances.outcome.append(result)
226
233
  print("You can inspect the details with the .inspect.outcome.instances(...) method.")
@@ -26,7 +26,7 @@ _T_Option = TypeVar("_T_Option")
26
26
 
27
27
 
28
28
  def _selection(message: str, options: Sequence[_T_Option]) -> _T_Option:
29
- option_text = "\n ".join([f"{i+1}) {option}" for i, option in enumerate(options)])
29
+ option_text = "\n ".join([f"{i + 1}) {option}" for i, option in enumerate(options)])
30
30
  selected_index = (
31
31
  IntPrompt().ask(f"{message}\n {option_text}\n", choices=list(map(str, range(1, len(options) + 1)))) - 1
32
32
  )
@@ -3,6 +3,8 @@ 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
7
+ from cognite.neat._issues.errors._general import NeatValueError
6
8
 
7
9
  from ._collector import _COLLECTOR
8
10
 
@@ -33,7 +35,12 @@ def _session_method_wrapper(func: Callable, cls_name: str):
33
35
  except NeatSessionError as e:
34
36
  action = _get_action()
35
37
  print(f"{_PREFIX} Cannot {action}: {e}")
36
- except (CDFMissingClientError, NeatImportError) as e:
38
+ except (
39
+ CDFMissingClientError,
40
+ NeatImportError,
41
+ NeatValueError,
42
+ OxigraphStorageLockedError,
43
+ ) as e:
37
44
  print(f"{_PREFIX} {escape(e.as_message())}")
38
45
  except ModuleNotFoundError as e:
39
46
  if e.name == "neatengine":