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.
- cognite/neat/_constants.py +35 -1
- cognite/neat/_graph/_shared.py +4 -0
- cognite/neat/_graph/extractors/_classic_cdf/_base.py +115 -14
- cognite/neat/_graph/extractors/_classic_cdf/_classic.py +87 -6
- cognite/neat/_graph/extractors/_classic_cdf/_relationships.py +48 -12
- cognite/neat/_graph/extractors/_classic_cdf/_sequences.py +19 -1
- cognite/neat/_graph/extractors/_dms.py +162 -47
- cognite/neat/_graph/extractors/_dms_graph.py +54 -4
- cognite/neat/_graph/extractors/_mock_graph_generator.py +1 -1
- cognite/neat/_graph/extractors/_rdf_file.py +3 -2
- cognite/neat/_graph/loaders/__init__.py +1 -3
- cognite/neat/_graph/loaders/_rdf2dms.py +20 -10
- cognite/neat/_graph/queries/_base.py +144 -84
- cognite/neat/_graph/queries/_construct.py +1 -1
- cognite/neat/_graph/transformers/__init__.py +3 -1
- cognite/neat/_graph/transformers/_base.py +4 -4
- cognite/neat/_graph/transformers/_classic_cdf.py +13 -13
- cognite/neat/_graph/transformers/_prune_graph.py +3 -3
- cognite/neat/_graph/transformers/_rdfpath.py +3 -4
- cognite/neat/_graph/transformers/_value_type.py +71 -13
- cognite/neat/_issues/errors/__init__.py +2 -0
- cognite/neat/_issues/errors/_external.py +8 -0
- cognite/neat/_issues/errors/_resources.py +1 -1
- cognite/neat/_issues/warnings/__init__.py +0 -2
- cognite/neat/_issues/warnings/_models.py +1 -1
- cognite/neat/_issues/warnings/_properties.py +0 -8
- cognite/neat/_issues/warnings/_resources.py +1 -1
- cognite/neat/_rules/catalog/classic_model.xlsx +0 -0
- cognite/neat/_rules/exporters/_rules2instance_template.py +3 -3
- cognite/neat/_rules/exporters/_rules2yaml.py +1 -1
- cognite/neat/_rules/importers/__init__.py +3 -1
- cognite/neat/_rules/importers/_dtdl2rules/spec.py +1 -2
- cognite/neat/_rules/importers/_rdf/__init__.py +2 -2
- cognite/neat/_rules/importers/_rdf/_base.py +2 -2
- cognite/neat/_rules/importers/_rdf/_inference2rules.py +310 -26
- cognite/neat/_rules/models/_base_rules.py +22 -11
- cognite/neat/_rules/models/dms/_exporter.py +5 -4
- cognite/neat/_rules/models/dms/_rules.py +1 -8
- cognite/neat/_rules/models/dms/_rules_input.py +4 -0
- cognite/neat/_rules/models/information/_rules_input.py +5 -0
- cognite/neat/_rules/transformers/__init__.py +10 -3
- cognite/neat/_rules/transformers/_base.py +6 -1
- cognite/neat/_rules/transformers/_converters.py +530 -364
- cognite/neat/_rules/transformers/_mapping.py +4 -4
- cognite/neat/_session/_base.py +100 -47
- cognite/neat/_session/_create.py +133 -0
- cognite/neat/_session/_drop.py +60 -2
- cognite/neat/_session/_fix.py +28 -0
- cognite/neat/_session/_inspect.py +22 -7
- cognite/neat/_session/_mapping.py +8 -8
- cognite/neat/_session/_prepare.py +3 -247
- cognite/neat/_session/_read.py +138 -17
- cognite/neat/_session/_set.py +50 -1
- cognite/neat/_session/_show.py +16 -43
- cognite/neat/_session/_state.py +53 -52
- cognite/neat/_session/_to.py +11 -4
- cognite/neat/_session/_wizard.py +1 -1
- cognite/neat/_session/exceptions.py +8 -1
- cognite/neat/_store/_graph_store.py +301 -146
- cognite/neat/_store/_provenance.py +36 -20
- cognite/neat/_store/_rules_store.py +253 -267
- cognite/neat/_store/exceptions.py +40 -4
- cognite/neat/_utils/auth.py +5 -3
- cognite/neat/_version.py +1 -1
- {cognite_neat-0.107.0.dist-info → cognite_neat-0.109.0.dist-info}/METADATA +1 -1
- {cognite_neat-0.107.0.dist-info → cognite_neat-0.109.0.dist-info}/RECORD +69 -67
- {cognite_neat-0.107.0.dist-info → cognite_neat-0.109.0.dist-info}/LICENSE +0 -0
- {cognite_neat-0.107.0.dist-info → cognite_neat-0.109.0.dist-info}/WHEEL +0 -0
- {cognite_neat-0.107.0.dist-info → cognite_neat-0.109.0.dist-info}/entry_points.txt +0 -0
|
@@ -13,10 +13,10 @@ from cognite.neat._rules.models.data_types import Enum
|
|
|
13
13
|
from cognite.neat._rules.models.dms import DMSContainer, DMSEnum, DMSProperty
|
|
14
14
|
from cognite.neat._rules.models.entities import ClassEntity, ContainerEntity, ViewEntity
|
|
15
15
|
|
|
16
|
-
from ._base import
|
|
16
|
+
from ._base import VerifiedRulesTransformer
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
class MapOntoTransformers(
|
|
19
|
+
class MapOntoTransformers(VerifiedRulesTransformer[DMSRules, DMSRules], ABC):
|
|
20
20
|
"""Base class for transformers that map one rule onto another."""
|
|
21
21
|
|
|
22
22
|
...
|
|
@@ -100,7 +100,7 @@ class MapOneToOne(MapOntoTransformers):
|
|
|
100
100
|
return solution
|
|
101
101
|
|
|
102
102
|
|
|
103
|
-
class RuleMapper(
|
|
103
|
+
class RuleMapper(VerifiedRulesTransformer[DMSRules, DMSRules]):
|
|
104
104
|
"""Maps properties and classes using the given mapping.
|
|
105
105
|
|
|
106
106
|
Args:
|
|
@@ -232,7 +232,7 @@ class RuleMapper(RulesTransformer[DMSRules, DMSRules]):
|
|
|
232
232
|
return f"Mapping to {self.mapping.metadata.as_data_model_id()!r}."
|
|
233
233
|
|
|
234
234
|
|
|
235
|
-
class AsParentPropertyId(
|
|
235
|
+
class AsParentPropertyId(VerifiedRulesTransformer[DMSRules, DMSRules]):
|
|
236
236
|
"""Looks up all view properties that map to the same container property,
|
|
237
237
|
and changes the child view property id to match the parent property id.
|
|
238
238
|
"""
|
cognite/neat/_session/_base.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from pathlib import Path
|
|
1
2
|
from typing import Literal
|
|
2
3
|
|
|
3
4
|
from cognite.client import CogniteClient
|
|
@@ -7,14 +8,23 @@ from cognite.neat import _version
|
|
|
7
8
|
from cognite.neat._client import NeatClient
|
|
8
9
|
from cognite.neat._issues import IssueList
|
|
9
10
|
from cognite.neat._issues.errors import RegexViolationError
|
|
11
|
+
from cognite.neat._issues.errors._general import NeatImportError
|
|
10
12
|
from cognite.neat._rules import importers
|
|
11
|
-
from cognite.neat._rules.models
|
|
13
|
+
from cognite.neat._rules.models import DMSRules
|
|
12
14
|
from cognite.neat._rules.models.information._rules import InformationRules
|
|
13
|
-
from cognite.neat._rules.transformers import
|
|
14
|
-
|
|
15
|
+
from cognite.neat._rules.transformers import (
|
|
16
|
+
InformationToDMS,
|
|
17
|
+
MergeDMSRules,
|
|
18
|
+
MergeInformationRules,
|
|
19
|
+
VerifyInformationRules,
|
|
20
|
+
)
|
|
21
|
+
from cognite.neat._store._rules_store import RulesEntity
|
|
22
|
+
from cognite.neat._utils.auxiliary import local_import
|
|
15
23
|
|
|
16
24
|
from ._collector import _COLLECTOR, Collector
|
|
25
|
+
from ._create import CreateAPI
|
|
17
26
|
from ._drop import DropAPI
|
|
27
|
+
from ._fix import FixAPI
|
|
18
28
|
from ._inspect import InspectAPI
|
|
19
29
|
from ._mapping import MappingAPI
|
|
20
30
|
from ._prepare import PrepareAPI
|
|
@@ -24,7 +34,7 @@ from ._show import ShowAPI
|
|
|
24
34
|
from ._state import SessionState
|
|
25
35
|
from ._to import ToAPI
|
|
26
36
|
from .engine import load_neat_engine
|
|
27
|
-
from .exceptions import
|
|
37
|
+
from .exceptions import session_class_wrapper
|
|
28
38
|
|
|
29
39
|
|
|
30
40
|
@session_class_wrapper
|
|
@@ -70,25 +80,42 @@ class NeatSession:
|
|
|
70
80
|
def __init__(
|
|
71
81
|
self,
|
|
72
82
|
client: CogniteClient | None = None,
|
|
73
|
-
storage: Literal["memory", "oxigraph"] =
|
|
83
|
+
storage: Literal["memory", "oxigraph"] | None = None,
|
|
84
|
+
storage_path: str | None = None,
|
|
74
85
|
verbose: bool = True,
|
|
75
86
|
load_engine: Literal["newest", "cache", "skip"] = "cache",
|
|
76
87
|
) -> None:
|
|
77
88
|
self._verbose = verbose
|
|
78
|
-
self._state = SessionState(
|
|
89
|
+
self._state = SessionState(
|
|
90
|
+
store_type=storage or self._select_most_performant_store(),
|
|
91
|
+
storage_path=Path(storage_path) if storage_path else None,
|
|
92
|
+
client=NeatClient(client) if client else None,
|
|
93
|
+
)
|
|
79
94
|
self.read = ReadAPI(self._state, verbose)
|
|
80
95
|
self.to = ToAPI(self._state, verbose)
|
|
96
|
+
self.fix = FixAPI(self._state, verbose)
|
|
81
97
|
self.prepare = PrepareAPI(self._state, verbose)
|
|
82
98
|
self.show = ShowAPI(self._state)
|
|
83
99
|
self.set = SetAPI(self._state, verbose)
|
|
84
100
|
self.inspect = InspectAPI(self._state)
|
|
85
101
|
self.mapping = MappingAPI(self._state)
|
|
86
102
|
self.drop = DropAPI(self._state)
|
|
103
|
+
self.create = CreateAPI(self._state)
|
|
87
104
|
self.opt = OptAPI()
|
|
88
105
|
self.opt._display()
|
|
89
106
|
if load_engine != "skip" and (engine_version := load_neat_engine(client, load_engine)):
|
|
90
107
|
print(f"Neat Engine {engine_version} loaded.")
|
|
91
108
|
|
|
109
|
+
def _select_most_performant_store(self) -> Literal["memory", "oxigraph"]:
|
|
110
|
+
"""Select the most performant store based on the current environment."""
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
local_import("pyoxigraph", "oxi")
|
|
114
|
+
local_import("oxrdflib", "oxi")
|
|
115
|
+
return "oxigraph"
|
|
116
|
+
except NeatImportError:
|
|
117
|
+
return "memory"
|
|
118
|
+
|
|
92
119
|
@property
|
|
93
120
|
def version(self) -> str:
|
|
94
121
|
"""Get the current version of neat.
|
|
@@ -118,46 +145,27 @@ class NeatSession:
|
|
|
118
145
|
neat.verify()
|
|
119
146
|
```
|
|
120
147
|
"""
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if not issues.has_errors:
|
|
124
|
-
rules = self._state.rule_store.last_verified_rule
|
|
125
|
-
if isinstance(rules, InformationRules):
|
|
126
|
-
self._state.instances.store.add_rules(rules)
|
|
127
|
-
|
|
128
|
-
if issues:
|
|
129
|
-
print("You can inspect the issues with the .inspect.issues(...) method.")
|
|
130
|
-
return issues
|
|
148
|
+
print("This action has no effect. Neat no longer supports unverified data models.")
|
|
149
|
+
return IssueList()
|
|
131
150
|
|
|
132
|
-
def convert(self,
|
|
151
|
+
def convert(self, reserved_properties: Literal["error", "warning"] = "warning") -> IssueList:
|
|
133
152
|
"""Converts the last verified data model to the target type.
|
|
134
153
|
|
|
135
154
|
Args:
|
|
136
|
-
|
|
155
|
+
reserved_properties: What to do with reserved properties. Can be "error" or "warning".
|
|
137
156
|
|
|
138
157
|
Example:
|
|
139
158
|
Convert to DMS rules
|
|
140
159
|
```python
|
|
141
|
-
neat.convert(
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
Example:
|
|
145
|
-
Convert to Information rules
|
|
146
|
-
```python
|
|
147
|
-
neat.convert(target="information")
|
|
160
|
+
neat.convert()
|
|
148
161
|
```
|
|
149
162
|
"""
|
|
150
|
-
converter
|
|
151
|
-
|
|
152
|
-
converter = InformationToDMS()
|
|
153
|
-
elif target == "information":
|
|
154
|
-
converter = ConvertToRules(InformationRules)
|
|
155
|
-
else:
|
|
156
|
-
raise NeatSessionError(f"Target {target} not supported.")
|
|
163
|
+
converter = InformationToDMS(reserved_properties=reserved_properties)
|
|
164
|
+
|
|
157
165
|
issues = self._state.rule_transform(converter)
|
|
158
166
|
|
|
159
167
|
if self._verbose and not issues.has_errors:
|
|
160
|
-
print(
|
|
168
|
+
print("Rules converted to dms.")
|
|
161
169
|
else:
|
|
162
170
|
print("Conversion failed.")
|
|
163
171
|
if issues:
|
|
@@ -174,13 +182,11 @@ class NeatSession:
|
|
|
174
182
|
"NeatInferredDataModel",
|
|
175
183
|
"v1",
|
|
176
184
|
),
|
|
177
|
-
max_number_of_instance: int = 100,
|
|
178
185
|
) -> IssueList:
|
|
179
186
|
"""Data model inference from instances.
|
|
180
187
|
|
|
181
188
|
Args:
|
|
182
189
|
model_id: The ID of the inferred data model.
|
|
183
|
-
max_number_of_instance: The maximum number of instances to use for inference.
|
|
184
190
|
|
|
185
191
|
Example:
|
|
186
192
|
Infer a data model after reading a source file
|
|
@@ -191,6 +197,12 @@ class NeatSession:
|
|
|
191
197
|
neat.infer()
|
|
192
198
|
```
|
|
193
199
|
"""
|
|
200
|
+
return self._infer_subclasses(model_id)
|
|
201
|
+
|
|
202
|
+
def _previous_inference(
|
|
203
|
+
self, model_id: dm.DataModelId | tuple[str, str, str], max_number_of_instance: int = 100
|
|
204
|
+
) -> IssueList:
|
|
205
|
+
# Temporary keeping the old inference method in case we need to revert back
|
|
194
206
|
model_id = dm.DataModelId.load(model_id)
|
|
195
207
|
importer = importers.InferenceImporter.from_graph_store(
|
|
196
208
|
store=self._state.instances.store,
|
|
@@ -199,29 +211,70 @@ class NeatSession:
|
|
|
199
211
|
)
|
|
200
212
|
return self._state.rule_import(importer)
|
|
201
213
|
|
|
214
|
+
def _infer_subclasses(
|
|
215
|
+
self,
|
|
216
|
+
model_id: dm.DataModelId | tuple[str, str, str] = (
|
|
217
|
+
"neat_space",
|
|
218
|
+
"NeatInferredDataModel",
|
|
219
|
+
"v1",
|
|
220
|
+
),
|
|
221
|
+
) -> IssueList:
|
|
222
|
+
"""Infer data model from instances."""
|
|
223
|
+
last_entity: RulesEntity | None = None
|
|
224
|
+
if self._state.rule_store.provenance:
|
|
225
|
+
last_entity = self._state.rule_store.provenance[-1].target_entity
|
|
226
|
+
|
|
227
|
+
# Note that this importer behaves as a transformer in the rule store when there is an existing rules.
|
|
228
|
+
# We are essentially transforming the last entity's information rules into a new set of information rules.
|
|
229
|
+
importer = importers.SubclassInferenceImporter(
|
|
230
|
+
issue_list=IssueList(),
|
|
231
|
+
graph=self._state.instances.store.graph(),
|
|
232
|
+
rules=last_entity.information if last_entity is not None else None,
|
|
233
|
+
data_model_id=dm.DataModelId.load(model_id) if last_entity is None else None,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
def action() -> tuple[InformationRules, DMSRules | None]:
|
|
237
|
+
unverified_information = importer.to_rules()
|
|
238
|
+
extra_info = VerifyInformationRules().transform(unverified_information)
|
|
239
|
+
if not last_entity:
|
|
240
|
+
return extra_info, None
|
|
241
|
+
merged_info = MergeInformationRules(extra_info).transform(last_entity.information)
|
|
242
|
+
if not last_entity.dms:
|
|
243
|
+
return merged_info, None
|
|
244
|
+
extra_dms = InformationToDMS(reserved_properties="warning").transform(extra_info)
|
|
245
|
+
merged_dms = MergeDMSRules(extra_dms).transform(last_entity.dms)
|
|
246
|
+
return merged_info, merged_dms
|
|
247
|
+
|
|
248
|
+
return self._state.rule_store.do_activity(action, importer)
|
|
249
|
+
|
|
202
250
|
def _repr_html_(self) -> str:
|
|
203
251
|
state = self._state
|
|
204
|
-
if
|
|
205
|
-
not state.instances.has_store
|
|
206
|
-
and not state.rule_store.has_unverified_rules
|
|
207
|
-
and not state.rule_store.has_verified_rules
|
|
208
|
-
):
|
|
252
|
+
if state.instances.empty and state.rule_store.empty:
|
|
209
253
|
return "<strong>Empty session</strong>. Get started by reading something with the <em>.read</em> attribute."
|
|
210
254
|
|
|
211
255
|
output = []
|
|
212
256
|
|
|
213
|
-
if state.rule_store.
|
|
214
|
-
|
|
215
|
-
|
|
257
|
+
if state.rule_store.provenance:
|
|
258
|
+
last_entity = state.rule_store.provenance[-1].target_entity
|
|
259
|
+
if last_entity.dms:
|
|
260
|
+
html = last_entity.dms._repr_html_()
|
|
261
|
+
else:
|
|
262
|
+
html = last_entity.information._repr_html_()
|
|
263
|
+
output.append(f"<H2>Data Model</H2><br />{html}") # type: ignore
|
|
216
264
|
|
|
217
|
-
if state.
|
|
218
|
-
output.append(f"<H2>Verified Data Model</H2><br />{state.rule_store.last_verified_rule._repr_html_()}") # type: ignore
|
|
219
|
-
|
|
220
|
-
if state.instances.has_store:
|
|
265
|
+
if not state.instances.empty:
|
|
221
266
|
output.append(f"<H2>Instances</H2> {state.instances.store._repr_html_()}")
|
|
222
267
|
|
|
223
268
|
return "<br />".join(output)
|
|
224
269
|
|
|
270
|
+
def close(self) -> None:
|
|
271
|
+
"""Close the session and release resources."""
|
|
272
|
+
self._state.instances.store.dataset.close()
|
|
273
|
+
|
|
274
|
+
def __del__(self) -> None:
|
|
275
|
+
"""Called by garbage collector"""
|
|
276
|
+
self.close()
|
|
277
|
+
|
|
225
278
|
|
|
226
279
|
@session_class_wrapper
|
|
227
280
|
class OptAPI:
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
from cognite.client.data_classes.data_modeling import DataModelIdentifier
|
|
4
|
+
|
|
5
|
+
from cognite.neat._issues import IssueList
|
|
6
|
+
from cognite.neat._rules.models.dms import DMSValidation
|
|
7
|
+
from cognite.neat._rules.transformers import (
|
|
8
|
+
IncludeReferenced,
|
|
9
|
+
ToDataProductModel,
|
|
10
|
+
ToEnterpriseModel,
|
|
11
|
+
ToSolutionModel,
|
|
12
|
+
VerifiedRulesTransformer,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from ._state import SessionState
|
|
16
|
+
from .exceptions import NeatSessionError, session_class_wrapper
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@session_class_wrapper
|
|
20
|
+
class CreateAPI:
|
|
21
|
+
"""
|
|
22
|
+
Create new data model based on the given data.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, state: SessionState):
|
|
26
|
+
self._state = state
|
|
27
|
+
|
|
28
|
+
def enterprise_model(
|
|
29
|
+
self,
|
|
30
|
+
data_model_id: DataModelIdentifier,
|
|
31
|
+
org_name: str = "My",
|
|
32
|
+
dummy_property: str = "GUID",
|
|
33
|
+
) -> IssueList:
|
|
34
|
+
"""Uses the current data model as a basis to create enterprise data model
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
data_model_id: The enterprise data model id that is being created
|
|
38
|
+
org_name: Organization name to use for the views in the enterprise data model.
|
|
39
|
+
dummy_property: The dummy property to use as placeholder for the views in the new data model.
|
|
40
|
+
|
|
41
|
+
!!! note "Enterprise Data Model Creation"
|
|
42
|
+
|
|
43
|
+
Always create an enterprise data model from a Cognite Data Model as this will
|
|
44
|
+
assure all the Cognite Data Fusion applications to run smoothly, such as
|
|
45
|
+
- Search
|
|
46
|
+
- Atlas AI
|
|
47
|
+
- ...
|
|
48
|
+
|
|
49
|
+
!!! note "Move Connections"
|
|
50
|
+
|
|
51
|
+
If you want to move the connections to the new data model, set the move_connections
|
|
52
|
+
to True. This will move the connections to the new data model and use new model
|
|
53
|
+
views as the source and target views.
|
|
54
|
+
|
|
55
|
+
"""
|
|
56
|
+
return self._state.rule_transform(
|
|
57
|
+
ToEnterpriseModel(
|
|
58
|
+
new_model_id=data_model_id,
|
|
59
|
+
org_name=org_name,
|
|
60
|
+
dummy_property=dummy_property,
|
|
61
|
+
move_connections=True,
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def solution_model(
|
|
66
|
+
self,
|
|
67
|
+
data_model_id: DataModelIdentifier,
|
|
68
|
+
direct_property: str = "enterprise",
|
|
69
|
+
view_prefix: str = "Enterprise",
|
|
70
|
+
) -> IssueList:
|
|
71
|
+
"""Uses the current data model as a basis to create solution data model
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
data_model_id: The solution data model id that is being created.
|
|
75
|
+
direct_property: The property to use for the direct connection between the views in the solution data model
|
|
76
|
+
and the enterprise data model.
|
|
77
|
+
view_prefix: The prefix to use for the views in the enterprise data model.
|
|
78
|
+
|
|
79
|
+
!!! note "Solution Data Model Mode"
|
|
80
|
+
|
|
81
|
+
The read-only solution model will only be able to read from the existing containers
|
|
82
|
+
from the enterprise data model, therefore the solution data model will not have
|
|
83
|
+
containers in the solution data model space. Meaning the solution data model views
|
|
84
|
+
will be read-only.
|
|
85
|
+
|
|
86
|
+
The write mode will have additional containers in the solution data model space,
|
|
87
|
+
allowing in addition to read through the solution model views, also writing to
|
|
88
|
+
the containers in the solution data model space.
|
|
89
|
+
|
|
90
|
+
"""
|
|
91
|
+
return self._state.rule_transform(
|
|
92
|
+
ToSolutionModel(
|
|
93
|
+
new_model_id=data_model_id,
|
|
94
|
+
properties="connection",
|
|
95
|
+
direct_property=direct_property,
|
|
96
|
+
view_prefix=view_prefix,
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def data_product_model(
|
|
101
|
+
self,
|
|
102
|
+
data_model_id: DataModelIdentifier,
|
|
103
|
+
include: Literal["same-space", "all"] = "same-space",
|
|
104
|
+
) -> None:
|
|
105
|
+
"""Uses the current data model as a basis to create data product data model.
|
|
106
|
+
|
|
107
|
+
A data product model is a data model that ONLY maps to containers and do not use implements. This is
|
|
108
|
+
typically used for defining the data in a data product.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
data_model_id: The data product data model id that is being created.
|
|
112
|
+
include: The views to include in the data product data model. Can be either "same-space" or "all".
|
|
113
|
+
If you set same-space, only the properties of the views in the same space as the data model
|
|
114
|
+
will be included.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
view_ids, container_ids = DMSValidation(
|
|
118
|
+
self._state.rule_store.last_verified_dms_rules
|
|
119
|
+
).imported_views_and_containers_ids()
|
|
120
|
+
transformers: list[VerifiedRulesTransformer] = []
|
|
121
|
+
client = self._state.client
|
|
122
|
+
if (view_ids or container_ids) and client is None:
|
|
123
|
+
raise NeatSessionError(
|
|
124
|
+
"No client provided. You are referencing unknown views and containers in your data model, "
|
|
125
|
+
"NEAT needs a client to lookup the definitions. "
|
|
126
|
+
"Please set the client in the session, NeatSession(client=client)."
|
|
127
|
+
)
|
|
128
|
+
elif (view_ids or container_ids) and client:
|
|
129
|
+
transformers.append(IncludeReferenced(client, include_properties=True))
|
|
130
|
+
|
|
131
|
+
transformers.append(ToDataProductModel(new_model_id=data_model_id, include=include))
|
|
132
|
+
|
|
133
|
+
self._state.rule_transform(*transformers)
|
cognite/neat/_session/_drop.py
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
|
+
import difflib
|
|
2
|
+
from collections.abc import Collection
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
from cognite.client.utils.useful_types import SequenceNotStr
|
|
1
6
|
from rdflib import URIRef
|
|
2
7
|
|
|
8
|
+
from cognite.neat._constants import COGNITE_MODELS
|
|
9
|
+
from cognite.neat._issues import IssueList
|
|
10
|
+
from cognite.neat._rules.transformers import DropModelViews
|
|
11
|
+
|
|
3
12
|
from ._state import SessionState
|
|
4
|
-
from .exceptions import session_class_wrapper
|
|
13
|
+
from .exceptions import NeatSessionError, session_class_wrapper
|
|
5
14
|
|
|
6
15
|
try:
|
|
7
16
|
from rich import print
|
|
@@ -17,6 +26,7 @@ class DropAPI:
|
|
|
17
26
|
|
|
18
27
|
def __init__(self, state: SessionState):
|
|
19
28
|
self._state = state
|
|
29
|
+
self.data_model = DropDataModelAPI(state)
|
|
20
30
|
|
|
21
31
|
def instances(self, type: str | list[str]) -> None:
|
|
22
32
|
"""Drop instances from the session.
|
|
@@ -31,7 +41,11 @@ class DropAPI:
|
|
|
31
41
|
```
|
|
32
42
|
"""
|
|
33
43
|
type_list = type if isinstance(type, list) else [type]
|
|
34
|
-
|
|
44
|
+
|
|
45
|
+
# Temporary solution until we agree on the form of specifying named graphs
|
|
46
|
+
# it will default to the default named graph
|
|
47
|
+
named_graph = self._state.instances.store.default_named_graph
|
|
48
|
+
uri_type_type = dict((v, k) for k, v in self._state.instances.store.queries.types(named_graph).items())
|
|
35
49
|
selected_uri_by_type: dict[URIRef, str] = {}
|
|
36
50
|
for type_item in type_list:
|
|
37
51
|
if type_item not in uri_type_type:
|
|
@@ -43,3 +57,47 @@ class DropAPI:
|
|
|
43
57
|
for type_uri, count in result.items():
|
|
44
58
|
print(f"Dropped {count} instances of type {selected_uri_by_type[type_uri]}")
|
|
45
59
|
return None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@session_class_wrapper
|
|
63
|
+
class DropDataModelAPI:
|
|
64
|
+
def __init__(self, state: SessionState):
|
|
65
|
+
self._state = state
|
|
66
|
+
|
|
67
|
+
def views(
|
|
68
|
+
self,
|
|
69
|
+
view_external_id: str | SequenceNotStr[str] | None = None,
|
|
70
|
+
group: Literal["3D", "Annotation", "BaseViews"]
|
|
71
|
+
| Collection[Literal["3D", "Annotation", "BaseViews"]]
|
|
72
|
+
| None = None,
|
|
73
|
+
) -> IssueList:
|
|
74
|
+
"""Drops views from the data model.
|
|
75
|
+
Args:
|
|
76
|
+
view_external_id: The externalId of the view to drop.
|
|
77
|
+
group: Only applies to CogniteCore model. This is a shorthand for dropping multiple views at once.
|
|
78
|
+
"""
|
|
79
|
+
if sum([view_external_id is not None, group is not None]) != 1:
|
|
80
|
+
raise NeatSessionError("Only one of view_external_id or group can be specified.")
|
|
81
|
+
last_dms = self._state.rule_store.last_verified_dms_rules
|
|
82
|
+
if group is not None and last_dms.metadata.as_data_model_id() not in COGNITE_MODELS:
|
|
83
|
+
raise NeatSessionError("Group can only be specified for CogniteCore models.")
|
|
84
|
+
if view_external_id is not None:
|
|
85
|
+
existing_views = {view.view.external_id for view in last_dms.views}
|
|
86
|
+
requested_views = {view_external_id} if isinstance(view_external_id, str) else set(view_external_id)
|
|
87
|
+
missing_views = requested_views - existing_views
|
|
88
|
+
if missing_views:
|
|
89
|
+
suggestions: list[str] = []
|
|
90
|
+
for view in missing_views:
|
|
91
|
+
suggestion = difflib.get_close_matches(view, existing_views, n=1)
|
|
92
|
+
if suggestion:
|
|
93
|
+
suggestions.append(f"{view} -> {suggestion[0]}")
|
|
94
|
+
else:
|
|
95
|
+
suggestions.append(f"{view} -> NOT FOUND")
|
|
96
|
+
raise NeatSessionError(
|
|
97
|
+
f"{len(missing_views)} view(s) not found in the data model.\nDid you mean {', '.join(suggestions)}?"
|
|
98
|
+
)
|
|
99
|
+
before = len(last_dms.views)
|
|
100
|
+
issues = self._state.rule_transform(DropModelViews(view_external_id, group))
|
|
101
|
+
after = len(self._state.rule_store.last_verified_dms_rules.views)
|
|
102
|
+
print(f"Dropped {before - after} views.")
|
|
103
|
+
return issues
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from cognite.neat._issues._base import IssueList
|
|
2
|
+
from cognite.neat._rules.transformers import (
|
|
3
|
+
ToCompliantEntities,
|
|
4
|
+
)
|
|
5
|
+
|
|
6
|
+
from ._state import SessionState
|
|
7
|
+
from .exceptions import session_class_wrapper
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@session_class_wrapper
|
|
11
|
+
class FixAPI:
|
|
12
|
+
"""Apply variety of fix methods to data model and isntances"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, state: SessionState, verbose: bool) -> None:
|
|
15
|
+
self._state = state
|
|
16
|
+
self._verbose = verbose
|
|
17
|
+
self.data_model = DataModelFixAPI(state, verbose)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@session_class_wrapper
|
|
21
|
+
class DataModelFixAPI:
|
|
22
|
+
def __init__(self, state: SessionState, verbose: bool) -> None:
|
|
23
|
+
self._state = state
|
|
24
|
+
self._verbose = verbose
|
|
25
|
+
|
|
26
|
+
def cdf_compliant_external_ids(self) -> IssueList:
|
|
27
|
+
"""Convert (information/logical) data model component external ids to CDF compliant entities."""
|
|
28
|
+
return self._state.rule_transform(ToCompliantEntities())
|
|
@@ -48,7 +48,6 @@ class InspectAPI:
|
|
|
48
48
|
self.issues = InspectIssues(state)
|
|
49
49
|
self.outcome = InspectOutcome(state)
|
|
50
50
|
|
|
51
|
-
@property
|
|
52
51
|
def properties(self) -> pd.DataFrame:
|
|
53
52
|
"""Returns the properties of the current data model.
|
|
54
53
|
|
|
@@ -59,7 +58,23 @@ class InspectAPI:
|
|
|
59
58
|
neat.inspect.properties
|
|
60
59
|
```
|
|
61
60
|
"""
|
|
62
|
-
|
|
61
|
+
if self._state.rule_store.empty:
|
|
62
|
+
return pd.DataFrame()
|
|
63
|
+
last_entity = self._state.rule_store.provenance[-1].target_entity
|
|
64
|
+
if last_entity.dms:
|
|
65
|
+
df = last_entity.dms.properties.to_pandas()
|
|
66
|
+
else:
|
|
67
|
+
df = last_entity.information.properties.to_pandas()
|
|
68
|
+
df.drop(columns=["neatId"], errors="ignore", inplace=True)
|
|
69
|
+
return df
|
|
70
|
+
|
|
71
|
+
def views(self) -> pd.DataFrame:
|
|
72
|
+
if self._state.rule_store.empty:
|
|
73
|
+
return pd.DataFrame()
|
|
74
|
+
last_entity = self._state.rule_store.provenance[-1].target_entity
|
|
75
|
+
if last_entity.dms is None:
|
|
76
|
+
return pd.DataFrame()
|
|
77
|
+
df = last_entity.dms.views.to_pandas()
|
|
63
78
|
df.drop(columns=["neatId"], errors="ignore", inplace=True)
|
|
64
79
|
return df
|
|
65
80
|
|
|
@@ -91,10 +106,10 @@ class InspectIssues:
|
|
|
91
106
|
return_dataframe: bool = (False if IN_NOTEBOOK else True), # type: ignore[assignment]
|
|
92
107
|
) -> pd.DataFrame | None:
|
|
93
108
|
"""Returns the issues of the current data model."""
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
issues =
|
|
109
|
+
issues = self._state.rule_store.last_issues
|
|
110
|
+
if issues is None and self._state.instances.store.provenance:
|
|
111
|
+
last_change = self._state.instances.store.provenance[-1]
|
|
112
|
+
issues = last_change.target_entity.issues
|
|
98
113
|
else:
|
|
99
114
|
self._print("No issues found.")
|
|
100
115
|
return pd.DataFrame() if return_dataframe else None
|
|
@@ -231,7 +246,7 @@ class InspectUploadOutcome:
|
|
|
231
246
|
if i < 50:
|
|
232
247
|
lines.append(f" * {v}")
|
|
233
248
|
elif i == 50 and total > 50:
|
|
234
|
-
lines.append(f" * ... {total-50} more")
|
|
249
|
+
lines.append(f" * ... {total - 50} more")
|
|
235
250
|
elif i == 50 and total == 50:
|
|
236
251
|
lines.append(f" * {v}")
|
|
237
252
|
else:
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
from cognite.neat._issues import IssueList
|
|
2
|
-
from cognite.neat._rules.models import DMSRules
|
|
3
2
|
from cognite.neat._rules.models.mapping import load_classic_to_core_mapping
|
|
4
3
|
from cognite.neat._rules.transformers import (
|
|
5
4
|
AsParentPropertyId,
|
|
6
5
|
ChangeViewPrefix,
|
|
7
6
|
IncludeReferenced,
|
|
8
7
|
RuleMapper,
|
|
9
|
-
|
|
8
|
+
VerifiedRulesTransformer,
|
|
10
9
|
)
|
|
11
10
|
|
|
12
11
|
from ._state import SessionState
|
|
@@ -43,15 +42,16 @@ class DataModelMappingAPI:
|
|
|
43
42
|
neat.mapping.classic_to_core(company_prefix="WindFarmX", use_parent_property_name=True)
|
|
44
43
|
```
|
|
45
44
|
"""
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
if self._state.rule_store.empty:
|
|
46
|
+
raise NeatSessionError("No rules to map")
|
|
47
|
+
last_entity = self._state.rule_store.provenance[-1].target_entity
|
|
48
|
+
if last_entity.dms is None:
|
|
49
|
+
raise NeatSessionError("Data model not converted to DMS. Try running `neat.convert('dms')` first.")
|
|
50
|
+
rules = last_entity.dms
|
|
51
51
|
if self._state.client is None:
|
|
52
52
|
raise NeatSessionError("Client is required to map classic to core")
|
|
53
53
|
|
|
54
|
-
transformers: list[
|
|
54
|
+
transformers: list[VerifiedRulesTransformer] = []
|
|
55
55
|
if company_prefix:
|
|
56
56
|
transformers.append(ChangeViewPrefix("Classic", company_prefix))
|
|
57
57
|
transformers.extend(
|