cognite-neat 0.102.0__py3-none-any.whl → 0.103.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 (66) hide show
  1. cognite/neat/__init__.py +1 -1
  2. cognite/neat/_app/api/routers/crud.py +1 -1
  3. cognite/neat/_client/__init__.py +1 -1
  4. cognite/neat/_client/_api/data_modeling_loaders.py +1 -1
  5. cognite/neat/_client/_api/schema.py +1 -1
  6. cognite/neat/_graph/_tracking/__init__.py +1 -1
  7. cognite/neat/_graph/extractors/__init__.py +8 -8
  8. cognite/neat/_graph/extractors/_mock_graph_generator.py +2 -3
  9. cognite/neat/_graph/loaders/_base.py +1 -1
  10. cognite/neat/_graph/loaders/_rdf2dms.py +165 -47
  11. cognite/neat/_graph/transformers/__init__.py +13 -9
  12. cognite/neat/_graph/transformers/_value_type.py +196 -2
  13. cognite/neat/_issues/__init__.py +6 -6
  14. cognite/neat/_issues/_base.py +4 -4
  15. cognite/neat/_issues/errors/__init__.py +22 -22
  16. cognite/neat/_issues/formatters.py +1 -1
  17. cognite/neat/_issues/warnings/__init__.py +20 -18
  18. cognite/neat/_issues/warnings/_properties.py +7 -0
  19. cognite/neat/_issues/warnings/user_modeling.py +2 -2
  20. cognite/neat/_rules/analysis/__init__.py +1 -1
  21. cognite/neat/_rules/catalog/__init__.py +1 -0
  22. cognite/neat/_rules/catalog/hello_world_pump.xlsx +0 -0
  23. cognite/neat/_rules/exporters/__init__.py +5 -5
  24. cognite/neat/_rules/exporters/_rules2excel.py +5 -4
  25. cognite/neat/_rules/importers/__init__.py +4 -4
  26. cognite/neat/_rules/importers/_base.py +7 -3
  27. cognite/neat/_rules/importers/_rdf/__init__.py +1 -1
  28. cognite/neat/_rules/models/__init__.py +5 -5
  29. cognite/neat/_rules/models/_base_rules.py +1 -1
  30. cognite/neat/_rules/models/dms/__init__.py +11 -11
  31. cognite/neat/_rules/models/dms/_validation.py +18 -10
  32. cognite/neat/_rules/models/entities/__init__.py +26 -26
  33. cognite/neat/_rules/models/entities/_single_value.py +25 -5
  34. cognite/neat/_rules/models/information/__init__.py +5 -5
  35. cognite/neat/_rules/models/mapping/_classic2core.yaml +54 -8
  36. cognite/neat/_rules/transformers/__init__.py +12 -12
  37. cognite/neat/_rules/transformers/_pipelines.py +10 -5
  38. cognite/neat/_session/_base.py +71 -0
  39. cognite/neat/_session/_collector.py +3 -1
  40. cognite/neat/_session/_drop.py +10 -0
  41. cognite/neat/_session/_inspect.py +35 -1
  42. cognite/neat/_session/_mapping.py +5 -0
  43. cognite/neat/_session/_prepare.py +121 -15
  44. cognite/neat/_session/_read.py +180 -20
  45. cognite/neat/_session/_set.py +11 -1
  46. cognite/neat/_session/_show.py +50 -11
  47. cognite/neat/_session/_to.py +58 -10
  48. cognite/neat/_session/engine/__init__.py +1 -1
  49. cognite/neat/_store/__init__.py +3 -2
  50. cognite/neat/_store/{_base.py → _graph_store.py} +33 -0
  51. cognite/neat/_store/_provenance.py +11 -1
  52. cognite/neat/_store/_rules_store.py +20 -0
  53. cognite/neat/_utils/auth.py +1 -1
  54. cognite/neat/_utils/io_.py +11 -0
  55. cognite/neat/_utils/reader/__init__.py +1 -1
  56. cognite/neat/_version.py +2 -2
  57. cognite/neat/_workflows/__init__.py +3 -3
  58. cognite/neat/_workflows/steps/lib/current/graph_extractor.py +1 -1
  59. cognite/neat/_workflows/steps/lib/current/rules_exporter.py +1 -1
  60. cognite/neat/_workflows/steps/lib/current/rules_importer.py +2 -2
  61. cognite/neat/_workflows/steps/lib/io/io_steps.py +3 -3
  62. {cognite_neat-0.102.0.dist-info → cognite_neat-0.103.1.dist-info}/METADATA +1 -1
  63. {cognite_neat-0.102.0.dist-info → cognite_neat-0.103.1.dist-info}/RECORD +66 -63
  64. {cognite_neat-0.102.0.dist-info → cognite_neat-0.103.1.dist-info}/LICENSE +0 -0
  65. {cognite_neat-0.102.0.dist-info → cognite_neat-0.103.1.dist-info}/WHEEL +0 -0
  66. {cognite_neat-0.102.0.dist-info → cognite_neat-0.103.1.dist-info}/entry_points.txt +0 -0
@@ -54,6 +54,26 @@ class NeatSession:
54
54
  load_engine: Whether to load the Neat Engine. Can be "newest", "cache", or "skip". "newest" will always
55
55
  check for the newest version of the engine. "cache" will load the engine if it has been downloaded before.
56
56
  "skip" will not load the engine.
57
+
58
+ Example:
59
+ Instantiate a NeatSession outside CDF jupyter notebook (needs instantiation of a CogniteClient)
60
+ ```python
61
+ from cognite.neat import get_cognite_client
62
+ from cognite.neat import NeatSession
63
+
64
+ client = get_cognite_client(env_file_name=".env")
65
+ neat = NeatSession(client)
66
+ ```
67
+
68
+ Example:
69
+ Instantiate a NeatSession inside a CDF jupyter notebook (use your user's CogniteClient directly)
70
+ ```python
71
+ from cognite.client import CogniteClient
72
+ from cognite.neat import NeatSession
73
+
74
+ client = CogniteClient()
75
+ neat = NeatSession(client)
76
+ ```
57
77
  """
58
78
 
59
79
  def __init__(
@@ -81,9 +101,33 @@ class NeatSession:
81
101
 
82
102
  @property
83
103
  def version(self) -> str:
104
+ """Get the current version of neat.
105
+
106
+ Returns:
107
+ The current version of neat used in the session.
108
+
109
+ Example:
110
+ ```python
111
+ neat.version
112
+ ```
113
+ """
84
114
  return _version.__version__
85
115
 
86
116
  def verify(self) -> IssueList:
117
+ """
118
+ Verify the Data Model schema before the model can be written to CDF. If verification was unsuccessful, use
119
+ `.inspect.issues()` to see what went wrong.
120
+
121
+ Example:
122
+ Verify a data model after reading a source file and inferring the data model
123
+ ```python
124
+ # From an active NeatSession
125
+ ...
126
+ neat.read.xml.dexpi("url_or_path_to_dexpi_file")
127
+ neat.infer()
128
+ neat.verify()
129
+ ```
130
+ """
87
131
  source_id, last_unverified_rule = self._state.data_model.last_unverified_rule
88
132
  transformer = VerifyAnyRules("continue", validate=False)
89
133
  start = datetime.now(timezone.utc)
@@ -135,6 +179,18 @@ class NeatSession:
135
179
  target: The target type to convert the data model to.
136
180
  mode: If the target is "dms", the mode to use for the conversion. None is used for default conversion.
137
181
  "edge_properties" treas classes that implements Edge as edge properties.
182
+
183
+ Example:
184
+ Convert to DMS rules
185
+ ```python
186
+ neat.convert(target="dms")
187
+ ```
188
+
189
+ Example:
190
+ Convert to Information rules
191
+ ```python
192
+ neat.convert(target="information")
193
+ ```
138
194
  """
139
195
  start = datetime.now(timezone.utc)
140
196
  issues = IssueList()
@@ -196,6 +252,15 @@ class NeatSession:
196
252
  Args:
197
253
  model_id: The ID of the inferred data model.
198
254
  max_number_of_instance: The maximum number of instances to use for inference.
255
+
256
+ Example:
257
+ Infer a data model after reading a source file
258
+ ```python
259
+ # From an active NeatSession
260
+ ...
261
+ neat.read.xml.dexpi("url_or_path_to_dexpi_file")
262
+ neat.infer()
263
+ ```
199
264
  """
200
265
  model_id = dm.DataModelId.load(model_id)
201
266
 
@@ -254,6 +319,10 @@ class NeatSession:
254
319
 
255
320
  @session_class_wrapper
256
321
  class OptAPI:
322
+ """For the user to decide if they want their usage of neat to be collected or not. We do not collect personal
323
+ information like name etc. only usage.
324
+ """
325
+
257
326
  def __init__(self, collector: Collector | None = None) -> None:
258
327
  self._collector = collector or _COLLECTOR
259
328
 
@@ -268,9 +337,11 @@ class OptAPI:
268
337
  )
269
338
 
270
339
  def in_(self) -> None:
340
+ """Consent to collection of neat user insights."""
271
341
  self._collector.enable()
272
342
  print("You have successfully opted in to data collection.")
273
343
 
274
344
  def out(self) -> None:
345
+ """Opt out of allowing usage of neat to be collected from current user."""
275
346
  self._collector.disable()
276
347
  print("You have successfully opted out of data collection.")
@@ -60,7 +60,7 @@ class Collector:
60
60
  if len(args) > 1:
61
61
  # The first argument is self.
62
62
  for i, arg in enumerate(args[1:]):
63
- event_information[f"arg{i}"] = arg
63
+ event_information[f"arg{i}"] = self._serialize_value(arg)[:500]
64
64
 
65
65
  if kwargs:
66
66
  for key, value in kwargs.items():
@@ -73,6 +73,8 @@ class Collector:
73
73
  return str(value)
74
74
  if isinstance(value, list | tuple | dict):
75
75
  return str(value)
76
+ if callable(value):
77
+ return value.__name__
76
78
  return str(type(value))
77
79
 
78
80
  def _track(self, event_name: str, event_information: dict[str, Any]) -> bool:
@@ -11,6 +11,10 @@ except ImportError:
11
11
 
12
12
  @session_class_wrapper
13
13
  class DropAPI:
14
+ """
15
+ Drop instances from the session. Check out `.instances()` for performing the operation.
16
+ """
17
+
14
18
  def __init__(self, state: SessionState):
15
19
  self._state = state
16
20
 
@@ -19,6 +23,12 @@ class DropAPI:
19
23
 
20
24
  Args:
21
25
  type: The type of instances to drop.
26
+
27
+ Example:
28
+ ```python
29
+ node_type_to_drop = "Pump"
30
+ neat.drop.instances(node_type_to_drop)
31
+ ```
22
32
  """
23
33
  type_list = type if isinstance(type, list) else [type]
24
34
  uri_type_type = dict((v, k) for k, v in self._state.instances.store.queries.types.items())
@@ -21,6 +21,28 @@ except ImportError:
21
21
 
22
22
  @session_class_wrapper
23
23
  class InspectAPI:
24
+ """Inspect issues or outcomes after performing operations with NeatSession.
25
+ To inspect properties of the current data model, try out `.properties()`.
26
+
27
+ Example:
28
+ Inspect issues
29
+ ```python
30
+ neat.inspect.issues()
31
+ ```
32
+
33
+ Example:
34
+ Inspect outcome after writing a data model
35
+ ```python
36
+ neat.inspect.outcome.data_model()
37
+ ```
38
+
39
+ Example:
40
+ Inspect outcome after writing instances
41
+ ```python
42
+ neat.inspect.outcome.instances()
43
+ ```
44
+ """
45
+
24
46
  def __init__(self, state: SessionState) -> None:
25
47
  self._state = state
26
48
  self.issues = InspectIssues(state)
@@ -28,7 +50,15 @@ class InspectAPI:
28
50
 
29
51
  @property
30
52
  def properties(self) -> pd.DataFrame:
31
- """Returns the properties of the current data model."""
53
+ """Returns the properties of the current data model.
54
+
55
+ Example:
56
+ Inspect properties of the current data model
57
+ ```python
58
+ # From an active NeatSession
59
+ neat.inspect.properties
60
+ ```
61
+ """
32
62
  return self._state.data_model.last_verified_rule[1].properties.to_pandas()
33
63
 
34
64
 
@@ -106,6 +136,10 @@ class InspectIssues:
106
136
 
107
137
  @session_class_wrapper
108
138
  class InspectOutcome:
139
+ """
140
+ Inspect the outcome after writing a Data Model and Instances to CDF.
141
+ """
142
+
109
143
  def __init__(self, state: SessionState) -> None:
110
144
  self.data_model = InspectUploadOutcome(lambda: state.data_model.last_outcome)
111
145
  self.instances = InspectUploadOutcome(lambda: state.instances.last_outcome)
@@ -38,6 +38,11 @@ class DataModelMappingAPI:
38
38
  If you extend CogniteAsset, with for example, ClassicAsset. You will map the property `parentId` to `parent`.
39
39
  If you set `user_parent_property_name` to True, the `parentId` will be renamed to `parent` after the
40
40
  mapping is done. If you set it to False, the property will remain `parentId`.
41
+
42
+ Example:
43
+ ```python
44
+ neat.mapping.classic_to_core(company_prefix="WindFarmX", use_parent_property_name=True)
45
+ ```
41
46
  """
42
47
  source_id, rules = self._state.data_model.last_verified_dms_rules
43
48
 
@@ -1,7 +1,7 @@
1
1
  import copy
2
- from collections.abc import Collection
2
+ from collections.abc import Callable, Collection
3
3
  from datetime import datetime, timezone
4
- from typing import Literal, cast
4
+ from typing import Any, Literal, cast
5
5
 
6
6
  from cognite.client.data_classes.data_modeling import DataModelIdentifier
7
7
  from rdflib import URIRef
@@ -13,6 +13,8 @@ from cognite.neat._constants import (
13
13
  )
14
14
  from cognite.neat._graph.transformers import (
15
15
  AttachPropertyFromTargetToSource,
16
+ ConvertLiteral,
17
+ LiteralToEntity,
16
18
  PruneDeadEndEdges,
17
19
  PruneInstancesOfUnknownType,
18
20
  PruneTypes,
@@ -47,6 +49,10 @@ except ImportError:
47
49
 
48
50
  @session_class_wrapper
49
51
  class PrepareAPI:
52
+ """Apply various operations on the knowledge graph as a necessary preprocessing step before for instance
53
+ inferring a data model or exporting the knowledge graph to a desired destination.
54
+ """
55
+
50
56
  def __init__(self, client: NeatClient | None, state: SessionState, verbose: bool) -> None:
51
57
  self._state = state
52
58
  self._verbose = verbose
@@ -56,6 +62,8 @@ class PrepareAPI:
56
62
 
57
63
  @session_class_wrapper
58
64
  class InstancePrepareAPI:
65
+ """Operations to perform on instances of data in the knowledge graph."""
66
+
59
67
  def __init__(self, state: SessionState, verbose: bool) -> None:
60
68
  self._state = state
61
69
  self._verbose = verbose
@@ -63,14 +71,20 @@ class InstancePrepareAPI:
63
71
  def dexpi(self) -> None:
64
72
  """Prepares extracted DEXPI graph for further usage in CDF
65
73
 
66
- This method bundles several graph transformers which:
67
- - attach values of generic attributes to nodes
68
- - create associations between nodes
69
- - remove unused generic attributes
70
- - remove associations between nodes that do not exist in the extracted graph
71
- - remove edges to nodes that do not exist in the extracted graph
74
+ !!! note "This method bundles several graph transformers which"
75
+ - attach values of generic attributes to nodes
76
+ - create associations between nodes
77
+ - remove unused generic attributes
78
+ - remove associations between nodes that do not exist in the extracted graph
79
+ - remove edges to nodes that do not exist in the extracted graph
72
80
 
73
81
  and therefore safeguard CDF from a bad graph
82
+
83
+ Example:
84
+ Apply Dexpi specific transformations:
85
+ ```python
86
+ neat.prepare.instances.dexpi()
87
+ ```
74
88
  """
75
89
 
76
90
  DEXPI = get_default_prefixes_and_namespaces()["dexpi"]
@@ -104,12 +118,18 @@ class InstancePrepareAPI:
104
118
  def aml(self) -> None:
105
119
  """Prepares extracted AutomationML graph for further usage in CDF
106
120
 
107
- This method bundles several graph transformers which:
108
- - attach values of attributes to nodes
109
- - remove unused attributes
110
- - remove edges to nodes that do not exist in the extracted graph
121
+ !!! note "This method bundles several graph transformers which"
122
+ - attach values of attributes to nodes
123
+ - remove unused attributes
124
+ - remove edges to nodes that do not exist in the extracted graph
111
125
 
112
126
  and therefore safeguard CDF from a bad graph
127
+
128
+ Example:
129
+ Apply AML specific transformations:
130
+ ```python
131
+ neat.prepare.instances.aml()
132
+ ```
113
133
  """
114
134
 
115
135
  AML = get_default_prefixes_and_namespaces()["aml"]
@@ -162,7 +182,32 @@ class InstancePrepareAPI:
162
182
  target value, as follows:
163
183
  (SourceType)-[connection]->(TargetType)
164
184
 
185
+ Example:
186
+ Make connection on exact match:
187
+ ```python
188
+ # From an active NeatSession
189
+ neat.read.csv("workitem.Table.csv",
190
+ type = "Activity",
191
+ primary_key="sourceId")
192
+
193
+ neat.read.csv("assets.Table.csv",
194
+ type="Asset",
195
+ primary_key="WMT_TAG_GLOBALID")
196
+
197
+ # Here we specify what column from the source table we should use when we link it with a column in the
198
+ # target table. In this case, it is the "workorderItemname" column in the source table
199
+ source = ("Activity", "workorderItemname")
200
+
201
+ # Here we give a name to the new property that is created when a match between the source and target is
202
+ # found
203
+ connection = "asset"
204
+
205
+ # Here we specify what column from the target table we should use when searching for a match.
206
+ # In this case, it is the "wmtTagName" column in the target table
207
+ target = ("Asset", "wmtTagName")
165
208
 
209
+ neat.prepare.instances.make_connection_on_exact_match(source, target, connection)
210
+ ```
166
211
  """
167
212
 
168
213
  subject_type, subject_predicate = self._get_type_and_property_uris(*source)
@@ -216,9 +261,67 @@ class InstancePrepareAPI:
216
261
  transformer = RelationshipAsEdgeTransformer(min_relationship_types, limit_per_type)
217
262
  self._state.instances.store.transform(transformer)
218
263
 
264
+ def convert_data_type(self, source: tuple[str, str], *, convert: Callable[[Any], Any] | None = None) -> None:
265
+ """Convert the data type of the given property.
266
+
267
+ This is, for example, useful when you have a boolean property that you want to convert to an enum.
268
+
269
+ Args:
270
+ source: The source of the conversion. A tuple of (type, property)
271
+ where property is the property that should be converted.
272
+ convert: The function to use for the conversion. The function should take the value of the property
273
+ as input and return the converted value. Default to assume you have a string that should be
274
+ converted to int, float, bool, or datetime.
275
+
276
+ Example:
277
+ Convert a boolean property to a string:
278
+ ```python
279
+ neat.prepare.instances.convert_data_type(
280
+ ("TimeSeries", "isString"),
281
+ convert=lambda is_string: "string" if is_string else "numeric"
282
+ )
283
+ ```
284
+
285
+ """
286
+ subject_type, subject_predicate = self._get_type_and_property_uris(*source)
287
+
288
+ transformer = ConvertLiteral(subject_type, subject_predicate, convert)
289
+ self._state.instances.store.transform(transformer)
290
+
291
+ def property_to_type(self, source: tuple[str | None, str], type: str, new_property: str | None = None) -> None:
292
+ """Convert a property to a new type.
293
+
294
+ Args:
295
+ source: The source of the conversion. A tuple of (type, property)
296
+ where property is the property that should be converted.
297
+ You can pass (None, property) to covert all properties with the given name.
298
+ type: The new type of the property.
299
+ new_property: Add the identifier as a new property. If None, the new entity will not have a property.
300
+
301
+ Example:
302
+ Convert the property 'source' to SourceSystem
303
+ ```python
304
+ neat.prepare.instances.property_to_type(
305
+ (None, "source"), "SourceSystem"
306
+ )
307
+ ```
308
+ """
309
+ subject_type: URIRef | None = None
310
+ if source[0] is not None:
311
+ subject_type, subject_predicate = self._get_type_and_property_uris(*source) # type: ignore[arg-type, assignment]
312
+ else:
313
+ subject_predicate = self._state.instances.store.queries.property_uri(source[1])[0]
314
+
315
+ transformer = LiteralToEntity(subject_type, subject_predicate, type, new_property)
316
+ self._state.instances.store.transform(transformer)
317
+
219
318
 
220
319
  @session_class_wrapper
221
320
  class DataModelPrepareAPI:
321
+ """Operations to perform on a data model as part of a workflow before writing the data model
322
+ to a desired destination.
323
+ """
324
+
222
325
  def __init__(self, client: NeatClient | None, state: SessionState, verbose: bool) -> None:
223
326
  self._client = client
224
327
  self._state = state
@@ -288,13 +391,15 @@ class DataModelPrepareAPI:
288
391
  move_connections: If True, the connections will be moved to the new data model.
289
392
 
290
393
  !!! note "Enterprise Data Model Creation"
394
+
291
395
  Always create an enterprise data model from a Cognite Data Model as this will
292
396
  assure all the Cognite Data Fusion applications to run smoothly, such as
293
- - Search
294
- - Atlas AI
295
- - ...
397
+ - Search
398
+ - Atlas AI
399
+ - ...
296
400
 
297
401
  !!! note "Move Connections"
402
+
298
403
  If you want to move the connections to the new data model, set the move_connections
299
404
  to True. This will move the connections to the new data model and use new model
300
405
  views as the source and target views.
@@ -345,6 +450,7 @@ class DataModelPrepareAPI:
345
450
  dummy_property: The dummy property to use as placeholder for the views in the new data model.
346
451
 
347
452
  !!! note "Solution Data Model Mode"
453
+
348
454
  The read-only solution model will only be able to read from the existing containers
349
455
  from the enterprise data model, therefore the solution data model will not have
350
456
  containers in the solution data model space. Meaning the solution data model views