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
|
@@ -1,36 +1,19 @@
|
|
|
1
|
-
from collections.abc import Callable
|
|
2
|
-
from typing import Any
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from typing import Any
|
|
3
3
|
|
|
4
|
-
from cognite.client.data_classes.data_modeling import DataModelIdentifier
|
|
5
4
|
from rdflib import URIRef
|
|
6
5
|
|
|
7
|
-
from cognite.neat._constants import (
|
|
8
|
-
get_default_prefixes_and_namespaces,
|
|
9
|
-
)
|
|
10
6
|
from cognite.neat._graph.transformers import (
|
|
11
|
-
AttachPropertyFromTargetToSource,
|
|
12
7
|
ConnectionToLiteral,
|
|
13
8
|
ConvertLiteral,
|
|
14
9
|
LiteralToEntity,
|
|
15
|
-
PruneDeadEndEdges,
|
|
16
|
-
PruneInstancesOfUnknownType,
|
|
17
|
-
PruneTypes,
|
|
18
10
|
RelationshipAsEdgeTransformer,
|
|
19
|
-
Transformers,
|
|
20
11
|
)
|
|
21
12
|
from cognite.neat._graph.transformers._rdfpath import MakeConnectionOnExactMatch
|
|
22
13
|
from cognite.neat._issues import IssueList
|
|
23
14
|
from cognite.neat._issues.errors import NeatValueError
|
|
24
|
-
from cognite.neat._rules.models.dms import DMSValidation
|
|
25
15
|
from cognite.neat._rules.transformers import (
|
|
26
|
-
IncludeReferenced,
|
|
27
16
|
PrefixEntities,
|
|
28
|
-
ReduceCogniteModel,
|
|
29
|
-
RulesTransformer,
|
|
30
|
-
ToCompliantEntities,
|
|
31
|
-
ToDataProductModel,
|
|
32
|
-
ToEnterpriseModel,
|
|
33
|
-
ToSolutionModel,
|
|
34
17
|
)
|
|
35
18
|
from cognite.neat._utils.text import humanize_collection
|
|
36
19
|
|
|
@@ -59,91 +42,6 @@ class InstancePrepareAPI:
|
|
|
59
42
|
self._state = state
|
|
60
43
|
self._verbose = verbose
|
|
61
44
|
|
|
62
|
-
def dexpi(self) -> None:
|
|
63
|
-
"""Prepares extracted DEXPI graph for further usage in CDF
|
|
64
|
-
|
|
65
|
-
!!! note "This method bundles several graph transformers which"
|
|
66
|
-
- attach values of generic attributes to nodes
|
|
67
|
-
- create associations between nodes
|
|
68
|
-
- remove unused generic attributes
|
|
69
|
-
- remove associations between nodes that do not exist in the extracted graph
|
|
70
|
-
- remove edges to nodes that do not exist in the extracted graph
|
|
71
|
-
|
|
72
|
-
and therefore safeguard CDF from a bad graph
|
|
73
|
-
|
|
74
|
-
Example:
|
|
75
|
-
Apply Dexpi specific transformations:
|
|
76
|
-
```python
|
|
77
|
-
neat.prepare.instances.dexpi()
|
|
78
|
-
```
|
|
79
|
-
"""
|
|
80
|
-
|
|
81
|
-
DEXPI = get_default_prefixes_and_namespaces()["dexpi"]
|
|
82
|
-
|
|
83
|
-
transformers = [
|
|
84
|
-
# Remove any instance which type is unknown
|
|
85
|
-
PruneInstancesOfUnknownType(),
|
|
86
|
-
# Directly connect generic attributes
|
|
87
|
-
AttachPropertyFromTargetToSource(
|
|
88
|
-
target_property=DEXPI.Value,
|
|
89
|
-
target_property_holding_new_property=DEXPI.Name,
|
|
90
|
-
target_node_type=DEXPI.GenericAttribute,
|
|
91
|
-
delete_target_node=True,
|
|
92
|
-
),
|
|
93
|
-
# Directly connect associations
|
|
94
|
-
AttachPropertyFromTargetToSource(
|
|
95
|
-
target_property=DEXPI.ItemID,
|
|
96
|
-
target_property_holding_new_property=DEXPI.Type,
|
|
97
|
-
target_node_type=DEXPI.Association,
|
|
98
|
-
delete_target_node=True,
|
|
99
|
-
),
|
|
100
|
-
# Remove unused generic attributes and associations
|
|
101
|
-
PruneTypes([DEXPI.GenericAttribute, DEXPI.Association]),
|
|
102
|
-
# Remove edges to nodes that do not exist in the extracted graph
|
|
103
|
-
PruneDeadEndEdges(),
|
|
104
|
-
]
|
|
105
|
-
|
|
106
|
-
for transformer in transformers:
|
|
107
|
-
self._state.instances.store.transform(cast(Transformers, transformer))
|
|
108
|
-
|
|
109
|
-
def aml(self) -> None:
|
|
110
|
-
"""Prepares extracted AutomationML graph for further usage in CDF
|
|
111
|
-
|
|
112
|
-
!!! note "This method bundles several graph transformers which"
|
|
113
|
-
- attach values of attributes to nodes
|
|
114
|
-
- remove unused attributes
|
|
115
|
-
- remove edges to nodes that do not exist in the extracted graph
|
|
116
|
-
|
|
117
|
-
and therefore safeguard CDF from a bad graph
|
|
118
|
-
|
|
119
|
-
Example:
|
|
120
|
-
Apply AML specific transformations:
|
|
121
|
-
```python
|
|
122
|
-
neat.prepare.instances.aml()
|
|
123
|
-
```
|
|
124
|
-
"""
|
|
125
|
-
|
|
126
|
-
AML = get_default_prefixes_and_namespaces()["aml"]
|
|
127
|
-
|
|
128
|
-
transformers = [
|
|
129
|
-
# Remove any instance which type is unknown
|
|
130
|
-
PruneInstancesOfUnknownType(),
|
|
131
|
-
# Directly connect generic attributes
|
|
132
|
-
AttachPropertyFromTargetToSource(
|
|
133
|
-
target_property=AML.Value,
|
|
134
|
-
target_property_holding_new_property=AML.Name,
|
|
135
|
-
target_node_type=AML.Attribute,
|
|
136
|
-
delete_target_node=True,
|
|
137
|
-
),
|
|
138
|
-
# Prune unused attributes
|
|
139
|
-
PruneTypes([AML.Attribute]),
|
|
140
|
-
# # Remove edges to nodes that do not exist in the extracted graph
|
|
141
|
-
PruneDeadEndEdges(),
|
|
142
|
-
]
|
|
143
|
-
|
|
144
|
-
for transformer in transformers:
|
|
145
|
-
self._state.instances.store.transform(cast(Transformers, transformer))
|
|
146
|
-
|
|
147
45
|
def make_connection_on_exact_match(
|
|
148
46
|
self,
|
|
149
47
|
source: tuple[str, str],
|
|
@@ -357,10 +255,6 @@ class DataModelPrepareAPI:
|
|
|
357
255
|
self._state = state
|
|
358
256
|
self._verbose = verbose
|
|
359
257
|
|
|
360
|
-
def cdf_compliant_external_ids(self) -> IssueList:
|
|
361
|
-
"""Convert data model component external ids to CDF compliant entities."""
|
|
362
|
-
return self._state.rule_transform(ToCompliantEntities())
|
|
363
|
-
|
|
364
258
|
def prefix(self, prefix: str) -> IssueList:
|
|
365
259
|
"""Prefix all views in the data model with the given prefix.
|
|
366
260
|
|
|
@@ -368,143 +262,5 @@ class DataModelPrepareAPI:
|
|
|
368
262
|
prefix: The prefix to add to the views in the data model.
|
|
369
263
|
|
|
370
264
|
"""
|
|
371
|
-
return self._state.rule_transform(PrefixEntities(prefix))
|
|
372
|
-
|
|
373
|
-
def to_enterprise(
|
|
374
|
-
self,
|
|
375
|
-
data_model_id: DataModelIdentifier,
|
|
376
|
-
org_name: str = "My",
|
|
377
|
-
dummy_property: str = "GUID",
|
|
378
|
-
move_connections: bool = False,
|
|
379
|
-
) -> IssueList:
|
|
380
|
-
"""Uses the current data model as a basis to create enterprise data model
|
|
381
|
-
|
|
382
|
-
Args:
|
|
383
|
-
data_model_id: The enterprise data model id that is being created
|
|
384
|
-
org_name: Organization name to use for the views in the enterprise data model.
|
|
385
|
-
dummy_property: The dummy property to use as placeholder for the views in the new data model.
|
|
386
|
-
move_connections: If True, the connections will be moved to the new data model.
|
|
387
|
-
|
|
388
|
-
!!! note "Enterprise Data Model Creation"
|
|
389
|
-
|
|
390
|
-
Always create an enterprise data model from a Cognite Data Model as this will
|
|
391
|
-
assure all the Cognite Data Fusion applications to run smoothly, such as
|
|
392
|
-
- Search
|
|
393
|
-
- Atlas AI
|
|
394
|
-
- ...
|
|
395
|
-
|
|
396
|
-
!!! note "Move Connections"
|
|
397
|
-
|
|
398
|
-
If you want to move the connections to the new data model, set the move_connections
|
|
399
|
-
to True. This will move the connections to the new data model and use new model
|
|
400
|
-
views as the source and target views.
|
|
401
|
-
|
|
402
|
-
"""
|
|
403
|
-
return self._state.rule_transform(
|
|
404
|
-
ToEnterpriseModel(
|
|
405
|
-
new_model_id=data_model_id,
|
|
406
|
-
org_name=org_name,
|
|
407
|
-
dummy_property=dummy_property,
|
|
408
|
-
move_connections=move_connections,
|
|
409
|
-
)
|
|
410
|
-
)
|
|
411
265
|
|
|
412
|
-
|
|
413
|
-
self,
|
|
414
|
-
data_model_id: DataModelIdentifier,
|
|
415
|
-
org_name: str = "My",
|
|
416
|
-
mode: Literal["read", "write"] = "read",
|
|
417
|
-
dummy_property: str = "GUID",
|
|
418
|
-
) -> IssueList:
|
|
419
|
-
"""Uses the current data model as a basis to create solution data model
|
|
420
|
-
|
|
421
|
-
Args:
|
|
422
|
-
data_model_id: The solution data model id that is being created.
|
|
423
|
-
org_name: Organization name to use for the views in the new data model.
|
|
424
|
-
mode: The mode of the solution data model. Can be either "read" or "write".
|
|
425
|
-
dummy_property: The dummy property to use as placeholder for the views in the new data model.
|
|
426
|
-
|
|
427
|
-
!!! note "Solution Data Model Mode"
|
|
428
|
-
|
|
429
|
-
The read-only solution model will only be able to read from the existing containers
|
|
430
|
-
from the enterprise data model, therefore the solution data model will not have
|
|
431
|
-
containers in the solution data model space. Meaning the solution data model views
|
|
432
|
-
will be read-only.
|
|
433
|
-
|
|
434
|
-
The write mode will have additional containers in the solution data model space,
|
|
435
|
-
allowing in addition to reading through the solution model views, also writing to
|
|
436
|
-
the containers in the solution data model space.
|
|
437
|
-
|
|
438
|
-
"""
|
|
439
|
-
return self._state.rule_transform(
|
|
440
|
-
ToSolutionModel(
|
|
441
|
-
new_model_id=data_model_id,
|
|
442
|
-
org_name=org_name,
|
|
443
|
-
mode=mode,
|
|
444
|
-
dummy_property=dummy_property,
|
|
445
|
-
)
|
|
446
|
-
)
|
|
447
|
-
|
|
448
|
-
def to_data_product(
|
|
449
|
-
self,
|
|
450
|
-
data_model_id: DataModelIdentifier,
|
|
451
|
-
org_name: str = "",
|
|
452
|
-
include: Literal["same-space", "all"] = "same-space",
|
|
453
|
-
) -> None:
|
|
454
|
-
"""Uses the current data model as a basis to create data product data model.
|
|
455
|
-
|
|
456
|
-
A data product model is a data model that ONLY maps to containers and do not use implements. This is
|
|
457
|
-
typically used for defining the data in a data product.
|
|
458
|
-
|
|
459
|
-
Args:
|
|
460
|
-
data_model_id: The data product data model id that is being created.
|
|
461
|
-
org_name: Organization name used as prefix if the model is building on top of a Cognite Data Model.
|
|
462
|
-
include: The views to include in the data product data model. Can be either "same-space" or "all".
|
|
463
|
-
If you set same-space, only the properties of the views in the same space as the data model
|
|
464
|
-
will be included.
|
|
465
|
-
"""
|
|
466
|
-
|
|
467
|
-
view_ids, container_ids = DMSValidation(
|
|
468
|
-
self._state.rule_store.last_verified_dms_rules
|
|
469
|
-
).imported_views_and_containers_ids()
|
|
470
|
-
transformers: list[RulesTransformer] = []
|
|
471
|
-
client = self._state.client
|
|
472
|
-
if (view_ids or container_ids) and client is None:
|
|
473
|
-
raise NeatSessionError(
|
|
474
|
-
"No client provided. You are referencing unknown views and containers in your data model, "
|
|
475
|
-
"NEAT needs a client to lookup the definitions. "
|
|
476
|
-
"Please set the client in the session, NeatSession(client=client)."
|
|
477
|
-
)
|
|
478
|
-
elif (view_ids or container_ids) and client:
|
|
479
|
-
transformers.append(IncludeReferenced(client, include_properties=True))
|
|
480
|
-
|
|
481
|
-
transformers.append(
|
|
482
|
-
ToDataProductModel(
|
|
483
|
-
new_model_id=data_model_id,
|
|
484
|
-
org_name=org_name,
|
|
485
|
-
include=include,
|
|
486
|
-
)
|
|
487
|
-
)
|
|
488
|
-
|
|
489
|
-
self._state.rule_transform(*transformers)
|
|
490
|
-
|
|
491
|
-
def reduce(self, drop: Collection[Literal["3D", "Annotation", "BaseViews"] | str]) -> IssueList:
|
|
492
|
-
"""This is a special method that allow you to drop parts of the data model.
|
|
493
|
-
This only applies to Cognite Data Models.
|
|
494
|
-
|
|
495
|
-
Args:
|
|
496
|
-
drop: What to drop from the data model. The values 3D, Annotation, and BaseViews are special values that
|
|
497
|
-
drops multiple views at once. You can also pass externalIds of views to drop individual views.
|
|
498
|
-
|
|
499
|
-
"""
|
|
500
|
-
return self._state.rule_transform(ReduceCogniteModel(drop))
|
|
501
|
-
|
|
502
|
-
def include_referenced(self) -> IssueList:
|
|
503
|
-
"""Include referenced views and containers in the data model."""
|
|
504
|
-
if self._state.client is None:
|
|
505
|
-
raise NeatSessionError(
|
|
506
|
-
"No client provided. You are referencing unknown views and containers in your data model, "
|
|
507
|
-
"NEAT needs a client to lookup the definitions. "
|
|
508
|
-
"Please set the client in the session, NeatSession(client=client)."
|
|
509
|
-
)
|
|
510
|
-
return self._state.rule_transform(IncludeReferenced(self._state.client))
|
|
266
|
+
return self._state.rule_transform(PrefixEntities(prefix)) # type: ignore[arg-type]
|
cognite/neat/_session/_read.py
CHANGED
|
@@ -4,10 +4,23 @@ from cognite.client.data_classes.data_modeling import DataModelId, DataModelIden
|
|
|
4
4
|
from cognite.client.utils.useful_types import SequenceNotStr
|
|
5
5
|
|
|
6
6
|
from cognite.neat._client import NeatClient
|
|
7
|
-
from cognite.neat._constants import
|
|
7
|
+
from cognite.neat._constants import (
|
|
8
|
+
CLASSIC_CDF_NAMESPACE,
|
|
9
|
+
get_default_prefixes_and_namespaces,
|
|
10
|
+
)
|
|
8
11
|
from cognite.neat._graph import examples as instances_examples
|
|
9
12
|
from cognite.neat._graph import extractors
|
|
10
|
-
from cognite.neat._graph.transformers import
|
|
13
|
+
from cognite.neat._graph.transformers import (
|
|
14
|
+
ConvertLiteral,
|
|
15
|
+
LiteralToEntity,
|
|
16
|
+
Transformers,
|
|
17
|
+
)
|
|
18
|
+
from cognite.neat._graph.transformers._prune_graph import (
|
|
19
|
+
AttachPropertyFromTargetToSource,
|
|
20
|
+
PruneDeadEndEdges,
|
|
21
|
+
PruneInstancesOfUnknownType,
|
|
22
|
+
PruneTypes,
|
|
23
|
+
)
|
|
11
24
|
from cognite.neat._issues import IssueList
|
|
12
25
|
from cognite.neat._issues.errors import NeatValueError
|
|
13
26
|
from cognite.neat._issues.warnings import MissingCogniteClientWarning
|
|
@@ -99,20 +112,41 @@ class CDFReadAPI(BaseReadAPI):
|
|
|
99
112
|
return self._state.rule_import(importer)
|
|
100
113
|
|
|
101
114
|
def graph(
|
|
102
|
-
self,
|
|
115
|
+
self,
|
|
116
|
+
data_model_id: DataModelIdentifier,
|
|
117
|
+
instance_space: str | SequenceNotStr[str] | None = None,
|
|
118
|
+
skip_cognite_views: bool = True,
|
|
103
119
|
) -> IssueList:
|
|
104
120
|
"""Reads a knowledge graph from Cognite Data Fusion (CDF).
|
|
105
121
|
|
|
106
122
|
Args:
|
|
107
123
|
data_model_id: Tuple of strings with the id of a CDF Data Model.
|
|
108
124
|
instance_space: The instance spaces to extract. If None, all instance spaces are extracted.
|
|
125
|
+
skip_cognite_views: If True, all Cognite Views are skipped. For example, if you have the CogniteAsset
|
|
126
|
+
view in you data model, it will ont be used to extract instances.
|
|
109
127
|
|
|
110
128
|
Returns:
|
|
111
129
|
IssueList: A list of issues that occurred during the extraction.
|
|
112
130
|
|
|
113
131
|
"""
|
|
132
|
+
return self._graph(data_model_id, instance_space, skip_cognite_views, unpack_json=False)
|
|
133
|
+
|
|
134
|
+
def _graph(
|
|
135
|
+
self,
|
|
136
|
+
data_model_id: DataModelIdentifier,
|
|
137
|
+
instance_space: str | SequenceNotStr[str] | None = None,
|
|
138
|
+
skip_cognite_views: bool = True,
|
|
139
|
+
unpack_json: bool = False,
|
|
140
|
+
str_to_ideal_type: bool = False,
|
|
141
|
+
) -> IssueList:
|
|
114
142
|
extractor = extractors.DMSGraphExtractor.from_data_model_id(
|
|
115
|
-
|
|
143
|
+
# We are skipping the Cognite Views
|
|
144
|
+
data_model_id,
|
|
145
|
+
self._get_client,
|
|
146
|
+
instance_space=instance_space,
|
|
147
|
+
skip_cognite_views=skip_cognite_views,
|
|
148
|
+
unpack_json=unpack_json,
|
|
149
|
+
str_to_ideal_type=str_to_ideal_type,
|
|
116
150
|
)
|
|
117
151
|
return self._state.write_graph(extractor)
|
|
118
152
|
|
|
@@ -130,7 +164,12 @@ class CDFClassicAPI(BaseReadAPI):
|
|
|
130
164
|
raise ValueError("No client provided. Please provide a client to read a data model.")
|
|
131
165
|
return self._state.client
|
|
132
166
|
|
|
133
|
-
def graph(
|
|
167
|
+
def graph(
|
|
168
|
+
self,
|
|
169
|
+
root_asset_external_id: str,
|
|
170
|
+
limit_per_type: int | None = None,
|
|
171
|
+
identifier: Literal["id", "externalId"] = "id",
|
|
172
|
+
) -> IssueList:
|
|
134
173
|
"""Reads the classic knowledge graph from CDF.
|
|
135
174
|
|
|
136
175
|
The Classic Graph consists of the following core resource type.
|
|
@@ -165,6 +204,8 @@ class CDFClassicAPI(BaseReadAPI):
|
|
|
165
204
|
Args:
|
|
166
205
|
root_asset_external_id: The external id of the root asset
|
|
167
206
|
limit_per_type: The maximum number of nodes to extract per core node type. If None, all nodes are extracted.
|
|
207
|
+
identifier: The identifier to use for the core nodes. Note selecting "id" can cause issues if the external
|
|
208
|
+
ID of the core nodes is missing. Default is "id".
|
|
168
209
|
|
|
169
210
|
Returns:
|
|
170
211
|
IssueList: A list of issues that occurred during the extraction.
|
|
@@ -174,6 +215,18 @@ class CDFClassicAPI(BaseReadAPI):
|
|
|
174
215
|
neat.read.cdf.graph("root_asset_external_id")
|
|
175
216
|
```
|
|
176
217
|
"""
|
|
218
|
+
return self._graph(
|
|
219
|
+
root_asset_external_id, limit_per_type, identifier, reference_timeseries=False, reference_files=False
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
def _graph(
|
|
223
|
+
self,
|
|
224
|
+
root_asset_external_id: str,
|
|
225
|
+
limit_per_type: int | None = None,
|
|
226
|
+
identifier: Literal["id", "externalId"] = "id",
|
|
227
|
+
reference_timeseries: bool = False,
|
|
228
|
+
reference_files: bool = False,
|
|
229
|
+
) -> IssueList:
|
|
177
230
|
namespace = CLASSIC_CDF_NAMESPACE
|
|
178
231
|
extractor = extractors.ClassicGraphExtractor(
|
|
179
232
|
self._get_client,
|
|
@@ -181,14 +234,12 @@ class CDFClassicAPI(BaseReadAPI):
|
|
|
181
234
|
limit_per_type=limit_per_type,
|
|
182
235
|
namespace=namespace,
|
|
183
236
|
prefix="Classic",
|
|
237
|
+
identifier=identifier,
|
|
184
238
|
)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
print("Use the .inspect.issues() for more details")
|
|
239
|
+
extract_issues = self._state.write_graph(extractor)
|
|
240
|
+
if identifier == "externalId":
|
|
241
|
+
self._state.quoted_source_identifiers = True
|
|
189
242
|
|
|
190
|
-
# Converting the instances from classic to core
|
|
191
|
-
self._state.instances.store.transform(LookupRelationshipSourceTarget(namespace, "Classic"))
|
|
192
243
|
self._state.instances.store.transform(
|
|
193
244
|
ConvertLiteral(
|
|
194
245
|
namespace["ClassicTimeSeries"],
|
|
@@ -200,11 +251,21 @@ class CDFClassicAPI(BaseReadAPI):
|
|
|
200
251
|
LiteralToEntity(None, namespace["source"], "ClassicSourceSystem", "name"),
|
|
201
252
|
)
|
|
202
253
|
# Updating the information model.
|
|
203
|
-
self._state.rule_store.transform(
|
|
254
|
+
prepare_issues = self._state.rule_store.transform(
|
|
255
|
+
ClassicPrepareCore(namespace, reference_timeseries, reference_files)
|
|
256
|
+
)
|
|
204
257
|
# Update the instance store with the latest rules
|
|
205
258
|
information_rules = self._state.rule_store.last_verified_information_rules
|
|
206
|
-
self._state.instances.store.rules = information_rules
|
|
207
|
-
|
|
259
|
+
self._state.instances.store.rules[self._state.instances.store.default_named_graph] = information_rules
|
|
260
|
+
|
|
261
|
+
all_issues = IssueList(extract_issues + prepare_issues)
|
|
262
|
+
# Update the provenance with all issue.
|
|
263
|
+
object.__setattr__(self._state.instances.store.provenance[-1].target_entity, "issues", all_issues)
|
|
264
|
+
all_issues.action = "Read Classic Graph"
|
|
265
|
+
if all_issues:
|
|
266
|
+
print("Use the .inspect.issues() for more details")
|
|
267
|
+
|
|
268
|
+
return all_issues
|
|
208
269
|
|
|
209
270
|
|
|
210
271
|
@session_class_wrapper
|
|
@@ -241,7 +302,6 @@ class ExcelReadAPI(BaseReadAPI):
|
|
|
241
302
|
class ExcelExampleAPI(BaseReadAPI):
|
|
242
303
|
"""Used as example for reading some data model into the NeatSession."""
|
|
243
304
|
|
|
244
|
-
@property
|
|
245
305
|
def pump_example(self) -> IssueList:
|
|
246
306
|
"""Reads the Hello World pump example into the NeatSession."""
|
|
247
307
|
importer: importers.ExcelImporter = importers.ExcelImporter(catalog.hello_world_pump)
|
|
@@ -340,7 +400,7 @@ class XMLReadAPI(BaseReadAPI):
|
|
|
340
400
|
raise NeatValueError("Only support XML files of DEXPI format at the moment.")
|
|
341
401
|
|
|
342
402
|
def dexpi(self, io: Any) -> None:
|
|
343
|
-
"""Reads a DEXPI file into the NeatSession.
|
|
403
|
+
"""Reads a DEXPI file into the NeatSession and executes set of predefined transformations.
|
|
344
404
|
|
|
345
405
|
Args:
|
|
346
406
|
io: file path or url to the DEXPI file
|
|
@@ -349,6 +409,13 @@ class XMLReadAPI(BaseReadAPI):
|
|
|
349
409
|
```python
|
|
350
410
|
neat.read.xml.dexpi("url_or_path_to_dexpi_file")
|
|
351
411
|
```
|
|
412
|
+
|
|
413
|
+
!!! note "This method bundles several graph transformers which"
|
|
414
|
+
- attach values of generic attributes to nodes
|
|
415
|
+
- create associations between nodes
|
|
416
|
+
- remove unused generic attributes
|
|
417
|
+
- remove associations between nodes that do not exist in the extracted graph
|
|
418
|
+
- remove edges to nodes that do not exist in the extracted graph
|
|
352
419
|
"""
|
|
353
420
|
path = NeatReader.create(io).materialize_path()
|
|
354
421
|
engine = import_engine()
|
|
@@ -357,8 +424,36 @@ class XMLReadAPI(BaseReadAPI):
|
|
|
357
424
|
extractor = engine.create_extractor()
|
|
358
425
|
self._state.instances.store.write(extractor)
|
|
359
426
|
|
|
427
|
+
DEXPI = get_default_prefixes_and_namespaces()["dexpi"]
|
|
428
|
+
|
|
429
|
+
transformers = [
|
|
430
|
+
# Remove any instance which type is unknown
|
|
431
|
+
PruneInstancesOfUnknownType(),
|
|
432
|
+
# Directly connect generic attributes
|
|
433
|
+
AttachPropertyFromTargetToSource(
|
|
434
|
+
target_property=DEXPI.Value,
|
|
435
|
+
target_property_holding_new_property=DEXPI.Name,
|
|
436
|
+
target_node_type=DEXPI.GenericAttribute,
|
|
437
|
+
delete_target_node=True,
|
|
438
|
+
),
|
|
439
|
+
# Directly connect associations
|
|
440
|
+
AttachPropertyFromTargetToSource(
|
|
441
|
+
target_property=DEXPI.ItemID,
|
|
442
|
+
target_property_holding_new_property=DEXPI.Type,
|
|
443
|
+
target_node_type=DEXPI.Association,
|
|
444
|
+
delete_target_node=True,
|
|
445
|
+
),
|
|
446
|
+
# Remove unused generic attributes and associations
|
|
447
|
+
PruneTypes([DEXPI.GenericAttribute, DEXPI.Association]),
|
|
448
|
+
# Remove edges to nodes that do not exist in the extracted graph
|
|
449
|
+
PruneDeadEndEdges(),
|
|
450
|
+
]
|
|
451
|
+
|
|
452
|
+
for transformer in transformers:
|
|
453
|
+
self._state.instances.store.transform(cast(Transformers, transformer))
|
|
454
|
+
|
|
360
455
|
def aml(self, io: Any):
|
|
361
|
-
"""Reads an AML file into NeatSession.
|
|
456
|
+
"""Reads an AML file into NeatSession and executes a set of predefined transformations.
|
|
362
457
|
|
|
363
458
|
Args:
|
|
364
459
|
io: file path or url to the AML file
|
|
@@ -367,6 +462,11 @@ class XMLReadAPI(BaseReadAPI):
|
|
|
367
462
|
```python
|
|
368
463
|
neat.read.xml.aml("url_or_path_to_aml_file")
|
|
369
464
|
```
|
|
465
|
+
|
|
466
|
+
!!! note "This method bundles several graph transformers which"
|
|
467
|
+
- attach values of attributes to nodes
|
|
468
|
+
- remove unused attributes
|
|
469
|
+
- remove edges to nodes that do not exist in the extracted graph
|
|
370
470
|
"""
|
|
371
471
|
path = NeatReader.create(io).materialize_path()
|
|
372
472
|
engine = import_engine()
|
|
@@ -375,6 +475,27 @@ class XMLReadAPI(BaseReadAPI):
|
|
|
375
475
|
extractor = engine.create_extractor()
|
|
376
476
|
self._state.instances.store.write(extractor)
|
|
377
477
|
|
|
478
|
+
AML = get_default_prefixes_and_namespaces()["aml"]
|
|
479
|
+
|
|
480
|
+
transformers = [
|
|
481
|
+
# Remove any instance which type is unknown
|
|
482
|
+
PruneInstancesOfUnknownType(),
|
|
483
|
+
# Directly connect generic attributes
|
|
484
|
+
AttachPropertyFromTargetToSource(
|
|
485
|
+
target_property=AML.Value,
|
|
486
|
+
target_property_holding_new_property=AML.Name,
|
|
487
|
+
target_node_type=AML.Attribute,
|
|
488
|
+
delete_target_node=True,
|
|
489
|
+
),
|
|
490
|
+
# Prune unused attributes
|
|
491
|
+
PruneTypes([AML.Attribute]),
|
|
492
|
+
# # Remove edges to nodes that do not exist in the extracted graph
|
|
493
|
+
PruneDeadEndEdges(),
|
|
494
|
+
]
|
|
495
|
+
|
|
496
|
+
for transformer in transformers:
|
|
497
|
+
self._state.instances.store.transform(cast(Transformers, transformer))
|
|
498
|
+
|
|
378
499
|
|
|
379
500
|
@session_class_wrapper
|
|
380
501
|
class RDFReadAPI(BaseReadAPI):
|
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 SetType
|
|
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
|
|
@@ -18,6 +21,7 @@ class SetAPI:
|
|
|
18
21
|
def __init__(self, state: SessionState, verbose: bool) -> None:
|
|
19
22
|
self._state = state
|
|
20
23
|
self._verbose = verbose
|
|
24
|
+
self.instances = SetInstances(state, verbose)
|
|
21
25
|
|
|
22
26
|
def data_model_id(self, new_model_id: dm.DataModelId | tuple[str, str, str]) -> IssueList:
|
|
23
27
|
"""Sets the data model ID of the latest verified data model. Set the data model id as a tuple of strings
|
|
@@ -29,7 +33,9 @@ class SetAPI:
|
|
|
29
33
|
neat.set.data_model_id(("my_data_model_space", "My_Data_Model", "v1"))
|
|
30
34
|
```
|
|
31
35
|
"""
|
|
32
|
-
|
|
36
|
+
if self._state.rule_store.empty:
|
|
37
|
+
raise NeatSessionError("No rules to set the data model ID.")
|
|
38
|
+
rules = self._state.rule_store.provenance[-1].target_entity.dms
|
|
33
39
|
if isinstance(rules, DMSRules):
|
|
34
40
|
if rules.metadata.as_data_model_id() in COGNITE_MODELS:
|
|
35
41
|
raise NeatSessionError(
|
|
@@ -44,3 +50,46 @@ class SetAPI:
|
|
|
44
50
|
if self._verbose:
|
|
45
51
|
print(f"Client set to {self._state.client.config.project} CDF project.")
|
|
46
52
|
return None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@session_class_wrapper
|
|
56
|
+
class SetInstances:
|
|
57
|
+
"""Used to change instances"""
|
|
58
|
+
|
|
59
|
+
def __init__(self, state: SessionState, verbose: bool) -> None:
|
|
60
|
+
self._state = state
|
|
61
|
+
self._verbose = verbose
|
|
62
|
+
|
|
63
|
+
def type_using_property(self, current_type: str, property_type: str, drop_property: bool = True) -> None:
|
|
64
|
+
"""Replaces the type of all instances with the value of a property.
|
|
65
|
+
|
|
66
|
+
Example:
|
|
67
|
+
All Assets have a property `assetCategory` that we want to use as the type of all asset instances.
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
neat.set.instances.replace_type("Asset", "assetCategory")
|
|
71
|
+
```
|
|
72
|
+
"""
|
|
73
|
+
type_uri = self._state.instances.store.queries.type_uri(current_type)
|
|
74
|
+
property_uri = self._state.instances.store.queries.property_uri(property_type)
|
|
75
|
+
|
|
76
|
+
if not type_uri:
|
|
77
|
+
raise NeatValueError(f"Type {current_type} does not exist in the graph.")
|
|
78
|
+
elif len(type_uri) > 1:
|
|
79
|
+
raise NeatValueError(
|
|
80
|
+
f"{current_type} has multiple ids found in the graph: {humanize_collection(type_uri)}."
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
if not property_uri:
|
|
84
|
+
raise NeatValueError(f"Property {property_type} does not exist in the graph.")
|
|
85
|
+
elif len(type_uri) > 1:
|
|
86
|
+
raise NeatValueError(
|
|
87
|
+
f"{property_type} has multiple ids found in the graph: {humanize_collection(property_uri)}."
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if not self._state.instances.store.queries.type_with_property(type_uri[0], property_uri[0]):
|
|
91
|
+
raise NeatValueError(f"Property {property_type} is not defined for type {current_type}.")
|
|
92
|
+
|
|
93
|
+
self._state.instances.store.transform(SetType(type_uri[0], property_uri[0], drop_property))
|
|
94
|
+
|
|
95
|
+
return None
|