cognite-neat 0.109.3__py3-none-any.whl → 0.110.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 (67) hide show
  1. cognite/neat/_alpha.py +2 -0
  2. cognite/neat/_client/_api/schema.py +17 -1
  3. cognite/neat/_client/data_classes/schema.py +3 -3
  4. cognite/neat/_constants.py +11 -0
  5. cognite/neat/_graph/extractors/_classic_cdf/_classic.py +9 -10
  6. cognite/neat/_graph/extractors/_iodd.py +3 -3
  7. cognite/neat/_graph/extractors/_mock_graph_generator.py +9 -7
  8. cognite/neat/_graph/loaders/_rdf2dms.py +285 -346
  9. cognite/neat/_graph/queries/_base.py +28 -92
  10. cognite/neat/_graph/transformers/__init__.py +1 -3
  11. cognite/neat/_graph/transformers/_rdfpath.py +2 -49
  12. cognite/neat/_issues/__init__.py +1 -6
  13. cognite/neat/_issues/_base.py +21 -252
  14. cognite/neat/_issues/_contextmanagers.py +46 -0
  15. cognite/neat/_issues/_factory.py +61 -0
  16. cognite/neat/_issues/errors/__init__.py +18 -4
  17. cognite/neat/_issues/errors/_wrapper.py +81 -3
  18. cognite/neat/_issues/formatters.py +4 -4
  19. cognite/neat/_issues/warnings/__init__.py +3 -2
  20. cognite/neat/_issues/warnings/_properties.py +8 -0
  21. cognite/neat/_rules/_constants.py +9 -0
  22. cognite/neat/_rules/_shared.py +3 -2
  23. cognite/neat/_rules/analysis/__init__.py +2 -3
  24. cognite/neat/_rules/analysis/_base.py +450 -258
  25. cognite/neat/_rules/catalog/info-rules-imf.xlsx +0 -0
  26. cognite/neat/_rules/exporters/_rules2excel.py +2 -8
  27. cognite/neat/_rules/exporters/_rules2instance_template.py +2 -2
  28. cognite/neat/_rules/exporters/_rules2ontology.py +5 -4
  29. cognite/neat/_rules/importers/_base.py +2 -47
  30. cognite/neat/_rules/importers/_dms2rules.py +7 -10
  31. cognite/neat/_rules/importers/_dtdl2rules/dtdl_importer.py +2 -2
  32. cognite/neat/_rules/importers/_rdf/_inference2rules.py +59 -25
  33. cognite/neat/_rules/importers/_rdf/_shared.py +1 -1
  34. cognite/neat/_rules/importers/_spreadsheet2rules.py +12 -9
  35. cognite/neat/_rules/models/dms/_rules.py +3 -1
  36. cognite/neat/_rules/models/dms/_rules_input.py +4 -0
  37. cognite/neat/_rules/models/dms/_validation.py +14 -4
  38. cognite/neat/_rules/models/entities/_loaders.py +1 -1
  39. cognite/neat/_rules/models/entities/_multi_value.py +2 -2
  40. cognite/neat/_rules/models/information/_rules.py +18 -17
  41. cognite/neat/_rules/models/information/_rules_input.py +2 -1
  42. cognite/neat/_rules/models/information/_validation.py +3 -1
  43. cognite/neat/_rules/transformers/__init__.py +8 -2
  44. cognite/neat/_rules/transformers/_converters.py +242 -43
  45. cognite/neat/_rules/transformers/_verification.py +5 -10
  46. cognite/neat/_session/_base.py +4 -4
  47. cognite/neat/_session/_prepare.py +12 -0
  48. cognite/neat/_session/_read.py +21 -17
  49. cognite/neat/_session/_show.py +11 -123
  50. cognite/neat/_session/_state.py +0 -2
  51. cognite/neat/_session/_subset.py +64 -0
  52. cognite/neat/_session/_to.py +63 -12
  53. cognite/neat/_store/_graph_store.py +5 -246
  54. cognite/neat/_utils/rdf_.py +2 -2
  55. cognite/neat/_utils/spreadsheet.py +44 -1
  56. cognite/neat/_utils/text.py +51 -32
  57. cognite/neat/_version.py +1 -1
  58. {cognite_neat-0.109.3.dist-info → cognite_neat-0.110.0.dist-info}/METADATA +1 -1
  59. {cognite_neat-0.109.3.dist-info → cognite_neat-0.110.0.dist-info}/RECORD +62 -64
  60. {cognite_neat-0.109.3.dist-info → cognite_neat-0.110.0.dist-info}/WHEEL +1 -1
  61. cognite/neat/_graph/queries/_construct.py +0 -187
  62. cognite/neat/_graph/queries/_shared.py +0 -173
  63. cognite/neat/_rules/analysis/_dms.py +0 -57
  64. cognite/neat/_rules/analysis/_information.py +0 -249
  65. cognite/neat/_rules/models/_rdfpath.py +0 -372
  66. {cognite_neat-0.109.3.dist-info → cognite_neat-0.110.0.dist-info}/LICENSE +0 -0
  67. {cognite_neat-0.109.3.dist-info → cognite_neat-0.110.0.dist-info}/entry_points.txt +0 -0
@@ -1,16 +1,13 @@
1
1
  import colorsys
2
2
  import random
3
- from typing import Any, cast
3
+ from typing import Any
4
4
 
5
5
  import networkx as nx
6
6
  from IPython.display import HTML, display
7
7
  from pyvis.network import Network as PyVisNetwork # type: ignore
8
8
 
9
9
  from cognite.neat._constants import IN_NOTEBOOK, IN_PYODIDE
10
- from cognite.neat._rules._constants import EntityTypes
11
- from cognite.neat._rules.models.dms._rules import DMSRules
12
- from cognite.neat._rules.models.entities._single_value import ClassEntity, ViewEntity
13
- from cognite.neat._rules.models.information._rules import InformationRules
10
+ from cognite.neat._rules.analysis._base import RulesAnalysis
14
11
  from cognite.neat._session.exceptions import NeatSessionError
15
12
  from cognite.neat._utils.io_ import to_directory_compatible
16
13
  from cognite.neat._utils.rdf_ import remove_namespace_from_uri, uri_display_name
@@ -99,79 +96,21 @@ class ShowDataModelAPI(ShowBaseAPI):
99
96
 
100
97
  def __call__(self) -> Any:
101
98
  if self._state.rule_store.empty:
102
- raise NeatSessionError("No data model available. Try using [bold].read[/bold] to read a new data model.")
99
+ raise NeatSessionError("No data model available. Try using [bold].read[/bold] to read a data model.")
100
+
103
101
  last_target = self._state.rule_store.provenance[-1].target_entity
104
102
  rules = last_target.dms or last_target.information
103
+ analysis = RulesAnalysis(dms=last_target.dms, information=last_target.information)
105
104
 
106
105
  if last_target.dms is not None:
107
- di_graph = self._generate_dms_di_graph(last_target.dms)
106
+ di_graph = analysis._dms_di_graph(format="data-model")
108
107
  else:
109
- di_graph = self._generate_info_di_graph(last_target.information)
108
+ di_graph = analysis._info_di_graph(format="data-model")
109
+
110
110
  identifier = to_directory_compatible(str(rules.metadata.identifier))
111
111
  name = f"{identifier}.html"
112
-
113
112
  return self._generate_visualization(di_graph, name)
114
113
 
115
- def _generate_dms_di_graph(self, rules: DMSRules) -> nx.DiGraph:
116
- """Generate a DiGraph from the last verified DMS rules."""
117
- di_graph = nx.DiGraph()
118
-
119
- # Views with properties or used as ValueType
120
- # If a view is not used in properties or as ValueType, it is not added to the graph
121
- # as we typically do not have the properties for it.
122
- used_views = {prop_.view for prop_ in rules.properties} | {
123
- prop_.value_type for prop_ in rules.properties if isinstance(prop_.value_type, ViewEntity)
124
- }
125
-
126
- # Add nodes and edges from Views sheet
127
- for view in rules.views:
128
- if view.view not in used_views:
129
- continue
130
- # if possible use humanreadable label coming from the view name
131
- if not di_graph.has_node(view.view.suffix):
132
- di_graph.add_node(view.view.suffix, label=view.view.suffix)
133
-
134
- # Add nodes and edges from Properties sheet
135
- for prop_ in rules.properties:
136
- if prop_.connection and isinstance(prop_.value_type, ViewEntity):
137
- if not di_graph.has_node(prop_.view.suffix):
138
- di_graph.add_node(prop_.view.suffix, label=prop_.view.suffix)
139
- di_graph.add_edge(
140
- prop_.view.suffix,
141
- prop_.value_type.suffix,
142
- label=prop_.name or prop_.view_property,
143
- )
144
-
145
- return di_graph
146
-
147
- def _generate_info_di_graph(self, rules: InformationRules) -> nx.DiGraph:
148
- """Generate DiGraph representing information data model."""
149
-
150
- di_graph = nx.DiGraph()
151
-
152
- # Add nodes and edges from Views sheet
153
- for class_ in rules.classes:
154
- # if possible use humanreadable label coming from the view name
155
- if not di_graph.has_node(class_.class_.suffix):
156
- di_graph.add_node(
157
- class_.class_.suffix,
158
- label=class_.name or class_.class_.suffix,
159
- )
160
-
161
- # Add nodes and edges from Properties sheet
162
- for prop_ in rules.properties:
163
- if prop_.type_ == EntityTypes.object_property:
164
- if not di_graph.has_node(prop_.class_.suffix):
165
- di_graph.add_node(prop_.class_.suffix, label=prop_.class_.suffix)
166
-
167
- di_graph.add_edge(
168
- prop_.class_.suffix,
169
- cast(ClassEntity, prop_.value_type).suffix,
170
- label=prop_.name or prop_.property_,
171
- )
172
-
173
- return di_graph
174
-
175
114
 
176
115
  @session_class_wrapper
177
116
  class ShowDataModelImplementsAPI(ShowBaseAPI):
@@ -185,67 +124,16 @@ class ShowDataModelImplementsAPI(ShowBaseAPI):
185
124
 
186
125
  last_target = self._state.rule_store.provenance[-1].target_entity
187
126
  rules = last_target.dms or last_target.information
127
+ analysis = RulesAnalysis(dms=last_target.dms, information=last_target.information)
188
128
 
189
129
  if last_target.dms is not None:
190
- di_graph = self._generate_dms_di_graph(last_target.dms)
130
+ di_graph = analysis._dms_di_graph(format="implements")
191
131
  else:
192
- di_graph = self._generate_info_di_graph(last_target.information)
132
+ di_graph = analysis._info_di_graph(format="implements")
193
133
  identifier = to_directory_compatible(str(rules.metadata.identifier))
194
134
  name = f"{identifier}_implements.html"
195
135
  return self._generate_visualization(di_graph, name)
196
136
 
197
- def _generate_dms_di_graph(self, rules: DMSRules) -> nx.DiGraph:
198
- """Generate a DiGraph from the last verified DMS rules."""
199
- di_graph = nx.DiGraph()
200
-
201
- # Add nodes and edges from Views sheet
202
- for view in rules.views:
203
- # add implements as edges
204
- if view.implements:
205
- if not di_graph.has_node(view.view.suffix):
206
- di_graph.add_node(view.view.suffix, label=view.view.suffix)
207
- for implement in view.implements:
208
- if not di_graph.has_node(implement.suffix):
209
- di_graph.add_node(implement.suffix, label=implement.suffix)
210
-
211
- di_graph.add_edge(
212
- view.view.suffix,
213
- implement.suffix,
214
- label="implements",
215
- dashes=True,
216
- )
217
-
218
- return di_graph
219
-
220
- def _generate_info_di_graph(self, rules: InformationRules) -> nx.DiGraph:
221
- """Generate DiGraph representing information data model."""
222
-
223
- di_graph = nx.DiGraph()
224
-
225
- # Add nodes and edges from Views sheet
226
- for class_ in rules.classes:
227
- # if possible use human readable label coming from the view name
228
-
229
- # add subClassOff as edges
230
- if class_.implements:
231
- if not di_graph.has_node(class_.class_.suffix):
232
- di_graph.add_node(
233
- class_.class_.suffix,
234
- label=class_.name or class_.class_.suffix,
235
- )
236
-
237
- for parent in class_.implements:
238
- if not di_graph.has_node(parent.suffix):
239
- di_graph.add_node(parent.suffix, label=parent.suffix)
240
- di_graph.add_edge(
241
- class_.class_.suffix,
242
- parent.suffix,
243
- label="implements",
244
- dashes=True,
245
- )
246
-
247
- return di_graph
248
-
249
137
 
250
138
  @session_class_wrapper
251
139
  class ShowDataModelProvenanceAPI(ShowBaseAPI):
@@ -37,7 +37,6 @@ class SessionState:
37
37
  last_entity = self.rule_store.provenance[-1].target_entity
38
38
  issues.action = f"{start} → {last_entity.display_name}"
39
39
  issues.hint = "Use the .inspect.issues() for more details."
40
- self.instances.store.add_rules(last_entity.information)
41
40
  return issues
42
41
 
43
42
  def rule_import(self, importer: BaseImporter, enable_manual_edit: bool = False) -> IssueList:
@@ -61,7 +60,6 @@ class SessionState:
61
60
  def write_graph(self, extractor: KnowledgeGraphExtractor) -> IssueList:
62
61
  extract_issues = self.instances.store.write(extractor)
63
62
  issues = self.rule_store.import_graph(extractor)
64
- self.instances.store.add_rules(self.rule_store.last_verified_information_rules)
65
63
  issues.extend(extract_issues)
66
64
  return issues
67
65
 
@@ -0,0 +1,64 @@
1
+ import warnings
2
+
3
+ from cognite.neat._alpha import AlphaFlags
4
+ from cognite.neat._issues._base import IssueList
5
+ from cognite.neat._rules.models.entities._single_value import ClassEntity, ViewEntity
6
+ from cognite.neat._rules.transformers import SubsetDMSRules, SubsetInformationRules
7
+
8
+ from ._state import SessionState
9
+ from .exceptions import NeatSessionError, session_class_wrapper
10
+
11
+ try:
12
+ from rich import print
13
+ except ImportError:
14
+ ...
15
+
16
+
17
+ @session_class_wrapper
18
+ class SubsetAPI:
19
+ """
20
+ Subset data model and instances in the session based on the desired subset of concepts.
21
+
22
+ """
23
+
24
+ def __init__(self, state: SessionState):
25
+ self._state = state
26
+
27
+ def data_model(self, concepts: str | list[str]) -> IssueList:
28
+ if self._state.rule_store.empty:
29
+ raise NeatSessionError("No rules to set the data model ID.")
30
+
31
+ warnings.filterwarnings("default")
32
+ AlphaFlags.data_model_subsetting.warn()
33
+
34
+ dms = self._state.rule_store.provenance[-1].target_entity.dms
35
+ information = self._state.rule_store.provenance[-1].target_entity.information
36
+
37
+ if dms:
38
+ views = {
39
+ ViewEntity(
40
+ space=dms.metadata.space,
41
+ externalId=concept,
42
+ version=dms.metadata.version,
43
+ )
44
+ for concept in concepts
45
+ }
46
+
47
+ issues = self._state.rule_transform(SubsetDMSRules(views=views))
48
+ if not issues:
49
+ after = len(self._state.rule_store.last_verified_dms_rules.views)
50
+
51
+ elif information:
52
+ classes = {ClassEntity(prefix=information.metadata.space, suffix=concept) for concept in concepts}
53
+
54
+ issues = self._state.rule_transform(SubsetInformationRules(classes=classes))
55
+ if not issues:
56
+ after = len(self._state.rule_store.last_verified_information_rules.classes)
57
+
58
+ else:
59
+ raise NeatSessionError("Something went terrible wrong. Please contact the neat team.")
60
+
61
+ if not issues:
62
+ print(f"Subset to {after} concepts.")
63
+
64
+ return issues
@@ -5,14 +5,18 @@ from pathlib import Path
5
5
  from typing import Any, Literal, overload
6
6
 
7
7
  from cognite.client import data_modeling as dm
8
+ from cognite.client.data_classes.data_modeling import DataModelIdentifier
8
9
 
9
10
  from cognite.neat._alpha import AlphaFlags
10
11
  from cognite.neat._constants import COGNITE_MODELS
11
12
  from cognite.neat._graph import loaders
13
+ from cognite.neat._issues import IssueList, catch_issues
12
14
  from cognite.neat._rules import exporters
13
15
  from cognite.neat._rules._constants import PATTERNS
14
16
  from cognite.neat._rules._shared import VerifiedRules
15
17
  from cognite.neat._rules.exporters._rules2dms import Component
18
+ from cognite.neat._rules.importers import DMSImporter
19
+ from cognite.neat._rules.models import DMSRules, InformationRules
16
20
  from cognite.neat._rules.models.dms import DMSMetadata
17
21
  from cognite.neat._utils.upload import UploadResultList
18
22
 
@@ -35,10 +39,10 @@ class ToAPI:
35
39
  def excel(
36
40
  self,
37
41
  io: Any,
38
- include_reference: bool = True,
42
+ include_reference: bool | DataModelIdentifier = True,
39
43
  include_properties: Literal["same-space", "all"] = "all",
40
44
  add_empty_rows: bool = False,
41
- ) -> None:
45
+ ) -> IssueList | None:
42
46
  """Export the verified data model to Excel.
43
47
 
44
48
  Args:
@@ -46,6 +50,7 @@ class ToAPI:
46
50
  include_reference: If True, the reference data model will be included. Defaults to True.
47
51
  Note that this only applies if you have created the data model using the
48
52
  create.enterprise_model(...), create.solution_model(), or create.data_product_model() methods.
53
+ You can also provide a DataModelIdentifier directly, which will be read from CDF
49
54
  include_properties: The properties to include in the Excel file. Defaults to "all".
50
55
  - "same-space": Only properties that are in the same space as the data model will be included.
51
56
  add_empty_rows: If True, empty rows will be added between each component. Defaults to False.
@@ -71,19 +76,45 @@ class ToAPI:
71
76
  dms_rules_file_name = "dms_rules.xlsx"
72
77
  neat.to.excel(dms_rules_file_name, include_reference=True)
73
78
  ```
79
+
80
+ Example:
81
+ Read the data model ("my_space", "ISA95Model", "v5") and export it to an excel file with the
82
+ CogniteCore model in the reference sheets.
83
+ ```python
84
+ client = CogniteClient()
85
+ neat = NeatSession(client)
86
+
87
+ neat.read.cdf(("my_space", "ISA95Model", "v5"))
88
+ dms_rules_file_name = "dms_rules.xlsx"
89
+ neat.to.excel(dms_rules_file_name, include_reference=("cdf_cdm", "CogniteCore", "v1"))
74
90
  """
75
91
  reference_rules_with_prefix: tuple[VerifiedRules, str] | None = None
76
92
  include_properties = include_properties.strip().lower()
77
93
 
78
- if include_reference and self._state.last_reference:
79
- if (
80
- isinstance(self._state.last_reference.metadata, DMSMetadata)
81
- and self._state.last_reference.metadata.as_data_model_id() in COGNITE_MODELS
82
- ):
83
- prefix = "CDM"
94
+ if include_reference is not False:
95
+ if include_reference is True and self._state.last_reference is not None:
96
+ ref_rules: InformationRules | DMSRules | None = self._state.last_reference
97
+ elif include_reference is True:
98
+ ref_rules = None
84
99
  else:
100
+ if not self._state.client:
101
+ raise NeatSessionError("No client provided!")
102
+ ref_rules = None
103
+ with catch_issues() as issues:
104
+ ref_read = DMSImporter.from_data_model_id(self._state.client, include_reference).to_rules()
105
+ if ref_read.rules is not None:
106
+ ref_rules = ref_read.rules.as_verified_rules()
107
+ if ref_rules is None or issues.has_errors:
108
+ issues.action = f"Read {include_reference}"
109
+ return issues
110
+ if ref_rules is not None:
85
111
  prefix = "Ref"
86
- reference_rules_with_prefix = self._state.last_reference, prefix
112
+ if (
113
+ isinstance(ref_rules.metadata, DMSMetadata)
114
+ and ref_rules.metadata.as_data_model_id() in COGNITE_MODELS
115
+ ):
116
+ prefix = "CDM"
117
+ reference_rules_with_prefix = ref_rules, prefix
87
118
 
88
119
  if include_properties == "same-space":
89
120
  warnings.filterwarnings("default")
@@ -95,7 +126,8 @@ class ToAPI:
95
126
  add_empty_rows=add_empty_rows,
96
127
  include_properties=include_properties, # type: ignore
97
128
  )
98
- return self._state.rule_store.export_to_file(exporter, Path(io))
129
+ self._state.rule_store.export_to_file(exporter, Path(io))
130
+ return None
99
131
 
100
132
  def session(self, io: Any) -> None:
101
133
  """Export the current session to a file.
@@ -212,12 +244,30 @@ class CDFToAPI:
212
244
  def instances(
213
245
  self,
214
246
  space: str | None = None,
247
+ space_property: str | None = None,
215
248
  ) -> UploadResultList:
216
249
  """Export the verified DMS instances to CDF.
217
250
 
218
251
  Args:
219
252
  space: Name of instance space to use. Default is to suffix the schema space with '_instances'.
220
- Note this space is required to be different than the space with the data model.
253
+ Note this space is required to be different from the space with the data model.
254
+ space_property: This is an alternative to the 'space' argument. If provided, the space will set to the
255
+ value of the property with the given name for each instance. If the property is not found, the
256
+ 'space' argument will be used. Defaults to None.
257
+
258
+ Returns:
259
+ UploadResultList: The result of the upload.
260
+
261
+ Example:
262
+ Export instances to CDF
263
+ ```python
264
+ neat.to.cdf.instances()
265
+ ```
266
+
267
+ Export instances to CDF using the `dataSetId` property as the space
268
+ ```python
269
+ neat.to.cdf.instances(space_property="dataSetId")
270
+ ```
221
271
 
222
272
  """
223
273
  if not self._state.client:
@@ -233,8 +283,9 @@ class CDFToAPI:
233
283
  if not client.data_modeling.spaces.retrieve(space):
234
284
  client.data_modeling.spaces.apply(dm.SpaceApply(space=space))
235
285
 
236
- loader = loaders.DMSLoader.from_rules(
286
+ loader = loaders.DMSLoader(
237
287
  self._state.rule_store.last_verified_dms_rules,
288
+ self._state.rule_store.last_verified_information_rules,
238
289
  self._state.instances.store,
239
290
  instance_space=space,
240
291
  client=client,