cognite-neat 0.109.4__py3-none-any.whl → 0.111.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 (88) hide show
  1. cognite/neat/_alpha.py +8 -0
  2. cognite/neat/_client/_api/schema.py +43 -1
  3. cognite/neat/_client/data_classes/schema.py +4 -4
  4. cognite/neat/_constants.py +15 -1
  5. cognite/neat/_graph/extractors/__init__.py +4 -0
  6. cognite/neat/_graph/extractors/_classic_cdf/_base.py +8 -16
  7. cognite/neat/_graph/extractors/_classic_cdf/_classic.py +48 -19
  8. cognite/neat/_graph/extractors/_classic_cdf/_relationships.py +23 -17
  9. cognite/neat/_graph/extractors/_classic_cdf/_sequences.py +15 -17
  10. cognite/neat/_graph/extractors/_dict.py +102 -0
  11. cognite/neat/_graph/extractors/_dms.py +27 -40
  12. cognite/neat/_graph/extractors/_dms_graph.py +30 -3
  13. cognite/neat/_graph/extractors/_iodd.py +3 -3
  14. cognite/neat/_graph/extractors/_mock_graph_generator.py +9 -7
  15. cognite/neat/_graph/extractors/_raw.py +67 -0
  16. cognite/neat/_graph/loaders/_base.py +20 -4
  17. cognite/neat/_graph/loaders/_rdf2dms.py +476 -383
  18. cognite/neat/_graph/queries/_base.py +163 -133
  19. cognite/neat/_graph/transformers/__init__.py +1 -3
  20. cognite/neat/_graph/transformers/_classic_cdf.py +6 -22
  21. cognite/neat/_graph/transformers/_rdfpath.py +2 -49
  22. cognite/neat/_issues/__init__.py +1 -6
  23. cognite/neat/_issues/_base.py +21 -252
  24. cognite/neat/_issues/_contextmanagers.py +46 -0
  25. cognite/neat/_issues/_factory.py +69 -0
  26. cognite/neat/_issues/errors/__init__.py +20 -4
  27. cognite/neat/_issues/errors/_external.py +7 -0
  28. cognite/neat/_issues/errors/_wrapper.py +81 -3
  29. cognite/neat/_issues/formatters.py +4 -4
  30. cognite/neat/_issues/warnings/__init__.py +3 -2
  31. cognite/neat/_issues/warnings/_properties.py +8 -0
  32. cognite/neat/_issues/warnings/user_modeling.py +12 -0
  33. cognite/neat/_rules/_constants.py +12 -0
  34. cognite/neat/_rules/_shared.py +3 -2
  35. cognite/neat/_rules/analysis/__init__.py +2 -3
  36. cognite/neat/_rules/analysis/_base.py +430 -259
  37. cognite/neat/_rules/catalog/info-rules-imf.xlsx +0 -0
  38. cognite/neat/_rules/exporters/_rules2excel.py +3 -9
  39. cognite/neat/_rules/exporters/_rules2instance_template.py +2 -2
  40. cognite/neat/_rules/exporters/_rules2ontology.py +5 -4
  41. cognite/neat/_rules/importers/_base.py +2 -47
  42. cognite/neat/_rules/importers/_dms2rules.py +7 -10
  43. cognite/neat/_rules/importers/_dtdl2rules/dtdl_importer.py +2 -2
  44. cognite/neat/_rules/importers/_rdf/_inference2rules.py +66 -26
  45. cognite/neat/_rules/importers/_rdf/_shared.py +1 -1
  46. cognite/neat/_rules/importers/_spreadsheet2rules.py +12 -9
  47. cognite/neat/_rules/models/_base_rules.py +0 -2
  48. cognite/neat/_rules/models/data_types.py +7 -0
  49. cognite/neat/_rules/models/dms/_exporter.py +9 -8
  50. cognite/neat/_rules/models/dms/_rules.py +29 -2
  51. cognite/neat/_rules/models/dms/_rules_input.py +9 -1
  52. cognite/neat/_rules/models/dms/_validation.py +115 -5
  53. cognite/neat/_rules/models/entities/_loaders.py +1 -1
  54. cognite/neat/_rules/models/entities/_multi_value.py +2 -2
  55. cognite/neat/_rules/models/entities/_single_value.py +8 -3
  56. cognite/neat/_rules/models/entities/_wrapped.py +2 -2
  57. cognite/neat/_rules/models/information/_rules.py +18 -17
  58. cognite/neat/_rules/models/information/_rules_input.py +3 -1
  59. cognite/neat/_rules/models/information/_validation.py +66 -17
  60. cognite/neat/_rules/transformers/__init__.py +8 -2
  61. cognite/neat/_rules/transformers/_converters.py +234 -44
  62. cognite/neat/_rules/transformers/_verification.py +5 -10
  63. cognite/neat/_session/_base.py +6 -4
  64. cognite/neat/_session/_explore.py +39 -0
  65. cognite/neat/_session/_inspect.py +25 -6
  66. cognite/neat/_session/_prepare.py +12 -0
  67. cognite/neat/_session/_read.py +88 -20
  68. cognite/neat/_session/_set.py +7 -1
  69. cognite/neat/_session/_show.py +11 -123
  70. cognite/neat/_session/_state.py +6 -2
  71. cognite/neat/_session/_subset.py +64 -0
  72. cognite/neat/_session/_to.py +177 -19
  73. cognite/neat/_store/_graph_store.py +9 -246
  74. cognite/neat/_utils/rdf_.py +36 -5
  75. cognite/neat/_utils/spreadsheet.py +44 -1
  76. cognite/neat/_utils/text.py +124 -37
  77. cognite/neat/_utils/upload.py +2 -0
  78. cognite/neat/_version.py +2 -2
  79. {cognite_neat-0.109.4.dist-info → cognite_neat-0.111.0.dist-info}/METADATA +1 -1
  80. {cognite_neat-0.109.4.dist-info → cognite_neat-0.111.0.dist-info}/RECORD +83 -82
  81. {cognite_neat-0.109.4.dist-info → cognite_neat-0.111.0.dist-info}/WHEEL +1 -1
  82. cognite/neat/_graph/queries/_construct.py +0 -187
  83. cognite/neat/_graph/queries/_shared.py +0 -173
  84. cognite/neat/_rules/analysis/_dms.py +0 -57
  85. cognite/neat/_rules/analysis/_information.py +0 -249
  86. cognite/neat/_rules/models/_rdfpath.py +0 -372
  87. {cognite_neat-0.109.4.dist-info → cognite_neat-0.111.0.dist-info}/LICENSE +0 -0
  88. {cognite_neat-0.109.4.dist-info → cognite_neat-0.111.0.dist-info}/entry_points.txt +0 -0
@@ -1,3 +1,4 @@
1
+ import warnings
1
2
  from collections.abc import Callable
2
3
  from typing import Any
3
4
 
@@ -14,6 +15,7 @@ from cognite.neat._graph.transformers._rdfpath import MakeConnectionOnExactMatch
14
15
  from cognite.neat._issues import IssueList
15
16
  from cognite.neat._issues.errors import NeatValueError
16
17
  from cognite.neat._rules.transformers import PrefixEntities, StandardizeNaming
18
+ from cognite.neat._rules.transformers._converters import StandardizeSpaceAndVersion
17
19
  from cognite.neat._utils.text import humanize_collection
18
20
 
19
21
  from ._state import SessionState
@@ -270,5 +272,15 @@ class DataModelPrepareAPI:
270
272
  For classes/views/containers, the naming will be standardized to PascalCase.
271
273
  For properties, the naming will be standardized to camelCase.
272
274
  """
275
+ warnings.filterwarnings("default")
273
276
  AlphaFlags.standardize_naming.warn()
274
277
  return self._state.rule_transform(StandardizeNaming())
278
+
279
+ def standardize_space_and_version(self) -> IssueList:
280
+ """Standardize space and version in the data model.
281
+
282
+ This method will standardize the space and version in the data model to the Cognite standard.
283
+ """
284
+ warnings.filterwarnings("default")
285
+ AlphaFlags.standardize_space_and_version.warn()
286
+ return self._state.rule_transform(StandardizeSpaceAndVersion())
@@ -50,6 +50,7 @@ class ReadAPI:
50
50
  self.csv = CSVReadAPI(state, verbose)
51
51
  self.yaml = YamlReadAPI(state, verbose)
52
52
  self.xml = XMLReadAPI(state, verbose)
53
+ self.examples = Examples(state)
53
54
 
54
55
  def session(self, io: Any) -> None:
55
56
  """Reads a Neat Session from a zip file.
@@ -152,6 +153,45 @@ class CDFReadAPI(BaseReadAPI):
152
153
  )
153
154
  return self._state.write_graph(extractor)
154
155
 
156
+ def raw(
157
+ self,
158
+ db_name: str,
159
+ table_name: str,
160
+ type: str | None = None,
161
+ foreign_keys: str | SequenceNotStr[str] | None = None,
162
+ unpack_json: bool = False,
163
+ str_to_ideal_type: bool = False,
164
+ ) -> IssueList:
165
+ """Reads a raw table from CDF to the knowledge graph.
166
+
167
+ Args:
168
+ db_name: The name of the database
169
+ table_name: The name of the table, this will be assumed to be the type of the instances.
170
+ type: The type of instances in the table. If None, the table name will be used.
171
+ foreign_keys: The name of the columns that are foreign keys. If None, no foreign keys are used.
172
+ unpack_json: If True, the JSON objects will be unpacked into the graph.
173
+ str_to_ideal_type: If True, the string values will be converted to ideal types.
174
+
175
+ Returns:
176
+ IssueList: A list of issues that occurred during the extraction.
177
+
178
+ Example:
179
+ ```python
180
+ neat.read.cdf.raw("my_db", "my_table", "Asset")
181
+ ```
182
+
183
+ """
184
+ extractor = extractors.RAWExtractor(
185
+ self._get_client,
186
+ db_name=db_name,
187
+ table_name=table_name,
188
+ table_type=type,
189
+ foreign_keys=foreign_keys,
190
+ unpack_json=unpack_json,
191
+ str_to_ideal_type=str_to_ideal_type,
192
+ )
193
+ return self._state.instances.store.write(extractor)
194
+
155
195
 
156
196
  @session_class_wrapper
157
197
  class CDFClassicAPI(BaseReadAPI):
@@ -228,6 +268,8 @@ class CDFClassicAPI(BaseReadAPI):
228
268
  identifier: Literal["id", "externalId"] = "id",
229
269
  reference_timeseries: bool = False,
230
270
  reference_files: bool = False,
271
+ unpack_metadata: bool = False,
272
+ skip_sequence_rows: bool = False,
231
273
  ) -> IssueList:
232
274
  namespace = CLASSIC_CDF_NAMESPACE
233
275
  extractor = extractors.ClassicGraphExtractor(
@@ -237,7 +279,11 @@ class CDFClassicAPI(BaseReadAPI):
237
279
  namespace=namespace,
238
280
  prefix="Classic",
239
281
  identifier=identifier,
282
+ unpack_metadata=unpack_metadata,
283
+ skip_sequence_rows=skip_sequence_rows,
240
284
  )
285
+ self._state.instances.neat_prefix_by_predicate_uri.update(extractor.neat_prefix_by_predicate_uri)
286
+ self._state.instances.neat_prefix_by_type_uri.update(extractor.neat_prefix_by_type_uri)
241
287
  extract_issues = self._state.write_graph(extractor)
242
288
  if identifier == "externalId":
243
289
  self._state.quoted_source_identifiers = True
@@ -256,9 +302,6 @@ class CDFClassicAPI(BaseReadAPI):
256
302
  prepare_issues = self._state.rule_store.transform(
257
303
  ClassicPrepareCore(namespace, reference_timeseries, reference_files)
258
304
  )
259
- # Update the instance store with the latest rules
260
- information_rules = self._state.rule_store.last_verified_information_rules
261
- self._state.instances.store.rules[self._state.instances.store.default_named_graph] = information_rules
262
305
 
263
306
  all_issues = IssueList(extract_issues + prepare_issues)
264
307
  # Update the provenance with all issue.
@@ -286,7 +329,6 @@ class ExcelReadAPI(BaseReadAPI):
286
329
 
287
330
  def __init__(self, state: SessionState, verbose: bool) -> None:
288
331
  super().__init__(state, verbose)
289
- self.examples = ExcelExampleAPI(state, verbose)
290
332
 
291
333
  def __call__(self, io: Any, enable_manual_edit: bool = False) -> IssueList:
292
334
  """Reads a Neat Excel Rules sheet to the graph store. The rules sheet may stem from an Information architect,
@@ -310,16 +352,6 @@ class ExcelReadAPI(BaseReadAPI):
310
352
  return self._state.rule_import(importers.ExcelImporter(path), enable_manual_edit)
311
353
 
312
354
 
313
- @session_class_wrapper
314
- class ExcelExampleAPI(BaseReadAPI):
315
- """Used as example for reading some data model into the NeatSession."""
316
-
317
- def pump_example(self) -> IssueList:
318
- """Reads the Hello World pump example into the NeatSession."""
319
- importer: importers.ExcelImporter = importers.ExcelImporter(catalog.hello_world_pump)
320
- return self._state.rule_import(importer)
321
-
322
-
323
355
  @session_class_wrapper
324
356
  class YamlReadAPI(BaseReadAPI):
325
357
  def __call__(self, io: Any, format: Literal["neat", "toolkit"] = "neat") -> IssueList:
@@ -374,6 +406,9 @@ class CSVReadAPI(BaseReadAPI):
374
406
  """
375
407
 
376
408
  def __call__(self, io: Any, type: str, primary_key: str) -> None:
409
+ warnings.filterwarnings("default")
410
+ AlphaFlags.csv_read.warn()
411
+
377
412
  engine = import_engine()
378
413
  engine.set.format = "csv"
379
414
  engine.set.file = NeatReader.create(io).materialize_path()
@@ -429,6 +464,9 @@ class XMLReadAPI(BaseReadAPI):
429
464
  - remove associations between nodes that do not exist in the extracted graph
430
465
  - remove edges to nodes that do not exist in the extracted graph
431
466
  """
467
+ warnings.filterwarnings("default")
468
+ AlphaFlags.dexpi_read.warn()
469
+
432
470
  path = NeatReader.create(io).materialize_path()
433
471
  engine = import_engine()
434
472
  engine.set.format = "dexpi"
@@ -480,6 +518,9 @@ class XMLReadAPI(BaseReadAPI):
480
518
  - remove unused attributes
481
519
  - remove edges to nodes that do not exist in the extracted graph
482
520
  """
521
+ warnings.filterwarnings("default")
522
+ AlphaFlags.aml_read.warn()
523
+
483
524
  path = NeatReader.create(io).materialize_path()
484
525
  engine = import_engine()
485
526
  engine.set.format = "aml"
@@ -519,7 +560,6 @@ class RDFReadAPI(BaseReadAPI):
519
560
 
520
561
  def __init__(self, state: SessionState, verbose: bool) -> None:
521
562
  super().__init__(state, verbose)
522
- self.examples = RDFExamples(state)
523
563
 
524
564
  def ontology(self, io: Any) -> IssueList:
525
565
  """Reads an OWL ontology source into NeatSession.
@@ -532,6 +572,9 @@ class RDFReadAPI(BaseReadAPI):
532
572
  neat.read.rdf.ontology("url_or_path_to_owl_source")
533
573
  ```
534
574
  """
575
+ warnings.filterwarnings("default")
576
+ AlphaFlags.ontology_read.warn()
577
+
535
578
  reader = NeatReader.create(io)
536
579
  importer = importers.OWLImporter.from_file(reader.materialize_path(), source_name=f"file {reader!s}")
537
580
  return self._state.rule_import(importer)
@@ -547,10 +590,18 @@ class RDFReadAPI(BaseReadAPI):
547
590
  neat.read.rdf.imf("url_or_path_to_imf_source")
548
591
  ```
549
592
  """
593
+ warnings.filterwarnings("default")
594
+ AlphaFlags.imf_read.warn()
595
+
550
596
  reader = NeatReader.create(io)
551
597
  importer = importers.IMFImporter.from_file(reader.materialize_path(), source_name=f"file {reader!s}")
552
598
  return self._state.rule_import(importer)
553
599
 
600
+ def instances(self, io: Any) -> IssueList:
601
+ reader = NeatReader.create(io)
602
+ self._state.instances.store.write(extractors.RdfFileExtractor(reader.materialize_path()))
603
+ return IssueList()
604
+
554
605
  def __call__(
555
606
  self,
556
607
  io: Any,
@@ -574,21 +625,38 @@ class RDFReadAPI(BaseReadAPI):
574
625
  raise ValueError(f"Expected ontology, imf types or instances, got {source}")
575
626
 
576
627
  elif type == "instances":
577
- reader = NeatReader.create(io)
578
- self._state.instances.store.write(extractors.RdfFileExtractor(reader.materialize_path()))
579
- return IssueList()
628
+ return self.instances(io)
629
+
580
630
  else:
581
631
  raise NeatSessionError(f"Expected data model or instances, got {type}")
582
632
 
583
633
 
584
634
  @session_class_wrapper
585
- class RDFExamples:
586
- """Used as example for reading some triples into the NeatSession knowledge grapgh."""
635
+ class Examples:
636
+ """Used as example for reading various sources into NeatSession."""
587
637
 
588
638
  def __init__(self, state: SessionState) -> None:
589
639
  self._state = state
590
640
 
641
+ @property
642
+ def _get_client(self) -> NeatClient:
643
+ if self._state.client is None:
644
+ raise NeatValueError("No client provided. Please provide a client to read a data model.")
645
+ return self._state.client
646
+
591
647
  def nordic44(self) -> IssueList:
592
648
  """Reads the Nordic 44 knowledge graph into the NeatSession graph store."""
593
649
  self._state.instances.store.write(extractors.RdfFileExtractor(instances_examples.nordic44_knowledge_graph))
594
650
  return IssueList()
651
+
652
+ def pump_example(self) -> IssueList:
653
+ """Reads the Hello World pump example into the NeatSession."""
654
+ importer: importers.ExcelImporter = importers.ExcelImporter(catalog.hello_world_pump)
655
+ return self._state.rule_import(importer)
656
+
657
+ def core_data_model(self) -> IssueList:
658
+ """Reads the core data model example into the NeatSession."""
659
+
660
+ cdm_v1 = DataModelId.load(("cdf_cdm", "CogniteCore", "v1"))
661
+ importer: importers.DMSImporter = importers.DMSImporter.from_data_model_id(self._get_client, cdm_v1)
662
+ return self._state.rule_import(importer)
@@ -23,14 +23,20 @@ class SetAPI:
23
23
  self._verbose = verbose
24
24
  self.instances = SetInstances(state, verbose)
25
25
 
26
- def data_model_id(self, new_model_id: dm.DataModelId | tuple[str, str, str]) -> IssueList:
26
+ def data_model_id(self, new_model_id: dm.DataModelId | tuple[str, str, str], name: str | None = None) -> IssueList:
27
27
  """Sets the data model ID of the latest verified data model. Set the data model id as a tuple of strings
28
28
  following the template (<data_model_space>, <data_model_name>, <data_model_version>).
29
29
 
30
+ Args:
31
+ new_model_id (dm.DataModelId | tuple[str, str, str]): The new data model id.
32
+ name (str, optional): The display name of the data model. If not set, the external ID will be used
33
+ to generate the name.
34
+
30
35
  Example:
31
36
  Set a new data model id:
32
37
  ```python
33
38
  neat.set.data_model_id(("my_data_model_space", "My_Data_Model", "v1"))
39
+ neat.set.data_model_id(("my_data_model_space", "MyDataModel", "v1"), name="My Data Model")
34
40
  ```
35
41
  """
36
42
  if self._state.rule_store.empty:
@@ -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):
@@ -1,6 +1,8 @@
1
1
  from pathlib import Path
2
2
  from typing import Literal, cast
3
3
 
4
+ from rdflib import URIRef
5
+
4
6
  from cognite.neat._client import NeatClient
5
7
  from cognite.neat._graph.extractors import KnowledgeGraphExtractor
6
8
  from cognite.neat._issues import IssueList
@@ -37,7 +39,6 @@ class SessionState:
37
39
  last_entity = self.rule_store.provenance[-1].target_entity
38
40
  issues.action = f"{start} &#8594; {last_entity.display_name}"
39
41
  issues.hint = "Use the .inspect.issues() for more details."
40
- self.instances.store.add_rules(last_entity.information)
41
42
  return issues
42
43
 
43
44
  def rule_import(self, importer: BaseImporter, enable_manual_edit: bool = False) -> IssueList:
@@ -61,7 +62,6 @@ class SessionState:
61
62
  def write_graph(self, extractor: KnowledgeGraphExtractor) -> IssueList:
62
63
  extract_issues = self.instances.store.write(extractor)
63
64
  issues = self.rule_store.import_graph(extractor)
64
- self.instances.store.add_rules(self.rule_store.last_verified_information_rules)
65
65
  issues.extend(extract_issues)
66
66
  return issues
67
67
 
@@ -76,6 +76,10 @@ class InstancesState:
76
76
  self.storage_path = storage_path
77
77
  self.issue_lists = IssueList()
78
78
  self.outcome = UploadResultList()
79
+ # These contain prefixes added by Neat at the extraction stage.
80
+ # We store them such that they can be removed in the load stage.
81
+ self.neat_prefix_by_predicate_uri: dict[URIRef, str] = {}
82
+ self.neat_prefix_by_type_uri: dict[URIRef, str] = {}
79
83
 
80
84
  # Ensure that error handling is done in the constructor
81
85
  self.store: NeatGraphStore = _session_method_wrapper(self._create_store, "NeatSession")()
@@ -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