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.
- cognite/neat/_alpha.py +8 -0
- cognite/neat/_client/_api/schema.py +43 -1
- cognite/neat/_client/data_classes/schema.py +4 -4
- cognite/neat/_constants.py +15 -1
- cognite/neat/_graph/extractors/__init__.py +4 -0
- cognite/neat/_graph/extractors/_classic_cdf/_base.py +8 -16
- cognite/neat/_graph/extractors/_classic_cdf/_classic.py +48 -19
- cognite/neat/_graph/extractors/_classic_cdf/_relationships.py +23 -17
- cognite/neat/_graph/extractors/_classic_cdf/_sequences.py +15 -17
- cognite/neat/_graph/extractors/_dict.py +102 -0
- cognite/neat/_graph/extractors/_dms.py +27 -40
- cognite/neat/_graph/extractors/_dms_graph.py +30 -3
- cognite/neat/_graph/extractors/_iodd.py +3 -3
- cognite/neat/_graph/extractors/_mock_graph_generator.py +9 -7
- cognite/neat/_graph/extractors/_raw.py +67 -0
- cognite/neat/_graph/loaders/_base.py +20 -4
- cognite/neat/_graph/loaders/_rdf2dms.py +476 -383
- cognite/neat/_graph/queries/_base.py +163 -133
- cognite/neat/_graph/transformers/__init__.py +1 -3
- cognite/neat/_graph/transformers/_classic_cdf.py +6 -22
- cognite/neat/_graph/transformers/_rdfpath.py +2 -49
- cognite/neat/_issues/__init__.py +1 -6
- cognite/neat/_issues/_base.py +21 -252
- cognite/neat/_issues/_contextmanagers.py +46 -0
- cognite/neat/_issues/_factory.py +69 -0
- cognite/neat/_issues/errors/__init__.py +20 -4
- cognite/neat/_issues/errors/_external.py +7 -0
- cognite/neat/_issues/errors/_wrapper.py +81 -3
- cognite/neat/_issues/formatters.py +4 -4
- cognite/neat/_issues/warnings/__init__.py +3 -2
- cognite/neat/_issues/warnings/_properties.py +8 -0
- cognite/neat/_issues/warnings/user_modeling.py +12 -0
- cognite/neat/_rules/_constants.py +12 -0
- cognite/neat/_rules/_shared.py +3 -2
- cognite/neat/_rules/analysis/__init__.py +2 -3
- cognite/neat/_rules/analysis/_base.py +430 -259
- cognite/neat/_rules/catalog/info-rules-imf.xlsx +0 -0
- cognite/neat/_rules/exporters/_rules2excel.py +3 -9
- cognite/neat/_rules/exporters/_rules2instance_template.py +2 -2
- cognite/neat/_rules/exporters/_rules2ontology.py +5 -4
- cognite/neat/_rules/importers/_base.py +2 -47
- cognite/neat/_rules/importers/_dms2rules.py +7 -10
- cognite/neat/_rules/importers/_dtdl2rules/dtdl_importer.py +2 -2
- cognite/neat/_rules/importers/_rdf/_inference2rules.py +66 -26
- cognite/neat/_rules/importers/_rdf/_shared.py +1 -1
- cognite/neat/_rules/importers/_spreadsheet2rules.py +12 -9
- cognite/neat/_rules/models/_base_rules.py +0 -2
- cognite/neat/_rules/models/data_types.py +7 -0
- cognite/neat/_rules/models/dms/_exporter.py +9 -8
- cognite/neat/_rules/models/dms/_rules.py +29 -2
- cognite/neat/_rules/models/dms/_rules_input.py +9 -1
- cognite/neat/_rules/models/dms/_validation.py +115 -5
- cognite/neat/_rules/models/entities/_loaders.py +1 -1
- cognite/neat/_rules/models/entities/_multi_value.py +2 -2
- cognite/neat/_rules/models/entities/_single_value.py +8 -3
- cognite/neat/_rules/models/entities/_wrapped.py +2 -2
- cognite/neat/_rules/models/information/_rules.py +18 -17
- cognite/neat/_rules/models/information/_rules_input.py +3 -1
- cognite/neat/_rules/models/information/_validation.py +66 -17
- cognite/neat/_rules/transformers/__init__.py +8 -2
- cognite/neat/_rules/transformers/_converters.py +234 -44
- cognite/neat/_rules/transformers/_verification.py +5 -10
- cognite/neat/_session/_base.py +6 -4
- cognite/neat/_session/_explore.py +39 -0
- cognite/neat/_session/_inspect.py +25 -6
- cognite/neat/_session/_prepare.py +12 -0
- cognite/neat/_session/_read.py +88 -20
- cognite/neat/_session/_set.py +7 -1
- cognite/neat/_session/_show.py +11 -123
- cognite/neat/_session/_state.py +6 -2
- cognite/neat/_session/_subset.py +64 -0
- cognite/neat/_session/_to.py +177 -19
- cognite/neat/_store/_graph_store.py +9 -246
- cognite/neat/_utils/rdf_.py +36 -5
- cognite/neat/_utils/spreadsheet.py +44 -1
- cognite/neat/_utils/text.py +124 -37
- cognite/neat/_utils/upload.py +2 -0
- cognite/neat/_version.py +2 -2
- {cognite_neat-0.109.4.dist-info → cognite_neat-0.111.0.dist-info}/METADATA +1 -1
- {cognite_neat-0.109.4.dist-info → cognite_neat-0.111.0.dist-info}/RECORD +83 -82
- {cognite_neat-0.109.4.dist-info → cognite_neat-0.111.0.dist-info}/WHEEL +1 -1
- cognite/neat/_graph/queries/_construct.py +0 -187
- cognite/neat/_graph/queries/_shared.py +0 -173
- cognite/neat/_rules/analysis/_dms.py +0 -57
- cognite/neat/_rules/analysis/_information.py +0 -249
- cognite/neat/_rules/models/_rdfpath.py +0 -372
- {cognite_neat-0.109.4.dist-info → cognite_neat-0.111.0.dist-info}/LICENSE +0 -0
- {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())
|
cognite/neat/_session/_read.py
CHANGED
|
@@ -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
|
-
|
|
578
|
-
|
|
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
|
|
586
|
-
"""Used as example for reading
|
|
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)
|
cognite/neat/_session/_set.py
CHANGED
|
@@ -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:
|
cognite/neat/_session/_show.py
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import colorsys
|
|
2
2
|
import random
|
|
3
|
-
from typing import Any
|
|
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.
|
|
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
|
|
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 =
|
|
106
|
+
di_graph = analysis._dms_di_graph(format="data-model")
|
|
108
107
|
else:
|
|
109
|
-
di_graph =
|
|
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 =
|
|
130
|
+
di_graph = analysis._dms_di_graph(format="implements")
|
|
191
131
|
else:
|
|
192
|
-
di_graph =
|
|
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):
|
cognite/neat/_session/_state.py
CHANGED
|
@@ -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} → {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
|