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.

Files changed (108) hide show
  1. cognite/neat/_version.py +1 -1
  2. cognite/neat/app/api/configuration.py +1 -10
  3. cognite/neat/app/api/routers/data_exploration.py +1 -1
  4. cognite/neat/config.py +84 -17
  5. cognite/neat/constants.py +11 -9
  6. cognite/neat/graph/extractors/_classic_cdf/_assets.py +1 -1
  7. cognite/neat/graph/extractors/_classic_cdf/_events.py +1 -1
  8. cognite/neat/graph/extractors/_classic_cdf/_files.py +1 -1
  9. cognite/neat/graph/extractors/_classic_cdf/_labels.py +1 -1
  10. cognite/neat/graph/extractors/_classic_cdf/_relationships.py +1 -1
  11. cognite/neat/graph/extractors/_classic_cdf/_sequences.py +1 -1
  12. cognite/neat/graph/extractors/_classic_cdf/_timeseries.py +1 -1
  13. cognite/neat/graph/extractors/_dexpi.py +1 -1
  14. cognite/neat/graph/extractors/_mock_graph_generator.py +8 -9
  15. cognite/neat/graph/loaders/__init__.py +5 -2
  16. cognite/neat/graph/loaders/_base.py +13 -5
  17. cognite/neat/graph/loaders/_rdf2asset.py +185 -55
  18. cognite/neat/graph/loaders/_rdf2dms.py +7 -7
  19. cognite/neat/graph/queries/_base.py +20 -11
  20. cognite/neat/graph/queries/_construct.py +5 -5
  21. cognite/neat/graph/queries/_shared.py +21 -7
  22. cognite/neat/graph/stores/_base.py +16 -4
  23. cognite/neat/graph/transformers/__init__.py +3 -0
  24. cognite/neat/graph/transformers/_rdfpath.py +42 -0
  25. cognite/neat/legacy/graph/extractors/_dexpi.py +0 -5
  26. cognite/neat/legacy/graph/extractors/_mock_graph_generator.py +1 -1
  27. cognite/neat/legacy/graph/loaders/_asset_loader.py +2 -2
  28. cognite/neat/legacy/graph/loaders/core/rdf_to_assets.py +5 -2
  29. cognite/neat/legacy/graph/loaders/core/rdf_to_relationships.py +4 -1
  30. cognite/neat/legacy/graph/loaders/rdf_to_dms.py +3 -1
  31. cognite/neat/legacy/graph/stores/_base.py +24 -8
  32. cognite/neat/legacy/graph/stores/_graphdb_store.py +3 -2
  33. cognite/neat/legacy/graph/stores/_memory_store.py +3 -3
  34. cognite/neat/legacy/graph/stores/_oxigraph_store.py +8 -4
  35. cognite/neat/legacy/graph/stores/_rdf_to_graph.py +5 -3
  36. cognite/neat/legacy/graph/transformations/query_generator/sparql.py +49 -16
  37. cognite/neat/legacy/graph/transformations/transformer.py +1 -1
  38. cognite/neat/legacy/rules/exporters/_rules2dms.py +8 -3
  39. cognite/neat/legacy/rules/exporters/_rules2graphql.py +1 -1
  40. cognite/neat/legacy/rules/exporters/_rules2ontology.py +2 -1
  41. cognite/neat/legacy/rules/exporters/_rules2pydantic_models.py +3 -4
  42. cognite/neat/legacy/rules/importers/_dms2rules.py +4 -1
  43. cognite/neat/legacy/rules/importers/_graph2rules.py +3 -3
  44. cognite/neat/legacy/rules/importers/_owl2rules/_owl2classes.py +1 -1
  45. cognite/neat/legacy/rules/importers/_owl2rules/_owl2metadata.py +2 -1
  46. cognite/neat/legacy/rules/importers/_owl2rules/_owl2properties.py +1 -1
  47. cognite/neat/legacy/rules/models/raw_rules.py +19 -7
  48. cognite/neat/legacy/rules/models/rules.py +32 -12
  49. cognite/neat/rules/_shared.py +6 -1
  50. cognite/neat/rules/analysis/__init__.py +4 -4
  51. cognite/neat/rules/analysis/_asset.py +143 -0
  52. cognite/neat/rules/analysis/_base.py +385 -6
  53. cognite/neat/rules/analysis/_information.py +183 -0
  54. cognite/neat/rules/exporters/_rules2dms.py +1 -1
  55. cognite/neat/rules/exporters/_rules2ontology.py +6 -5
  56. cognite/neat/rules/importers/_dms2rules.py +3 -1
  57. cognite/neat/rules/importers/_dtdl2rules/dtdl_converter.py +2 -8
  58. cognite/neat/rules/importers/_inference2rules.py +3 -7
  59. cognite/neat/rules/importers/_owl2rules/_owl2classes.py +1 -1
  60. cognite/neat/rules/importers/_owl2rules/_owl2metadata.py +2 -1
  61. cognite/neat/rules/importers/_owl2rules/_owl2properties.py +1 -1
  62. cognite/neat/rules/issues/spreadsheet.py +35 -0
  63. cognite/neat/rules/models/_base.py +7 -7
  64. cognite/neat/rules/models/_rdfpath.py +17 -21
  65. cognite/neat/rules/models/asset/_rules.py +4 -5
  66. cognite/neat/rules/models/asset/_validation.py +38 -1
  67. cognite/neat/rules/models/dms/_converter.py +1 -2
  68. cognite/neat/rules/models/dms/_exporter.py +7 -3
  69. cognite/neat/rules/models/dms/_rules.py +3 -0
  70. cognite/neat/rules/models/dms/_schema.py +5 -4
  71. cognite/neat/rules/models/domain.py +5 -2
  72. cognite/neat/rules/models/entities.py +28 -17
  73. cognite/neat/rules/models/information/_rules.py +10 -8
  74. cognite/neat/rules/models/information/_rules_input.py +1 -2
  75. cognite/neat/rules/models/information/_validation.py +2 -2
  76. cognite/neat/utils/__init__.py +0 -3
  77. cognite/neat/utils/auth.py +47 -28
  78. cognite/neat/utils/auxiliary.py +141 -1
  79. cognite/neat/utils/cdf/__init__.py +0 -0
  80. cognite/neat/utils/{cdf_classes.py → cdf/data_classes.py} +122 -2
  81. cognite/neat/utils/{cdf_loaders → cdf/loaders}/_data_modeling.py +37 -0
  82. cognite/neat/utils/{cdf_loaders → cdf/loaders}/_ingestion.py +2 -1
  83. cognite/neat/utils/collection_.py +18 -0
  84. cognite/neat/utils/rdf_.py +165 -0
  85. cognite/neat/utils/text.py +4 -0
  86. cognite/neat/utils/time_.py +17 -0
  87. cognite/neat/utils/upload.py +13 -1
  88. cognite/neat/workflows/_exceptions.py +5 -5
  89. cognite/neat/workflows/base.py +1 -1
  90. cognite/neat/workflows/steps/lib/current/graph_store.py +28 -8
  91. cognite/neat/workflows/steps/lib/current/rules_validator.py +2 -2
  92. cognite/neat/workflows/steps/lib/legacy/graph_extractor.py +130 -28
  93. cognite/neat/workflows/steps/lib/legacy/graph_loader.py +1 -1
  94. cognite/neat/workflows/steps/lib/legacy/graph_store.py +4 -4
  95. cognite/neat/workflows/steps/lib/legacy/rules_exporter.py +1 -1
  96. cognite/neat/workflows/steps/lib/legacy/rules_importer.py +1 -1
  97. {cognite_neat-0.86.0.dist-info → cognite_neat-0.87.3.dist-info}/METADATA +2 -2
  98. {cognite_neat-0.86.0.dist-info → cognite_neat-0.87.3.dist-info}/RECORD +103 -102
  99. cognite/neat/rules/analysis/_information_rules.py +0 -476
  100. cognite/neat/utils/cdf.py +0 -59
  101. cognite/neat/utils/cdf_loaders/data_classes.py +0 -121
  102. cognite/neat/utils/exceptions.py +0 -41
  103. cognite/neat/utils/utils.py +0 -429
  104. /cognite/neat/utils/{cdf_loaders → cdf/loaders}/__init__.py +0 -0
  105. /cognite/neat/utils/{cdf_loaders → cdf/loaders}/_base.py +0 -0
  106. {cognite_neat-0.86.0.dist-info → cognite_neat-0.87.3.dist-info}/LICENSE +0 -0
  107. {cognite_neat-0.86.0.dist-info → cognite_neat-0.87.3.dist-info}/WHEEL +0 -0
  108. {cognite_neat-0.86.0.dist-info → cognite_neat-0.87.3.dist-info}/entry_points.txt +0 -0
@@ -1,16 +1,25 @@
1
- from collections.abc import Sequence
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.orphanage_external_id = (
64
- f"{asset_external_id_prefix or ''}orphanage-{data_set_id}" if use_orphanage else None
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
- @classmethod
74
- def from_rules(
75
- cls,
76
- rules: AssetRules,
77
- graph_store: NeatGraphStore,
78
- data_set_id: int,
79
- use_orphanage: bool = False,
80
- use_labels: bool = False,
81
- asset_external_id_prefix: str | None = None,
82
- metadata_keys: AssetLoaderMetadataKeys | None = None,
83
- ) -> "AssetLoader":
84
- issues: list[NeatIssue] = []
85
-
86
- return cls(
87
- rules, graph_store, data_set_id, use_orphanage, use_labels, asset_external_id_prefix, metadata_keys, issues
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
- def _create_validation_classes(self) -> None:
91
- # need to get back class-property pairs where are definition of
92
- # asset implementations, extend InformationRulesAnalysis make it generic
93
-
94
- # by default if there is not explicitly stated external_id
95
- # use rdf:type and drop the prefix
96
-
97
- # based on those create pydantic model AssetDefinition
98
- # which will have .to_asset_write()
99
-
100
- raise NotImplementedError("Not implemented yet, this is placeholder")
101
-
102
- def categorize_assets(self, client: CogniteClient) -> None:
103
- """Categorize assets to those to be created, updated, decommissioned, or resurrected"""
104
-
105
- raise NotImplementedError("Not implemented yet, this is placeholder")
106
-
107
- def load_to_cdf(self, client: CogniteClient, dry_run: bool = False) -> Sequence[AssetWrite]:
108
- # generate assets
109
- # check for circular asset hierarchy
110
- # check for orphaned assets
111
- # batch upsert of assets to CDF (otherwise we will hit the API rate limit)
112
-
113
- raise NotImplementedError("Not implemented yet, this is placeholder")
114
-
115
- @classmethod
116
- def _check_for_circular_asset_hierarchy(cls, assets: list[AssetWrite]) -> None:
117
- """Check for circular references in the asset rules"""
118
- raise NotImplementedError("Not implemented yet, this is placeholder")
119
-
120
- @classmethod
121
- def _check_for_orphaned_assets(cls, assets: list[AssetWrite]) -> None:
122
- """Check for circular references in the asset rules"""
123
- raise NotImplementedError("Not implemented yet, this is placeholder")
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
- """Load data from Cognite Data Fusions Data Modeling Service (DMS) into Neat.
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[Model], dict[str, dm.EdgeConnection], NeatIssueList]:
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[Model],
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.utils import remove_namespace_from_uri
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
- # We cannot include the RDF.type in case there is a neat:type property
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 reduce time to process triples by 10-15x
125
- identifier, property_, value = cast( # type: ignore[misc]
126
- (str, str, str),
127
- remove_namespace_from_uri(*(subject, predicate, object_), validation="prefix"),
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
- if property_renaming_config:
131
- predicate = property_renaming_config.get(property_, property_)
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 InformationArchitectRulesAnalysis
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.utils import most_occurring_element
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 := InformationArchitectRulesAnalysis(rules)
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, AllReferences):
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 PREFIXES
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.utils import remove_namespace_from_uri, uri_to_short_form
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] = PREFIXES) -> str:
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, default PREFIXES
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, subject_type_id: str, object_type_id: str, prefixes: dict[str, Namespace] = PREFIXES
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, default PREFIXES
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(graph: Graph, hop: Hop, prefixes: dict[str, Namespace]) -> str:
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 InformationArchitectRulesAnalysis
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
- if InformationArchitectRulesAnalysis(self.rules).has_hop_transformations():
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
- "Rules contain Hop rdfpath, run ReduceHopTraversal transformer first!",
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 = InformationArchitectRulesAnalysis(self.rules).define_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.utils import remove_namespace_from_uri
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.utils import epoch_now_ms
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