cognite-neat 0.109.1__py3-none-any.whl → 0.109.2__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/_alpha.py ADDED
@@ -0,0 +1,14 @@
1
+ import warnings
2
+
3
+
4
+ class AlphaWarning(UserWarning):
5
+ def __init__(self, feature_name: str):
6
+ super().__init__(f"Alpha feature '{feature_name}' is subject to change without notice")
7
+
8
+ def warn(self) -> None:
9
+ warnings.warn(self, stacklevel=2)
10
+
11
+
12
+ class AlphaFlags:
13
+ manual_rules_edit = AlphaWarning("enable_manual_edit")
14
+ same_space_properties_only_export = AlphaWarning("same-space-properties-only")
@@ -22,6 +22,7 @@ from cognite.neat._rules.models import (
22
22
  from cognite.neat._rules.models.dms import DMSMetadata
23
23
  from cognite.neat._rules.models.information import InformationMetadata
24
24
  from cognite.neat._rules.models.information._rules import InformationRules
25
+ from cognite.neat._utils.spreadsheet import find_column_with_value
25
26
 
26
27
  from ._base import BaseExporter
27
28
 
@@ -63,12 +64,22 @@ class ExcelExporter(BaseExporter[VerifiedRules, Workbook]):
63
64
  style_options = get_args(Style)
64
65
  dump_options = get_args(DumpOptions)
65
66
 
67
+ _internal_columns: ClassVar[list[str]] = [
68
+ "physical",
69
+ "logical",
70
+ "conceptual",
71
+ "Neat ID",
72
+ ]
73
+
66
74
  def __init__(
67
75
  self,
68
76
  styling: Style = "default",
69
77
  new_model_id: tuple[str, str] | None = None,
70
78
  sheet_prefix: str | None = None,
71
79
  reference_rules_with_prefix: tuple[VerifiedRules, str] | None = None,
80
+ add_empty_rows: bool = False,
81
+ hide_internal_columns: bool = True,
82
+ include_properties: Literal["same-space", "all"] = "all",
72
83
  ):
73
84
  self.sheet_prefix = sheet_prefix or ""
74
85
  if styling not in self.style_options:
@@ -77,6 +88,9 @@ class ExcelExporter(BaseExporter[VerifiedRules, Workbook]):
77
88
  self._styling_level = self.style_options.index(styling)
78
89
  self.new_model_id = new_model_id
79
90
  self.reference_rules_with_prefix = reference_rules_with_prefix
91
+ self.add_empty_rows = add_empty_rows
92
+ self.hide_internal_columns = hide_internal_columns
93
+ self.include_properties = include_properties
80
94
 
81
95
  @property
82
96
  def description(self) -> str:
@@ -112,6 +126,16 @@ class ExcelExporter(BaseExporter[VerifiedRules, Workbook]):
112
126
  if self._styling_level > 0:
113
127
  self._adjust_column_widths(workbook)
114
128
 
129
+ if self.hide_internal_columns:
130
+ for sheet in workbook.sheetnames:
131
+ if sheet.lower() == "metadata":
132
+ continue
133
+ ws = workbook[sheet]
134
+ for col in self._internal_columns:
135
+ column_letter = find_column_with_value(ws, col)
136
+ if column_letter:
137
+ ws.column_dimensions[column_letter].hidden = True
138
+
115
139
  return workbook
116
140
 
117
141
  def _write_sheets(
@@ -149,13 +173,19 @@ class ExcelExporter(BaseExporter[VerifiedRules, Workbook]):
149
173
  is_properties = sheet_name == "Properties"
150
174
  is_new_class = class_ != last_class and last_class is not None
151
175
  if self._styling_level > 2 and is_new_class and is_properties:
152
- sheet.append([""] * len(headers))
176
+ if self.add_empty_rows:
177
+ sheet.append([""] * len(headers))
153
178
  for cell in sheet[sheet.max_row]:
154
179
  cell.fill = PatternFill(fgColor=fill_color, patternType="solid")
155
180
  side = Side(style="thin", color="000000")
156
181
  cell.border = Border(left=side, right=side, top=side, bottom=side)
157
182
  fill_color = next(fill_colors)
158
183
 
184
+ if is_properties and self.include_properties == "same-space":
185
+ space = class_.split(":")[0] if ":" in class_ else rules.metadata.space
186
+ if space != rules.metadata.space:
187
+ continue
188
+
159
189
  sheet.append(row)
160
190
  if self._styling_level > 2 and is_properties:
161
191
  for cell in sheet[sheet.max_row]:
@@ -201,3 +201,6 @@ class RawFilter(DMSFilter):
201
201
 
202
202
  def __repr__(self) -> str:
203
203
  return self.filter
204
+
205
+ def __str__(self):
206
+ return f"{self.name}({self.filter})"
@@ -65,7 +65,10 @@ class Collector:
65
65
  if kwargs:
66
66
  for key, value in kwargs.items():
67
67
  event_information[key] = self._serialize_value(value)[:500]
68
- self._track(command, event_information)
68
+
69
+ with suppress(RuntimeError):
70
+ # In case any thread issues, the tracking should not crash the program
71
+ self._track(command, event_information)
69
72
 
70
73
  @staticmethod
71
74
  def _serialize_value(value: Any) -> str:
@@ -1,8 +1,10 @@
1
+ import warnings
1
2
  from typing import Any, Literal, cast
2
3
 
3
4
  from cognite.client.data_classes.data_modeling import DataModelId, DataModelIdentifier
4
5
  from cognite.client.utils.useful_types import SequenceNotStr
5
6
 
7
+ from cognite.neat._alpha import AlphaFlags
6
8
  from cognite.neat._client import NeatClient
7
9
  from cognite.neat._constants import (
8
10
  CLASSIC_CDF_NAMESPACE,
@@ -286,16 +288,26 @@ class ExcelReadAPI(BaseReadAPI):
286
288
  super().__init__(state, verbose)
287
289
  self.examples = ExcelExampleAPI(state, verbose)
288
290
 
289
- def __call__(self, io: Any) -> IssueList:
291
+ def __call__(self, io: Any, enable_manual_edit: bool = False) -> IssueList:
290
292
  """Reads a Neat Excel Rules sheet to the graph store. The rules sheet may stem from an Information architect,
291
293
  or a DMS Architect.
292
294
 
293
295
  Args:
294
296
  io: file path to the Excel sheet
297
+ enable_manual_edit: If True, the user will be able to re-import rules which where edit outside NeatSession
298
+
299
+ !!! note "Manual Edit Warning"
300
+ This is an alpha feature and is subject to change without notice.
301
+ It is expected to have some limitations and may not work as expected in all cases.
295
302
  """
296
303
  reader = NeatReader.create(io)
297
304
  path = reader.materialize_path()
298
- return self._state.rule_import(importers.ExcelImporter(path))
305
+
306
+ if enable_manual_edit:
307
+ warnings.filterwarnings("default")
308
+ AlphaFlags.manual_rules_edit.warn()
309
+
310
+ return self._state.rule_import(importers.ExcelImporter(path), enable_manual_edit)
299
311
 
300
312
 
301
313
  @session_class_wrapper
@@ -40,8 +40,12 @@ class SessionState:
40
40
  self.instances.store.add_rules(last_entity.information)
41
41
  return issues
42
42
 
43
- def rule_import(self, importer: BaseImporter) -> IssueList:
44
- issues = self.rule_store.import_rules(importer, client=self.client)
43
+ def rule_import(self, importer: BaseImporter, enable_manual_edit: bool = False) -> IssueList:
44
+ issues = self.rule_store.import_rules(
45
+ importer,
46
+ client=self.client,
47
+ enable_manual_edit=enable_manual_edit,
48
+ )
45
49
  if self.rule_store.empty:
46
50
  result = "failed"
47
51
  else:
@@ -6,6 +6,7 @@ from typing import Any, Literal, overload
6
6
 
7
7
  from cognite.client import data_modeling as dm
8
8
 
9
+ from cognite.neat._alpha import AlphaFlags
9
10
  from cognite.neat._constants import COGNITE_MODELS
10
11
  from cognite.neat._graph import loaders
11
12
  from cognite.neat._rules import exporters
@@ -35,6 +36,8 @@ class ToAPI:
35
36
  self,
36
37
  io: Any,
37
38
  include_reference: bool = True,
39
+ include_properties: Literal["same-space", "all"] = "all",
40
+ add_empty_rows: bool = False,
38
41
  ) -> None:
39
42
  """Export the verified data model to Excel.
40
43
 
@@ -42,7 +45,10 @@ class ToAPI:
42
45
  io: The file path or file-like object to write the Excel file to.
43
46
  include_reference: If True, the reference data model will be included. Defaults to True.
44
47
  Note that this only applies if you have created the data model using the
45
- .to_enterprise(), .to_solution(), or .to_data_product() methods.
48
+ create.enterprise_model(...), create.solution_model(), or create.data_product_model() methods.
49
+ include_properties: The properties to include in the Excel file. Defaults to "all".
50
+ - "same-space": Only properties that are in the same space as the data model will be included.
51
+ add_empty_rows: If True, empty rows will be added between each component. Defaults to False.
46
52
 
47
53
  Example:
48
54
  Export information model to excel rules sheet
@@ -58,17 +64,17 @@ class ToAPI:
58
64
  neat = NeatSession(client)
59
65
 
60
66
  neat.read.cdf(("cdf_cdm", "CogniteCore", "v1"))
61
- neat.verify()
62
- neat.prepare.data_model.to_enterprise(
67
+ neat.create.enterprise_model(
63
68
  data_model_id=("sp_doctrino_space", "ExtensionCore", "v1"),
64
69
  org_name="MyOrg",
65
- move_connections=True
66
70
  )
67
71
  dms_rules_file_name = "dms_rules.xlsx"
68
72
  neat.to.excel(dms_rules_file_name, include_reference=True)
69
73
  ```
70
74
  """
71
75
  reference_rules_with_prefix: tuple[VerifiedRules, str] | None = None
76
+ include_properties = include_properties.strip().lower()
77
+
72
78
  if include_reference and self._state.last_reference:
73
79
  if (
74
80
  isinstance(self._state.last_reference.metadata, DMSMetadata)
@@ -79,7 +85,16 @@ class ToAPI:
79
85
  prefix = "Ref"
80
86
  reference_rules_with_prefix = self._state.last_reference, prefix
81
87
 
82
- exporter = exporters.ExcelExporter(styling="maximal", reference_rules_with_prefix=reference_rules_with_prefix)
88
+ if include_properties == "same-space":
89
+ warnings.filterwarnings("default")
90
+ AlphaFlags.same_space_properties_only_export.warn()
91
+
92
+ exporter = exporters.ExcelExporter(
93
+ styling="maximal",
94
+ reference_rules_with_prefix=reference_rules_with_prefix,
95
+ add_empty_rows=add_empty_rows,
96
+ include_properties=include_properties, # type: ignore
97
+ )
83
98
  return self._state.rule_store.export_to_file(exporter, Path(io))
84
99
 
85
100
  def session(self, io: Any) -> None:
@@ -1,7 +1,9 @@
1
1
  import functools
2
+ import warnings
2
3
  from collections.abc import Callable
3
4
  from typing import Any
4
5
 
6
+ from cognite.neat._alpha import AlphaWarning
5
7
  from cognite.neat._issues.errors import CDFMissingClientError, NeatImportError
6
8
  from cognite.neat._issues.errors._external import OxigraphStorageLockedError
7
9
  from cognite.neat._issues.errors._general import NeatValueError
@@ -12,9 +14,11 @@ try:
12
14
  from rich import print
13
15
  from rich.markup import escape
14
16
 
15
- _PREFIX = "[bold red][ERROR][/bold red]"
17
+ _ERROR_PREFIX = "[bold red][ERROR][/bold red]"
18
+ _WARNING_PREFIX = "[bold bright_magenta][WARNING][/bold bright_magenta]"
16
19
  except ImportError:
17
- _PREFIX = "[ERROR]"
20
+ _ERROR_PREFIX = "[ERROR]"
21
+ _WARNING_PREFIX = "[WARNING]"
18
22
 
19
23
  def escape(x: Any, *_: Any, **__: Any) -> Any: # type: ignore[misc]
20
24
  return x
@@ -31,21 +35,27 @@ def _session_method_wrapper(func: Callable, cls_name: str):
31
35
  def wrapper(*args: Any, **kwargs: Any):
32
36
  _COLLECTOR.track_session_command(f"{cls_name}.{func.__name__}", *args, **kwargs)
33
37
  try:
34
- return func(*args, **kwargs)
38
+ with warnings.catch_warnings(record=True) as w:
39
+ result = func(*args, **kwargs)
40
+ for warning in w:
41
+ if isinstance(warning.message, AlphaWarning):
42
+ print(f"{_WARNING_PREFIX} {warning.message}")
43
+
44
+ return result
35
45
  except NeatSessionError as e:
36
46
  action = _get_action()
37
- print(f"{_PREFIX} Cannot {action}: {e}")
47
+ print(f"{_ERROR_PREFIX} Cannot {action}: {e}")
38
48
  except (
39
49
  CDFMissingClientError,
40
50
  NeatImportError,
41
51
  NeatValueError,
42
52
  OxigraphStorageLockedError,
43
53
  ) as e:
44
- print(f"{_PREFIX} {escape(e.as_message())}")
54
+ print(f"{_ERROR_PREFIX} {escape(e.as_message())}")
45
55
  except ModuleNotFoundError as e:
46
56
  if e.name == "neatengine":
47
57
  action = _get_action()
48
- print(f"{_PREFIX} The functionality {action} requires the NeatEngine.")
58
+ print(f"{_ERROR_PREFIX} The functionality {action} requires the NeatEngine.")
49
59
  else:
50
60
  raise e
51
61
 
@@ -44,6 +44,7 @@ class Agent:
44
44
  CDF_AGENT = Agent(acted_on_behalf_of="UNKNOWN", id_=CDF_NAMESPACE["agent"])
45
45
  NEAT_AGENT = Agent(acted_on_behalf_of="UNKNOWN", id_=DEFAULT_NAMESPACE["agent"])
46
46
  UNKNOWN_AGENT = Agent(acted_on_behalf_of="UNKNOWN", id_=DEFAULT_NAMESPACE["unknown-agent"])
47
+ EXTERNAL_AGENT = Agent(acted_on_behalf_of="HUMAN", id_=DEFAULT_NAMESPACE["external-agent"])
47
48
 
48
49
 
49
50
  @dataclass(frozen=True)
@@ -3,11 +3,11 @@ from collections import defaultdict
3
3
  from collections.abc import Callable, Hashable
4
4
  from dataclasses import dataclass
5
5
  from datetime import datetime, timezone
6
+ from functools import partial
6
7
  from pathlib import Path
7
- from typing import Any
8
+ from typing import Any, cast
8
9
 
9
10
  import rdflib
10
- from cognite.client import data_modeling as dm
11
11
  from rdflib import URIRef
12
12
 
13
13
  from cognite.neat._client import NeatClient
@@ -15,7 +15,7 @@ from cognite.neat._constants import DEFAULT_NAMESPACE
15
15
  from cognite.neat._graph.extractors import DMSGraphExtractor, KnowledgeGraphExtractor
16
16
  from cognite.neat._issues import IssueList, catch_issues
17
17
  from cognite.neat._issues.errors import NeatValueError
18
- from cognite.neat._rules._shared import ReadRules, Rules, T_VerifiedRules, VerifiedRules
18
+ from cognite.neat._rules._shared import T_VerifiedRules, VerifiedRules
19
19
  from cognite.neat._rules.exporters import BaseExporter
20
20
  from cognite.neat._rules.exporters._base import CDFExporter, T_Export
21
21
  from cognite.neat._rules.importers import BaseImporter
@@ -23,7 +23,14 @@ from cognite.neat._rules.models import DMSRules, InformationRules
23
23
  from cognite.neat._rules.transformers import DMSToInformation, VerifiedRulesTransformer, VerifyAnyRules
24
24
  from cognite.neat._utils.upload import UploadResultList
25
25
 
26
- from ._provenance import UNKNOWN_AGENT, Activity, Change, Entity, Provenance
26
+ from ._provenance import (
27
+ EXTERNAL_AGENT,
28
+ UNKNOWN_AGENT,
29
+ Activity,
30
+ Change,
31
+ Entity,
32
+ Provenance,
33
+ )
27
34
  from .exceptions import EmptyStore, InvalidActivityInput
28
35
 
29
36
 
@@ -66,63 +73,156 @@ class NeatRulesStore:
66
73
  return calculated_hash[:8]
67
74
  return calculated_hash
68
75
 
69
- def import_rules(
70
- self, importer: BaseImporter, validate: bool = True, client: NeatClient | None = None
71
- ) -> IssueList:
72
- if self.empty:
73
- return self._import_rules(importer, validate, client)
76
+ def _rules_import_verify_convert(
77
+ self,
78
+ importer: BaseImporter,
79
+ validate: bool,
80
+ client: NeatClient | None = None,
81
+ ) -> tuple[InformationRules, DMSRules | None]:
82
+ """Action that imports rules, verifies them and optionally converts them."""
83
+ read_rules = importer.to_rules()
84
+ verified = VerifyAnyRules(validate, client).transform(read_rules) # type: ignore[arg-type]
85
+ if isinstance(verified, InformationRules):
86
+ return verified, None
87
+ elif isinstance(verified, DMSRules):
88
+ return DMSToInformation().transform(verified), verified
74
89
  else:
75
- # Importing can be used as a manual transformation.
76
- return self._manual_transform(importer, validate, client)
90
+ # Bug in the code
91
+ raise ValueError(f"Invalid output from importer: {type(verified)}")
77
92
 
78
- def _import_rules(
79
- self, importer: BaseImporter, validate: bool = True, client: NeatClient | None = None
80
- ) -> IssueList:
81
- def action() -> tuple[InformationRules, DMSRules | None]:
82
- read_rules = importer.to_rules()
83
- verified = VerifyAnyRules(validate, client).transform(read_rules) # type: ignore[arg-type]
84
- if isinstance(verified, InformationRules):
85
- return verified, None
86
- elif isinstance(verified, DMSRules):
87
- return DMSToInformation().transform(verified), verified
88
- else:
89
- # Bug in the code
90
- raise ValueError(f"Invalid output from importer: {type(verified)}")
91
-
92
- return self.import_action(action, importer)
93
+ def _graph_import_verify_convert(
94
+ self,
95
+ extractor: KnowledgeGraphExtractor,
96
+ ) -> tuple[InformationRules, DMSRules | None]:
97
+ info = extractor.get_information_rules()
98
+ dms: DMSRules | None = None
99
+ if isinstance(extractor, DMSGraphExtractor):
100
+ dms = extractor.get_dms_rules()
101
+ return info, dms
93
102
 
94
103
  def _manual_transform(
95
104
  self, importer: BaseImporter, validate: bool = True, client: NeatClient | None = None
96
105
  ) -> IssueList:
97
- raise NotImplementedError("Manual transformation is not yet implemented.")
106
+ result, issue_list, start, end = self._do_activity(
107
+ partial(self._rules_import_verify_convert, importer, validate, client)
108
+ )
98
109
 
99
- def import_graph(self, extractor: KnowledgeGraphExtractor) -> IssueList:
100
- def action() -> tuple[InformationRules, DMSRules | None]:
101
- info = extractor.get_information_rules()
102
- dms: DMSRules | None = None
103
- if isinstance(extractor, DMSGraphExtractor):
104
- dms = extractor.get_dms_rules()
105
- return info, dms
110
+ if not result:
111
+ return issue_list
112
+
113
+ info, dms = result
114
+ last_change = self.provenance[-1]
115
+
116
+ outside_agent = EXTERNAL_AGENT
117
+ outside_activity = Activity(
118
+ was_associated_with=outside_agent,
119
+ started_at_time=last_change.activity.ended_at_time,
120
+ ended_at_time=end,
121
+ used=last_change.target_entity,
122
+ )
123
+
124
+ # Case 1: Source of imported rules is not known
125
+ if not (source_id := self._get_source_id(result)):
126
+ raise NeatValueError(
127
+ "The source of the imported rules is unknown."
128
+ " Import will be skipped. Start a new NEAT session and import the data model there."
129
+ )
130
+
131
+ # Case 2: Source of imported rules not in rules_store
132
+ if not (source_entity := self.provenance.target_entity(source_id)) or not isinstance(
133
+ source_entity, RulesEntity
134
+ ):
135
+ raise NeatValueError(
136
+ "The source of the imported rules is not in the provenance."
137
+ " Import will be skipped. Start a new NEAT session and import the data model there."
138
+ )
139
+
140
+ # Case 3: Source is not the latest source entity in the provenance change
141
+ if source_entity.id_ != last_change.target_entity.id_:
142
+ raise NeatValueError(
143
+ "Imported rules are detached from the provenance chain."
144
+ " Import will be skipped. Start a new NEAT session and import the data model there."
145
+ )
146
+
147
+ # Case 4: Provenance is already at the physical state of the data model, going back to logical not possible
148
+ if not dms and source_entity.dms:
149
+ raise NeatValueError(
150
+ "Rules are already in physical state, import of logical model not possible."
151
+ " Import will be skipped. Start a new NEAT session and import the data model there."
152
+ )
153
+
154
+ # modification took place on information rules
155
+ if not dms and not source_entity.dms:
156
+ outside_target_entity = RulesEntity(
157
+ was_attributed_to=outside_agent,
158
+ was_generated_by=outside_activity,
159
+ information=info,
160
+ dms=dms,
161
+ issues=issue_list,
162
+ id_=self._create_id(info, dms),
163
+ )
164
+
165
+ # modification took place on dms rules, keep latest information rules
166
+ elif dms and source_entity.dms:
167
+ outside_target_entity = RulesEntity(
168
+ was_attributed_to=outside_agent,
169
+ was_generated_by=outside_activity,
170
+ information=last_change.target_entity.information,
171
+ dms=dms,
172
+ issues=issue_list,
173
+ id_=self._create_id(info, dms),
174
+ )
175
+
176
+ else:
177
+ raise NeatValueError("Invalid state of rules for manual transformation")
178
+
179
+ outside_change = Change(
180
+ source_entity=last_change.target_entity,
181
+ agent=outside_agent,
182
+ activity=outside_activity,
183
+ target_entity=outside_target_entity,
184
+ description="Manual transformation of rules outside of NEAT",
185
+ )
186
+
187
+ self._last_issues = issue_list
188
+ # record change that took place outside of neat
189
+ self.provenance.append(outside_change)
106
190
 
107
- return self.import_action(action, extractor)
191
+ return issue_list
108
192
 
109
- def import_action(
193
+ def import_graph(self, extractor: KnowledgeGraphExtractor) -> IssueList:
194
+ if not self.empty:
195
+ raise NeatValueError(f"Data model already exists. Cannot import {extractor.source_uri}.")
196
+ else:
197
+ return self.do_activity(partial(self._graph_import_verify_convert, extractor), extractor)
198
+
199
+ def import_rules(
110
200
  self,
111
- action: Callable[[], tuple[InformationRules, DMSRules | None]],
112
- agent_tool: BaseImporter | KnowledgeGraphExtractor,
201
+ importer: BaseImporter,
202
+ validate: bool = True,
203
+ client: NeatClient | None = None,
204
+ enable_manual_edit: bool = False,
113
205
  ) -> IssueList:
114
- if self.provenance:
115
- raise NeatValueError(f"Data model already exists. Cannot import {agent_tool.source_uri}.")
116
- return self.do_activity(action, agent_tool)
206
+ if self.empty:
207
+ return self.do_activity(
208
+ partial(self._rules_import_verify_convert, importer, validate, client),
209
+ importer,
210
+ )
211
+ elif enable_manual_edit:
212
+ return self._manual_transform(importer, validate, client)
213
+ else:
214
+ raise NeatValueError("Re-importing rules in the rules store is not allowed.")
117
215
 
118
216
  def transform(self, *transformer: VerifiedRulesTransformer) -> IssueList:
119
217
  if not self.provenance:
120
218
  raise EmptyStore()
121
219
 
122
220
  all_issues = IssueList()
123
- for item in transformer:
221
+ for agent_tool in transformer:
124
222
 
125
- def action(transformer_item=item) -> tuple[InformationRules, DMSRules | None]:
223
+ def action(
224
+ transformer_item=agent_tool,
225
+ ) -> tuple[InformationRules, DMSRules | None]:
126
226
  last_change = self.provenance[-1]
127
227
  source_entity = last_change.target_entity
128
228
  transformer_input = self._get_transformer_input(source_entity, transformer_item)
@@ -131,8 +231,9 @@ class NeatRulesStore:
131
231
  return transformer_output, None
132
232
  return last_change.target_entity.information, transformer_output
133
233
 
134
- issues = self.do_activity(action, item)
234
+ issues = self.do_activity(action, agent_tool)
135
235
  all_issues.extend(issues)
236
+
136
237
  return all_issues
137
238
 
138
239
  def export(self, exporter: BaseExporter[T_VerifiedRules, T_Export]) -> T_Export:
@@ -154,34 +255,40 @@ class NeatRulesStore:
154
255
  self,
155
256
  action: Callable[[], tuple[InformationRules, DMSRules | None]],
156
257
  agent_tool: BaseImporter | VerifiedRulesTransformer | KnowledgeGraphExtractor,
157
- ) -> IssueList:
258
+ ):
259
+ result, issue_list, start, end = self._do_activity(action)
260
+ self._last_issues = issue_list
261
+
262
+ if result:
263
+ self._update_provenance(agent_tool, result, issue_list, start, end)
264
+ return issue_list
265
+
266
+ def _update_provenance(
267
+ self,
268
+ agent_tool: BaseImporter | VerifiedRulesTransformer | KnowledgeGraphExtractor,
269
+ result: tuple[InformationRules, DMSRules | None],
270
+ issue_list: IssueList,
271
+ activity_start: datetime,
272
+ activity_end: datetime,
273
+ ) -> None:
274
+ # set source entity
158
275
  if isinstance(agent_tool, BaseImporter | KnowledgeGraphExtractor):
159
276
  source_entity = Entity.create_with_defaults(
160
277
  was_attributed_to=UNKNOWN_AGENT,
161
278
  id_=agent_tool.source_uri,
162
279
  )
163
280
  else:
164
- # This is a transformer
165
281
  source_entity = self.provenance[-1].target_entity
166
282
 
167
- start = datetime.now(timezone.utc)
168
- result: tuple[InformationRules, DMSRules | None] | None = None
169
- with catch_issues() as issue_list:
170
- result = action()
171
-
172
- end = datetime.now(timezone.utc)
173
- self._last_issues = issue_list
174
-
283
+ # setting the rest of provenance components
284
+ info, dms = result
175
285
  agent = agent_tool.agent
176
286
  activity = Activity(
177
287
  was_associated_with=agent,
178
- ended_at_time=end,
179
- started_at_time=start,
288
+ ended_at_time=activity_end,
289
+ started_at_time=activity_start,
180
290
  used=source_entity,
181
291
  )
182
- if result is None:
183
- return issue_list
184
- info, dms = result
185
292
 
186
293
  target_entity = RulesEntity(
187
294
  was_attributed_to=agent,
@@ -199,8 +306,20 @@ class NeatRulesStore:
199
306
  description=agent_tool.description,
200
307
  source_entity=source_entity,
201
308
  )
309
+
202
310
  self.provenance.append(change)
203
- return issue_list
311
+
312
+ def _do_activity(
313
+ self,
314
+ action: Callable[[], tuple[InformationRules, DMSRules | None]],
315
+ ):
316
+ """This private method is used to execute an activity and return the result and issues."""
317
+ start = datetime.now(timezone.utc)
318
+ result: tuple[InformationRules, DMSRules | None] | None = None
319
+ with catch_issues() as issue_list:
320
+ result = action()
321
+ end = datetime.now(timezone.utc)
322
+ return result, issue_list, start, end
204
323
 
205
324
  def _export_activity(self, action: Callable, exporter: BaseExporter, target_id: URIRef, *exporter_args: Any) -> Any:
206
325
  if self.empty:
@@ -210,15 +329,18 @@ class NeatRulesStore:
210
329
  expected_types = exporter.source_types()
211
330
 
212
331
  if source_entity.dms is not None and isinstance(source_entity.dms, expected_types):
213
- input_ = source_entity.dms
332
+ input_ = cast(VerifiedRules, source_entity.dms).model_copy(deep=True)
214
333
  elif isinstance(source_entity.information, expected_types):
215
- input_ = source_entity.information
334
+ input_ = cast(VerifiedRules, source_entity.information).model_copy(deep=True)
216
335
  else:
217
336
  available: list[type] = [InformationRules]
218
337
  if source_entity.dms is not None:
219
338
  available.append(DMSRules)
220
339
  raise InvalidActivityInput(expected=expected_types, have=tuple(available))
221
340
 
341
+ # need to write source prior the export
342
+ input_.metadata.source_id = source_entity.id_
343
+
222
344
  agent = exporter.agent
223
345
  start = datetime.now(timezone.utc)
224
346
  with catch_issues() as issue_list:
@@ -252,6 +374,7 @@ class NeatRulesStore:
252
374
  description=exporter.description,
253
375
  source_entity=source_entity,
254
376
  )
377
+
255
378
  self.exports_by_source_entity_id[source_entity.id_].append(change)
256
379
  if isinstance(result, UploadResultList):
257
380
  self._last_outcome = result
@@ -272,72 +395,14 @@ class NeatRulesStore:
272
395
  # Case 3: We have both information and dms rules and the transformer is compatible with information rules
273
396
  raise InvalidActivityInput(expected=(InformationRules,), have=(DMSRules,))
274
397
 
275
- def _update_source_entity(self, source_entity: Entity, result: Rules, issue_list: IssueList) -> Entity:
276
- """Update source entity to keep the unbroken provenance chain of changes."""
277
-
278
- # Case 1: Store not empty, source of imported rules is not known
279
- if not (source_id := self._get_source_id(result)):
280
- raise NeatValueError(
281
- "The data model to be read to the current NEAT session"
282
- " has no relation to the session content."
283
- " Import will be skipped."
284
- "\n\nSuggestions:\n\t(1) Start a new NEAT session and "
285
- "import the data model there."
286
- )
287
-
288
- # We are taking target entity as it is the entity that produce rules
289
- # which were updated by activities outside of the rules tore
290
- update_source_entity: Entity | None = self.provenance.target_entity(source_id)
291
-
292
- # Case 2: source of imported rules not in rules_store
293
- if not update_source_entity:
294
- raise NeatValueError(
295
- "The source of the data model being imported is not in"
296
- " the content of this NEAT session."
297
- " Import will be skipped."
298
- "\n\nSuggestions:"
299
- "\n\t(1) Start a new NEAT session and import the data model source"
300
- "\n\t(2) Then import the data model itself"
301
- )
398
+ def _get_source_id(self, result: tuple[InformationRules, DMSRules | None]) -> rdflib.URIRef | None:
399
+ """Return the source of the result.
302
400
 
303
- # Case 3: source_entity in rules_store and but it is not the latest entity
304
- if self.provenance[-1].target_entity.id_ != update_source_entity.id_:
305
- raise NeatValueError(
306
- "Source of imported data model is not the latest entity in the data model provenance."
307
- "Pruning required to set the source entity to the latest entity in the provenance."
308
- )
309
-
310
- # Case 4: source_entity in rules_store and it is not the latest target entity
311
- if self.provenance[-1].target_entity.id_ != update_source_entity.id_:
312
- raise NeatValueError(
313
- "Source of imported rules is not the latest entity in the provenance."
314
- "Pruning required to set the source entity to the latest entity in the provenance."
315
- )
316
-
317
- # Case 5: source_entity in rules_store and it is the latest entity
318
- # Here we need to check if the source and target entities are identical
319
- # if they are ... we should raise an error and skip importing
320
- # for now we will just return the source entity that we managed to extract
321
-
322
- return update_source_entity or source_entity
323
-
324
- def _get_source_id(self, result: Rules) -> rdflib.URIRef | None:
325
- """Return the source of the result."""
326
-
327
- if isinstance(result, ReadRules) and result.rules is not None and result.rules.metadata.source_id:
328
- return rdflib.URIRef(result.rules.metadata.source_id)
329
- if isinstance(result, VerifiedRules):
330
- return result.metadata.source_id
331
- return None
332
-
333
- def _get_data_model_id(self, result: Rules) -> dm.DataModelId | None:
334
- """Return the source of the result."""
335
-
336
- if isinstance(result, ReadRules) and result.rules is not None:
337
- return result.rules.metadata.as_data_model_id()
338
- if isinstance(result, VerifiedRules):
339
- return result.metadata.as_data_model_id()
340
- return None
401
+ !!! note
402
+ This method prioritizes the source_id of the DMS rules
403
+ """
404
+ info, dms = result
405
+ return dms.metadata.source_id if dms else info.metadata.source_id
341
406
 
342
407
  def _create_id(self, info: InformationRules, dms: DMSRules | None) -> rdflib.URIRef:
343
408
  if dms is None:
@@ -1,5 +1,5 @@
1
1
  from dataclasses import dataclass, field
2
- from typing import Literal, cast, overload
2
+ from typing import Any, Literal, cast, overload
3
3
 
4
4
  import pandas as pd
5
5
  from openpyxl import load_workbook
@@ -81,3 +81,12 @@ def _get_row_number(sheet: Worksheet, values_to_find: list[str]) -> int | None:
81
81
  if any(value in row for value in values_to_find):
82
82
  return row_number
83
83
  return None
84
+
85
+
86
+ def find_column_with_value(sheet: Worksheet, value: Any) -> str | None:
87
+ for row in sheet.iter_rows():
88
+ for cell in row:
89
+ if cell.value and isinstance(cell.value, str) and cell.value.lower() == value.lower():
90
+ return cell.column_letter # type: ignore
91
+
92
+ return None
cognite/neat/_version.py CHANGED
@@ -1,2 +1,2 @@
1
- __version__ = "0.109.1"
1
+ __version__ = "0.109.2"
2
2
  __engine__ = "^2.0.3"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: cognite-neat
3
- Version: 0.109.1
3
+ Version: 0.109.2
4
4
  Summary: Knowledge graph transformation
5
5
  License: Apache-2.0
6
6
  Author: Nikola Vasiljevic
@@ -1,4 +1,5 @@
1
1
  cognite/neat/__init__.py,sha256=AXLMIF5t8RmjOpSaIlfqT8ltbPc__Tb8nAVbc1pkE0Q,176
2
+ cognite/neat/_alpha.py,sha256=v-PRdeEikqmm4lHxGAQUOkC7WsySf5X3RPIHQwr2j2o,423
2
3
  cognite/neat/_client/__init__.py,sha256=RQ7MwL8mwGqGHokRzsPqO3XStDzmI4-c12_gz1UPJ74,177
3
4
  cognite/neat/_client/_api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
5
  cognite/neat/_client/_api/data_modeling_loaders.py,sha256=0BZ5_NeszBvs4DHuHr0cGXytmvkTxrQ8k7v6iaX_mn4,35932
@@ -83,7 +84,7 @@ cognite/neat/_rules/catalog/info-rules-imf.xlsx,sha256=hj3ej7F1RYYyhGjX0rjVSOe_Z
83
84
  cognite/neat/_rules/exporters/__init__.py,sha256=IYBa0DIYlx8cFItgYRw9W4FY_LmVEjuaqMz3JORZZX0,1204
84
85
  cognite/neat/_rules/exporters/_base.py,sha256=VkNMy8wsH-x4tAjS44cXgzzNH0CM2k_4RhkMwK50J7g,2284
85
86
  cognite/neat/_rules/exporters/_rules2dms.py,sha256=7I3a8ZPwkIBQAClQbMjJ2D2aIITY-OBVUD-8hirCmzM,19183
86
- cognite/neat/_rules/exporters/_rules2excel.py,sha256=Y7QXtRoZNXuxM0gZIvy4u5_-V3r0qZ1ZfmMr-aMgH8A,12513
87
+ cognite/neat/_rules/exporters/_rules2excel.py,sha256=z2KOL-sNqnv0xi4Kjuo_2YAPMDeGTdMxaBswcs41lPU,13756
87
88
  cognite/neat/_rules/exporters/_rules2instance_template.py,sha256=xICzDU8Ij3Q7FhYDbEjttLc8vwqbMajfN88lrTDpn9U,5944
88
89
  cognite/neat/_rules/exporters/_rules2ontology.py,sha256=l0QJKh0qJ5CDtReWDeQ2jt7wa__v0FEgVMX9jCKHT2U,22120
89
90
  cognite/neat/_rules/exporters/_rules2yaml.py,sha256=ggaPR8FO8PwZk1_nhwb5wVHk_C4s6qh1RrlbPkNcbBo,3160
@@ -121,7 +122,7 @@ cognite/neat/_rules/models/entities/_loaders.py,sha256=jFllRty5XpS6uLklr9wJkx7Bz
121
122
  cognite/neat/_rules/models/entities/_multi_value.py,sha256=6j-nlUA392yQP_uB_CFB6_qFReNhi54ZlbFTcOKpbKY,2755
122
123
  cognite/neat/_rules/models/entities/_single_value.py,sha256=GW1R8ko1vwjGhAs2Fl5BGgpz3qtiYnYoahT4hBgJdkA,19441
123
124
  cognite/neat/_rules/models/entities/_types.py,sha256=df9rnXJJKciv2Bp-Ve2q4xdEJt6WWniq12Z0hW2d6sk,1917
124
- cognite/neat/_rules/models/entities/_wrapped.py,sha256=-LP20mEv8H2NAoZplP-IJud-kaMHNFWE8fqbeJmdpk4,7579
125
+ cognite/neat/_rules/models/entities/_wrapped.py,sha256=SpXIpglXBgaZt6geRparCWdY2F6SH0Fy99fcnA8x8Uk,7648
125
126
  cognite/neat/_rules/models/information/__init__.py,sha256=lV7l8RTfWBvN_DFkzea8OzADjq0rZGgWe_0Fiwtfje0,556
126
127
  cognite/neat/_rules/models/information/_rules.py,sha256=RQDcXEVZz9GG6KghpK-Vp4W_4ThoC12kfsPQdMkW75o,13766
127
128
  cognite/neat/_rules/models/information/_rules_input.py,sha256=njpi37IxTBLH4k51tRJeASU6yQXt324NSuxcZ_CjyU8,5940
@@ -136,29 +137,29 @@ cognite/neat/_rules/transformers/_mapping.py,sha256=lf-RKN__5Bg3-tZjEOCa1Sf_JtM_
136
137
  cognite/neat/_rules/transformers/_verification.py,sha256=jKTppklUCVwVlRfYyMfnUtV8r2ACTY-AtsoMF6L-KXo,4593
137
138
  cognite/neat/_session/__init__.py,sha256=fxQ5URVlUnmEGYyB8Baw7IDq-uYacqkigbc4b-Pr9Fw,58
138
139
  cognite/neat/_session/_base.py,sha256=WEAvz1b9Q8LsPOozLEUEOWLh9XWn1EtxwHw774qLi3E,11744
139
- cognite/neat/_session/_collector.py,sha256=SPCb1fEuOVIMHMQsVUNS7nkUUPhtUuNatnWPAIfQMcE,4093
140
+ cognite/neat/_session/_collector.py,sha256=RcOGY0DjTCCKJt9j_p0gnQXn4omhsIX2G8Aq3ZFHIt4,4218
140
141
  cognite/neat/_session/_create.py,sha256=NATzf_5u2aYKXiBmDfNCrcdzQjQsE71XPi--f_iNGcE,5239
141
142
  cognite/neat/_session/_drop.py,sha256=gOkDAnddASpFxYxkPjlTyhkpNfnmDEj94GRI8tnHFR0,4167
142
143
  cognite/neat/_session/_fix.py,sha256=gpmbJ4TbB_v2nw4fEA2Qtf0ifO3UDEMHGdztha28S_U,898
143
144
  cognite/neat/_session/_inspect.py,sha256=VkmDfIjh49NPS8wNa1OdWqhW-oJy1EvMDhKiooMvcjI,9040
144
145
  cognite/neat/_session/_mapping.py,sha256=AkQwmqYH-0EgqoXHqCFwJY92hNSGzfojOelhVFlqH4c,2655
145
146
  cognite/neat/_session/_prepare.py,sha256=WoVp2Go2RnVaL2RvnCE2-X1kS4ZWxvupOlOmWTce0i0,11626
146
- cognite/neat/_session/_read.py,sha256=tx-sM8tdCFjwP8h_qXDlwYpfThuvu7s73vzM2tczqho,21941
147
+ cognite/neat/_session/_read.py,sha256=V2F7InkvIODdML7jy4aGXH4PE91tWFzIGcNzOWnsTvM,22514
147
148
  cognite/neat/_session/_set.py,sha256=ZYR5G97fi-y68edbG-ftLqM_FRtn9G8V5zDtpslR9go,4070
148
149
  cognite/neat/_session/_show.py,sha256=3hUMWBlHCyMiVTbX1GmpVFfthpv3yBIZ293aU0mDxZ8,15156
149
- cognite/neat/_session/_state.py,sha256=KYmt1QEFu0LoXBuheu0ARA9gq2FsQvjyDWcJt_OiPHQ,3939
150
- cognite/neat/_session/_to.py,sha256=e6A2BeSL9NjQz1Fk2_RaJom7NPZErILqGi_UFDXWCZQ,10897
150
+ cognite/neat/_session/_state.py,sha256=gDyt2gf2kuiHu42zDMO-Pg0zjRllEKC4O8lJ0pMGCkU,4059
151
+ cognite/neat/_session/_to.py,sha256=w7ukhG0cjkKlMPhhivaL5Poq4W_-p8ZEWQSWuVnQGiw,11670
151
152
  cognite/neat/_session/_wizard.py,sha256=9idlzhZy54h2Iwupe9iXKX3RDb5jJQuBZFEouni50L0,1476
152
153
  cognite/neat/_session/engine/__init__.py,sha256=D3MxUorEs6-NtgoICqtZ8PISQrjrr4dvca6n48bu_bI,120
153
154
  cognite/neat/_session/engine/_import.py,sha256=1QxA2_EK613lXYAHKQbZyw2yjo5P9XuiX4Z6_6-WMNQ,169
154
155
  cognite/neat/_session/engine/_interface.py,sha256=ItJ1VMrPt-pKKvpSpglD9n9yFC6ehF9xV2DUVCyfQB0,533
155
156
  cognite/neat/_session/engine/_load.py,sha256=LcoYVthQyCzLWKzRE_75_nElS-n_eMWSPAgNJBnh5dA,5193
156
- cognite/neat/_session/exceptions.py,sha256=6wx3gAqU_nuXMAwuPijGvJg3NSSimXyNSefWa_p5Lo8,2717
157
+ cognite/neat/_session/exceptions.py,sha256=XCLVaEe7SMIyNW7mwt0WF7_e5K8KfV9yHkEtsjL60Tg,3182
157
158
  cognite/neat/_shared.py,sha256=Ov59SWYboRRsncB_5V1ZC_BAoACfNLjo80vqE5Ru6wo,2325
158
159
  cognite/neat/_store/__init__.py,sha256=RrvuZrMdezqun5dOrwHWSk26kampZcvqiHBqSFumkEE,130
159
160
  cognite/neat/_store/_graph_store.py,sha256=ru0wuJtBAZoN2HJOJhBmldAsbRpp9zMcIcwfTzEcsN0,26882
160
- cognite/neat/_store/_provenance.py,sha256=TgEtFc3RmdYO_hAj7NWgB_sj57DGoSxOiIFRAdYVeNM,7168
161
- cognite/neat/_store/_rules_store.py,sha256=ABBdEElx3va4F62IFebF99tkGF1cQ3qfQ0ibVnGHRtQ,16218
161
+ cognite/neat/_store/_provenance.py,sha256=0HeWyzvfCTxQiG2PygLLqWpdCAooVsFRckpGsnJbwzk,7260
162
+ cognite/neat/_store/_rules_store.py,sha256=R-bkiNiwqGQ9udrEmamvuEidQsemFpGBif1zmf4H-QA,17713
162
163
  cognite/neat/_store/exceptions.py,sha256=fbed_CGDdYU4oELgCL0_c5d85HGrUiYvXmL2D0WIDww,1593
163
164
  cognite/neat/_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
164
165
  cognite/neat/_utils/auth.py,sha256=hpNQjXpM3i7r0RU13ZLen1sa78nvPd4E1Sh3j1TMK4g,14701
@@ -169,15 +170,15 @@ cognite/neat/_utils/io_.py,sha256=D2Mg8sOxfBoDg3fC0jBzaxO3vkXmr0QvZSgYIv6xRkM,38
169
170
  cognite/neat/_utils/rdf_.py,sha256=b3sE3aTW9lu4gJWQJEaq_NCLbI54cc1o12utz-xbLh8,9023
170
171
  cognite/neat/_utils/reader/__init__.py,sha256=fPkrNB_9hLB7CyHTCFV_xEbIfOMqUQzNly5JN33-QfM,146
171
172
  cognite/neat/_utils/reader/_base.py,sha256=Q35hz8tqAiQiELjE4DsDDKQHLtRmSTrty4Gep9rg_CU,5444
172
- cognite/neat/_utils/spreadsheet.py,sha256=ssNWat42srjbQfzzApaGiNanyEy-oFlAQ6qQXCZOhoE,2777
173
+ cognite/neat/_utils/spreadsheet.py,sha256=20L_44m0hg3UdBFwCxnmAravxvCOolNZwVOGHfjYGR4,3089
173
174
  cognite/neat/_utils/text.py,sha256=0IffvBIAmeGh92F4T6xiEdd-vv3B7FOGEMbfuTktO5Y,4017
174
175
  cognite/neat/_utils/time_.py,sha256=O30LUiDH9TdOYz8_a9pFqTtJdg8vEjC3qHCk8xZblG8,345
175
176
  cognite/neat/_utils/upload.py,sha256=iWKmsQgw4EHLv-11NjYu7zAj5LtqTAfNa87a1kWeuaU,5727
176
177
  cognite/neat/_utils/xml_.py,sha256=FQkq84u35MUsnKcL6nTMJ9ajtG9D5i1u4VBnhGqP2DQ,1710
177
- cognite/neat/_version.py,sha256=pz3Yym8IbrxHrT2W25xDALwRmxfX19CEap9qQr071ZA,46
178
+ cognite/neat/_version.py,sha256=C9cGHhgqgxsB1rbURMXLvpzrANgFHbmOFq3MJSffn3w,46
178
179
  cognite/neat/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
179
- cognite_neat-0.109.1.dist-info/LICENSE,sha256=W8VmvFia4WHa3Gqxq1Ygrq85McUNqIGDVgtdvzT-XqA,11351
180
- cognite_neat-0.109.1.dist-info/METADATA,sha256=BoVGM-RFM3Rah07vsO8VhrI0OBu8QEqwA_O5ibbpcoQ,5361
181
- cognite_neat-0.109.1.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
182
- cognite_neat-0.109.1.dist-info/entry_points.txt,sha256=SsQlnl8SNMSSjE3acBI835JYFtsIinLSbVmHmMEXv6E,51
183
- cognite_neat-0.109.1.dist-info/RECORD,,
180
+ cognite_neat-0.109.2.dist-info/LICENSE,sha256=W8VmvFia4WHa3Gqxq1Ygrq85McUNqIGDVgtdvzT-XqA,11351
181
+ cognite_neat-0.109.2.dist-info/METADATA,sha256=lskGgPFezWb9blta8e5kdLXvTROMUKyMpVxrh8yIQvI,5361
182
+ cognite_neat-0.109.2.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
183
+ cognite_neat-0.109.2.dist-info/entry_points.txt,sha256=SsQlnl8SNMSSjE3acBI835JYFtsIinLSbVmHmMEXv6E,51
184
+ cognite_neat-0.109.2.dist-info/RECORD,,