cognite-neat 0.106.0__py3-none-any.whl → 0.108.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/__init__.py +5 -1
- cognite/neat/_graph/extractors/_base.py +32 -0
- cognite/neat/_graph/extractors/_classic_cdf/_base.py +128 -14
- cognite/neat/_graph/extractors/_classic_cdf/_classic.py +156 -12
- cognite/neat/_graph/extractors/_classic_cdf/_relationships.py +50 -12
- cognite/neat/_graph/extractors/_classic_cdf/_sequences.py +26 -1
- cognite/neat/_graph/extractors/_dms.py +196 -47
- cognite/neat/_graph/extractors/_dms_graph.py +199 -0
- cognite/neat/_graph/extractors/_mock_graph_generator.py +1 -1
- cognite/neat/_graph/extractors/_rdf_file.py +33 -5
- cognite/neat/_graph/loaders/__init__.py +1 -3
- cognite/neat/_graph/loaders/_rdf2dms.py +123 -19
- cognite/neat/_graph/queries/_base.py +140 -84
- cognite/neat/_graph/queries/_construct.py +2 -2
- cognite/neat/_graph/transformers/__init__.py +8 -1
- cognite/neat/_graph/transformers/_base.py +9 -1
- cognite/neat/_graph/transformers/_classic_cdf.py +90 -3
- cognite/neat/_graph/transformers/_rdfpath.py +3 -3
- cognite/neat/_graph/transformers/_value_type.py +106 -45
- 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/_rules/analysis/_base.py +1 -1
- cognite/neat/_rules/analysis/_information.py +14 -13
- cognite/neat/_rules/catalog/__init__.py +1 -0
- cognite/neat/_rules/catalog/classic_model.xlsx +0 -0
- cognite/neat/_rules/catalog/info-rules-imf.xlsx +0 -0
- cognite/neat/_rules/exporters/_rules2instance_template.py +3 -3
- cognite/neat/_rules/importers/__init__.py +3 -1
- cognite/neat/_rules/importers/_dms2rules.py +7 -5
- 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 +242 -19
- cognite/neat/_rules/models/_base_rules.py +13 -15
- cognite/neat/_rules/models/_types.py +5 -0
- cognite/neat/_rules/models/dms/_rules.py +51 -10
- cognite/neat/_rules/models/dms/_rules_input.py +4 -0
- cognite/neat/_rules/models/information/_rules.py +48 -5
- cognite/neat/_rules/models/information/_rules_input.py +6 -1
- cognite/neat/_rules/models/mapping/_classic2core.py +4 -5
- cognite/neat/_rules/transformers/__init__.py +10 -0
- cognite/neat/_rules/transformers/_converters.py +300 -62
- cognite/neat/_session/_base.py +57 -10
- cognite/neat/_session/_drop.py +5 -1
- cognite/neat/_session/_inspect.py +3 -2
- cognite/neat/_session/_mapping.py +17 -6
- cognite/neat/_session/_prepare.py +0 -47
- cognite/neat/_session/_read.py +115 -10
- cognite/neat/_session/_set.py +27 -0
- cognite/neat/_session/_show.py +4 -4
- cognite/neat/_session/_state.py +12 -1
- cognite/neat/_session/_to.py +43 -2
- cognite/neat/_session/_wizard.py +1 -1
- cognite/neat/_session/exceptions.py +8 -3
- cognite/neat/_store/_graph_store.py +331 -136
- cognite/neat/_store/_rules_store.py +130 -1
- cognite/neat/_utils/auth.py +3 -1
- cognite/neat/_version.py +1 -1
- {cognite_neat-0.106.0.dist-info → cognite_neat-0.108.0.dist-info}/METADATA +2 -2
- {cognite_neat-0.106.0.dist-info → cognite_neat-0.108.0.dist-info}/RECORD +67 -65
- {cognite_neat-0.106.0.dist-info → cognite_neat-0.108.0.dist-info}/WHEEL +1 -1
- {cognite_neat-0.106.0.dist-info → cognite_neat-0.108.0.dist-info}/LICENSE +0 -0
- {cognite_neat-0.106.0.dist-info → cognite_neat-0.108.0.dist-info}/entry_points.txt +0 -0
cognite/neat/_session/_drop.py
CHANGED
|
@@ -31,7 +31,11 @@ class DropAPI:
|
|
|
31
31
|
```
|
|
32
32
|
"""
|
|
33
33
|
type_list = type if isinstance(type, list) else [type]
|
|
34
|
-
|
|
34
|
+
|
|
35
|
+
# Temporary solution until we agree on the form of specifying named graphs
|
|
36
|
+
# it will default to the default named graph
|
|
37
|
+
named_graph = self._state.instances.store.default_named_graph
|
|
38
|
+
uri_type_type = dict((v, k) for k, v in self._state.instances.store.queries.types(named_graph).items())
|
|
35
39
|
selected_uri_by_type: dict[URIRef, str] = {}
|
|
36
40
|
for type_item in type_list:
|
|
37
41
|
if type_item not in uri_type_type:
|
|
@@ -94,7 +94,8 @@ class InspectIssues:
|
|
|
94
94
|
if self._state.rule_store.provenance:
|
|
95
95
|
issues = self._state.rule_store.last_issues
|
|
96
96
|
elif self._state.instances.store.provenance:
|
|
97
|
-
|
|
97
|
+
last_change = self._state.instances.store.provenance[-1]
|
|
98
|
+
issues = last_change.target_entity.issues
|
|
98
99
|
else:
|
|
99
100
|
self._print("No issues found.")
|
|
100
101
|
return pd.DataFrame() if return_dataframe else None
|
|
@@ -231,7 +232,7 @@ class InspectUploadOutcome:
|
|
|
231
232
|
if i < 50:
|
|
232
233
|
lines.append(f" * {v}")
|
|
233
234
|
elif i == 50 and total > 50:
|
|
234
|
-
lines.append(f" * ... {total-50} more")
|
|
235
|
+
lines.append(f" * ... {total - 50} more")
|
|
235
236
|
elif i == 50 and total == 50:
|
|
236
237
|
lines.append(f" * {v}")
|
|
237
238
|
else:
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
from cognite.neat._issues import IssueList
|
|
2
2
|
from cognite.neat._rules.models import DMSRules
|
|
3
3
|
from cognite.neat._rules.models.mapping import load_classic_to_core_mapping
|
|
4
|
-
from cognite.neat._rules.transformers import
|
|
4
|
+
from cognite.neat._rules.transformers import (
|
|
5
|
+
AsParentPropertyId,
|
|
6
|
+
ChangeViewPrefix,
|
|
7
|
+
IncludeReferenced,
|
|
8
|
+
RuleMapper,
|
|
9
|
+
RulesTransformer,
|
|
10
|
+
)
|
|
5
11
|
|
|
6
12
|
from ._state import SessionState
|
|
7
13
|
from .exceptions import NeatSessionError, session_class_wrapper
|
|
@@ -18,7 +24,7 @@ class DataModelMappingAPI:
|
|
|
18
24
|
def __init__(self, state: SessionState):
|
|
19
25
|
self._state = state
|
|
20
26
|
|
|
21
|
-
def classic_to_core(self, company_prefix: str, use_parent_property_name: bool = True) -> IssueList:
|
|
27
|
+
def classic_to_core(self, company_prefix: str | None = None, use_parent_property_name: bool = True) -> IssueList:
|
|
22
28
|
"""Map classic types to core types.
|
|
23
29
|
|
|
24
30
|
Note this automatically creates an extended CogniteCore model.
|
|
@@ -45,10 +51,15 @@ class DataModelMappingAPI:
|
|
|
45
51
|
if self._state.client is None:
|
|
46
52
|
raise NeatSessionError("Client is required to map classic to core")
|
|
47
53
|
|
|
48
|
-
transformers = [
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
54
|
+
transformers: list[RulesTransformer] = []
|
|
55
|
+
if company_prefix:
|
|
56
|
+
transformers.append(ChangeViewPrefix("Classic", company_prefix))
|
|
57
|
+
transformers.extend(
|
|
58
|
+
[
|
|
59
|
+
RuleMapper(load_classic_to_core_mapping(company_prefix, rules.metadata.space, rules.metadata.version)),
|
|
60
|
+
IncludeReferenced(self._state.client),
|
|
61
|
+
]
|
|
62
|
+
)
|
|
52
63
|
if use_parent_property_name:
|
|
53
64
|
transformers.append(AsParentPropertyId(self._state.client))
|
|
54
65
|
return self._state.rule_transform(*transformers)
|
|
@@ -7,7 +7,6 @@ from rdflib import URIRef
|
|
|
7
7
|
from cognite.neat._constants import (
|
|
8
8
|
get_default_prefixes_and_namespaces,
|
|
9
9
|
)
|
|
10
|
-
from cognite.neat._graph import extractors
|
|
11
10
|
from cognite.neat._graph.transformers import (
|
|
12
11
|
AttachPropertyFromTargetToSource,
|
|
13
12
|
ConnectionToLiteral,
|
|
@@ -24,7 +23,6 @@ from cognite.neat._issues import IssueList
|
|
|
24
23
|
from cognite.neat._issues.errors import NeatValueError
|
|
25
24
|
from cognite.neat._rules.models.dms import DMSValidation
|
|
26
25
|
from cognite.neat._rules.transformers import (
|
|
27
|
-
AddClassImplements,
|
|
28
26
|
IncludeReferenced,
|
|
29
27
|
PrefixEntities,
|
|
30
28
|
ReduceCogniteModel,
|
|
@@ -348,41 +346,6 @@ class InstancePrepareAPI:
|
|
|
348
346
|
transformer = ConnectionToLiteral(subject_type, subject_predicate)
|
|
349
347
|
self._state.instances.store.transform(transformer)
|
|
350
348
|
|
|
351
|
-
def classic_to_core(self) -> None:
|
|
352
|
-
"""Prepares extracted CDF classic graph for the Core Data model.
|
|
353
|
-
|
|
354
|
-
!!! note "This method bundles several graph transformers which"
|
|
355
|
-
- Convert relationships to edges
|
|
356
|
-
- Convert TimeSeries.type from bool to enum
|
|
357
|
-
- Convert all properties 'source' to a connection to SourceSystem
|
|
358
|
-
- Convert all properties 'labels' from a connection to a string
|
|
359
|
-
|
|
360
|
-
Example:
|
|
361
|
-
Apply classic to core transformations:
|
|
362
|
-
```python
|
|
363
|
-
neat.prepare.instances.classic_to_core()
|
|
364
|
-
```
|
|
365
|
-
"""
|
|
366
|
-
self.relationships_as_edges()
|
|
367
|
-
self.convert_data_type(
|
|
368
|
-
("TimeSeries", "isString"), convert=lambda is_string: "string" if is_string else "numeric"
|
|
369
|
-
)
|
|
370
|
-
self.property_to_type((None, "source"), "SourceSystem", "name")
|
|
371
|
-
for type_ in [
|
|
372
|
-
extractors.EventsExtractor._default_rdf_type,
|
|
373
|
-
extractors.AssetsExtractor._default_rdf_type,
|
|
374
|
-
extractors.FilesExtractor._default_rdf_type,
|
|
375
|
-
]:
|
|
376
|
-
try:
|
|
377
|
-
subject_type, subject_predicate = self._get_type_and_property_uris(type_, "labels")
|
|
378
|
-
except NeatValueError:
|
|
379
|
-
# If the type_.labels does not exist, continue. This is not an error, it just means that the
|
|
380
|
-
# Labels is not used in the graph for that type.
|
|
381
|
-
continue
|
|
382
|
-
else:
|
|
383
|
-
transformer = ConnectionToLiteral(subject_type, subject_predicate)
|
|
384
|
-
self._state.instances.store.transform(transformer)
|
|
385
|
-
|
|
386
349
|
|
|
387
350
|
@session_class_wrapper
|
|
388
351
|
class DataModelPrepareAPI:
|
|
@@ -545,13 +508,3 @@ class DataModelPrepareAPI:
|
|
|
545
508
|
"Please set the client in the session, NeatSession(client=client)."
|
|
546
509
|
)
|
|
547
510
|
return self._state.rule_transform(IncludeReferenced(self._state.client))
|
|
548
|
-
|
|
549
|
-
def add_implements_to_classes(self, suffix: Literal["Edge"], implements: str = "Edge") -> IssueList:
|
|
550
|
-
"""All classes with the suffix will have the implements property set to the given value.
|
|
551
|
-
|
|
552
|
-
Args:
|
|
553
|
-
suffix: The suffix of the classes to add the implements property to.
|
|
554
|
-
implements: The value of the implements property to set.
|
|
555
|
-
|
|
556
|
-
"""
|
|
557
|
-
return self._state.rule_transform(AddClassImplements(implements, suffix))
|
cognite/neat/_session/_read.py
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
from typing import Any, Literal, cast
|
|
2
2
|
|
|
3
3
|
from cognite.client.data_classes.data_modeling import DataModelId, DataModelIdentifier
|
|
4
|
+
from cognite.client.utils.useful_types import SequenceNotStr
|
|
4
5
|
|
|
5
6
|
from cognite.neat._client import NeatClient
|
|
7
|
+
from cognite.neat._constants import CLASSIC_CDF_NAMESPACE
|
|
6
8
|
from cognite.neat._graph import examples as instances_examples
|
|
7
9
|
from cognite.neat._graph import extractors
|
|
10
|
+
from cognite.neat._graph.transformers import ConvertLiteral, LiteralToEntity
|
|
8
11
|
from cognite.neat._issues import IssueList
|
|
9
12
|
from cognite.neat._issues.errors import NeatValueError
|
|
10
13
|
from cognite.neat._issues.warnings import MissingCogniteClientWarning
|
|
11
14
|
from cognite.neat._rules import catalog, importers
|
|
12
15
|
from cognite.neat._rules.importers import BaseImporter
|
|
16
|
+
from cognite.neat._rules.transformers import ClassicPrepareCore
|
|
13
17
|
from cognite.neat._utils.reader import NeatReader
|
|
14
18
|
|
|
15
19
|
from ._state import SessionState
|
|
@@ -32,6 +36,22 @@ class ReadAPI:
|
|
|
32
36
|
self.yaml = YamlReadAPI(state, verbose)
|
|
33
37
|
self.xml = XMLReadAPI(state, verbose)
|
|
34
38
|
|
|
39
|
+
def session(self, io: Any) -> None:
|
|
40
|
+
"""Reads a Neat Session from a zip file.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
io: file path to the Neat Session
|
|
44
|
+
|
|
45
|
+
Example:
|
|
46
|
+
```python
|
|
47
|
+
neat.read.session("path_to_neat_session")
|
|
48
|
+
```
|
|
49
|
+
"""
|
|
50
|
+
reader = NeatReader.create(io)
|
|
51
|
+
path = reader.materialize_path()
|
|
52
|
+
|
|
53
|
+
self._state.instances.store.write(extractors.RdfFileExtractor.from_zip(path))
|
|
54
|
+
|
|
35
55
|
|
|
36
56
|
@session_class_wrapper
|
|
37
57
|
class BaseReadAPI:
|
|
@@ -78,6 +98,45 @@ class CDFReadAPI(BaseReadAPI):
|
|
|
78
98
|
importer = importers.DMSImporter.from_data_model_id(self._get_client, data_model_id)
|
|
79
99
|
return self._state.rule_import(importer)
|
|
80
100
|
|
|
101
|
+
def graph(
|
|
102
|
+
self,
|
|
103
|
+
data_model_id: DataModelIdentifier,
|
|
104
|
+
instance_space: str | SequenceNotStr[str] | None = None,
|
|
105
|
+
skip_cognite_views: bool = True,
|
|
106
|
+
) -> IssueList:
|
|
107
|
+
"""Reads a knowledge graph from Cognite Data Fusion (CDF).
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
data_model_id: Tuple of strings with the id of a CDF Data Model.
|
|
111
|
+
instance_space: The instance spaces to extract. If None, all instance spaces are extracted.
|
|
112
|
+
skip_cognite_views: If True, all Cognite Views are skipped. For example, if you have the CogniteAsset
|
|
113
|
+
view in you data model, it will ont be used to extract instances.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
IssueList: A list of issues that occurred during the extraction.
|
|
117
|
+
|
|
118
|
+
"""
|
|
119
|
+
return self._graph(data_model_id, instance_space, skip_cognite_views, unpack_json=False)
|
|
120
|
+
|
|
121
|
+
def _graph(
|
|
122
|
+
self,
|
|
123
|
+
data_model_id: DataModelIdentifier,
|
|
124
|
+
instance_space: str | SequenceNotStr[str] | None = None,
|
|
125
|
+
skip_cognite_views: bool = True,
|
|
126
|
+
unpack_json: bool = False,
|
|
127
|
+
str_to_ideal_type: bool = False,
|
|
128
|
+
) -> IssueList:
|
|
129
|
+
extractor = extractors.DMSGraphExtractor.from_data_model_id(
|
|
130
|
+
# We are skipping the Cognite Views
|
|
131
|
+
data_model_id,
|
|
132
|
+
self._get_client,
|
|
133
|
+
instance_space=instance_space,
|
|
134
|
+
skip_cognite_views=skip_cognite_views,
|
|
135
|
+
unpack_json=unpack_json,
|
|
136
|
+
str_to_ideal_type=str_to_ideal_type,
|
|
137
|
+
)
|
|
138
|
+
return self._state.write_graph(extractor)
|
|
139
|
+
|
|
81
140
|
|
|
82
141
|
@session_class_wrapper
|
|
83
142
|
class CDFClassicAPI(BaseReadAPI):
|
|
@@ -92,7 +151,12 @@ class CDFClassicAPI(BaseReadAPI):
|
|
|
92
151
|
raise ValueError("No client provided. Please provide a client to read a data model.")
|
|
93
152
|
return self._state.client
|
|
94
153
|
|
|
95
|
-
def graph(
|
|
154
|
+
def graph(
|
|
155
|
+
self,
|
|
156
|
+
root_asset_external_id: str,
|
|
157
|
+
limit_per_type: int | None = None,
|
|
158
|
+
identifier: Literal["id", "externalId"] = "id",
|
|
159
|
+
) -> IssueList:
|
|
96
160
|
"""Reads the classic knowledge graph from CDF.
|
|
97
161
|
|
|
98
162
|
The Classic Graph consists of the following core resource type.
|
|
@@ -127,6 +191,8 @@ class CDFClassicAPI(BaseReadAPI):
|
|
|
127
191
|
Args:
|
|
128
192
|
root_asset_external_id: The external id of the root asset
|
|
129
193
|
limit_per_type: The maximum number of nodes to extract per core node type. If None, all nodes are extracted.
|
|
194
|
+
identifier: The identifier to use for the core nodes. Note selecting "id" can cause issues if the external
|
|
195
|
+
ID of the core nodes is missing. Default is "id".
|
|
130
196
|
|
|
131
197
|
Returns:
|
|
132
198
|
IssueList: A list of issues that occurred during the extraction.
|
|
@@ -135,17 +201,58 @@ class CDFClassicAPI(BaseReadAPI):
|
|
|
135
201
|
```python
|
|
136
202
|
neat.read.cdf.graph("root_asset_external_id")
|
|
137
203
|
```
|
|
138
|
-
|
|
139
204
|
"""
|
|
140
|
-
|
|
141
|
-
|
|
205
|
+
return self._graph(
|
|
206
|
+
root_asset_external_id, limit_per_type, identifier, reference_timeseries=False, reference_files=False
|
|
142
207
|
)
|
|
143
208
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
209
|
+
def _graph(
|
|
210
|
+
self,
|
|
211
|
+
root_asset_external_id: str,
|
|
212
|
+
limit_per_type: int | None = None,
|
|
213
|
+
identifier: Literal["id", "externalId"] = "id",
|
|
214
|
+
reference_timeseries: bool = False,
|
|
215
|
+
reference_files: bool = False,
|
|
216
|
+
) -> IssueList:
|
|
217
|
+
namespace = CLASSIC_CDF_NAMESPACE
|
|
218
|
+
extractor = extractors.ClassicGraphExtractor(
|
|
219
|
+
self._get_client,
|
|
220
|
+
root_asset_external_id=root_asset_external_id,
|
|
221
|
+
limit_per_type=limit_per_type,
|
|
222
|
+
namespace=namespace,
|
|
223
|
+
prefix="Classic",
|
|
224
|
+
identifier=identifier,
|
|
225
|
+
)
|
|
226
|
+
extract_issues = self._state.write_graph(extractor)
|
|
227
|
+
if identifier == "externalId":
|
|
228
|
+
self._state.quoted_source_identifiers = True
|
|
229
|
+
|
|
230
|
+
self._state.instances.store.transform(
|
|
231
|
+
ConvertLiteral(
|
|
232
|
+
namespace["ClassicTimeSeries"],
|
|
233
|
+
namespace["isString"],
|
|
234
|
+
lambda is_string: "string" if is_string else "numeric",
|
|
235
|
+
)
|
|
236
|
+
)
|
|
237
|
+
self._state.instances.store.transform(
|
|
238
|
+
LiteralToEntity(None, namespace["source"], "ClassicSourceSystem", "name"),
|
|
239
|
+
)
|
|
240
|
+
# Updating the information model.
|
|
241
|
+
prepare_issues = self._state.rule_store.transform(
|
|
242
|
+
ClassicPrepareCore(namespace, reference_timeseries, reference_files)
|
|
243
|
+
)
|
|
244
|
+
# Update the instance store with the latest rules
|
|
245
|
+
information_rules = self._state.rule_store.last_verified_information_rules
|
|
246
|
+
self._state.instances.store.rules[self._state.instances.store.default_named_graph] = information_rules
|
|
247
|
+
|
|
248
|
+
all_issues = IssueList(extract_issues + prepare_issues)
|
|
249
|
+
# Update the provenance with all issue.
|
|
250
|
+
object.__setattr__(self._state.instances.store.provenance[-1].target_entity, "issues", all_issues)
|
|
251
|
+
all_issues.action = "Read Classic Graph"
|
|
252
|
+
if all_issues:
|
|
147
253
|
print("Use the .inspect.issues() for more details")
|
|
148
|
-
|
|
254
|
+
|
|
255
|
+
return all_issues
|
|
149
256
|
|
|
150
257
|
|
|
151
258
|
@session_class_wrapper
|
|
@@ -182,7 +289,6 @@ class ExcelReadAPI(BaseReadAPI):
|
|
|
182
289
|
class ExcelExampleAPI(BaseReadAPI):
|
|
183
290
|
"""Used as example for reading some data model into the NeatSession."""
|
|
184
291
|
|
|
185
|
-
@property
|
|
186
292
|
def pump_example(self) -> IssueList:
|
|
187
293
|
"""Reads the Hello World pump example into the NeatSession."""
|
|
188
294
|
importer: importers.ExcelImporter = importers.ExcelImporter(catalog.hello_world_pump)
|
|
@@ -396,7 +502,6 @@ class RDFExamples:
|
|
|
396
502
|
def __init__(self, state: SessionState) -> None:
|
|
397
503
|
self._state = state
|
|
398
504
|
|
|
399
|
-
@property
|
|
400
505
|
def nordic44(self) -> IssueList:
|
|
401
506
|
"""Reads the Nordic 44 knowledge graph into the NeatSession graph store."""
|
|
402
507
|
self._state.instances.store.write(extractors.RdfFileExtractor(instances_examples.nordic44_knowledge_graph))
|
cognite/neat/_session/_set.py
CHANGED
|
@@ -3,9 +3,12 @@ from cognite.client import data_modeling as dm
|
|
|
3
3
|
|
|
4
4
|
from cognite.neat._client import NeatClient
|
|
5
5
|
from cognite.neat._constants import COGNITE_MODELS
|
|
6
|
+
from cognite.neat._graph.transformers import SetNeatType
|
|
6
7
|
from cognite.neat._issues import IssueList
|
|
8
|
+
from cognite.neat._issues.errors import NeatValueError
|
|
7
9
|
from cognite.neat._rules.models import DMSRules
|
|
8
10
|
from cognite.neat._rules.transformers import SetIDDMSModel
|
|
11
|
+
from cognite.neat._utils.text import humanize_collection
|
|
9
12
|
|
|
10
13
|
from ._state import SessionState
|
|
11
14
|
from .exceptions import NeatSessionError, session_class_wrapper
|
|
@@ -44,3 +47,27 @@ class SetAPI:
|
|
|
44
47
|
if self._verbose:
|
|
45
48
|
print(f"Client set to {self._state.client.config.project} CDF project.")
|
|
46
49
|
return None
|
|
50
|
+
|
|
51
|
+
def _instance_sub_type(self, type: str, property: str, drop_property: bool = False) -> None:
|
|
52
|
+
"""Sets the sub type of an instance based on the property."""
|
|
53
|
+
type_uri = self._state.instances.store.queries.type_uri(type)
|
|
54
|
+
property_uri = self._state.instances.store.queries.property_uri(property)
|
|
55
|
+
|
|
56
|
+
if not type_uri:
|
|
57
|
+
raise NeatValueError(f"Type {type} does not exist in the graph.")
|
|
58
|
+
elif len(type_uri) > 1:
|
|
59
|
+
raise NeatValueError(f"{type} has multiple ids found in the graph: {humanize_collection(type_uri)}.")
|
|
60
|
+
|
|
61
|
+
if not property_uri:
|
|
62
|
+
raise NeatValueError(f"Property {property} does not exist in the graph.")
|
|
63
|
+
elif len(type_uri) > 1:
|
|
64
|
+
raise NeatValueError(
|
|
65
|
+
f"{property} has multiple ids found in the graph: {humanize_collection(property_uri)}."
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
if not self._state.instances.store.queries.type_with_property(type_uri[0], property_uri[0]):
|
|
69
|
+
raise NeatValueError(f"Property {property} is not defined for type {type}.")
|
|
70
|
+
|
|
71
|
+
self._state.instances.store.transform(SetNeatType(type_uri[0], property_uri[0], drop_property))
|
|
72
|
+
|
|
73
|
+
return None
|
cognite/neat/_session/_show.py
CHANGED
|
@@ -112,7 +112,7 @@ class ShowDataModelAPI(ShowBaseAPI):
|
|
|
112
112
|
else:
|
|
113
113
|
# This should never happen, but we need to handle it to satisfy mypy
|
|
114
114
|
raise NeatSessionError(
|
|
115
|
-
f"Unsupported type {type(rules)
|
|
115
|
+
f"Unsupported type {type(rules)}. Make sure you have either information or DMS rules."
|
|
116
116
|
)
|
|
117
117
|
identifier = to_directory_compatible(str(rules.metadata.identifier))
|
|
118
118
|
name = f"{identifier}.html"
|
|
@@ -201,7 +201,7 @@ class ShowDataModelImplementsAPI(ShowBaseAPI):
|
|
|
201
201
|
else:
|
|
202
202
|
# This should never happen, but we need to handle it to satisfy mypy
|
|
203
203
|
raise NeatSessionError(
|
|
204
|
-
f"Unsupported type {type(rules)
|
|
204
|
+
f"Unsupported type {type(rules)}. Make sure you have either information or DMS rules."
|
|
205
205
|
)
|
|
206
206
|
identifier = to_directory_compatible(str(rules.metadata.identifier))
|
|
207
207
|
name = f"{identifier}_implements.html"
|
|
@@ -364,7 +364,7 @@ class ShowInstanceAPI(ShowBaseAPI):
|
|
|
364
364
|
'Try setting [bold]NeatSession(storage="oxigraph")[/bold] enable Oxigraph store.'
|
|
365
365
|
)
|
|
366
366
|
|
|
367
|
-
if not self._state.instances.store.
|
|
367
|
+
if not self._state.instances.store.dataset:
|
|
368
368
|
raise NeatSessionError("No instances available. Try using [bold].read[/bold] to load instances.")
|
|
369
369
|
|
|
370
370
|
di_graph = self._generate_instance_di_graph_and_types()
|
|
@@ -395,7 +395,7 @@ class ShowInstanceAPI(ShowBaseAPI):
|
|
|
395
395
|
object,
|
|
396
396
|
subject_type,
|
|
397
397
|
object_type,
|
|
398
|
-
) in self._state.instances.store.
|
|
398
|
+
) in self._state.instances.store.dataset.query(query):
|
|
399
399
|
subject = remove_namespace_from_uri(subject)
|
|
400
400
|
property_ = remove_namespace_from_uri(property_)
|
|
401
401
|
object = remove_namespace_from_uri(object)
|
cognite/neat/_session/_state.py
CHANGED
|
@@ -2,6 +2,7 @@ from dataclasses import dataclass, field
|
|
|
2
2
|
from typing import Literal, cast
|
|
3
3
|
|
|
4
4
|
from cognite.neat._client import NeatClient
|
|
5
|
+
from cognite.neat._graph.extractors import KnowledgeGraphExtractor
|
|
5
6
|
from cognite.neat._issues import IssueList
|
|
6
7
|
from cognite.neat._rules.importers import BaseImporter, InferenceImporter
|
|
7
8
|
from cognite.neat._rules.models import DMSRules, InformationRules
|
|
@@ -21,11 +22,14 @@ class SessionState:
|
|
|
21
22
|
self.rule_store = NeatRulesStore()
|
|
22
23
|
self.last_reference: DMSRules | InformationRules | None = None
|
|
23
24
|
self.client = client
|
|
25
|
+
self.quoted_source_identifiers = False
|
|
24
26
|
|
|
25
27
|
def rule_transform(self, *transformer: RulesTransformer) -> IssueList:
|
|
26
28
|
if not transformer:
|
|
27
29
|
raise NeatSessionError("No transformers provided.")
|
|
28
30
|
first_transformer = transformer[0]
|
|
31
|
+
|
|
32
|
+
# This should not be allowed to be done automatically
|
|
29
33
|
pruned = self.rule_store.prune_until_compatible(first_transformer)
|
|
30
34
|
if pruned:
|
|
31
35
|
type_hint = first_transformer.transform_type_hint()
|
|
@@ -62,6 +66,13 @@ class SessionState:
|
|
|
62
66
|
issues.hint = "Use the .inspect.issues() for more details."
|
|
63
67
|
return issues
|
|
64
68
|
|
|
69
|
+
def write_graph(self, extractor: KnowledgeGraphExtractor) -> IssueList:
|
|
70
|
+
extract_issues = self.instances.store.write(extractor)
|
|
71
|
+
issues = self.rule_store.import_graph(extractor)
|
|
72
|
+
self.instances.store.add_rules(self.rule_store.last_verified_information_rules)
|
|
73
|
+
issues.extend(extract_issues)
|
|
74
|
+
return issues
|
|
75
|
+
|
|
65
76
|
|
|
66
77
|
@dataclass
|
|
67
78
|
class InstancesState:
|
|
@@ -74,7 +85,7 @@ class InstancesState:
|
|
|
74
85
|
def store(self) -> NeatGraphStore:
|
|
75
86
|
if not self.has_store:
|
|
76
87
|
if self.store_type == "oxigraph":
|
|
77
|
-
self._store = NeatGraphStore.
|
|
88
|
+
self._store = NeatGraphStore.from_oxi_local_store()
|
|
78
89
|
else:
|
|
79
90
|
self._store = NeatGraphStore.from_memory_store()
|
|
80
91
|
return cast(NeatGraphStore, self._store)
|
cognite/neat/_session/_to.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
import zipfile
|
|
1
3
|
from collections.abc import Collection
|
|
2
4
|
from pathlib import Path
|
|
3
5
|
from typing import Any, Literal, overload
|
|
@@ -80,11 +82,47 @@ class ToAPI:
|
|
|
80
82
|
exporter = exporters.ExcelExporter(styling="maximal", reference_rules_with_prefix=reference_rules_with_prefix)
|
|
81
83
|
return self._state.rule_store.export_to_file(exporter, Path(io))
|
|
82
84
|
|
|
85
|
+
def session(self, io: Any) -> None:
|
|
86
|
+
"""Export the current session to a file.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
io: The file path to file-like object to write the session to.
|
|
90
|
+
|
|
91
|
+
Example:
|
|
92
|
+
Export the session to a file
|
|
93
|
+
```python
|
|
94
|
+
session_file_name = "neat_session.zip"
|
|
95
|
+
neat.to.session(session_file_name)
|
|
96
|
+
```
|
|
97
|
+
"""
|
|
98
|
+
filepath = Path(io)
|
|
99
|
+
if filepath.suffix not in {".zip"}:
|
|
100
|
+
warnings.warn("File extension is not .zip, adding it to the file name", stacklevel=2)
|
|
101
|
+
filepath = filepath.with_suffix(".zip")
|
|
102
|
+
|
|
103
|
+
filepath.parent.mkdir(exist_ok=True, parents=True)
|
|
104
|
+
|
|
105
|
+
with zipfile.ZipFile(filepath, "w") as zip_ref:
|
|
106
|
+
zip_ref.writestr(
|
|
107
|
+
"neat-session/instances/instances.trig",
|
|
108
|
+
self._state.instances.store.serialize(),
|
|
109
|
+
)
|
|
110
|
+
|
|
83
111
|
@overload
|
|
84
|
-
def yaml(
|
|
112
|
+
def yaml(
|
|
113
|
+
self,
|
|
114
|
+
io: None,
|
|
115
|
+
format: Literal["neat"] = "neat",
|
|
116
|
+
skip_system_spaces: bool = True,
|
|
117
|
+
) -> str: ...
|
|
85
118
|
|
|
86
119
|
@overload
|
|
87
|
-
def yaml(
|
|
120
|
+
def yaml(
|
|
121
|
+
self,
|
|
122
|
+
io: Any,
|
|
123
|
+
format: Literal["neat", "toolkit"] = "neat",
|
|
124
|
+
skip_system_spaces: bool = True,
|
|
125
|
+
) -> None: ...
|
|
88
126
|
|
|
89
127
|
def yaml(
|
|
90
128
|
self, io: Any | None = None, format: Literal["neat", "toolkit"] = "neat", skip_system_spaces: bool = True
|
|
@@ -182,6 +220,9 @@ class CDFToAPI:
|
|
|
182
220
|
self._state.instances.store,
|
|
183
221
|
instance_space=space,
|
|
184
222
|
client=client,
|
|
223
|
+
# In case urllib.parse.quote() was run on the extraction, we need to run
|
|
224
|
+
# urllib.parse.unquote() on the load.
|
|
225
|
+
unquote_external_ids=self._state.quoted_source_identifiers,
|
|
185
226
|
)
|
|
186
227
|
result = loader.load_into_cdf(client)
|
|
187
228
|
self._state.instances.outcome.append(result)
|
cognite/neat/_session/_wizard.py
CHANGED
|
@@ -26,7 +26,7 @@ _T_Option = TypeVar("_T_Option")
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
def _selection(message: str, options: Sequence[_T_Option]) -> _T_Option:
|
|
29
|
-
option_text = "\n ".join([f"{i+1}) {option}" for i, option in enumerate(options)])
|
|
29
|
+
option_text = "\n ".join([f"{i + 1}) {option}" for i, option in enumerate(options)])
|
|
30
30
|
selected_index = (
|
|
31
31
|
IntPrompt().ask(f"{message}\n {option_text}\n", choices=list(map(str, range(1, len(options) + 1)))) - 1
|
|
32
32
|
)
|
|
@@ -2,17 +2,22 @@ import functools
|
|
|
2
2
|
from collections.abc import Callable
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
|
-
from cognite.neat._issues.errors import CDFMissingClientError
|
|
5
|
+
from cognite.neat._issues.errors import CDFMissingClientError, NeatImportError
|
|
6
|
+
from cognite.neat._issues.errors._general import NeatValueError
|
|
6
7
|
|
|
7
8
|
from ._collector import _COLLECTOR
|
|
8
9
|
|
|
9
10
|
try:
|
|
10
11
|
from rich import print
|
|
12
|
+
from rich.markup import escape
|
|
11
13
|
|
|
12
14
|
_PREFIX = "[bold red][ERROR][/bold red]"
|
|
13
15
|
except ImportError:
|
|
14
16
|
_PREFIX = "[ERROR]"
|
|
15
17
|
|
|
18
|
+
def escape(x: Any, *_: Any, **__: Any) -> Any: # type: ignore[misc]
|
|
19
|
+
return x
|
|
20
|
+
|
|
16
21
|
|
|
17
22
|
class NeatSessionError(Exception):
|
|
18
23
|
"""Base class for all exceptions raised by the NeatSession class."""
|
|
@@ -29,8 +34,8 @@ def _session_method_wrapper(func: Callable, cls_name: str):
|
|
|
29
34
|
except NeatSessionError as e:
|
|
30
35
|
action = _get_action()
|
|
31
36
|
print(f"{_PREFIX} Cannot {action}: {e}")
|
|
32
|
-
except CDFMissingClientError as e:
|
|
33
|
-
print(f"{_PREFIX} {e.as_message()}")
|
|
37
|
+
except (CDFMissingClientError, NeatImportError, NeatValueError) as e:
|
|
38
|
+
print(f"{_PREFIX} {escape(e.as_message())}")
|
|
34
39
|
except ModuleNotFoundError as e:
|
|
35
40
|
if e.name == "neatengine":
|
|
36
41
|
action = _get_action()
|