cognite-neat 0.86.0__py3-none-any.whl → 0.87.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of cognite-neat might be problematic. Click here for more details.
- cognite/neat/_version.py +1 -1
- cognite/neat/app/api/configuration.py +1 -10
- cognite/neat/app/api/routers/data_exploration.py +1 -1
- cognite/neat/config.py +84 -17
- cognite/neat/constants.py +11 -9
- cognite/neat/graph/extractors/_classic_cdf/_assets.py +1 -1
- cognite/neat/graph/extractors/_classic_cdf/_events.py +1 -1
- cognite/neat/graph/extractors/_classic_cdf/_files.py +1 -1
- cognite/neat/graph/extractors/_classic_cdf/_labels.py +1 -1
- cognite/neat/graph/extractors/_classic_cdf/_relationships.py +1 -1
- cognite/neat/graph/extractors/_classic_cdf/_sequences.py +1 -1
- cognite/neat/graph/extractors/_classic_cdf/_timeseries.py +1 -1
- cognite/neat/graph/extractors/_dexpi.py +1 -1
- cognite/neat/graph/extractors/_mock_graph_generator.py +8 -9
- cognite/neat/graph/loaders/__init__.py +5 -2
- cognite/neat/graph/loaders/_base.py +13 -5
- cognite/neat/graph/loaders/_rdf2asset.py +185 -55
- cognite/neat/graph/loaders/_rdf2dms.py +7 -7
- cognite/neat/graph/queries/_base.py +20 -11
- cognite/neat/graph/queries/_construct.py +5 -5
- cognite/neat/graph/queries/_shared.py +21 -7
- cognite/neat/graph/stores/_base.py +16 -4
- cognite/neat/graph/transformers/__init__.py +3 -0
- cognite/neat/graph/transformers/_rdfpath.py +42 -0
- cognite/neat/legacy/graph/extractors/_dexpi.py +0 -5
- cognite/neat/legacy/graph/extractors/_mock_graph_generator.py +1 -1
- cognite/neat/legacy/graph/loaders/_asset_loader.py +2 -2
- cognite/neat/legacy/graph/loaders/core/rdf_to_assets.py +5 -2
- cognite/neat/legacy/graph/loaders/core/rdf_to_relationships.py +4 -1
- cognite/neat/legacy/graph/loaders/rdf_to_dms.py +3 -1
- cognite/neat/legacy/graph/stores/_base.py +24 -8
- cognite/neat/legacy/graph/stores/_graphdb_store.py +3 -2
- cognite/neat/legacy/graph/stores/_memory_store.py +3 -3
- cognite/neat/legacy/graph/stores/_oxigraph_store.py +8 -4
- cognite/neat/legacy/graph/stores/_rdf_to_graph.py +5 -3
- cognite/neat/legacy/graph/transformations/query_generator/sparql.py +49 -16
- cognite/neat/legacy/graph/transformations/transformer.py +1 -1
- cognite/neat/legacy/rules/exporters/_rules2dms.py +8 -3
- cognite/neat/legacy/rules/exporters/_rules2graphql.py +1 -1
- cognite/neat/legacy/rules/exporters/_rules2ontology.py +2 -1
- cognite/neat/legacy/rules/exporters/_rules2pydantic_models.py +3 -4
- cognite/neat/legacy/rules/importers/_dms2rules.py +4 -1
- cognite/neat/legacy/rules/importers/_graph2rules.py +3 -3
- cognite/neat/legacy/rules/importers/_owl2rules/_owl2classes.py +1 -1
- cognite/neat/legacy/rules/importers/_owl2rules/_owl2metadata.py +2 -1
- cognite/neat/legacy/rules/importers/_owl2rules/_owl2properties.py +1 -1
- cognite/neat/legacy/rules/models/raw_rules.py +19 -7
- cognite/neat/legacy/rules/models/rules.py +32 -12
- cognite/neat/rules/_shared.py +6 -1
- cognite/neat/rules/analysis/__init__.py +4 -4
- cognite/neat/rules/analysis/_asset.py +143 -0
- cognite/neat/rules/analysis/_base.py +385 -6
- cognite/neat/rules/analysis/_information.py +183 -0
- cognite/neat/rules/exporters/_rules2dms.py +1 -1
- cognite/neat/rules/exporters/_rules2ontology.py +6 -5
- cognite/neat/rules/importers/_dms2rules.py +3 -1
- cognite/neat/rules/importers/_dtdl2rules/dtdl_converter.py +2 -8
- cognite/neat/rules/importers/_inference2rules.py +3 -7
- cognite/neat/rules/importers/_owl2rules/_owl2classes.py +1 -1
- cognite/neat/rules/importers/_owl2rules/_owl2metadata.py +2 -1
- cognite/neat/rules/importers/_owl2rules/_owl2properties.py +1 -1
- cognite/neat/rules/issues/spreadsheet.py +35 -0
- cognite/neat/rules/models/_base.py +7 -7
- cognite/neat/rules/models/_rdfpath.py +17 -21
- cognite/neat/rules/models/asset/_rules.py +4 -5
- cognite/neat/rules/models/asset/_validation.py +38 -1
- cognite/neat/rules/models/dms/_converter.py +1 -2
- cognite/neat/rules/models/dms/_exporter.py +7 -3
- cognite/neat/rules/models/dms/_rules.py +3 -0
- cognite/neat/rules/models/dms/_schema.py +5 -4
- cognite/neat/rules/models/domain.py +5 -2
- cognite/neat/rules/models/entities.py +28 -17
- cognite/neat/rules/models/information/_rules.py +10 -8
- cognite/neat/rules/models/information/_rules_input.py +1 -2
- cognite/neat/rules/models/information/_validation.py +2 -2
- cognite/neat/utils/__init__.py +0 -3
- cognite/neat/utils/auth.py +47 -28
- cognite/neat/utils/auxiliary.py +141 -1
- cognite/neat/utils/cdf/__init__.py +0 -0
- cognite/neat/utils/{cdf_classes.py → cdf/data_classes.py} +122 -2
- cognite/neat/utils/{cdf_loaders → cdf/loaders}/_data_modeling.py +37 -0
- cognite/neat/utils/{cdf_loaders → cdf/loaders}/_ingestion.py +2 -1
- cognite/neat/utils/collection_.py +18 -0
- cognite/neat/utils/rdf_.py +165 -0
- cognite/neat/utils/text.py +4 -0
- cognite/neat/utils/time_.py +17 -0
- cognite/neat/utils/upload.py +13 -1
- cognite/neat/workflows/_exceptions.py +5 -5
- cognite/neat/workflows/base.py +1 -1
- cognite/neat/workflows/steps/lib/current/graph_store.py +28 -8
- cognite/neat/workflows/steps/lib/current/rules_validator.py +2 -2
- cognite/neat/workflows/steps/lib/legacy/graph_extractor.py +130 -28
- cognite/neat/workflows/steps/lib/legacy/graph_loader.py +1 -1
- cognite/neat/workflows/steps/lib/legacy/graph_store.py +4 -4
- cognite/neat/workflows/steps/lib/legacy/rules_exporter.py +1 -1
- cognite/neat/workflows/steps/lib/legacy/rules_importer.py +1 -1
- {cognite_neat-0.86.0.dist-info → cognite_neat-0.87.3.dist-info}/METADATA +2 -2
- {cognite_neat-0.86.0.dist-info → cognite_neat-0.87.3.dist-info}/RECORD +103 -102
- cognite/neat/rules/analysis/_information_rules.py +0 -476
- cognite/neat/utils/cdf.py +0 -59
- cognite/neat/utils/cdf_loaders/data_classes.py +0 -121
- cognite/neat/utils/exceptions.py +0 -41
- cognite/neat/utils/utils.py +0 -429
- /cognite/neat/utils/{cdf_loaders → cdf/loaders}/__init__.py +0 -0
- /cognite/neat/utils/{cdf_loaders → cdf/loaders}/_base.py +0 -0
- {cognite_neat-0.86.0.dist-info → cognite_neat-0.87.3.dist-info}/LICENSE +0 -0
- {cognite_neat-0.86.0.dist-info → cognite_neat-0.87.3.dist-info}/WHEEL +0 -0
- {cognite_neat-0.86.0.dist-info → cognite_neat-0.87.3.dist-info}/entry_points.txt +0 -0
|
@@ -1,16 +1,25 @@
|
|
|
1
|
-
|
|
1
|
+
import json
|
|
2
|
+
from collections.abc import Iterable, Sequence
|
|
2
3
|
from dataclasses import dataclass, fields
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import cast
|
|
3
6
|
|
|
7
|
+
import yaml
|
|
4
8
|
from cognite.client import CogniteClient
|
|
5
9
|
from cognite.client.data_classes import AssetWrite
|
|
10
|
+
from cognite.client.data_classes.capabilities import AssetsAcl, Capability
|
|
11
|
+
from cognite.client.exceptions import CogniteAPIError
|
|
6
12
|
|
|
7
13
|
from cognite.neat.graph._tracking.base import Tracker
|
|
8
14
|
from cognite.neat.graph._tracking.log import LogTracker
|
|
15
|
+
from cognite.neat.graph.issues import loader as loader_issues
|
|
9
16
|
from cognite.neat.graph.stores import NeatGraphStore
|
|
10
17
|
from cognite.neat.issues import NeatIssue, NeatIssueList
|
|
18
|
+
from cognite.neat.rules.analysis._asset import AssetAnalysis
|
|
11
19
|
from cognite.neat.rules.models import AssetRules
|
|
20
|
+
from cognite.neat.utils.upload import UploadResult
|
|
12
21
|
|
|
13
|
-
from ._base import CDFLoader
|
|
22
|
+
from ._base import _END_OF_CLASS, CDFLoader
|
|
14
23
|
|
|
15
24
|
|
|
16
25
|
@dataclass(frozen=True)
|
|
@@ -41,10 +50,27 @@ class AssetLoaderMetadataKeys:
|
|
|
41
50
|
|
|
42
51
|
|
|
43
52
|
class AssetLoader(CDFLoader[AssetWrite]):
|
|
53
|
+
"""Load Assets from NeatGraph to Cognite Data Fusions.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
graph_store (NeatGraphStore): The graph store to load the data into.
|
|
57
|
+
rules (AssetRules): The rules to load the assets with.
|
|
58
|
+
data_set_id (int): The CDF data set id to load the Assets into.
|
|
59
|
+
use_orphanage (bool): Whether to use an orphanage for assets that are not part
|
|
60
|
+
of the hierarchy. Defaults to False.
|
|
61
|
+
use_labels (bool): Whether to use labels for assets. Defaults to False.
|
|
62
|
+
asset_external_id_prefix (str | None): The prefix to use for the external id of the assets.
|
|
63
|
+
Defaults to None.
|
|
64
|
+
metadata_keys (AssetLoaderMetadataKeys | None): Mapping between NEAT metadata key names and
|
|
65
|
+
their desired names in CDF Asset metadata. Defaults to None.
|
|
66
|
+
create_issues (Sequence[NeatIssue] | None): A list of issues that occurred during reading. Defaults to None.
|
|
67
|
+
tracker (type[Tracker] | None): The tracker to use. Defaults to None.
|
|
68
|
+
"""
|
|
69
|
+
|
|
44
70
|
def __init__(
|
|
45
71
|
self,
|
|
46
|
-
rules: AssetRules,
|
|
47
72
|
graph_store: NeatGraphStore,
|
|
73
|
+
rules: AssetRules,
|
|
48
74
|
data_set_id: int,
|
|
49
75
|
use_orphanage: bool = False,
|
|
50
76
|
use_labels: bool = False,
|
|
@@ -58,10 +84,20 @@ class AssetLoader(CDFLoader[AssetWrite]):
|
|
|
58
84
|
self.rules = rules
|
|
59
85
|
self.data_set_id = data_set_id
|
|
60
86
|
self.use_labels = use_labels
|
|
61
|
-
self.use_orphanage = use_orphanage
|
|
62
87
|
|
|
63
|
-
self.
|
|
64
|
-
|
|
88
|
+
self.orphanage = (
|
|
89
|
+
AssetWrite.load(
|
|
90
|
+
{
|
|
91
|
+
"dataSetId": self.data_set_id,
|
|
92
|
+
"externalId": (
|
|
93
|
+
f"{asset_external_id_prefix or ''}orphanage-{data_set_id}" if use_orphanage else None
|
|
94
|
+
),
|
|
95
|
+
"name": "Orphanage",
|
|
96
|
+
"description": "Orphanage for assets whose parents do not exist",
|
|
97
|
+
}
|
|
98
|
+
)
|
|
99
|
+
if use_orphanage
|
|
100
|
+
else None
|
|
65
101
|
)
|
|
66
102
|
|
|
67
103
|
self.asset_external_id_prefix = asset_external_id_prefix
|
|
@@ -70,54 +106,148 @@ class AssetLoader(CDFLoader[AssetWrite]):
|
|
|
70
106
|
self._issues = NeatIssueList[NeatIssue](create_issues or [])
|
|
71
107
|
self._tracker: type[Tracker] = tracker or LogTracker
|
|
72
108
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
109
|
+
def _load(self, stop_on_exception: bool = False) -> Iterable[AssetWrite | NeatIssue | type[_END_OF_CLASS]]:
|
|
110
|
+
if self._issues.has_errors and stop_on_exception:
|
|
111
|
+
raise self._issues.as_exception()
|
|
112
|
+
elif self._issues.has_errors:
|
|
113
|
+
yield from self._issues
|
|
114
|
+
return
|
|
115
|
+
if not self.rules:
|
|
116
|
+
# There should already be an error in this case.
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
ordered_classes = AssetAnalysis(self.rules).class_topological_sort()
|
|
120
|
+
|
|
121
|
+
tracker = self._tracker(
|
|
122
|
+
type(self).__name__,
|
|
123
|
+
[repr(class_.id) for class_ in ordered_classes],
|
|
124
|
+
"classes",
|
|
88
125
|
)
|
|
89
126
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
127
|
+
processed_instances = set()
|
|
128
|
+
|
|
129
|
+
if self.orphanage:
|
|
130
|
+
yield self.orphanage
|
|
131
|
+
processed_instances.add(self.orphanage.external_id)
|
|
132
|
+
|
|
133
|
+
for class_ in ordered_classes:
|
|
134
|
+
tracker.start(repr(class_.id))
|
|
135
|
+
|
|
136
|
+
property_renaming_config = AssetAnalysis(self.rules).define_property_renaming_config(class_)
|
|
137
|
+
|
|
138
|
+
for identifier, properties in self.graph_store.read(class_.suffix):
|
|
139
|
+
fields = _process_properties(properties, property_renaming_config)
|
|
140
|
+
# set data set id and external id
|
|
141
|
+
fields["dataSetId"] = self.data_set_id
|
|
142
|
+
fields["externalId"] = identifier
|
|
143
|
+
|
|
144
|
+
# check on parent
|
|
145
|
+
if "parentExternalId" in fields and fields["parentExternalId"] not in processed_instances:
|
|
146
|
+
error = loader_issues.InvalidInstanceError(
|
|
147
|
+
type_="asset",
|
|
148
|
+
identifier=identifier,
|
|
149
|
+
reason=(
|
|
150
|
+
f"Parent asset {fields['parentExternalId']} does not exist or failed creation"
|
|
151
|
+
f""" {
|
|
152
|
+
f', moving the asset {identifier} under orphanage {self.orphanage.external_id}'
|
|
153
|
+
if self.orphanage
|
|
154
|
+
else ''}"""
|
|
155
|
+
),
|
|
156
|
+
)
|
|
157
|
+
tracker.issue(error)
|
|
158
|
+
if stop_on_exception:
|
|
159
|
+
raise error.as_exception()
|
|
160
|
+
yield error
|
|
161
|
+
|
|
162
|
+
# if orphanage is set asset will use orphanage as parent
|
|
163
|
+
if self.orphanage:
|
|
164
|
+
fields["parentExternalId"] = self.orphanage.external_id
|
|
165
|
+
|
|
166
|
+
# otherwise asset will be skipped
|
|
167
|
+
else:
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
yield AssetWrite.load(fields)
|
|
172
|
+
processed_instances.add(identifier)
|
|
173
|
+
except KeyError as e:
|
|
174
|
+
error = loader_issues.InvalidInstanceError(type_="asset", identifier=identifier, reason=str(e))
|
|
175
|
+
tracker.issue(error)
|
|
176
|
+
if stop_on_exception:
|
|
177
|
+
raise error.as_exception() from e
|
|
178
|
+
yield error
|
|
179
|
+
|
|
180
|
+
yield _END_OF_CLASS
|
|
181
|
+
|
|
182
|
+
def _get_required_capabilities(self) -> list[Capability]:
|
|
183
|
+
return [
|
|
184
|
+
AssetsAcl(
|
|
185
|
+
actions=[
|
|
186
|
+
AssetsAcl.Action.Write,
|
|
187
|
+
AssetsAcl.Action.Read,
|
|
188
|
+
],
|
|
189
|
+
scope=AssetsAcl.Scope.DataSet([self.data_set_id]),
|
|
190
|
+
)
|
|
191
|
+
]
|
|
192
|
+
|
|
193
|
+
def _upload_to_cdf(
|
|
194
|
+
self,
|
|
195
|
+
client: CogniteClient,
|
|
196
|
+
items: list[AssetWrite],
|
|
197
|
+
dry_run: bool,
|
|
198
|
+
read_issues: NeatIssueList,
|
|
199
|
+
) -> Iterable[UploadResult]:
|
|
200
|
+
try:
|
|
201
|
+
upserted = client.assets.upsert(items, mode="replace")
|
|
202
|
+
except CogniteAPIError as e:
|
|
203
|
+
result = UploadResult[str](name="Asset", issues=read_issues)
|
|
204
|
+
result.error_messages.append(str(e))
|
|
205
|
+
result.failed_upserted.update(item.as_id() for item in e.failed + e.unknown)
|
|
206
|
+
result.upserted.update(item.as_id() for item in e.successful)
|
|
207
|
+
yield result
|
|
208
|
+
else:
|
|
209
|
+
for asset in upserted:
|
|
210
|
+
result = UploadResult[str](name="asset", issues=read_issues)
|
|
211
|
+
result.upserted.add(cast(str, asset.external_id))
|
|
212
|
+
yield result
|
|
213
|
+
|
|
214
|
+
def write_to_file(self, filepath: Path) -> None:
|
|
215
|
+
if filepath.suffix not in [".json", ".yaml", ".yml"]:
|
|
216
|
+
raise ValueError(f"File format {filepath.suffix} is not supported")
|
|
217
|
+
dumped: dict[str, list] = {"assets": []}
|
|
218
|
+
for item in self.load(stop_on_exception=False):
|
|
219
|
+
key = {
|
|
220
|
+
AssetWrite: "assets",
|
|
221
|
+
NeatIssue: "issues",
|
|
222
|
+
_END_OF_CLASS: "end_of_class",
|
|
223
|
+
}.get(type(item))
|
|
224
|
+
if key is None:
|
|
225
|
+
# This should never happen, and is a bug in neat
|
|
226
|
+
raise ValueError(f"Item {item} is not supported. This is a bug in neat please report it.")
|
|
227
|
+
if key == "end_of_class":
|
|
228
|
+
continue
|
|
229
|
+
dumped[key].append(item.dump())
|
|
230
|
+
with filepath.open("w", encoding=self._encoding, newline=self._new_line) as f:
|
|
231
|
+
if filepath.suffix == ".json":
|
|
232
|
+
json.dump(dumped, f, indent=2)
|
|
233
|
+
else:
|
|
234
|
+
yaml.safe_dump(dumped, f, sort_keys=False)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _process_properties(properties: dict[str, list[str]], property_renaming_config: dict[str, str]) -> dict:
|
|
238
|
+
metadata: dict[str, str] = {}
|
|
239
|
+
fields: dict[str, str | dict] = {}
|
|
240
|
+
|
|
241
|
+
for original_property, values in properties.items():
|
|
242
|
+
if renamed_property := property_renaming_config.get(original_property, None):
|
|
243
|
+
if renamed_property.startswith("metadata."):
|
|
244
|
+
# Asset metadata contains only string values
|
|
245
|
+
metadata[original_property] = ", ".join(values)
|
|
246
|
+
else:
|
|
247
|
+
# Asset fields can contain only one value
|
|
248
|
+
fields[renamed_property] = values[0]
|
|
249
|
+
|
|
250
|
+
if metadata:
|
|
251
|
+
fields["metadata"] = metadata
|
|
252
|
+
|
|
253
|
+
return fields
|
|
@@ -8,11 +8,11 @@ from cognite.client import CogniteClient
|
|
|
8
8
|
from cognite.client import data_modeling as dm
|
|
9
9
|
from cognite.client.data_classes.capabilities import Capability, DataModelInstancesAcl
|
|
10
10
|
from cognite.client.data_classes.data_modeling import ViewId
|
|
11
|
+
from cognite.client.data_classes.data_modeling.data_types import ListablePropertyType
|
|
11
12
|
from cognite.client.data_classes.data_modeling.ids import InstanceId
|
|
12
13
|
from cognite.client.data_classes.data_modeling.views import SingleEdgeConnection
|
|
13
14
|
from cognite.client.exceptions import CogniteAPIError
|
|
14
|
-
from pydantic import ValidationInfo, create_model, field_validator
|
|
15
|
-
from pydantic.main import Model
|
|
15
|
+
from pydantic import BaseModel, ValidationInfo, create_model, field_validator
|
|
16
16
|
|
|
17
17
|
from cognite.neat.graph._tracking import LogTracker, Tracker
|
|
18
18
|
from cognite.neat.graph.issues import loader as loader_issues
|
|
@@ -20,14 +20,14 @@ from cognite.neat.graph.stores import NeatGraphStore
|
|
|
20
20
|
from cognite.neat.issues import NeatIssue, NeatIssueList
|
|
21
21
|
from cognite.neat.rules.models import DMSRules
|
|
22
22
|
from cognite.neat.rules.models.data_types import _DATA_TYPE_BY_DMS_TYPE, Json
|
|
23
|
+
from cognite.neat.utils.auxiliary import create_sha256_hash
|
|
23
24
|
from cognite.neat.utils.upload import UploadResult
|
|
24
|
-
from cognite.neat.utils.utils import create_sha256_hash
|
|
25
25
|
|
|
26
26
|
from ._base import CDFLoader
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
class DMSLoader(CDFLoader[dm.InstanceApply]):
|
|
30
|
-
"""
|
|
30
|
+
"""Loads Instances to Cognite Data Fusion Data Model Service from NeatGraph.
|
|
31
31
|
|
|
32
32
|
Args:
|
|
33
33
|
graph_store (NeatGraphStore): The graph store to load the data into.
|
|
@@ -140,7 +140,7 @@ class DMSLoader(CDFLoader[dm.InstanceApply]):
|
|
|
140
140
|
|
|
141
141
|
def _create_validation_classes(
|
|
142
142
|
self, view: dm.View
|
|
143
|
-
) -> tuple[type[
|
|
143
|
+
) -> tuple[type[BaseModel], dict[str, dm.EdgeConnection], NeatIssueList]:
|
|
144
144
|
issues = NeatIssueList[NeatIssue]()
|
|
145
145
|
field_definitions: dict[str, tuple[type, Any]] = {}
|
|
146
146
|
edge_by_property: dict[str, dm.EdgeConnection] = {}
|
|
@@ -168,7 +168,7 @@ class DMSLoader(CDFLoader[dm.InstanceApply]):
|
|
|
168
168
|
if data_type == Json:
|
|
169
169
|
json_fields.append(prop_name)
|
|
170
170
|
python_type = data_type.python
|
|
171
|
-
if prop.type.is_list:
|
|
171
|
+
if isinstance(prop.type, ListablePropertyType) and prop.type.is_list:
|
|
172
172
|
python_type = list[python_type]
|
|
173
173
|
default_value: Any = prop.default_value
|
|
174
174
|
if prop.nullable:
|
|
@@ -223,7 +223,7 @@ class DMSLoader(CDFLoader[dm.InstanceApply]):
|
|
|
223
223
|
self,
|
|
224
224
|
identifier: str,
|
|
225
225
|
properties: dict[str, list[str]],
|
|
226
|
-
pydantic_cls: type[
|
|
226
|
+
pydantic_cls: type[BaseModel],
|
|
227
227
|
view_id: dm.ViewId,
|
|
228
228
|
) -> dm.InstanceApply:
|
|
229
229
|
created = pydantic_cls.model_validate(properties)
|
|
@@ -8,7 +8,7 @@ from rdflib.query import ResultRow
|
|
|
8
8
|
|
|
9
9
|
from cognite.neat.rules.models.entities import ClassEntity
|
|
10
10
|
from cognite.neat.rules.models.information import InformationRules
|
|
11
|
-
from cognite.neat.utils.
|
|
11
|
+
from cognite.neat.utils.rdf_ import remove_namespace_from_uri
|
|
12
12
|
|
|
13
13
|
from ._construct import build_construct_query
|
|
14
14
|
|
|
@@ -112,25 +112,34 @@ class Queries:
|
|
|
112
112
|
property_values: dict[str, list[str]] = defaultdict(list)
|
|
113
113
|
|
|
114
114
|
for subject, predicate, object_ in cast(list[ResultRow], self.graph.query(f"DESCRIBE <{instance_id}>")):
|
|
115
|
-
|
|
116
|
-
# or if the object is empty
|
|
117
|
-
if predicate != RDF.type and object_.lower() not in [
|
|
115
|
+
if object_.lower() not in [
|
|
118
116
|
"",
|
|
119
117
|
"none",
|
|
120
118
|
"nan",
|
|
121
119
|
"null",
|
|
122
120
|
]:
|
|
123
121
|
# we are skipping deep validation with Pydantic to remove namespace here
|
|
124
|
-
# as it
|
|
125
|
-
identifier,
|
|
126
|
-
(str, str
|
|
127
|
-
remove_namespace_from_uri(*(subject,
|
|
122
|
+
# as it reduces time to process triples by 10-15x
|
|
123
|
+
identifier, value = cast( # type: ignore[misc]
|
|
124
|
+
(str, str),
|
|
125
|
+
remove_namespace_from_uri(*(subject, object_), validation="prefix"),
|
|
128
126
|
) # type: ignore[misc, index]
|
|
129
127
|
|
|
130
|
-
|
|
131
|
-
|
|
128
|
+
# use-case: calling describe without renaming properties
|
|
129
|
+
# losing the namespace from the predicate!
|
|
130
|
+
if not property_renaming_config and predicate != RDF.type:
|
|
131
|
+
property_values[remove_namespace_from_uri(predicate, validation="prefix")].append(value)
|
|
132
|
+
|
|
133
|
+
# use-case: calling describe with renaming properties
|
|
134
|
+
# renaming the property to the new name, if the property is defined
|
|
135
|
+
# in the RULES sheet
|
|
136
|
+
elif property_renaming_config and (property_ := property_renaming_config.get(predicate, None)):
|
|
137
|
+
property_values[property_].append(value)
|
|
138
|
+
|
|
139
|
+
# use-case: skip the property if it is not defined in property_renaming_config
|
|
140
|
+
else:
|
|
141
|
+
continue
|
|
132
142
|
|
|
133
|
-
property_values[property_].append(value)
|
|
134
143
|
if property_values:
|
|
135
144
|
return (
|
|
136
145
|
identifier,
|
|
@@ -3,17 +3,17 @@ from typing import cast
|
|
|
3
3
|
|
|
4
4
|
from rdflib import Graph, URIRef
|
|
5
5
|
|
|
6
|
-
from cognite.neat.rules.analysis import
|
|
6
|
+
from cognite.neat.rules.analysis import InformationAnalysis
|
|
7
7
|
from cognite.neat.rules.models._rdfpath import (
|
|
8
|
-
AllReferences,
|
|
9
8
|
Hop,
|
|
10
9
|
RDFPath,
|
|
10
|
+
SelfReferenceProperty,
|
|
11
11
|
SingleProperty,
|
|
12
12
|
Traversal,
|
|
13
13
|
)
|
|
14
14
|
from cognite.neat.rules.models.entities import ClassEntity
|
|
15
15
|
from cognite.neat.rules.models.information import InformationProperty, InformationRules
|
|
16
|
-
from cognite.neat.utils.
|
|
16
|
+
from cognite.neat.utils.collection_ import most_occurring_element
|
|
17
17
|
|
|
18
18
|
from ._shared import Triple, hop2property_path
|
|
19
19
|
|
|
@@ -55,7 +55,7 @@ def build_construct_query(
|
|
|
55
55
|
"""
|
|
56
56
|
|
|
57
57
|
if (
|
|
58
|
-
transformations :=
|
|
58
|
+
transformations := InformationAnalysis(rules)
|
|
59
59
|
.class_property_pairs(only_rdfpath=True, consider_inheritance=True)
|
|
60
60
|
.get(class_, None)
|
|
61
61
|
):
|
|
@@ -121,7 +121,7 @@ def to_construct_triples(
|
|
|
121
121
|
templates.append(graph_template_triple)
|
|
122
122
|
|
|
123
123
|
# use case AllReferences: binding instance to certain rdf property
|
|
124
|
-
if isinstance(traversal,
|
|
124
|
+
if isinstance(traversal, SelfReferenceProperty):
|
|
125
125
|
graph_pattern_triple = Triple(
|
|
126
126
|
subject="BIND(?instance",
|
|
127
127
|
predicate="AS",
|
|
@@ -6,12 +6,12 @@ from pydantic import BaseModel, ConfigDict, Field
|
|
|
6
6
|
from rdflib import Graph, Literal, Namespace
|
|
7
7
|
from rdflib.term import URIRef
|
|
8
8
|
|
|
9
|
-
from cognite.neat.constants import
|
|
9
|
+
from cognite.neat.constants import get_default_prefixes
|
|
10
10
|
from cognite.neat.rules.models._rdfpath import (
|
|
11
11
|
Hop,
|
|
12
12
|
Step,
|
|
13
13
|
)
|
|
14
|
-
from cognite.neat.utils.
|
|
14
|
+
from cognite.neat.utils.rdf_ import remove_namespace_from_uri, uri_to_short_form
|
|
15
15
|
|
|
16
16
|
if sys.version_info >= (3, 11):
|
|
17
17
|
from typing import Self
|
|
@@ -37,24 +37,30 @@ class Triple(BaseModel):
|
|
|
37
37
|
return cls(subject=triple[0], predicate=triple[1], object=triple[2])
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
def generate_prefix_header(prefixes: dict[str, Namespace] =
|
|
40
|
+
def generate_prefix_header(prefixes: dict[str, Namespace] | None = None) -> str:
|
|
41
41
|
"""Generate prefix header which is added to SPARQL query and allows for shorten query statements
|
|
42
42
|
|
|
43
43
|
Parameters
|
|
44
44
|
----------
|
|
45
45
|
prefixes : dict
|
|
46
|
-
Dict containing prefix - namespace pairs,
|
|
46
|
+
Dict containing prefix - namespace pairs, defaults to internal set of prefixes
|
|
47
47
|
|
|
48
48
|
Returns
|
|
49
49
|
-------
|
|
50
50
|
str
|
|
51
51
|
Prefix header
|
|
52
52
|
"""
|
|
53
|
+
|
|
54
|
+
prefixes = prefixes or get_default_prefixes()
|
|
55
|
+
|
|
53
56
|
return "".join(f"PREFIX {key}:<{value}>\n" for key, value in prefixes.items())
|
|
54
57
|
|
|
55
58
|
|
|
56
59
|
def get_predicate_id(
|
|
57
|
-
graph: Graph,
|
|
60
|
+
graph: Graph,
|
|
61
|
+
subject_type_id: str,
|
|
62
|
+
object_type_id: str,
|
|
63
|
+
prefixes: dict[str, Namespace] | None = None,
|
|
58
64
|
) -> URIRef:
|
|
59
65
|
"""Returns predicate (aka property) URI (i.e., ID) that connects subject and object
|
|
60
66
|
|
|
@@ -67,13 +73,16 @@ def get_predicate_id(
|
|
|
67
73
|
object_type_id : str
|
|
68
74
|
ID of object type (aka object class)
|
|
69
75
|
prefixes : dict, optional
|
|
70
|
-
Dict containing prefix - namespace pairs,
|
|
76
|
+
Dict containing prefix - namespace pairs, defaults to internal set of prefixes
|
|
71
77
|
|
|
72
78
|
Returns
|
|
73
79
|
-------
|
|
74
80
|
URIRef
|
|
75
81
|
ID of predicate (aka property) connecting subject and object
|
|
76
82
|
"""
|
|
83
|
+
|
|
84
|
+
prefixes = prefixes or get_default_prefixes()
|
|
85
|
+
|
|
77
86
|
query = """
|
|
78
87
|
|
|
79
88
|
SELECT ?predicateTypeID
|
|
@@ -93,7 +102,11 @@ def get_predicate_id(
|
|
|
93
102
|
return res[0][0]
|
|
94
103
|
|
|
95
104
|
|
|
96
|
-
def hop2property_path(
|
|
105
|
+
def hop2property_path(
|
|
106
|
+
graph: Graph,
|
|
107
|
+
hop: Hop,
|
|
108
|
+
prefixes: dict[str, Namespace] | None = None,
|
|
109
|
+
) -> str:
|
|
97
110
|
"""Converts hop to property path string
|
|
98
111
|
|
|
99
112
|
Parameters
|
|
@@ -110,6 +123,7 @@ def hop2property_path(graph: Graph, hop: Hop, prefixes: dict[str, Namespace]) ->
|
|
|
110
123
|
str
|
|
111
124
|
Property path string for hop traversal (e.g. ^rdf:type/rdfs:subClassOf)
|
|
112
125
|
"""
|
|
126
|
+
prefixes = prefixes if prefixes else get_default_prefixes()
|
|
113
127
|
|
|
114
128
|
# setting previous step to origin, as we are starting from there
|
|
115
129
|
previous_step = Step(class_=hop.class_, direction="origin")
|
|
@@ -15,7 +15,7 @@ from cognite.neat.graph.extractors import RdfFileExtractor, TripleExtractors
|
|
|
15
15
|
from cognite.neat.graph.models import Triple
|
|
16
16
|
from cognite.neat.graph.queries import Queries
|
|
17
17
|
from cognite.neat.graph.transformers import Transformers
|
|
18
|
-
from cognite.neat.rules.analysis import
|
|
18
|
+
from cognite.neat.rules.analysis import InformationAnalysis
|
|
19
19
|
from cognite.neat.rules.models import InformationRules
|
|
20
20
|
from cognite.neat.rules.models.entities import ClassEntity
|
|
21
21
|
from cognite.neat.utils.auxiliary import local_import
|
|
@@ -179,16 +179,28 @@ class NeatGraphStore:
|
|
|
179
179
|
warnings.warn("Desired type not found in graph!", stacklevel=2)
|
|
180
180
|
return None
|
|
181
181
|
|
|
182
|
-
|
|
182
|
+
analysis = InformationAnalysis(self.rules)
|
|
183
|
+
has_hop_transformations = analysis.has_hop_transformations()
|
|
184
|
+
has_self_reference_transformations = analysis.has_self_reference_property_transformations()
|
|
185
|
+
if has_hop_transformations or has_self_reference_transformations:
|
|
186
|
+
msg = (
|
|
187
|
+
f"Rules contain [{'Hop' if has_hop_transformations else '' }"
|
|
188
|
+
f", {'SelfReferenceProperty' if has_self_reference_transformations else '' }]"
|
|
189
|
+
" rdfpath."
|
|
190
|
+
f" Run [{'ReduceHopTraversal' if has_hop_transformations else '' }"
|
|
191
|
+
f", {'AddSelfReferenceProperty' if has_self_reference_transformations else '' }]"
|
|
192
|
+
" transformer(s) first!"
|
|
193
|
+
)
|
|
194
|
+
|
|
183
195
|
warnings.warn(
|
|
184
|
-
|
|
196
|
+
msg,
|
|
185
197
|
stacklevel=2,
|
|
186
198
|
)
|
|
187
199
|
return None
|
|
188
200
|
|
|
189
201
|
instance_ids = self.queries.list_instances_ids_of_class(self.rules.metadata.namespace[class_])
|
|
190
202
|
|
|
191
|
-
property_renaming_config =
|
|
203
|
+
property_renaming_config = InformationAnalysis(self.rules).define_property_renaming_config(class_entity)
|
|
192
204
|
|
|
193
205
|
for instance_id in instance_ids:
|
|
194
206
|
yield self.queries.describe(instance_id, property_renaming_config)
|
|
@@ -6,6 +6,7 @@ from ._classic_cdf import (
|
|
|
6
6
|
AssetSequenceConnector,
|
|
7
7
|
AssetTimeSeriesConnector,
|
|
8
8
|
)
|
|
9
|
+
from ._rdfpath import AddSelfReferenceProperty
|
|
9
10
|
|
|
10
11
|
__all__ = [
|
|
11
12
|
"AddAssetDepth",
|
|
@@ -14,6 +15,7 @@ __all__ = [
|
|
|
14
15
|
"AssetFileConnector",
|
|
15
16
|
"AssetEventConnector",
|
|
16
17
|
"AssetRelationshipConnector",
|
|
18
|
+
"AddSelfReferenceProperty",
|
|
17
19
|
]
|
|
18
20
|
|
|
19
21
|
Transformers = (
|
|
@@ -23,4 +25,5 @@ Transformers = (
|
|
|
23
25
|
| AssetFileConnector
|
|
24
26
|
| AssetEventConnector
|
|
25
27
|
| AssetRelationshipConnector
|
|
28
|
+
| AddSelfReferenceProperty
|
|
26
29
|
)
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
from rdflib import RDF, Graph
|
|
2
|
+
|
|
3
|
+
from cognite.neat.rules.analysis import InformationAnalysis
|
|
4
|
+
from cognite.neat.rules.models._rdfpath import RDFPath, SingleProperty
|
|
5
|
+
from cognite.neat.rules.models.information import InformationRules
|
|
6
|
+
|
|
1
7
|
from ._base import BaseTransformer
|
|
2
8
|
|
|
3
9
|
|
|
@@ -5,3 +11,39 @@ class ReduceHopTraversal(BaseTransformer):
|
|
|
5
11
|
"""ReduceHopTraversal is a transformer that reduces the number of hops to direct connection."""
|
|
6
12
|
|
|
7
13
|
...
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AddSelfReferenceProperty(BaseTransformer):
|
|
17
|
+
description: str = "Adds property that contains id of reference to all references of given class in Rules"
|
|
18
|
+
_use_only_once: bool = True
|
|
19
|
+
_need_changes = frozenset({})
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
rules: InformationRules,
|
|
24
|
+
):
|
|
25
|
+
self.rules = rules
|
|
26
|
+
self.properties = InformationAnalysis(rules).all_reference_transformations()
|
|
27
|
+
|
|
28
|
+
def transform(self, graph: Graph) -> None:
|
|
29
|
+
for property_ in self.properties:
|
|
30
|
+
prefix = property_.transformation.traversal.class_.prefix
|
|
31
|
+
suffix = property_.transformation.traversal.class_.suffix
|
|
32
|
+
|
|
33
|
+
namespace = self.rules.prefixes[prefix] if prefix in self.rules.prefixes else self.rules.metadata.namespace
|
|
34
|
+
|
|
35
|
+
for reference in graph.subjects(RDF.type, namespace[suffix]):
|
|
36
|
+
graph.add(
|
|
37
|
+
(
|
|
38
|
+
reference,
|
|
39
|
+
self.rules.metadata.namespace[property_.property_],
|
|
40
|
+
reference,
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
traversal = SingleProperty.from_string(
|
|
45
|
+
class_=property_.class_.id,
|
|
46
|
+
property_=f"{self.rules.metadata.prefix}:{property_.property_}",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
property_.transformation = RDFPath(traversal=traversal)
|
|
@@ -9,11 +9,6 @@ from cognite.neat.legacy.graph.models import Triple
|
|
|
9
9
|
|
|
10
10
|
from ._base import BaseExtractor
|
|
11
11
|
|
|
12
|
-
_DEXPI_PREFIXES = {
|
|
13
|
-
"dexpi": Namespace("http://sandbox.dexpi.org/rdl/"),
|
|
14
|
-
"posccaesar": Namespace("http://data.posccaesar.org/rdl/"),
|
|
15
|
-
}
|
|
16
|
-
|
|
17
12
|
|
|
18
13
|
class DexpiXML(BaseExtractor):
|
|
19
14
|
"""
|
|
@@ -20,7 +20,7 @@ from cognite.neat.legacy.rules.analysis import (
|
|
|
20
20
|
from cognite.neat.legacy.rules.exporters._rules2rules import subset_rules
|
|
21
21
|
from cognite.neat.legacy.rules.models import Rules
|
|
22
22
|
from cognite.neat.legacy.rules.models.value_types import XSD_VALUE_TYPE_MAPPINGS
|
|
23
|
-
from cognite.neat.utils.
|
|
23
|
+
from cognite.neat.utils.rdf_ import remove_namespace_from_uri
|
|
24
24
|
|
|
25
25
|
from ._base import BaseExtractor
|
|
26
26
|
|
|
@@ -15,8 +15,8 @@ from rdflib.query import ResultRow
|
|
|
15
15
|
from cognite.neat.legacy.graph.stores import NeatGraphStoreBase
|
|
16
16
|
from cognite.neat.legacy.rules.models import Rules
|
|
17
17
|
from cognite.neat.legacy.rules.models.rules import Property
|
|
18
|
-
from cognite.neat.utils import remove_namespace_from_uri
|
|
19
|
-
from cognite.neat.utils.
|
|
18
|
+
from cognite.neat.utils.rdf_ import remove_namespace_from_uri
|
|
19
|
+
from cognite.neat.utils.time_ import epoch_now_ms
|
|
20
20
|
|
|
21
21
|
from ._base import CogniteLoader
|
|
22
22
|
|