cognite-neat 0.87.6__py3-none-any.whl → 0.88.1__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 (171) hide show
  1. cognite/neat/_version.py +1 -1
  2. cognite/neat/app/api/data_classes/rest.py +0 -19
  3. cognite/neat/app/api/explorer.py +6 -4
  4. cognite/neat/app/api/routers/configuration.py +1 -1
  5. cognite/neat/app/api/routers/crud.py +11 -21
  6. cognite/neat/app/api/routers/workflows.py +24 -94
  7. cognite/neat/app/ui/neat-app/build/asset-manifest.json +7 -7
  8. cognite/neat/app/ui/neat-app/build/index.html +1 -1
  9. cognite/neat/app/ui/neat-app/build/static/css/{main.38a62222.css → main.72e3d92e.css} +2 -2
  10. cognite/neat/app/ui/neat-app/build/static/css/main.72e3d92e.css.map +1 -0
  11. cognite/neat/app/ui/neat-app/build/static/js/main.5a52cf09.js +3 -0
  12. cognite/neat/app/ui/neat-app/build/static/js/{main.ec7f72e2.js.LICENSE.txt → main.5a52cf09.js.LICENSE.txt} +0 -9
  13. cognite/neat/app/ui/neat-app/build/static/js/main.5a52cf09.js.map +1 -0
  14. cognite/neat/config.py +44 -27
  15. cognite/neat/exceptions.py +6 -0
  16. cognite/neat/graph/extractors/_classic_cdf/_assets.py +21 -73
  17. cognite/neat/graph/extractors/_classic_cdf/_base.py +102 -0
  18. cognite/neat/graph/extractors/_classic_cdf/_events.py +46 -42
  19. cognite/neat/graph/extractors/_classic_cdf/_files.py +41 -45
  20. cognite/neat/graph/extractors/_classic_cdf/_labels.py +75 -52
  21. cognite/neat/graph/extractors/_classic_cdf/_relationships.py +49 -27
  22. cognite/neat/graph/extractors/_classic_cdf/_sequences.py +47 -50
  23. cognite/neat/graph/extractors/_classic_cdf/_timeseries.py +47 -49
  24. cognite/neat/graph/queries/_base.py +22 -29
  25. cognite/neat/graph/queries/_shared.py +1 -1
  26. cognite/neat/graph/stores/_base.py +24 -11
  27. cognite/neat/graph/transformers/_rdfpath.py +3 -2
  28. cognite/neat/issues.py +8 -0
  29. cognite/neat/rules/exporters/_rules2ontology.py +28 -20
  30. cognite/neat/rules/exporters/_validation.py +15 -21
  31. cognite/neat/rules/importers/_inference2rules.py +31 -35
  32. cognite/neat/rules/importers/_owl2rules/_owl2metadata.py +3 -7
  33. cognite/neat/rules/importers/_spreadsheet2rules.py +30 -27
  34. cognite/neat/rules/issues/dms.py +20 -0
  35. cognite/neat/rules/issues/importing.py +15 -0
  36. cognite/neat/rules/issues/ontology.py +298 -0
  37. cognite/neat/rules/issues/spreadsheet.py +48 -0
  38. cognite/neat/rules/issues/tables.py +72 -0
  39. cognite/neat/rules/models/_rdfpath.py +4 -4
  40. cognite/neat/rules/models/_types/_field.py +9 -19
  41. cognite/neat/rules/models/information/_rules.py +5 -4
  42. cognite/neat/utils/rdf_.py +17 -9
  43. cognite/neat/utils/regex_patterns.py +52 -0
  44. cognite/neat/workflows/steps/data_contracts.py +17 -43
  45. cognite/neat/workflows/steps/lib/current/graph_extractor.py +28 -24
  46. cognite/neat/workflows/steps/lib/current/graph_loader.py +4 -21
  47. cognite/neat/workflows/steps/lib/current/graph_store.py +18 -134
  48. cognite/neat/workflows/steps_registry.py +5 -7
  49. {cognite_neat-0.87.6.dist-info → cognite_neat-0.88.1.dist-info}/METADATA +2 -6
  50. cognite_neat-0.88.1.dist-info/RECORD +209 -0
  51. cognite/neat/app/api/routers/core.py +0 -91
  52. cognite/neat/app/api/routers/data_exploration.py +0 -336
  53. cognite/neat/app/api/routers/rules.py +0 -203
  54. cognite/neat/app/ui/neat-app/build/static/css/main.38a62222.css.map +0 -1
  55. cognite/neat/app/ui/neat-app/build/static/js/main.ec7f72e2.js +0 -3
  56. cognite/neat/app/ui/neat-app/build/static/js/main.ec7f72e2.js.map +0 -1
  57. cognite/neat/graph/stores/_oxrdflib.py +0 -247
  58. cognite/neat/legacy/__init__.py +0 -0
  59. cognite/neat/legacy/graph/__init__.py +0 -3
  60. cognite/neat/legacy/graph/examples/Knowledge-Graph-Nordic44-dirty.xml +0 -20182
  61. cognite/neat/legacy/graph/examples/Knowledge-Graph-Nordic44.xml +0 -20163
  62. cognite/neat/legacy/graph/examples/__init__.py +0 -10
  63. cognite/neat/legacy/graph/examples/skos-capturing-sheet-wind-topics.xlsx +0 -0
  64. cognite/neat/legacy/graph/exceptions.py +0 -90
  65. cognite/neat/legacy/graph/extractors/__init__.py +0 -6
  66. cognite/neat/legacy/graph/extractors/_base.py +0 -14
  67. cognite/neat/legacy/graph/extractors/_dexpi.py +0 -44
  68. cognite/neat/legacy/graph/extractors/_graph_capturing_sheet.py +0 -403
  69. cognite/neat/legacy/graph/extractors/_mock_graph_generator.py +0 -361
  70. cognite/neat/legacy/graph/loaders/__init__.py +0 -23
  71. cognite/neat/legacy/graph/loaders/_asset_loader.py +0 -511
  72. cognite/neat/legacy/graph/loaders/_base.py +0 -67
  73. cognite/neat/legacy/graph/loaders/_exceptions.py +0 -85
  74. cognite/neat/legacy/graph/loaders/core/__init__.py +0 -0
  75. cognite/neat/legacy/graph/loaders/core/labels.py +0 -58
  76. cognite/neat/legacy/graph/loaders/core/models.py +0 -136
  77. cognite/neat/legacy/graph/loaders/core/rdf_to_assets.py +0 -1046
  78. cognite/neat/legacy/graph/loaders/core/rdf_to_relationships.py +0 -559
  79. cognite/neat/legacy/graph/loaders/rdf_to_dms.py +0 -309
  80. cognite/neat/legacy/graph/loaders/validator.py +0 -87
  81. cognite/neat/legacy/graph/models.py +0 -6
  82. cognite/neat/legacy/graph/stores/__init__.py +0 -13
  83. cognite/neat/legacy/graph/stores/_base.py +0 -400
  84. cognite/neat/legacy/graph/stores/_graphdb_store.py +0 -52
  85. cognite/neat/legacy/graph/stores/_memory_store.py +0 -43
  86. cognite/neat/legacy/graph/stores/_oxigraph_store.py +0 -151
  87. cognite/neat/legacy/graph/stores/_oxrdflib.py +0 -247
  88. cognite/neat/legacy/graph/stores/_rdf_to_graph.py +0 -42
  89. cognite/neat/legacy/graph/transformations/__init__.py +0 -0
  90. cognite/neat/legacy/graph/transformations/entity_matcher.py +0 -101
  91. cognite/neat/legacy/graph/transformations/query_generator/__init__.py +0 -3
  92. cognite/neat/legacy/graph/transformations/query_generator/sparql.py +0 -575
  93. cognite/neat/legacy/graph/transformations/transformer.py +0 -322
  94. cognite/neat/legacy/rules/__init__.py +0 -0
  95. cognite/neat/legacy/rules/analysis.py +0 -231
  96. cognite/neat/legacy/rules/examples/Rules-Nordic44-to-graphql.xlsx +0 -0
  97. cognite/neat/legacy/rules/examples/Rules-Nordic44.xlsx +0 -0
  98. cognite/neat/legacy/rules/examples/__init__.py +0 -18
  99. cognite/neat/legacy/rules/examples/power-grid-containers.yaml +0 -124
  100. cognite/neat/legacy/rules/examples/power-grid-example.xlsx +0 -0
  101. cognite/neat/legacy/rules/examples/power-grid-model.yaml +0 -224
  102. cognite/neat/legacy/rules/examples/rules-template.xlsx +0 -0
  103. cognite/neat/legacy/rules/examples/sheet2cdf-transformation-rules.xlsx +0 -0
  104. cognite/neat/legacy/rules/examples/skos-rules.xlsx +0 -0
  105. cognite/neat/legacy/rules/examples/source-to-solution-mapping-rules.xlsx +0 -0
  106. cognite/neat/legacy/rules/examples/wind-energy.owl +0 -1511
  107. cognite/neat/legacy/rules/exceptions.py +0 -2972
  108. cognite/neat/legacy/rules/exporters/__init__.py +0 -20
  109. cognite/neat/legacy/rules/exporters/_base.py +0 -45
  110. cognite/neat/legacy/rules/exporters/_core/__init__.py +0 -5
  111. cognite/neat/legacy/rules/exporters/_core/rules2labels.py +0 -24
  112. cognite/neat/legacy/rules/exporters/_rules2dms.py +0 -885
  113. cognite/neat/legacy/rules/exporters/_rules2excel.py +0 -213
  114. cognite/neat/legacy/rules/exporters/_rules2graphql.py +0 -183
  115. cognite/neat/legacy/rules/exporters/_rules2ontology.py +0 -524
  116. cognite/neat/legacy/rules/exporters/_rules2pydantic_models.py +0 -748
  117. cognite/neat/legacy/rules/exporters/_rules2rules.py +0 -105
  118. cognite/neat/legacy/rules/exporters/_rules2triples.py +0 -38
  119. cognite/neat/legacy/rules/exporters/_validation.py +0 -146
  120. cognite/neat/legacy/rules/importers/__init__.py +0 -22
  121. cognite/neat/legacy/rules/importers/_base.py +0 -66
  122. cognite/neat/legacy/rules/importers/_dict2rules.py +0 -158
  123. cognite/neat/legacy/rules/importers/_dms2rules.py +0 -194
  124. cognite/neat/legacy/rules/importers/_graph2rules.py +0 -308
  125. cognite/neat/legacy/rules/importers/_json2rules.py +0 -39
  126. cognite/neat/legacy/rules/importers/_owl2rules/__init__.py +0 -3
  127. cognite/neat/legacy/rules/importers/_owl2rules/_owl2classes.py +0 -239
  128. cognite/neat/legacy/rules/importers/_owl2rules/_owl2metadata.py +0 -260
  129. cognite/neat/legacy/rules/importers/_owl2rules/_owl2properties.py +0 -217
  130. cognite/neat/legacy/rules/importers/_owl2rules/_owl2rules.py +0 -290
  131. cognite/neat/legacy/rules/importers/_spreadsheet2rules.py +0 -45
  132. cognite/neat/legacy/rules/importers/_xsd2rules.py +0 -20
  133. cognite/neat/legacy/rules/importers/_yaml2rules.py +0 -39
  134. cognite/neat/legacy/rules/models/__init__.py +0 -5
  135. cognite/neat/legacy/rules/models/_base.py +0 -151
  136. cognite/neat/legacy/rules/models/raw_rules.py +0 -316
  137. cognite/neat/legacy/rules/models/rdfpath.py +0 -237
  138. cognite/neat/legacy/rules/models/rules.py +0 -1289
  139. cognite/neat/legacy/rules/models/tables.py +0 -9
  140. cognite/neat/legacy/rules/models/value_types.py +0 -118
  141. cognite/neat/legacy/workflows/examples/Export_DMS/workflow.yaml +0 -89
  142. cognite/neat/legacy/workflows/examples/Export_Rules_to_Ontology/workflow.yaml +0 -152
  143. cognite/neat/legacy/workflows/examples/Extract_DEXPI_Graph_and_Export_Rules/workflow.yaml +0 -139
  144. cognite/neat/legacy/workflows/examples/Extract_RDF_Graph_and_Generate_Assets/workflow.yaml +0 -270
  145. cognite/neat/legacy/workflows/examples/Import_DMS/workflow.yaml +0 -65
  146. cognite/neat/legacy/workflows/examples/Ontology_to_Data_Model/workflow.yaml +0 -116
  147. cognite/neat/legacy/workflows/examples/Validate_Rules/workflow.yaml +0 -67
  148. cognite/neat/legacy/workflows/examples/Validate_Solution_Model/workflow.yaml +0 -64
  149. cognite/neat/legacy/workflows/examples/Visualize_Data_Model_Using_Mock_Graph/workflow.yaml +0 -95
  150. cognite/neat/legacy/workflows/examples/Visualize_Semantic_Data_Model/workflow.yaml +0 -111
  151. cognite/neat/rules/exceptions.py +0 -2972
  152. cognite/neat/rules/models/_types/_base.py +0 -16
  153. cognite/neat/workflows/examples/Export_Rules_to_Ontology/workflow.yaml +0 -152
  154. cognite/neat/workflows/examples/Extract_DEXPI_Graph_and_Export_Rules/workflow.yaml +0 -139
  155. cognite/neat/workflows/examples/Extract_RDF_Graph_and_Generate_Assets/workflow.yaml +0 -270
  156. cognite/neat/workflows/examples/Ontology_to_Data_Model/workflow.yaml +0 -116
  157. cognite/neat/workflows/migration/__init__.py +0 -0
  158. cognite/neat/workflows/migration/steps.py +0 -91
  159. cognite/neat/workflows/migration/wf_manifests.py +0 -33
  160. cognite/neat/workflows/steps/lib/legacy/__init__.py +0 -7
  161. cognite/neat/workflows/steps/lib/legacy/graph_contextualization.py +0 -82
  162. cognite/neat/workflows/steps/lib/legacy/graph_extractor.py +0 -746
  163. cognite/neat/workflows/steps/lib/legacy/graph_loader.py +0 -606
  164. cognite/neat/workflows/steps/lib/legacy/graph_store.py +0 -307
  165. cognite/neat/workflows/steps/lib/legacy/graph_transformer.py +0 -58
  166. cognite/neat/workflows/steps/lib/legacy/rules_exporter.py +0 -511
  167. cognite/neat/workflows/steps/lib/legacy/rules_importer.py +0 -612
  168. cognite_neat-0.87.6.dist-info/RECORD +0 -319
  169. {cognite_neat-0.87.6.dist-info → cognite_neat-0.88.1.dist-info}/LICENSE +0 -0
  170. {cognite_neat-0.87.6.dist-info → cognite_neat-0.88.1.dist-info}/WHEEL +0 -0
  171. {cognite_neat-0.87.6.dist-info → cognite_neat-0.88.1.dist-info}/entry_points.txt +0 -0
@@ -1,511 +0,0 @@
1
- import logging
2
- import sys
3
- from collections import defaultdict
4
- from collections.abc import Iterable
5
- from dataclasses import dataclass, fields
6
- from datetime import datetime
7
- from itertools import groupby
8
- from typing import Any, Literal, overload
9
-
10
- from cognite.client import CogniteClient
11
- from cognite.client.data_classes import AssetWrite, LabelDefinitionWrite, RelationshipWrite, RelationshipWriteList
12
- from pydantic_core import ErrorDetails
13
- from rdflib.query import ResultRow
14
-
15
- from cognite.neat.legacy.graph.stores import NeatGraphStoreBase
16
- from cognite.neat.legacy.rules.models import Rules
17
- from cognite.neat.legacy.rules.models.rules import Property
18
- from cognite.neat.utils.rdf_ import remove_namespace_from_uri
19
- from cognite.neat.utils.time_ import epoch_now_ms
20
-
21
- from ._base import CogniteLoader
22
-
23
- if sys.version_info >= (3, 11):
24
- from datetime import UTC
25
- else:
26
- from datetime import timezone
27
-
28
- UTC = timezone.utc
29
-
30
- METADATA_VALUE_MAX_LENGTH = 5120
31
-
32
-
33
- @dataclass(frozen=True)
34
- class AssetLoaderMetadataKeys:
35
- """Class holding mapping between NEAT metadata key names and their desired names
36
- in CDF Asset metadata
37
-
38
- Args:
39
- start_time: Start time key name
40
- end_time: End time key name
41
- update_time: Update time key name
42
- resurrection_time: Resurrection time key name
43
- identifier: Identifier key name
44
- active: Active key name
45
- type: Type key name
46
- """
47
-
48
- start_time: str = "start_time"
49
- end_time: str = "end_time"
50
- update_time: str = "update_time"
51
- resurrection_time: str = "resurrection_time"
52
- identifier: str = "identifier"
53
- active: str = "active"
54
- type: str = "type"
55
-
56
- def as_aliases(self) -> dict[str, str]:
57
- return {str(field.default): getattr(self, field.name) for field in fields(self)}
58
-
59
-
60
- AssetResource = AssetWrite | RelationshipWrite | LabelDefinitionWrite
61
-
62
-
63
- class AssetLoader(CogniteLoader[AssetResource]):
64
- """The Asset Loader is used to load a triple store into the CDF core Asset Hierarchy model.
65
-
66
- Args:
67
- rules: The transformation rules to use
68
- graph_store: The graph store to load from
69
- data_set_id: The data set id to load into
70
- label_data_set_id: The data set id to load labels into. If not set, the same as data_set_id will be used.
71
- use_orphanage: Whether to use an orphanage for assets without a parent
72
- use_labels: Whether to use labels attached to the assets and relationships.
73
- asset_external_id_prefix: Prefix to add to all external ids
74
- default_metadata_value: Default metadata value to use for assets without a value. If set to None,
75
- the metadata key will be omitted. Setting this to an empty string will set the metadata key to an empty
76
- string, thus ensuring that all assets have the metadata keys.
77
- metadata_keys: Metadata key names to use
78
- always_store_in_metadata: Whether to store all properties in metadata. This will be the same as setting
79
- resource_type_property to metadata for all properties. For example, if you have the property `Terminal.name`
80
- set with resource_type_property = ["name"], then the property will be stored in metadata as
81
- `Terminal.name=<Value>`. This also includes properties that are relationships.
82
- """
83
-
84
- # This is guaranteed ot be in the data
85
- _identifier: str = "identifier"
86
- # This label is added to all assets if use_labels is True
87
- _non_historic_label: str = "non-historic"
88
-
89
- def __init__(
90
- self,
91
- rules: Rules,
92
- graph_store: NeatGraphStoreBase,
93
- data_set_id: int,
94
- label_data_set_id: int | None = None,
95
- use_orphanage: bool = True,
96
- use_labels: bool = True,
97
- asset_external_id_prefix: str | None = None,
98
- default_metadata_value: str | None = "",
99
- metadata_keys: AssetLoaderMetadataKeys | None = None,
100
- always_store_in_metadata: bool = False,
101
- ):
102
- super().__init__(rules, graph_store)
103
- self._data_set_id = data_set_id
104
- self._label_data_set_id = label_data_set_id or data_set_id
105
- self._use_labels = use_labels
106
- self._use_orphanage = use_orphanage
107
- self._orphanage_external_id = (
108
- f"{asset_external_id_prefix or ''}orphanage-{data_set_id}" if use_orphanage else None
109
- )
110
- self._asset_external_id_prefix = asset_external_id_prefix
111
- self._default_metadata_value = default_metadata_value
112
- self._metadata_keys = metadata_keys or AssetLoaderMetadataKeys()
113
- # This is used in a hot loop, so we cache it
114
- self._metadata_key_aliases = self._metadata_keys.as_aliases()
115
- self._always_store_in_metadata = always_store_in_metadata
116
-
117
- # State:
118
- self._loaded_assets: set[str] = set()
119
- self._loaded_labels: set[str] = set()
120
-
121
- @overload
122
- def load(self, stop_on_exception: Literal[True]) -> Iterable[AssetResource]: ...
123
-
124
- @overload
125
- def load(self, stop_on_exception: Literal[False] = False) -> Iterable[AssetResource | ErrorDetails]: ...
126
-
127
- def load(self, stop_on_exception: bool = False) -> Iterable[AssetResource | ErrorDetails]:
128
- if self.rules.metadata.namespace is None:
129
- raise ValueError("Namespace must be provided in transformation rules!")
130
- namespace = self.rules.metadata.namespace
131
-
132
- properties_by_class_name: dict[str, list[Property]] = defaultdict(list)
133
- for prop in self.rules.properties.values():
134
- properties_by_class_name[prop.class_id].append(prop)
135
-
136
- if self._use_labels:
137
- self._loaded_labels.add(self._non_historic_label)
138
- yield LabelDefinitionWrite(
139
- external_id=self._non_historic_label, name=self._non_historic_label, data_set_id=self._label_data_set_id
140
- )
141
-
142
- # Todo Extend rules to sort topological sorting to ensure that parents are loaded before children
143
- counter = 0
144
- for class_name in self.rules.classes.keys():
145
- if self._use_labels:
146
- self._loaded_labels.add(class_name)
147
- yield LabelDefinitionWrite(external_id=class_name, name=class_name, data_set_id=self._label_data_set_id)
148
-
149
- class_uri = namespace[class_name]
150
- logging.debug(f"Processing class <{class_uri}>.")
151
-
152
- try:
153
- result = self.graph_store.queries.list_instances_of_type(class_uri)
154
- except Exception as e:
155
- logging.error(f"Error while querying for instances of class <{class_uri}> into cache. Reason: {e}")
156
- if stop_on_exception:
157
- raise e
158
- yield ErrorDetails(
159
- input={"class_uri": class_uri},
160
- loc=("AssetLoader", "load"),
161
- msg=f"Error while querying instances of class <{class_uri}> into cache. Reason: {e}",
162
- type=f"Exception of type {type(e).__name__} occurred when processing instance of {class_name}",
163
- )
164
- continue
165
-
166
- logging.debug(f"Class <{class_name}> has {len(result)} instances")
167
-
168
- for instance_uri, properties_values in groupby(result, lambda x: x[0]):
169
- instance_id = remove_namespace_from_uri(instance_uri)
170
- values_by_property = self._prepare_instance_data(instance_id, properties_values)
171
- try:
172
- asset = self._load_asset(
173
- properties_by_class_name[class_name],
174
- values_by_property,
175
- instance_id,
176
- class_name,
177
- )
178
- except Exception as e:
179
- logging.error(f"Error while loading asset from instance <{instance_id}>. Reason: {e}")
180
- if stop_on_exception:
181
- raise e
182
- yield ErrorDetails(
183
- input={"instance_id": instance_id},
184
- loc=("AssetLoader", "load"),
185
- msg=f"Error while loading asset from <{instance_id}>. Reason: {e}",
186
- type=f"Exception of type {type(e).__name__} occurred when processing instance of {class_name}",
187
- )
188
- continue
189
- else:
190
- # We know that external_id is always set
191
- self._loaded_assets.add(asset.external_id) # type: ignore[arg-type]
192
- yield asset
193
-
194
- try:
195
- relationships = self._load_relationships(
196
- properties_by_class_name[class_name],
197
- values_by_property,
198
- remove_namespace_from_uri(instance_id),
199
- class_name,
200
- )
201
- except Exception as e:
202
- logging.error(f"Error while loading relationships from instance <{instance_id}>. Reason: {e}")
203
- if stop_on_exception:
204
- raise e
205
- yield ErrorDetails(
206
- input={"instance_id": instance_id},
207
- loc=("AssetLoader", "load"),
208
- msg=f"Error while loading relationships from <{instance_id}>. Reason: {e}",
209
- type=f"Exception of type {type(e).__name__} occurred when processing instance of {class_name}",
210
- )
211
- continue
212
- else:
213
- for relationship in relationships:
214
- for label in relationship.labels or []:
215
- if label.external_id and label.external_id not in self._loaded_labels:
216
- self._loaded_labels.add(label.external_id)
217
- yield LabelDefinitionWrite(
218
- name=label.external_id,
219
- external_id=label.external_id,
220
- data_set_id=self._label_data_set_id,
221
- )
222
-
223
- yield from relationships
224
-
225
- counter += 1
226
- # log every 10000 assets
227
- if counter % 10_000 == 0:
228
- logging.info(" Next 10,000 Assets processed")
229
-
230
- logging.debug(f"Class <{class_name}> processed")
231
-
232
- # Todo Move orphanage to the beginning to ensure that it is loaded first.
233
- if self._use_orphanage and self._orphanage_external_id not in self._loaded_assets:
234
- logging.warning(f"Orphanage with external id {self._orphanage_external_id} not found in asset hierarchy!")
235
- logging.warning(f"Adding default orphanage with external id {self._orphanage_external_id}")
236
- now = str(datetime.now(UTC))
237
- if self._use_labels:
238
- yield LabelDefinitionWrite(
239
- external_id="Orphanage", name="Orphanage", data_set_id=self._label_data_set_id
240
- )
241
-
242
- yield AssetWrite(
243
- external_id=self._orphanage_external_id,
244
- name="Orphanage",
245
- data_set_id=self._data_set_id,
246
- parent_external_id=None,
247
- description="Used to store all assets which parent does not exist",
248
- labels=["Orphanage", self._non_historic_label] if self._use_labels else None,
249
- metadata={
250
- self._metadata_keys.type: "Orphanage",
251
- "cdfResourceType": "Asset",
252
- self._metadata_keys.start_time: now,
253
- self._metadata_keys.update_time: now,
254
- self._metadata_keys.identifier: "orphanage",
255
- self._metadata_keys.active: "true",
256
- },
257
- )
258
-
259
- @overload
260
- def load_assets(self, stop_on_exception: Literal[True]) -> Iterable[AssetWrite]: ...
261
-
262
- @overload
263
- def load_assets(self, stop_on_exception: Literal[False] = False) -> Iterable[AssetWrite | ErrorDetails]: ...
264
-
265
- def load_assets(self, stop_on_exception: Literal[True, False] = False) -> Iterable[AssetWrite | ErrorDetails]:
266
- for asset_resource in self.load(stop_on_exception):
267
- if isinstance(asset_resource, AssetWrite):
268
- yield asset_resource
269
-
270
- @overload
271
- def load_relationships(self, stop_on_exception: Literal[True]) -> Iterable[RelationshipWrite]: ...
272
-
273
- @overload
274
- def load_relationships(
275
- self, stop_on_exception: Literal[False] = False
276
- ) -> Iterable[RelationshipWrite | ErrorDetails]: ...
277
-
278
- def load_relationships(
279
- self, stop_on_exception: Literal[True, False] = False
280
- ) -> Iterable[RelationshipWrite | ErrorDetails]:
281
- for asset_resource in self.load(stop_on_exception):
282
- if isinstance(asset_resource, RelationshipWrite):
283
- yield asset_resource
284
-
285
- def load_to_cdf(
286
- self, client: CogniteClient, batch_size: int | None = 1000, max_retries: int = 1, retry_delay: int = 3
287
- ) -> None:
288
- raise NotImplementedError
289
-
290
- def _load_asset(
291
- self, properties: list[Property], values_by_property: dict[str, str | list[str]], instance_id, class_name: str
292
- ) -> AssetWrite:
293
- """Converts a set of properties and values to an AssetWrite object."""
294
- asset_raw = self._load_asset_data(properties, values_by_property, instance_id, class_name)
295
-
296
- # Loading Asset assumes camel case.
297
- # Todo Change rules to use camel case?
298
- for snake_case, came_case in [
299
- ("external_id", "externalId"),
300
- ("parent_external_id", "parentExternalId"),
301
- ("geo_location", "geoLocation"),
302
- ]:
303
- if snake_case in asset_raw:
304
- asset_raw[came_case] = asset_raw.pop(snake_case)
305
-
306
- return AssetWrite.load(asset_raw)
307
-
308
- def _load_asset_data(
309
- self,
310
- properties: list[Property],
311
- values_by_property: dict[str, str | list[str]],
312
- instance_id: str,
313
- class_name: str,
314
- ) -> dict[str, Any]:
315
- """Creates a raw asset dict from a set of properties and values."""
316
- asset_raw, missing_metadata_properties = self._load_instance_data(properties, values_by_property)
317
-
318
- self._append_missing_properties(asset_raw, missing_metadata_properties, instance_id, class_name)
319
-
320
- asset_raw["dataSetId"] = self._data_set_id
321
-
322
- # Neat specific metadata keys
323
- if self._use_labels:
324
- asset_raw["labels"] = [class_name, self._non_historic_label]
325
- now = str(datetime.now(UTC))
326
- asset_raw["metadata"][self._metadata_keys.start_time] = now
327
- asset_raw["metadata"][self._metadata_keys.update_time] = now
328
- asset_raw["metadata"][self._metadata_keys.identifier] = instance_id
329
- asset_raw["metadata"][self._metadata_keys.active] = "true"
330
- asset_raw["metadata"][self._metadata_keys.type] = class_name
331
-
332
- # Rename metadata keys based on configuration
333
- asset_raw["metadata"] = {self._metadata_key_aliases.get(k, k): v for k, v in asset_raw["metadata"].items()}
334
- return asset_raw
335
-
336
- def _prepare_instance_data(
337
- self, instance_id: str, properties_values: Iterable[ResultRow]
338
- ) -> dict[str, str | list[str]]:
339
- """Groups the properties and values by property type.
340
-
341
- Returns:
342
- A dictionary with property type as key and a list of values as value.
343
- """
344
- properties_value_tuples: list[tuple[str, str]] = [
345
- remove_namespace_from_uri(prop, value) # type: ignore[misc]
346
- for _, prop, value in properties_values
347
- ]
348
- # We add an identifier which will be used as fallback for external_id
349
- properties_value_tuples.append((self._identifier, remove_namespace_from_uri(instance_id)))
350
- values_by_property: dict[str, str | list[str]] = {}
351
- for prop, values in groupby(sorted(properties_value_tuples), lambda x: x[0]):
352
- values_list: list[str] = [value for _, value in values] # type: ignore[misc, has-type]
353
- if len(values_list) == 1:
354
- values_by_property[prop] = values_list[0]
355
- else:
356
- values_by_property[prop] = values_list
357
- return values_by_property
358
-
359
- def _load_instance_data(
360
- self, properties: list[Property], values_by_property: dict[str, str | list[str]]
361
- ) -> tuple[dict[str, Any], set[str]]:
362
- """This function loads the instance data into a raw asset dict. It also returns a set of metadata keys that
363
- were not found in the instance data.
364
-
365
- Returns:
366
- A tuple with the raw asset dict and a set of metadata keys that were not found in the instance data.
367
- """
368
- asset_raw: dict[str, Any] = {"metadata": {}}
369
- missing_metadata_properties: set[str] = set()
370
- for prop in properties:
371
- if prop.property_name is None:
372
- continue
373
- is_relationship = "Asset" not in prop.cdf_resource_type
374
- if not self._always_store_in_metadata and is_relationship:
375
- continue
376
-
377
- if prop.property_name not in values_by_property:
378
- for property_type in prop.resource_type_property or []:
379
- if property_type.casefold() == "metadata":
380
- missing_metadata_properties.add(prop.property_name)
381
- elif self._always_store_in_metadata:
382
- missing_metadata_properties.add(prop.property_name)
383
- continue
384
- values = values_by_property[prop.property_name]
385
- for property_type in prop.resource_type_property or []:
386
- if property_type.casefold() == "metadata":
387
- asset_raw["metadata"][prop.property_name] = self._to_metadata_value(values)
388
- else:
389
- if property_type not in asset_raw:
390
- asset_raw[property_type] = values
391
- if self._always_store_in_metadata and prop.property_name not in asset_raw["metadata"]:
392
- asset_raw["metadata"][prop.property_name] = self._to_metadata_value(values)
393
- if is_relationship:
394
- asset_raw["metadata"][prop.property_name] = self._to_metadata_value(values)
395
-
396
- return asset_raw, missing_metadata_properties
397
-
398
- def _append_missing_properties(
399
- self, asset_raw: dict[str, Any], missing_metadata_properties: set[str], identifier: str, class_name: str
400
- ) -> None:
401
- """This function ensures that the raw asset dict has all the required properties such as external_id, name,
402
- and parent_external_id. It also ensures that the metadata dict has all the required keys."""
403
-
404
- if "external_id" not in asset_raw:
405
- msg = f"Missing external_id for {class_name} instance {identifier}. Using value <{identifier}>."
406
- logging.debug(msg)
407
- asset_raw["external_id"] = identifier
408
- elif "external_id" in asset_raw and isinstance(asset_raw["external_id"], list):
409
- external_ids = asset_raw["external_id"]
410
- msg = (
411
- f"Multiple values for {class_name} instance {identifier} external_id. "
412
- f"Using the first one <{external_ids[0]}>."
413
- )
414
- logging.debug(msg)
415
- asset_raw["external_id"] = external_ids[0]
416
-
417
- if self._asset_external_id_prefix:
418
- asset_raw["external_id"] = f"{self._asset_external_id_prefix}{asset_raw['external_id']}"
419
-
420
- external_id = asset_raw["external_id"]
421
-
422
- if "name" not in asset_raw:
423
- msg = f"Missing name for {class_name} instance {external_id}. Using value <{identifier}>."
424
- logging.debug(msg)
425
- asset_raw["name"] = identifier
426
- elif "name" in asset_raw and isinstance(asset_raw["name"], list):
427
- msg = f"Multiple values for {class_name} instance {external_id} name. Joining them into one."
428
- logging.debug(msg)
429
- asset_raw["name"] = self._to_metadata_value(asset_raw["name"])
430
-
431
- if "description" in asset_raw and isinstance(asset_raw["description"], list):
432
- msg = f"Multiple values for {class_name} instance {external_id} description. Joining them into one."
433
- logging.debug(msg)
434
- asset_raw["description"] = self._to_metadata_value(asset_raw["description"])
435
-
436
- if "parent_external_id" not in asset_raw and self._orphanage_external_id is not None:
437
- asset_raw["parent_external_id"] = self._orphanage_external_id
438
- elif "parent_external_id" in asset_raw and isinstance(asset_raw["parent_external_id"], list):
439
- msg = (
440
- f"Multiple values for {class_name} instance {external_id} parent_external_id. "
441
- f"Using the first one <{asset_raw['parent_external_id'][0]}>."
442
- )
443
- logging.debug(msg)
444
- asset_raw["parent_external_id"] = asset_raw["parent_external_id"][0]
445
-
446
- if "parent_external_id" in asset_raw:
447
- asset_raw["parent_external_id"] = f"{self._asset_external_id_prefix or ''}{asset_raw['parent_external_id']}"
448
-
449
- for metadata_key in missing_metadata_properties:
450
- msg = f"{external_id} of type {class_name} is missing metadata key {metadata_key}."
451
- logging.debug(msg)
452
- if self._default_metadata_value is not None and metadata_key not in asset_raw["metadata"]:
453
- asset_raw["metadata"][metadata_key] = self._default_metadata_value
454
- logging.debug(f"\tKey {metadata_key} added to <{external_id}> metadata!")
455
-
456
- @staticmethod
457
- def _to_metadata_value(values: str | list[str]) -> str:
458
- """A helper function to convert a list of values to a metadata value string respecting metadata value length."""
459
- # Sorting for deterministic results
460
- return values if isinstance(values, str) else ", ".join(sorted(values))[: METADATA_VALUE_MAX_LENGTH - 1]
461
-
462
- def _load_relationships(
463
- self,
464
- properties: list[Property],
465
- values_by_property: dict[str, str | list[str]],
466
- instance_id: str,
467
- class_name: str,
468
- ) -> RelationshipWriteList:
469
- """Converts a set of properties and values to a RelationshipWriteList object."""
470
- relationships = RelationshipWriteList([])
471
- relationship_properties = [prop for prop in properties if "Relationship" in prop.cdf_resource_type]
472
- epoch_now = epoch_now_ms()
473
- for prop in relationship_properties:
474
- if not prop.property_name:
475
- continue
476
- values = values_by_property.get(prop.property_name, [])
477
- if not values:
478
- continue
479
- value_list = [values] if isinstance(values, str) else values
480
- for value in value_list:
481
- relationship = self._load_relationship(prop, value, instance_id, class_name, epoch_now)
482
- relationships.append(relationship)
483
-
484
- return relationships
485
-
486
- def _load_relationship(
487
- self,
488
- prop: Property,
489
- target_id: str,
490
- instance_id: str,
491
- class_name: str,
492
- epoch_now: int,
493
- ) -> RelationshipWrite:
494
- """Converts a set of properties and values to a RelationshipWrite object."""
495
- prefix = self._asset_external_id_prefix or ""
496
- labels = (
497
- [class_name, self._non_historic_label, prop.expected_value_type.suffix, prop.property_id]
498
- if self._use_labels
499
- else None
500
- )
501
- relationship_raw = {
502
- "externalId": f"{prefix}{instance_id}:{prefix}{target_id}",
503
- "sourceExternalId": f"{prefix}{instance_id}",
504
- "targetExternalId": f"{prefix}{target_id}",
505
- "sourceType": prop.source_type,
506
- "targetType": prop.target_type,
507
- "dataSetId": self._data_set_id,
508
- "labels": labels,
509
- "startTime": epoch_now,
510
- }
511
- return RelationshipWrite.load(relationship_raw)
@@ -1,67 +0,0 @@
1
- import logging
2
- from abc import ABC, abstractmethod
3
- from collections.abc import Iterable
4
- from typing import Generic, Literal, TypeVar, overload
5
-
6
- from cognite.client import CogniteClient
7
- from pydantic_core import ErrorDetails
8
-
9
- from cognite.neat.legacy.graph.models import Triple
10
- from cognite.neat.legacy.graph.stores import NeatGraphStoreBase
11
- from cognite.neat.legacy.graph.transformations.query_generator.sparql import build_construct_query
12
- from cognite.neat.legacy.rules.models import Rules
13
-
14
- T_Output = TypeVar("T_Output")
15
-
16
-
17
- class BaseLoader(ABC, Generic[T_Output]):
18
- """Base class for all loaders.
19
-
20
- A loader is a class that loads data from a source graph into
21
- target outside Neat.
22
- """
23
-
24
- def __init__(self, rules: Rules, graph_store: NeatGraphStoreBase):
25
- self.rules = rules
26
- self.graph_store = graph_store
27
-
28
- @overload
29
- def load(self, stop_on_exception: Literal[True]) -> Iterable[T_Output]: ...
30
-
31
- @overload
32
- def load(self, stop_on_exception: Literal[False] = False) -> Iterable[T_Output | ErrorDetails]: ...
33
-
34
- @abstractmethod
35
- def load(self, stop_on_exception: bool = False) -> Iterable[T_Output | ErrorDetails]:
36
- """Load the graph with data."""
37
- pass
38
-
39
- def _iterate_class_triples(self, exclude_classes: set[str] | None = None) -> Iterable[tuple[str, Iterable[Triple]]]:
40
- """Iterate over all classes and their triples."""
41
- for class_name in self.rules.classes:
42
- if exclude_classes is not None and class_name in exclude_classes:
43
- continue
44
- try:
45
- sparql_construct_query = build_construct_query(
46
- self.graph_store.graph, class_name, self.rules, properties_optional=True
47
- )
48
- except Exception as e:
49
- logging.error(f"Failed to build construct query for class {class_name}: {e}")
50
- continue
51
-
52
- yield class_name, self.graph_store.query_delayed(sparql_construct_query)
53
-
54
-
55
- class CogniteLoader(BaseLoader[T_Output], ABC):
56
- """Base class for all loaders.
57
-
58
- A loader is a class that loads data from a source graph into
59
- target outside Neat.
60
- """
61
-
62
- @abstractmethod
63
- def load_to_cdf(
64
- self, client: CogniteClient, batch_size: int | None = 1000, max_retries: int = 1, retry_delay: int = 3
65
- ) -> None:
66
- """Load the graph with data."""
67
- pass
@@ -1,85 +0,0 @@
1
- """This module contains the definition of validation errors and warnings raised by various extractors"""
2
-
3
- from pydantic_core import ErrorDetails, PydanticCustomError
4
-
5
-
6
- class NeatError(Exception):
7
- type_: str
8
- code: int
9
- description: str
10
- example: str
11
- fix: str
12
- message: str
13
-
14
- def to_pydantic_custom_error(self):
15
- return PydanticCustomError(
16
- self.type_,
17
- self.message,
18
- dict(type_=self.type_, code=self.code, description=self.description, example=self.example, fix=self.fix),
19
- )
20
-
21
- def to_error_dict(self) -> ErrorDetails:
22
- return {
23
- "type": self.type_,
24
- "loc": (),
25
- "msg": self.message,
26
- "input": None,
27
- "ctx": dict(
28
- type_=self.type_,
29
- code=self.code,
30
- description=self.description,
31
- example=self.example,
32
- fix=self.fix,
33
- ),
34
- }
35
-
36
-
37
- class NeatWarning(UserWarning):
38
- type_: str
39
- code: int
40
- description: str
41
- example: str
42
- fix: str
43
- message: str
44
-
45
-
46
- class Warning1(NeatWarning):
47
- type_: str = "OWLGeneratedTransformationRulesHasErrors"
48
- code: int = 1
49
- description: str = (
50
- "This warning occurs when generating transformation rules from OWL ontology are invalid/incomplete."
51
- )
52
- example: str = ""
53
- fix: str = "Go through the generated report file and fix the warnings in generated Transformation Rules."
54
-
55
- def __init__(self, verbose=False):
56
- self.message = (
57
- "Transformation rules generated from OWL ontology are invalid!"
58
- " Consult report.txt for details on the errors and fix them before using the rules file."
59
- )
60
- if verbose:
61
- self.message += f"\nDescription: {self.description}"
62
- self.message += f"\nExample: {self.example}"
63
- self.message += f"\nFix: {self.fix}"
64
- # hint on a specific web docs page
65
-
66
-
67
- class Warning2(NeatWarning):
68
- type_: str = "OWLGeneratedTransformationRulesHasWarnings"
69
- code: int = 2
70
- description: str = (
71
- "This warning occurs when generating transformation rules from OWL ontology are invalid/incomplete."
72
- )
73
- example: str = ""
74
- fix: str = "Go through the generated report file and fix the warnings in generated Transformation Rules."
75
-
76
- def __init__(self, verbose=False):
77
- self.message = (
78
- "Transformation rules generated from OWL ontology raised warnings!"
79
- " Consult report.txt for details on warnings, and fix them prior using the rules file."
80
- )
81
- if verbose:
82
- self.message += f"\nDescription: {self.description}"
83
- self.message += f"\nExample: {self.example}"
84
- self.message += f"\nFix: {self.fix}"
85
- # hint on a specific web docs page
File without changes