cognite-neat 0.103.1__py3-none-any.whl → 0.105.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/_client/_api/data_modeling_loaders.py +83 -23
- cognite/neat/_client/_api/schema.py +2 -1
- cognite/neat/_client/data_classes/neat_sequence.py +261 -0
- cognite/neat/_client/data_classes/schema.py +5 -1
- cognite/neat/_client/testing.py +33 -0
- cognite/neat/_constants.py +56 -0
- cognite/neat/_graph/extractors/_classic_cdf/_base.py +6 -5
- cognite/neat/_graph/extractors/_classic_cdf/_sequences.py +225 -11
- cognite/neat/_graph/extractors/_mock_graph_generator.py +2 -2
- cognite/neat/_graph/loaders/_rdf2dms.py +13 -2
- cognite/neat/_graph/transformers/__init__.py +3 -1
- cognite/neat/_graph/transformers/_base.py +109 -1
- cognite/neat/_graph/transformers/_classic_cdf.py +6 -1
- cognite/neat/_graph/transformers/_prune_graph.py +103 -47
- cognite/neat/_graph/transformers/_rdfpath.py +41 -17
- cognite/neat/_graph/transformers/_value_type.py +188 -151
- cognite/neat/_issues/__init__.py +0 -2
- cognite/neat/_issues/_base.py +54 -43
- cognite/neat/_issues/warnings/__init__.py +4 -1
- cognite/neat/_issues/warnings/_general.py +7 -0
- cognite/neat/_issues/warnings/_resources.py +12 -1
- cognite/neat/_rules/_shared.py +18 -34
- cognite/neat/_rules/exporters/_base.py +28 -2
- cognite/neat/_rules/exporters/_rules2dms.py +39 -1
- cognite/neat/_rules/exporters/_rules2excel.py +13 -2
- cognite/neat/_rules/exporters/_rules2instance_template.py +4 -0
- cognite/neat/_rules/exporters/_rules2ontology.py +13 -1
- cognite/neat/_rules/exporters/_rules2yaml.py +4 -0
- cognite/neat/_rules/importers/_base.py +9 -0
- cognite/neat/_rules/importers/_dms2rules.py +80 -57
- cognite/neat/_rules/importers/_dtdl2rules/dtdl_importer.py +5 -2
- cognite/neat/_rules/importers/_rdf/_base.py +10 -8
- cognite/neat/_rules/importers/_rdf/_imf2rules.py +4 -0
- cognite/neat/_rules/importers/_rdf/_inference2rules.py +7 -0
- cognite/neat/_rules/importers/_rdf/_owl2rules.py +4 -0
- cognite/neat/_rules/importers/_spreadsheet2rules.py +17 -8
- cognite/neat/_rules/importers/_yaml2rules.py +21 -7
- cognite/neat/_rules/models/_base_input.py +1 -1
- cognite/neat/_rules/models/_base_rules.py +9 -1
- cognite/neat/_rules/models/dms/_rules.py +4 -0
- cognite/neat/_rules/models/dms/_rules_input.py +9 -0
- cognite/neat/_rules/models/entities/_wrapped.py +10 -5
- cognite/neat/_rules/models/information/_rules.py +4 -0
- cognite/neat/_rules/models/information/_rules_input.py +9 -0
- cognite/neat/_rules/models/mapping/_classic2core.py +2 -5
- cognite/neat/_rules/models/mapping/_classic2core.yaml +239 -38
- cognite/neat/_rules/transformers/__init__.py +13 -6
- cognite/neat/_rules/transformers/_base.py +41 -65
- cognite/neat/_rules/transformers/_converters.py +404 -234
- cognite/neat/_rules/transformers/_mapping.py +93 -72
- cognite/neat/_rules/transformers/_verification.py +50 -38
- cognite/neat/_session/_base.py +32 -121
- cognite/neat/_session/_inspect.py +5 -3
- cognite/neat/_session/_mapping.py +17 -105
- cognite/neat/_session/_prepare.py +138 -268
- cognite/neat/_session/_read.py +39 -195
- cognite/neat/_session/_set.py +6 -30
- cognite/neat/_session/_show.py +40 -21
- cognite/neat/_session/_state.py +49 -107
- cognite/neat/_session/_to.py +44 -33
- cognite/neat/_shared.py +23 -2
- cognite/neat/_store/_provenance.py +3 -82
- cognite/neat/_store/_rules_store.py +368 -10
- cognite/neat/_store/exceptions.py +23 -0
- cognite/neat/_utils/graph_transformations_report.py +36 -0
- cognite/neat/_utils/rdf_.py +8 -0
- cognite/neat/_utils/reader/_base.py +27 -0
- cognite/neat/_utils/spreadsheet.py +5 -4
- cognite/neat/_version.py +1 -1
- {cognite_neat-0.103.1.dist-info → cognite_neat-0.105.0.dist-info}/METADATA +3 -2
- cognite_neat-0.105.0.dist-info/RECORD +179 -0
- {cognite_neat-0.103.1.dist-info → cognite_neat-0.105.0.dist-info}/WHEEL +1 -1
- cognite/neat/_app/api/__init__.py +0 -0
- cognite/neat/_app/api/asgi/metrics.py +0 -4
- cognite/neat/_app/api/configuration.py +0 -98
- cognite/neat/_app/api/context_manager/__init__.py +0 -3
- cognite/neat/_app/api/context_manager/manager.py +0 -16
- cognite/neat/_app/api/data_classes/__init__.py +0 -0
- cognite/neat/_app/api/data_classes/rest.py +0 -59
- cognite/neat/_app/api/explorer.py +0 -66
- cognite/neat/_app/api/routers/configuration.py +0 -25
- cognite/neat/_app/api/routers/crud.py +0 -102
- cognite/neat/_app/api/routers/metrics.py +0 -10
- cognite/neat/_app/api/routers/workflows.py +0 -224
- cognite/neat/_app/api/utils/__init__.py +0 -0
- cognite/neat/_app/api/utils/data_mapping.py +0 -17
- cognite/neat/_app/api/utils/logging.py +0 -26
- cognite/neat/_app/api/utils/query_templates.py +0 -92
- cognite/neat/_app/main.py +0 -17
- cognite/neat/_app/monitoring/__init__.py +0 -0
- cognite/neat/_app/monitoring/metrics.py +0 -69
- cognite/neat/_app/ui/index.html +0 -1
- cognite/neat/_app/ui/neat-app/.gitignore +0 -23
- cognite/neat/_app/ui/neat-app/README.md +0 -70
- cognite/neat/_app/ui/neat-app/build/asset-manifest.json +0 -14
- cognite/neat/_app/ui/neat-app/build/favicon.ico +0 -0
- cognite/neat/_app/ui/neat-app/build/img/architect-icon.svg +0 -116
- cognite/neat/_app/ui/neat-app/build/img/developer-icon.svg +0 -112
- cognite/neat/_app/ui/neat-app/build/img/sme-icon.svg +0 -34
- cognite/neat/_app/ui/neat-app/build/index.html +0 -1
- cognite/neat/_app/ui/neat-app/build/logo192.png +0 -0
- cognite/neat/_app/ui/neat-app/build/manifest.json +0 -25
- cognite/neat/_app/ui/neat-app/build/robots.txt +0 -3
- cognite/neat/_app/ui/neat-app/build/static/css/main.72e3d92e.css +0 -2
- cognite/neat/_app/ui/neat-app/build/static/css/main.72e3d92e.css.map +0 -1
- cognite/neat/_app/ui/neat-app/build/static/js/main.5a52cf09.js +0 -3
- cognite/neat/_app/ui/neat-app/build/static/js/main.5a52cf09.js.LICENSE.txt +0 -88
- cognite/neat/_app/ui/neat-app/build/static/js/main.5a52cf09.js.map +0 -1
- cognite/neat/_app/ui/neat-app/build/static/media/logo.8093b84df9ed36a174c629d6fe0b730d.svg +0 -1
- cognite/neat/_app/ui/neat-app/package-lock.json +0 -18306
- cognite/neat/_app/ui/neat-app/package.json +0 -62
- cognite/neat/_app/ui/neat-app/public/favicon.ico +0 -0
- cognite/neat/_app/ui/neat-app/public/img/architect-icon.svg +0 -116
- cognite/neat/_app/ui/neat-app/public/img/developer-icon.svg +0 -112
- cognite/neat/_app/ui/neat-app/public/img/sme-icon.svg +0 -34
- cognite/neat/_app/ui/neat-app/public/index.html +0 -43
- cognite/neat/_app/ui/neat-app/public/logo192.png +0 -0
- cognite/neat/_app/ui/neat-app/public/manifest.json +0 -25
- cognite/neat/_app/ui/neat-app/public/robots.txt +0 -3
- cognite/neat/_app/ui/neat-app/src/App.css +0 -38
- cognite/neat/_app/ui/neat-app/src/App.js +0 -17
- cognite/neat/_app/ui/neat-app/src/App.test.js +0 -8
- cognite/neat/_app/ui/neat-app/src/MainContainer.tsx +0 -70
- cognite/neat/_app/ui/neat-app/src/components/JsonViewer.tsx +0 -43
- cognite/neat/_app/ui/neat-app/src/components/LocalUploader.tsx +0 -124
- cognite/neat/_app/ui/neat-app/src/components/OverviewComponentEditorDialog.tsx +0 -63
- cognite/neat/_app/ui/neat-app/src/components/StepEditorDialog.tsx +0 -511
- cognite/neat/_app/ui/neat-app/src/components/TabPanel.tsx +0 -36
- cognite/neat/_app/ui/neat-app/src/components/Utils.tsx +0 -56
- cognite/neat/_app/ui/neat-app/src/components/WorkflowDeleteDialog.tsx +0 -60
- cognite/neat/_app/ui/neat-app/src/components/WorkflowExecutionReport.tsx +0 -112
- cognite/neat/_app/ui/neat-app/src/components/WorkflowImportExportDialog.tsx +0 -67
- cognite/neat/_app/ui/neat-app/src/components/WorkflowMetadataDialog.tsx +0 -79
- cognite/neat/_app/ui/neat-app/src/index.css +0 -13
- cognite/neat/_app/ui/neat-app/src/index.js +0 -13
- cognite/neat/_app/ui/neat-app/src/logo.svg +0 -1
- cognite/neat/_app/ui/neat-app/src/reportWebVitals.js +0 -13
- cognite/neat/_app/ui/neat-app/src/setupTests.js +0 -5
- cognite/neat/_app/ui/neat-app/src/types/WorkflowTypes.ts +0 -388
- cognite/neat/_app/ui/neat-app/src/views/AboutView.tsx +0 -61
- cognite/neat/_app/ui/neat-app/src/views/ConfigView.tsx +0 -184
- cognite/neat/_app/ui/neat-app/src/views/GlobalConfigView.tsx +0 -180
- cognite/neat/_app/ui/neat-app/src/views/WorkflowView.tsx +0 -570
- cognite/neat/_app/ui/neat-app/tsconfig.json +0 -27
- cognite/neat/_rules/transformers/_pipelines.py +0 -70
- cognite/neat/_workflows/__init__.py +0 -17
- cognite/neat/_workflows/base.py +0 -590
- cognite/neat/_workflows/cdf_store.py +0 -393
- cognite/neat/_workflows/examples/Export_DMS/workflow.yaml +0 -89
- cognite/neat/_workflows/examples/Export_Semantic_Data_Model/workflow.yaml +0 -66
- cognite/neat/_workflows/examples/Import_DMS/workflow.yaml +0 -65
- cognite/neat/_workflows/examples/Validate_Rules/workflow.yaml +0 -67
- cognite/neat/_workflows/examples/Validate_Solution_Model/workflow.yaml +0 -64
- cognite/neat/_workflows/manager.py +0 -292
- cognite/neat/_workflows/model.py +0 -203
- cognite/neat/_workflows/steps/__init__.py +0 -0
- cognite/neat/_workflows/steps/data_contracts.py +0 -109
- cognite/neat/_workflows/steps/lib/__init__.py +0 -0
- cognite/neat/_workflows/steps/lib/current/__init__.py +0 -6
- cognite/neat/_workflows/steps/lib/current/graph_extractor.py +0 -100
- cognite/neat/_workflows/steps/lib/current/graph_loader.py +0 -51
- cognite/neat/_workflows/steps/lib/current/graph_store.py +0 -48
- cognite/neat/_workflows/steps/lib/current/rules_exporter.py +0 -537
- cognite/neat/_workflows/steps/lib/current/rules_importer.py +0 -398
- cognite/neat/_workflows/steps/lib/current/rules_validator.py +0 -106
- cognite/neat/_workflows/steps/lib/io/__init__.py +0 -1
- cognite/neat/_workflows/steps/lib/io/io_steps.py +0 -393
- cognite/neat/_workflows/steps/step_model.py +0 -79
- cognite/neat/_workflows/steps_registry.py +0 -218
- cognite/neat/_workflows/tasks.py +0 -18
- cognite/neat/_workflows/triggers.py +0 -169
- cognite/neat/_workflows/utils.py +0 -19
- cognite_neat-0.103.1.dist-info/RECORD +0 -275
- {cognite_neat-0.103.1.dist-info → cognite_neat-0.105.0.dist-info}/LICENSE +0 -0
- {cognite_neat-0.103.1.dist-info → cognite_neat-0.105.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,37 +1,251 @@
|
|
|
1
|
-
|
|
1
|
+
import itertools
|
|
2
|
+
import json
|
|
3
|
+
from collections.abc import Callable, Iterable, Set
|
|
2
4
|
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
3
6
|
|
|
4
7
|
from cognite.client import CogniteClient
|
|
5
|
-
from cognite.client.data_classes import Sequence, SequenceFilter
|
|
8
|
+
from cognite.client.data_classes import Sequence, SequenceFilter
|
|
9
|
+
from rdflib import RDF, XSD, Literal, Namespace, URIRef
|
|
6
10
|
|
|
7
|
-
from .
|
|
11
|
+
from cognite.neat._client.data_classes.neat_sequence import NeatSequence, NeatSequenceList
|
|
12
|
+
from cognite.neat._shared import Triple
|
|
8
13
|
|
|
14
|
+
from ._base import DEFAULT_SKIP_METADATA_VALUES, ClassicCDFBaseExtractor, InstanceIdPrefix
|
|
9
15
|
|
|
10
|
-
|
|
11
|
-
|
|
16
|
+
|
|
17
|
+
class SequencesExtractor(ClassicCDFBaseExtractor[NeatSequence]):
|
|
18
|
+
"""Extract data from Cognite Data Fusions Sequences into Neat.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
items (Iterable[T_CogniteResource]): An iterable of classic resource.
|
|
22
|
+
namespace (Namespace, optional): The namespace to use. Defaults to DEFAULT_NAMESPACE.
|
|
23
|
+
to_type (Callable[[T_CogniteResource], str | None], optional): A function to convert an item to a type.
|
|
24
|
+
Defaults to None. If None or if the function returns None, the asset will be set to the default type.
|
|
25
|
+
total (int, optional): The total number of items to load. If passed, you will get a progress bar if rich
|
|
26
|
+
is installed. Defaults to None.
|
|
27
|
+
limit (int, optional): The maximal number of items to load. Defaults to None. This is typically used for
|
|
28
|
+
testing setup of the extractor. For example, if you are extracting 100 000 assets, you might want to
|
|
29
|
+
limit the extraction to 1000 assets to test the setup.
|
|
30
|
+
unpack_metadata (bool, optional): Whether to unpack metadata. Defaults to False, which yields the metadata as
|
|
31
|
+
a JSON string.
|
|
32
|
+
skip_metadata_values (set[str] | frozenset[str] | None, optional): If you are unpacking metadata, then
|
|
33
|
+
values in this set will be skipped.
|
|
34
|
+
camel_case (bool, optional): Whether to use camelCase instead of snake_case for property names.
|
|
35
|
+
Defaults to True.
|
|
36
|
+
as_write (bool, optional): Whether to use the write/request format of the items. Defaults to False.
|
|
37
|
+
unpack_columns (bool, optional): Whether to unpack columns. Defaults to False.
|
|
38
|
+
"""
|
|
12
39
|
|
|
13
40
|
_default_rdf_type = "Sequence"
|
|
41
|
+
_column_rdf_type = "ColumnClass"
|
|
14
42
|
_instance_id_prefix = InstanceIdPrefix.sequence
|
|
15
43
|
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
items: Iterable[NeatSequence],
|
|
47
|
+
namespace: Namespace | None = None,
|
|
48
|
+
to_type: Callable[[NeatSequence], str | None] | None = None,
|
|
49
|
+
total: int | None = None,
|
|
50
|
+
limit: int | None = None,
|
|
51
|
+
unpack_metadata: bool = True,
|
|
52
|
+
skip_metadata_values: Set[str] | None = DEFAULT_SKIP_METADATA_VALUES,
|
|
53
|
+
camel_case: bool = True,
|
|
54
|
+
as_write: bool = False,
|
|
55
|
+
unpack_columns: bool = False,
|
|
56
|
+
):
|
|
57
|
+
super().__init__(
|
|
58
|
+
items, namespace, to_type, total, limit, unpack_metadata, skip_metadata_values, camel_case, as_write
|
|
59
|
+
)
|
|
60
|
+
self.unpack_columns = unpack_columns
|
|
61
|
+
|
|
16
62
|
@classmethod
|
|
17
|
-
def
|
|
63
|
+
def from_dataset(
|
|
64
|
+
cls,
|
|
65
|
+
client: CogniteClient,
|
|
66
|
+
data_set_external_id: str,
|
|
67
|
+
namespace: Namespace | None = None,
|
|
68
|
+
to_type: Callable[[NeatSequence], str | None] | None = None,
|
|
69
|
+
limit: int | None = None,
|
|
70
|
+
unpack_metadata: bool = True,
|
|
71
|
+
skip_metadata_values: Set[str] | None = DEFAULT_SKIP_METADATA_VALUES,
|
|
72
|
+
camel_case: bool = True,
|
|
73
|
+
as_write: bool = False,
|
|
74
|
+
unpack_columns: bool = False,
|
|
75
|
+
):
|
|
76
|
+
total, items = cls._from_dataset(client, data_set_external_id)
|
|
77
|
+
return cls(
|
|
78
|
+
items,
|
|
79
|
+
namespace,
|
|
80
|
+
to_type,
|
|
81
|
+
total,
|
|
82
|
+
limit,
|
|
83
|
+
unpack_metadata,
|
|
84
|
+
skip_metadata_values,
|
|
85
|
+
camel_case,
|
|
86
|
+
as_write,
|
|
87
|
+
unpack_columns,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def from_hierarchy(
|
|
92
|
+
cls,
|
|
93
|
+
client: CogniteClient,
|
|
94
|
+
root_asset_external_id: str,
|
|
95
|
+
namespace: Namespace | None = None,
|
|
96
|
+
to_type: Callable[[NeatSequence], str | None] | None = None,
|
|
97
|
+
limit: int | None = None,
|
|
98
|
+
unpack_metadata: bool = True,
|
|
99
|
+
skip_metadata_values: Set[str] | None = DEFAULT_SKIP_METADATA_VALUES,
|
|
100
|
+
camel_case: bool = True,
|
|
101
|
+
as_write: bool = False,
|
|
102
|
+
unpack_columns: bool = False,
|
|
103
|
+
):
|
|
104
|
+
total, items = cls._from_hierarchy(client, root_asset_external_id)
|
|
105
|
+
return cls(
|
|
106
|
+
items,
|
|
107
|
+
namespace,
|
|
108
|
+
to_type,
|
|
109
|
+
total,
|
|
110
|
+
limit,
|
|
111
|
+
unpack_metadata,
|
|
112
|
+
skip_metadata_values,
|
|
113
|
+
camel_case,
|
|
114
|
+
as_write,
|
|
115
|
+
unpack_columns,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
@classmethod
|
|
119
|
+
def from_file(
|
|
120
|
+
cls,
|
|
121
|
+
file_path: str | Path,
|
|
122
|
+
namespace: Namespace | None = None,
|
|
123
|
+
to_type: Callable[[NeatSequence], str | None] | None = None,
|
|
124
|
+
limit: int | None = None,
|
|
125
|
+
unpack_metadata: bool = True,
|
|
126
|
+
skip_metadata_values: Set[str] | None = DEFAULT_SKIP_METADATA_VALUES,
|
|
127
|
+
camel_case: bool = True,
|
|
128
|
+
as_write: bool = False,
|
|
129
|
+
unpack_columns: bool = False,
|
|
130
|
+
):
|
|
131
|
+
total, items = cls._from_file(file_path)
|
|
132
|
+
return cls(
|
|
133
|
+
items,
|
|
134
|
+
namespace,
|
|
135
|
+
to_type,
|
|
136
|
+
total,
|
|
137
|
+
limit,
|
|
138
|
+
unpack_metadata,
|
|
139
|
+
skip_metadata_values,
|
|
140
|
+
camel_case,
|
|
141
|
+
as_write,
|
|
142
|
+
unpack_columns,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
@classmethod
|
|
146
|
+
def _from_dataset(
|
|
147
|
+
cls, client: CogniteClient, data_set_external_id: str
|
|
148
|
+
) -> tuple[int | None, Iterable[NeatSequence]]:
|
|
18
149
|
total = client.sequences.aggregate_count(
|
|
19
150
|
filter=SequenceFilter(data_set_ids=[{"externalId": data_set_external_id}])
|
|
20
151
|
)
|
|
21
152
|
items = client.sequences(data_set_external_ids=data_set_external_id)
|
|
22
|
-
return total, items
|
|
153
|
+
return total, cls._lookup_rows(items, client)
|
|
23
154
|
|
|
24
155
|
@classmethod
|
|
25
156
|
def _from_hierarchy(
|
|
26
157
|
cls, client: CogniteClient, root_asset_external_id: str
|
|
27
|
-
) -> tuple[int | None, Iterable[
|
|
158
|
+
) -> tuple[int | None, Iterable[NeatSequence]]:
|
|
28
159
|
total = client.sequences.aggregate_count(
|
|
29
160
|
filter=SequenceFilter(asset_subtree_ids=[{"externalId": root_asset_external_id}])
|
|
30
161
|
)
|
|
31
162
|
items = client.sequences(asset_subtree_external_ids=[root_asset_external_id])
|
|
32
|
-
return total, items
|
|
163
|
+
return total, cls._lookup_rows(items, client)
|
|
33
164
|
|
|
34
165
|
@classmethod
|
|
35
|
-
def _from_file(cls, file_path: str | Path) -> tuple[int | None, Iterable[
|
|
36
|
-
sequences =
|
|
166
|
+
def _from_file(cls, file_path: str | Path) -> tuple[int | None, Iterable[NeatSequence]]:
|
|
167
|
+
sequences = NeatSequenceList.load(Path(file_path).read_text())
|
|
37
168
|
return len(sequences), sequences
|
|
169
|
+
|
|
170
|
+
@classmethod
|
|
171
|
+
def _lookup_rows(cls, sequence_iterable: Iterable[Sequence], client: CogniteClient) -> Iterable[NeatSequence]:
|
|
172
|
+
iterator = iter(sequence_iterable)
|
|
173
|
+
for sequences in iter(lambda: list(itertools.islice(iterator, client.config.max_workers)), []):
|
|
174
|
+
# The PySDK uses max_workers to limit the number of requests made in parallel.
|
|
175
|
+
# We can only get one set of sequence rows per request, so we chunk the sequences up into groups of
|
|
176
|
+
# max_workers and then make a request to get all the rows for those sequences in one go.
|
|
177
|
+
sequence_list = list(sequences)
|
|
178
|
+
row_list = client.sequences.rows.retrieve(id=[seq.id for seq in sequence_list])
|
|
179
|
+
rows_by_sequence_id = {row.id: row.rows for row in row_list}
|
|
180
|
+
for seq in sequence_list:
|
|
181
|
+
yield NeatSequence.from_cognite_sequence(seq, rows_by_sequence_id.get(seq.id))
|
|
182
|
+
|
|
183
|
+
def _item2triples_special_cases(self, id_: URIRef, dumped: dict[str, Any]) -> list[Triple]:
|
|
184
|
+
"""For sequences, columns and rows are special cases.'"""
|
|
185
|
+
if self.unpack_columns:
|
|
186
|
+
return self._unpack_columns(id_, dumped)
|
|
187
|
+
else:
|
|
188
|
+
return self._default_columns_and_rows(id_, dumped)
|
|
189
|
+
|
|
190
|
+
def _default_columns_and_rows(self, id_: URIRef, dumped: dict[str, Any]) -> list[Triple]:
|
|
191
|
+
triples: list[Triple] = []
|
|
192
|
+
if "columns" in dumped:
|
|
193
|
+
columns = dumped.pop("columns")
|
|
194
|
+
triples.extend(
|
|
195
|
+
[
|
|
196
|
+
(
|
|
197
|
+
id_,
|
|
198
|
+
self.namespace.columns,
|
|
199
|
+
# Rows have a rowNumber, so we introduce colNumber here to be consistent.
|
|
200
|
+
Literal(json.dumps({"colNumber": no, **col}), datatype=XSD._NS["json"]),
|
|
201
|
+
)
|
|
202
|
+
for no, col in enumerate(columns, 1)
|
|
203
|
+
]
|
|
204
|
+
)
|
|
205
|
+
if "rows" in dumped:
|
|
206
|
+
rows = dumped.pop("rows")
|
|
207
|
+
triples.extend(
|
|
208
|
+
[(id_, self.namespace.rows, Literal(json.dumps(row), datatype=XSD._NS["json"])) for row in rows]
|
|
209
|
+
)
|
|
210
|
+
return triples
|
|
211
|
+
|
|
212
|
+
def _unpack_columns(self, id_: URIRef, dumped: dict[str, Any]) -> list[Triple]:
|
|
213
|
+
triples: list[Triple] = []
|
|
214
|
+
columnValueTypes: list[str] = []
|
|
215
|
+
column_order: list[str] = []
|
|
216
|
+
if columns := dumped.pop("columns", None):
|
|
217
|
+
for col in columns:
|
|
218
|
+
external_id = col.pop("externalId")
|
|
219
|
+
column_order.append(external_id)
|
|
220
|
+
value_type = col.pop("valueType")
|
|
221
|
+
columnValueTypes.append(value_type)
|
|
222
|
+
|
|
223
|
+
col_id = self.namespace[f"Column_{external_id}"]
|
|
224
|
+
triples.append((id_, self.namespace[external_id], col_id))
|
|
225
|
+
type_ = self.namespace[self._column_rdf_type]
|
|
226
|
+
triples.append((col_id, RDF.type, type_))
|
|
227
|
+
if metadata := col.pop("metadata", None):
|
|
228
|
+
triples.extend(self._metadata_to_triples(col_id, metadata))
|
|
229
|
+
# Should only be name and description left in col
|
|
230
|
+
for key, value in col.items():
|
|
231
|
+
if value is None:
|
|
232
|
+
continue
|
|
233
|
+
triples.append((col_id, self.namespace[key], Literal(value, datatype=XSD.string)))
|
|
234
|
+
|
|
235
|
+
triples.append(
|
|
236
|
+
(id_, self.namespace.columnOrder, Literal(json.dumps(column_order), datatype=XSD._NS["json"]))
|
|
237
|
+
)
|
|
238
|
+
triples.append(
|
|
239
|
+
(id_, self.namespace.columnValueTypes, Literal(json.dumps(columnValueTypes), datatype=XSD._NS["json"]))
|
|
240
|
+
)
|
|
241
|
+
if rows := dumped.pop("rows", None):
|
|
242
|
+
values_by_column: list[list[Any]] = [[] for _ in column_order]
|
|
243
|
+
for row in rows:
|
|
244
|
+
for i, value in enumerate(row["values"]):
|
|
245
|
+
values_by_column[i].append(value)
|
|
246
|
+
for col_name, values in zip(column_order, values_by_column, strict=False):
|
|
247
|
+
triples.append(
|
|
248
|
+
(id_, self.namespace[f"{col_name}Values"], Literal(json.dumps(values), datatype=XSD._NS["json"]))
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
return triples
|
|
@@ -46,7 +46,7 @@ class MockGraphGenerator(BaseExtractor):
|
|
|
46
46
|
# fixes potential issues with circular dependencies
|
|
47
47
|
from cognite.neat._rules.transformers import DMSToInformation
|
|
48
48
|
|
|
49
|
-
self.rules = DMSToInformation().transform(rules)
|
|
49
|
+
self.rules = DMSToInformation().transform(rules)
|
|
50
50
|
elif isinstance(rules, InformationRules):
|
|
51
51
|
self.rules = rules
|
|
52
52
|
else:
|
|
@@ -183,7 +183,7 @@ def _get_generation_order(
|
|
|
183
183
|
parent_col: str = "source_class",
|
|
184
184
|
child_col: str = "target_class",
|
|
185
185
|
) -> dict:
|
|
186
|
-
parent_child_list: list[list[str]] = class_linkage[[parent_col, child_col]].values.tolist()
|
|
186
|
+
parent_child_list: list[list[str]] = class_linkage[[parent_col, child_col]].values.tolist() # type: ignore[assignment]
|
|
187
187
|
# Build a directed graph and a list of all names that have no parent
|
|
188
188
|
graph: dict[str, set] = {name: set() for tup in parent_child_list for name in tup}
|
|
189
189
|
has_parent: dict[str, bool] = {name: False for tup in parent_child_list for name in tup}
|
|
@@ -19,6 +19,7 @@ from pydantic import BaseModel, ValidationInfo, create_model, field_validator
|
|
|
19
19
|
from rdflib import RDF, URIRef
|
|
20
20
|
|
|
21
21
|
from cognite.neat._client import NeatClient
|
|
22
|
+
from cognite.neat._constants import is_readonly_property
|
|
22
23
|
from cognite.neat._graph._tracking import LogTracker, Tracker
|
|
23
24
|
from cognite.neat._issues import IssueList, NeatIssue, NeatIssueList
|
|
24
25
|
from cognite.neat._issues.errors import (
|
|
@@ -303,6 +304,9 @@ class DMSLoader(CDFLoader[dm.InstanceApply]):
|
|
|
303
304
|
if isinstance(prop, dm.EdgeConnection):
|
|
304
305
|
edge_by_property[prop_id] = prop_id, prop
|
|
305
306
|
if isinstance(prop, dm.MappedProperty):
|
|
307
|
+
if is_readonly_property(prop.container, prop.container_property_identifier):
|
|
308
|
+
continue
|
|
309
|
+
|
|
306
310
|
if isinstance(prop.type, dm.DirectRelation):
|
|
307
311
|
if prop.container == dm.ContainerId("cdf_cdm", "CogniteTimeSeries") and prop_id == "unit":
|
|
308
312
|
unit_properties.append(prop_id)
|
|
@@ -343,9 +347,14 @@ class DMSLoader(CDFLoader[dm.InstanceApply]):
|
|
|
343
347
|
|
|
344
348
|
return value
|
|
345
349
|
|
|
346
|
-
def parse_json_string(cls, value: Any, info: ValidationInfo) -> dict:
|
|
350
|
+
def parse_json_string(cls, value: Any, info: ValidationInfo) -> dict | list:
|
|
347
351
|
if isinstance(value, dict):
|
|
348
352
|
return value
|
|
353
|
+
elif isinstance(value, list):
|
|
354
|
+
try:
|
|
355
|
+
return [json.loads(v) if isinstance(v, str) else v for v in value]
|
|
356
|
+
except json.JSONDecodeError as error:
|
|
357
|
+
raise ValueError(f"Not valid JSON string for {info.field_name}: {value}, error {error}") from error
|
|
349
358
|
elif isinstance(value, str):
|
|
350
359
|
try:
|
|
351
360
|
return json.loads(value)
|
|
@@ -401,7 +410,9 @@ class DMSLoader(CDFLoader[dm.InstanceApply]):
|
|
|
401
410
|
space=self.instance_space,
|
|
402
411
|
external_id=identifier,
|
|
403
412
|
type=(dm.DirectRelationReference(view_id.space, view_id.external_id) if type_ is not None else None),
|
|
404
|
-
sources=[
|
|
413
|
+
sources=[
|
|
414
|
+
dm.NodeOrEdgeData(source=view_id, properties=dict(created.model_dump(exclude_unset=True).items()))
|
|
415
|
+
],
|
|
405
416
|
)
|
|
406
417
|
|
|
407
418
|
def _create_edges(
|
|
@@ -15,7 +15,7 @@ from ._prune_graph import (
|
|
|
15
15
|
PruneTypes,
|
|
16
16
|
)
|
|
17
17
|
from ._rdfpath import AddSelfReferenceProperty, MakeConnectionOnExactMatch
|
|
18
|
-
from ._value_type import ConvertLiteral, LiteralToEntity, SplitMultiValueProperty
|
|
18
|
+
from ._value_type import ConnectionToLiteral, ConvertLiteral, LiteralToEntity, SplitMultiValueProperty
|
|
19
19
|
|
|
20
20
|
__all__ = [
|
|
21
21
|
"AddAssetDepth",
|
|
@@ -26,6 +26,7 @@ __all__ = [
|
|
|
26
26
|
"AssetSequenceConnector",
|
|
27
27
|
"AssetTimeSeriesConnector",
|
|
28
28
|
"AttachPropertyFromTargetToSource",
|
|
29
|
+
"ConnectionToLiteral",
|
|
29
30
|
"ConvertLiteral",
|
|
30
31
|
"LiteralToEntity",
|
|
31
32
|
"MakeConnectionOnExactMatch",
|
|
@@ -55,4 +56,5 @@ Transformers = (
|
|
|
55
56
|
| PruneInstancesOfUnknownType
|
|
56
57
|
| ConvertLiteral
|
|
57
58
|
| LiteralToEntity
|
|
59
|
+
| ConnectionToLiteral
|
|
58
60
|
)
|
|
@@ -1,7 +1,27 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import warnings
|
|
1
3
|
from abc import ABC, abstractmethod
|
|
2
|
-
from typing import ClassVar
|
|
4
|
+
from typing import ClassVar, TypeAlias, cast
|
|
3
5
|
|
|
4
6
|
from rdflib import Graph
|
|
7
|
+
from rdflib.query import ResultRow
|
|
8
|
+
|
|
9
|
+
from cognite.neat._issues.warnings import NeatValueWarning
|
|
10
|
+
from cognite.neat._shared import Triple
|
|
11
|
+
from cognite.neat._utils.collection_ import iterate_progress_bar
|
|
12
|
+
from cognite.neat._utils.graph_transformations_report import GraphTransformationResult
|
|
13
|
+
|
|
14
|
+
To_Add_Triples: TypeAlias = list[Triple]
|
|
15
|
+
To_Remove_Triples: TypeAlias = list[Triple]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclasses.dataclass
|
|
19
|
+
class RowTransformationOutput:
|
|
20
|
+
remove_triples: To_Remove_Triples = dataclasses.field(default_factory=list)
|
|
21
|
+
add_triples: To_Add_Triples = dataclasses.field(default_factory=list)
|
|
22
|
+
instances_removed_count: int = 0
|
|
23
|
+
instances_added_count: int = 0
|
|
24
|
+
instances_modified_count: int = 0
|
|
5
25
|
|
|
6
26
|
|
|
7
27
|
class BaseTransformer(ABC):
|
|
@@ -12,3 +32,91 @@ class BaseTransformer(ABC):
|
|
|
12
32
|
@abstractmethod
|
|
13
33
|
def transform(self, graph: Graph) -> None:
|
|
14
34
|
raise NotImplementedError()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class BaseTransformerStandardised(ABC):
|
|
38
|
+
"""Standardised base transformer to use in case a transformer is adding or removing triples from a graph. If you
|
|
39
|
+
are doing more specialised operations, please overwrite the .transform() method.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
description: str
|
|
43
|
+
_use_only_once: bool = False
|
|
44
|
+
_need_changes: ClassVar[frozenset[str]] = frozenset()
|
|
45
|
+
_use_iterate_bar_threshold: int = 500
|
|
46
|
+
|
|
47
|
+
@abstractmethod
|
|
48
|
+
def operation(self, query_result_row: ResultRow) -> RowTransformationOutput:
|
|
49
|
+
"""The operations to perform on each row resulting from the ._iterate_query() method.
|
|
50
|
+
The operation should return a list of triples to add and to remove.
|
|
51
|
+
"""
|
|
52
|
+
raise NotImplementedError()
|
|
53
|
+
|
|
54
|
+
@abstractmethod
|
|
55
|
+
def _count_query(self) -> str:
|
|
56
|
+
"""
|
|
57
|
+
Overwrite to fetch all affected properties in the graph as a result of the transformation.
|
|
58
|
+
Returns:
|
|
59
|
+
A query string.
|
|
60
|
+
"""
|
|
61
|
+
raise NotImplementedError()
|
|
62
|
+
|
|
63
|
+
@abstractmethod
|
|
64
|
+
def _iterate_query(self) -> str:
|
|
65
|
+
"""
|
|
66
|
+
The query to use for extracting target triples from the graph and performing the transformation.
|
|
67
|
+
Returns:
|
|
68
|
+
A query string.
|
|
69
|
+
"""
|
|
70
|
+
raise NotImplementedError()
|
|
71
|
+
|
|
72
|
+
def _skip_count_query(self) -> str:
|
|
73
|
+
"""
|
|
74
|
+
The query to use for extracting target triples from the graph and performing the transformation.
|
|
75
|
+
Returns:
|
|
76
|
+
A query string.
|
|
77
|
+
"""
|
|
78
|
+
return ""
|
|
79
|
+
|
|
80
|
+
def transform(self, graph: Graph) -> GraphTransformationResult:
|
|
81
|
+
outcome = GraphTransformationResult(self.__class__.__name__)
|
|
82
|
+
outcome.added = outcome.modified = outcome.removed = 0
|
|
83
|
+
|
|
84
|
+
iteration_count_res = list(graph.query(self._count_query()))
|
|
85
|
+
iteration_count = int(iteration_count_res[0][0]) # type: ignore [index, arg-type]
|
|
86
|
+
|
|
87
|
+
outcome.affected_nodes_count = iteration_count
|
|
88
|
+
|
|
89
|
+
if self._skip_count_query():
|
|
90
|
+
skipped_count_res = list(graph.query(self._skip_count_query()))
|
|
91
|
+
skipped_count = int(skipped_count_res[0][0]) # type: ignore [index, arg-type]
|
|
92
|
+
warnings.warn(
|
|
93
|
+
NeatValueWarning(f"Skipping {skipped_count} properties in transformation {self.__class__.__name__}"),
|
|
94
|
+
stacklevel=2,
|
|
95
|
+
)
|
|
96
|
+
outcome.skipped = skipped_count
|
|
97
|
+
|
|
98
|
+
if iteration_count == 0:
|
|
99
|
+
return outcome
|
|
100
|
+
|
|
101
|
+
result_iterable = graph.query(self._iterate_query())
|
|
102
|
+
if iteration_count > self._use_iterate_bar_threshold:
|
|
103
|
+
result_iterable = iterate_progress_bar( # type: ignore[misc, assignment]
|
|
104
|
+
result_iterable,
|
|
105
|
+
total=iteration_count,
|
|
106
|
+
description=self.description,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
for row in result_iterable:
|
|
110
|
+
row = cast(ResultRow, row)
|
|
111
|
+
row_output = self.operation(row)
|
|
112
|
+
|
|
113
|
+
outcome.added += row_output.instances_added_count
|
|
114
|
+
outcome.removed += row_output.instances_removed_count
|
|
115
|
+
outcome.modified += row_output.instances_modified_count
|
|
116
|
+
|
|
117
|
+
for triple in row_output.add_triples:
|
|
118
|
+
graph.add(triple)
|
|
119
|
+
for triple in row_output.remove_triples:
|
|
120
|
+
graph.remove(triple)
|
|
121
|
+
|
|
122
|
+
return outcome
|
|
@@ -21,6 +21,7 @@ from cognite.neat._utils.rdf_ import (
|
|
|
21
21
|
from ._base import BaseTransformer
|
|
22
22
|
|
|
23
23
|
|
|
24
|
+
# TODO: standardise
|
|
24
25
|
class AddAssetDepth(BaseTransformer):
|
|
25
26
|
description: str = "Adds depth of asset in the asset hierarchy to the graph"
|
|
26
27
|
_use_only_once: bool = True
|
|
@@ -84,6 +85,7 @@ class AddAssetDepth(BaseTransformer):
|
|
|
84
85
|
return None
|
|
85
86
|
|
|
86
87
|
|
|
88
|
+
# TODO: standardise
|
|
87
89
|
class BaseAssetConnector(BaseTransformer, ABC):
|
|
88
90
|
_asset_type: URIRef = DEFAULT_NAMESPACE.Asset
|
|
89
91
|
_item_type: URIRef
|
|
@@ -166,6 +168,7 @@ class AssetEventConnector(BaseAssetConnector):
|
|
|
166
168
|
_connection_type = DEFAULT_NAMESPACE.event
|
|
167
169
|
|
|
168
170
|
|
|
171
|
+
# TODO: standardise
|
|
169
172
|
class AssetRelationshipConnector(BaseTransformer):
|
|
170
173
|
description: str = "Connects assets via relationships"
|
|
171
174
|
_use_only_once: bool = True
|
|
@@ -242,6 +245,7 @@ class AssetRelationshipConnector(BaseTransformer):
|
|
|
242
245
|
graph.remove((relationship_id, self.relationship_target_xid_prop, None))
|
|
243
246
|
|
|
244
247
|
|
|
248
|
+
# TODO: standardise
|
|
245
249
|
class RelationshipAsEdgeTransformer(BaseTransformer):
|
|
246
250
|
"""Converts relationships into edges in the graph.
|
|
247
251
|
|
|
@@ -371,7 +375,8 @@ WHERE {{
|
|
|
371
375
|
) -> list[Triple]:
|
|
372
376
|
relationship_triples = cast(list[Triple], list(graph.query(f"DESCRIBE <{relationship_id}>")))
|
|
373
377
|
object_by_predicates = cast(
|
|
374
|
-
dict[str, URIRef | Literal],
|
|
378
|
+
dict[str, URIRef | Literal],
|
|
379
|
+
{remove_namespace_from_uri(row[1]): row[2] for row in relationship_triples if row[1] != RDF.type},
|
|
375
380
|
)
|
|
376
381
|
source_external_id = cast(URIRef, object_by_predicates["sourceExternalId"])
|
|
377
382
|
target_source_id = cast(URIRef, object_by_predicates["targetExternalId"])
|