cognite-neat 0.107.0__py3-none-any.whl → 0.109.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of cognite-neat might be problematic. Click here for more details.

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