cognite-neat 0.109.1__py3-none-any.whl → 0.109.3__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.
- cognite/neat/_alpha.py +15 -0
- cognite/neat/_client/testing.py +1 -1
- cognite/neat/_issues/_base.py +33 -9
- cognite/neat/_issues/errors/__init__.py +2 -10
- cognite/neat/_issues/errors/_general.py +1 -1
- cognite/neat/_issues/errors/_wrapper.py +11 -0
- cognite/neat/_rules/exporters/_rules2excel.py +31 -1
- cognite/neat/_rules/models/_rdfpath.py +2 -0
- cognite/neat/_rules/models/_types.py +4 -2
- cognite/neat/_rules/models/dms/_rules.py +0 -36
- cognite/neat/_rules/models/entities/_constants.py +3 -0
- cognite/neat/_rules/models/entities/_single_value.py +6 -1
- cognite/neat/_rules/models/entities/_wrapped.py +3 -0
- cognite/neat/_rules/transformers/__init__.py +4 -0
- cognite/neat/_rules/transformers/_converters.py +221 -15
- cognite/neat/_session/_base.py +7 -0
- cognite/neat/_session/_collector.py +4 -1
- cognite/neat/_session/_create.py +46 -12
- cognite/neat/_session/_prepare.py +11 -3
- cognite/neat/_session/_read.py +14 -2
- cognite/neat/_session/_state.py +7 -3
- cognite/neat/_session/_to.py +20 -5
- cognite/neat/_session/exceptions.py +16 -6
- cognite/neat/_store/_provenance.py +1 -0
- cognite/neat/_store/_rules_store.py +192 -127
- cognite/neat/_utils/spreadsheet.py +10 -1
- cognite/neat/_utils/text.py +40 -9
- cognite/neat/_version.py +1 -1
- {cognite_neat-0.109.1.dist-info → cognite_neat-0.109.3.dist-info}/METADATA +1 -1
- {cognite_neat-0.109.1.dist-info → cognite_neat-0.109.3.dist-info}/RECORD +33 -32
- cognite/neat/_issues/errors/_workflow.py +0 -36
- {cognite_neat-0.109.1.dist-info → cognite_neat-0.109.3.dist-info}/LICENSE +0 -0
- {cognite_neat-0.109.1.dist-info → cognite_neat-0.109.3.dist-info}/WHEEL +0 -0
- {cognite_neat-0.109.1.dist-info → cognite_neat-0.109.3.dist-info}/entry_points.txt +0 -0
|
@@ -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
|
-
|
|
17
|
+
_ERROR_PREFIX = "[bold red][ERROR][/bold red]"
|
|
18
|
+
_WARNING_PREFIX = "[bold bright_magenta][WARNING][/bold bright_magenta]"
|
|
16
19
|
except ImportError:
|
|
17
|
-
|
|
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
|
-
|
|
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"{
|
|
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"{
|
|
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"{
|
|
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
|
|
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
|
|
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
|
|
70
|
-
self,
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
#
|
|
76
|
-
|
|
90
|
+
# Bug in the code
|
|
91
|
+
raise ValueError(f"Invalid output from importer: {type(verified)}")
|
|
77
92
|
|
|
78
|
-
def
|
|
79
|
-
self,
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
106
|
+
result, issue_list, start, end = self._do_activity(
|
|
107
|
+
partial(self._rules_import_verify_convert, importer, validate, client)
|
|
108
|
+
)
|
|
98
109
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
|
191
|
+
return issue_list
|
|
108
192
|
|
|
109
|
-
def
|
|
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
|
-
|
|
112
|
-
|
|
201
|
+
importer: BaseImporter,
|
|
202
|
+
validate: bool = True,
|
|
203
|
+
client: NeatClient | None = None,
|
|
204
|
+
enable_manual_edit: bool = False,
|
|
113
205
|
) -> IssueList:
|
|
114
|
-
if self.
|
|
115
|
-
|
|
116
|
-
|
|
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
|
|
221
|
+
for agent_tool in transformer:
|
|
124
222
|
|
|
125
|
-
def action(
|
|
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,
|
|
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
|
-
)
|
|
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
|
-
|
|
168
|
-
|
|
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=
|
|
179
|
-
started_at_time=
|
|
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
|
-
|
|
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
|
|
276
|
-
"""
|
|
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
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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/_utils/text.py
CHANGED
|
@@ -17,21 +17,23 @@ def to_camel(string: str) -> str:
|
|
|
17
17
|
>>> to_camel("ScenarioInstance_priceForecast")
|
|
18
18
|
'scenarioInstancePriceForecast'
|
|
19
19
|
"""
|
|
20
|
+
string = re.sub(r"[\s_-]", "_", string)
|
|
21
|
+
string = re.sub("_+", "_", string)
|
|
20
22
|
if "_" in string:
|
|
21
|
-
|
|
22
|
-
parts = string.split("_")
|
|
23
|
-
pascal_splits = [to_pascal(subpart) for part in parts for subpart in part.split("-") if subpart]
|
|
24
|
-
elif "-" in string:
|
|
25
|
-
# Could be a combination of kebab and pascal/camel case
|
|
26
|
-
parts = string.split("-")
|
|
27
|
-
pascal_splits = [to_pascal(subpart) for part in parts for subpart in part.split("_") if subpart]
|
|
23
|
+
pascal_splits = [to_pascal(part) for part in string.split("_")]
|
|
28
24
|
else:
|
|
29
|
-
# Assume is pascal/camel case
|
|
30
25
|
# Ensure pascal
|
|
31
26
|
string = string[0].upper() + string[1:]
|
|
32
27
|
pascal_splits = [string]
|
|
33
|
-
|
|
28
|
+
cleaned: list[str] = []
|
|
34
29
|
for part in pascal_splits:
|
|
30
|
+
if part.upper() == part:
|
|
31
|
+
cleaned.append(part.capitalize())
|
|
32
|
+
else:
|
|
33
|
+
cleaned.append(part)
|
|
34
|
+
|
|
35
|
+
string_split = []
|
|
36
|
+
for part in cleaned:
|
|
35
37
|
string_split.extend(re.findall(r"[A-Z][a-z0-9]*", part))
|
|
36
38
|
if not string_split:
|
|
37
39
|
string_split = [string]
|
|
@@ -135,3 +137,32 @@ def humanize_collection(collection: Collection[Any], /, *, sort: bool = True) ->
|
|
|
135
137
|
sequence = list(strings)
|
|
136
138
|
|
|
137
139
|
return f"{', '.join(sequence[:-1])} and {sequence[-1]}"
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class NamingStandardization:
|
|
143
|
+
_clean_pattern = re.compile(r"[^a-zA-Z0-9_]+")
|
|
144
|
+
_multi_underscore_pattern = re.compile(r"_+")
|
|
145
|
+
_start_letter_pattern = re.compile(r"^[a-zA-Z]")
|
|
146
|
+
|
|
147
|
+
@classmethod
|
|
148
|
+
def standardize_class_str(cls, raw: str) -> str:
|
|
149
|
+
clean = cls._clean_string(raw)
|
|
150
|
+
if not cls._start_letter_pattern.match(clean):
|
|
151
|
+
# Underscore ensure that 'Class' it treated as a separate word
|
|
152
|
+
# in the to_pascale function
|
|
153
|
+
clean = f"Class_{clean}"
|
|
154
|
+
return to_pascal(clean)
|
|
155
|
+
|
|
156
|
+
@classmethod
|
|
157
|
+
def standardize_property_str(cls, raw: str) -> str:
|
|
158
|
+
clean = cls._clean_string(raw)
|
|
159
|
+
if not cls._start_letter_pattern.match(clean):
|
|
160
|
+
# Underscore ensure that 'property' it treated as a separate word
|
|
161
|
+
# in the to_camel function
|
|
162
|
+
clean = f"property_{clean}"
|
|
163
|
+
return to_camel(clean)
|
|
164
|
+
|
|
165
|
+
@classmethod
|
|
166
|
+
def _clean_string(cls, raw: str) -> str:
|
|
167
|
+
raw = cls._clean_pattern.sub("_", raw)
|
|
168
|
+
return cls._multi_underscore_pattern.sub("_", raw)
|
cognite/neat/_version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
__version__ = "0.109.
|
|
1
|
+
__version__ = "0.109.3"
|
|
2
2
|
__engine__ = "^2.0.3"
|