cognite-neat 0.121.1__py3-none-any.whl → 0.122.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/_version.py +1 -1
- cognite/neat/core/_client/_api/statistics.py +91 -0
- cognite/neat/core/_client/_api_client.py +2 -0
- cognite/neat/core/_client/data_classes/statistics.py +125 -0
- cognite/neat/core/_client/testing.py +4 -0
- cognite/neat/core/_constants.py +6 -7
- cognite/neat/core/_data_model/_constants.py +23 -16
- cognite/neat/core/_data_model/_shared.py +33 -17
- cognite/neat/core/_data_model/analysis/__init__.py +2 -2
- cognite/neat/core/_data_model/analysis/_base.py +186 -183
- cognite/neat/core/_data_model/catalog/__init__.py +2 -2
- cognite/neat/core/_data_model/exporters/__init__.py +6 -6
- cognite/neat/core/_data_model/exporters/_base.py +10 -8
- cognite/neat/core/_data_model/exporters/{_rules2dms.py → _data_model2dms.py} +22 -18
- cognite/neat/core/_data_model/exporters/{_rules2excel.py → _data_model2excel.py} +51 -51
- cognite/neat/core/_data_model/exporters/{_rules2instance_template.py → _data_model2instance_template.py} +14 -14
- cognite/neat/core/_data_model/exporters/{_rules2ontology.py → _data_model2ontology.py} +50 -50
- cognite/neat/core/_data_model/exporters/{_rules2yaml.py → _data_model2yaml.py} +21 -18
- cognite/neat/core/_data_model/importers/__init__.py +8 -8
- cognite/neat/core/_data_model/importers/_base.py +8 -6
- cognite/neat/core/_data_model/importers/_base_file_reader.py +56 -0
- cognite/neat/core/_data_model/importers/{_yaml2rules.py → _dict2data_model.py} +50 -25
- cognite/neat/core/_data_model/importers/{_dms2rules.py → _dms2data_model.py} +58 -49
- cognite/neat/core/_data_model/importers/{_dtdl2rules → _dtdl2data_model}/dtdl_converter.py +22 -22
- cognite/neat/core/_data_model/importers/{_dtdl2rules → _dtdl2data_model}/dtdl_importer.py +7 -7
- cognite/neat/core/_data_model/importers/{_dtdl2rules → _dtdl2data_model}/spec.py +3 -3
- cognite/neat/core/_data_model/importers/_rdf/__init__.py +3 -3
- cognite/neat/core/_data_model/importers/_rdf/_base.py +15 -15
- cognite/neat/core/_data_model/importers/_rdf/{_imf2rules.py → _imf2data_model.py} +17 -17
- cognite/neat/core/_data_model/importers/_rdf/{_inference2rules.py → _inference2rdata_model.py} +59 -59
- cognite/neat/core/_data_model/importers/_rdf/{_owl2rules.py → _owl2data_model.py} +17 -17
- cognite/neat/core/_data_model/importers/_rdf/_shared.py +25 -25
- cognite/neat/core/_data_model/importers/{_spreadsheet2rules.py → _spreadsheet2data_model.py} +76 -19
- cognite/neat/core/_data_model/models/__init__.py +11 -9
- cognite/neat/core/_data_model/models/_base_unverified.py +12 -12
- cognite/neat/core/_data_model/models/_base_verified.py +9 -14
- cognite/neat/core/_data_model/models/_types.py +6 -6
- cognite/neat/core/_data_model/models/conceptual/__init__.py +6 -6
- cognite/neat/core/_data_model/models/conceptual/_unverified.py +20 -20
- cognite/neat/core/_data_model/models/conceptual/_validation.py +88 -78
- cognite/neat/core/_data_model/models/conceptual/_verified.py +54 -52
- cognite/neat/core/_data_model/models/data_types.py +2 -2
- cognite/neat/core/_data_model/models/entities/__init__.py +8 -8
- cognite/neat/core/_data_model/models/entities/_loaders.py +11 -10
- cognite/neat/core/_data_model/models/entities/_multi_value.py +5 -5
- cognite/neat/core/_data_model/models/entities/_single_value.py +44 -38
- cognite/neat/core/_data_model/models/entities/_types.py +9 -3
- cognite/neat/core/_data_model/models/entities/_wrapped.py +3 -3
- cognite/neat/core/_data_model/models/mapping/_classic2core.py +12 -9
- cognite/neat/core/_data_model/models/physical/__init__.py +40 -0
- cognite/neat/core/_data_model/models/{dms → physical}/_exporter.py +75 -55
- cognite/neat/core/_data_model/models/{dms/_rules_input.py → physical/_unverified.py} +48 -39
- cognite/neat/core/_data_model/models/{dms → physical}/_validation.py +17 -15
- cognite/neat/core/_data_model/models/{dms/_rules.py → physical/_verified.py} +68 -60
- cognite/neat/core/_data_model/transformers/__init__.py +29 -25
- cognite/neat/core/_data_model/transformers/_base.py +27 -20
- cognite/neat/core/_data_model/transformers/_converters.py +707 -622
- cognite/neat/core/_data_model/transformers/_mapping.py +74 -55
- cognite/neat/core/_data_model/transformers/_verification.py +64 -55
- cognite/neat/core/_instances/extractors/_base.py +2 -2
- cognite/neat/core/_instances/extractors/_classic_cdf/_classic.py +9 -9
- cognite/neat/core/_instances/extractors/_dms_graph.py +42 -34
- cognite/neat/core/_instances/extractors/_mock_graph_generator.py +107 -103
- cognite/neat/core/_instances/loaders/_base.py +3 -3
- cognite/neat/core/_instances/loaders/_rdf2dms.py +22 -22
- cognite/neat/core/_instances/transformers/_base.py +7 -4
- cognite/neat/core/_instances/transformers/_rdfpath.py +1 -1
- cognite/neat/core/_instances/transformers/_value_type.py +2 -6
- cognite/neat/core/_issues/_base.py +4 -4
- cognite/neat/core/_issues/_factory.py +1 -1
- cognite/neat/core/_issues/errors/__init__.py +2 -2
- cognite/neat/core/_issues/errors/_resources.py +1 -1
- cognite/neat/core/_issues/errors/_wrapper.py +2 -2
- cognite/neat/core/_issues/warnings/_models.py +4 -4
- cognite/neat/core/_issues/warnings/_properties.py +1 -1
- cognite/neat/core/_store/__init__.py +3 -3
- cognite/neat/core/_store/{_rules_store.py → _data_model.py} +119 -112
- cognite/neat/core/_store/{_graph_store.py → _instance.py} +3 -4
- cognite/neat/core/_store/_provenance.py +2 -2
- cognite/neat/core/_store/exceptions.py +2 -2
- cognite/neat/core/_utils/rdf_.py +14 -0
- cognite/neat/core/_utils/text.py +1 -1
- cognite/neat/session/_base.py +42 -36
- cognite/neat/session/_drop.py +2 -2
- cognite/neat/session/_experimental.py +1 -1
- cognite/neat/session/_inspect.py +13 -13
- cognite/neat/session/_mapping.py +15 -9
- cognite/neat/session/_read.py +39 -37
- cognite/neat/session/_set.py +6 -6
- cognite/neat/session/_show.py +24 -21
- cognite/neat/session/_state/README.md +1 -1
- cognite/neat/session/_state.py +27 -27
- cognite/neat/session/_subset.py +14 -11
- cognite/neat/session/_template.py +23 -21
- cognite/neat/session/_to.py +42 -42
- {cognite_neat-0.121.1.dist-info → cognite_neat-0.122.0.dist-info}/METADATA +14 -7
- {cognite_neat-0.121.1.dist-info → cognite_neat-0.122.0.dist-info}/RECORD +102 -100
- cognite/neat/core/_data_model/exporters/_validation.py +0 -14
- cognite/neat/core/_data_model/models/dms/__init__.py +0 -32
- /cognite/neat/core/_data_model/catalog/{info-rules-imf.xlsx → conceptual-imf-data-model.xlsx} +0 -0
- /cognite/neat/core/_data_model/importers/{_dtdl2rules → _dtdl2data_model}/__init__.py +0 -0
- /cognite/neat/core/_data_model/importers/{_dtdl2rules → _dtdl2data_model}/_unit_lookup.py +0 -0
- {cognite_neat-0.121.1.dist-info → cognite_neat-0.122.0.dist-info}/WHEEL +0 -0
- {cognite_neat-0.121.1.dist-info → cognite_neat-0.122.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -10,15 +10,16 @@ from rdflib.collection import Collection as GraphCollection
|
|
|
10
10
|
|
|
11
11
|
from cognite.neat.core._constants import DEFAULT_NAMESPACE as NEAT_NAMESPACE
|
|
12
12
|
from cognite.neat.core._data_model._constants import EntityTypes
|
|
13
|
-
from cognite.neat.core._data_model.analysis import
|
|
13
|
+
from cognite.neat.core._data_model.analysis import DataModelAnalysis
|
|
14
14
|
from cognite.neat.core._data_model.models.conceptual import (
|
|
15
|
-
|
|
15
|
+
Concept,
|
|
16
16
|
ConceptualDataModel,
|
|
17
17
|
ConceptualMetadata,
|
|
18
18
|
ConceptualProperty,
|
|
19
19
|
)
|
|
20
|
+
from cognite.neat.core._data_model.models.conceptual._validation import duplicated_properties
|
|
20
21
|
from cognite.neat.core._data_model.models.data_types import DataType
|
|
21
|
-
from cognite.neat.core._data_model.models.entities import
|
|
22
|
+
from cognite.neat.core._data_model.models.entities import ConceptEntity
|
|
22
23
|
from cognite.neat.core._issues import MultiValueError
|
|
23
24
|
from cognite.neat.core._issues.errors import (
|
|
24
25
|
PropertyDefinitionDuplicatedError,
|
|
@@ -27,7 +28,6 @@ from cognite.neat.core._issues.warnings import PropertyDefinitionDuplicatedWarni
|
|
|
27
28
|
from cognite.neat.core._utils.rdf_ import remove_namespace_from_uri
|
|
28
29
|
|
|
29
30
|
from ._base import BaseExporter
|
|
30
|
-
from ._validation import duplicated_properties
|
|
31
31
|
|
|
32
32
|
if sys.version_info >= (3, 11):
|
|
33
33
|
from typing import Self
|
|
@@ -36,26 +36,26 @@ else:
|
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
class GraphExporter(BaseExporter[ConceptualDataModel, Graph], ABC):
|
|
39
|
-
def export_to_file(self,
|
|
40
|
-
self.export(
|
|
39
|
+
def export_to_file(self, data_model: ConceptualDataModel, filepath: Path) -> None:
|
|
40
|
+
self.export(data_model).serialize(destination=filepath, encoding=self._encoding, newline=self._new_line)
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
class OWLExporter(GraphExporter):
|
|
44
|
-
"""Exports verified
|
|
44
|
+
"""Exports verified conceptual data model to an OWL ontology."""
|
|
45
45
|
|
|
46
|
-
def export(self,
|
|
47
|
-
return Ontology.
|
|
46
|
+
def export(self, data_model: ConceptualDataModel) -> Graph:
|
|
47
|
+
return Ontology.from_data_model(data_model).as_owl()
|
|
48
48
|
|
|
49
49
|
@property
|
|
50
50
|
def description(self) -> str:
|
|
51
|
-
return "Export verified
|
|
51
|
+
return "Export verified conceptual data model to OWL."
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
class SHACLExporter(GraphExporter):
|
|
55
|
-
"""Exports
|
|
55
|
+
"""Exports data_model to a SHACL graph."""
|
|
56
56
|
|
|
57
|
-
def export(self,
|
|
58
|
-
return Ontology.
|
|
57
|
+
def export(self, data_model: ConceptualDataModel) -> Graph:
|
|
58
|
+
return Ontology.from_data_model(data_model).as_shacl()
|
|
59
59
|
|
|
60
60
|
@property
|
|
61
61
|
def description(self) -> str:
|
|
@@ -65,8 +65,8 @@ class SHACLExporter(GraphExporter):
|
|
|
65
65
|
class SemanticDataModelExporter(GraphExporter):
|
|
66
66
|
"""Exports verified information model to a semantic data model."""
|
|
67
67
|
|
|
68
|
-
def export(self,
|
|
69
|
-
return Ontology.
|
|
68
|
+
def export(self, data_model: ConceptualDataModel) -> Graph:
|
|
69
|
+
return Ontology.from_data_model(data_model).as_semantic_data_model()
|
|
70
70
|
|
|
71
71
|
@property
|
|
72
72
|
def description(self) -> str:
|
|
@@ -79,7 +79,7 @@ class OntologyModel(BaseModel):
|
|
|
79
79
|
|
|
80
80
|
class Ontology(OntologyModel):
|
|
81
81
|
"""
|
|
82
|
-
Represents an ontology.
|
|
82
|
+
Represents an ontology. This class is used to generate an OWL ontology from conceptual data model.
|
|
83
83
|
|
|
84
84
|
Args:
|
|
85
85
|
properties: A list of OWL properties.
|
|
@@ -96,23 +96,23 @@ class Ontology(OntologyModel):
|
|
|
96
96
|
prefixes: dict[str, Namespace]
|
|
97
97
|
|
|
98
98
|
@classmethod
|
|
99
|
-
def
|
|
99
|
+
def from_data_model(cls, data_model: ConceptualDataModel) -> Self:
|
|
100
100
|
"""
|
|
101
|
-
Generates an ontology from a set of transformation
|
|
101
|
+
Generates an ontology from a set of transformation data_model.
|
|
102
102
|
|
|
103
103
|
Args:
|
|
104
|
-
|
|
104
|
+
data_model: The data_model to generate the ontology from.
|
|
105
105
|
|
|
106
106
|
Returns:
|
|
107
107
|
An instance of Ontology.
|
|
108
108
|
"""
|
|
109
|
-
if duplicates := duplicated_properties(
|
|
109
|
+
if duplicates := duplicated_properties(data_model.properties):
|
|
110
110
|
errors = []
|
|
111
|
-
for (
|
|
111
|
+
for (concept, property_), definitions in duplicates.items():
|
|
112
112
|
errors.append(
|
|
113
113
|
PropertyDefinitionDuplicatedError(
|
|
114
|
-
|
|
115
|
-
"
|
|
114
|
+
concept,
|
|
115
|
+
"concept",
|
|
116
116
|
property_,
|
|
117
117
|
frozenset({str(definition[1].value_type) for definition in definitions}),
|
|
118
118
|
tuple(definition[0] for definition in definitions),
|
|
@@ -121,35 +121,35 @@ class Ontology(OntologyModel):
|
|
|
121
121
|
)
|
|
122
122
|
raise MultiValueError(errors)
|
|
123
123
|
|
|
124
|
-
analysis =
|
|
125
|
-
|
|
124
|
+
analysis = DataModelAnalysis(data_model)
|
|
125
|
+
concept_by_suffix = analysis.concept_by_suffix()
|
|
126
126
|
return cls(
|
|
127
127
|
properties=[
|
|
128
|
-
OWLProperty.from_list_of_properties(definition,
|
|
128
|
+
OWLProperty.from_list_of_properties(definition, data_model.metadata.namespace)
|
|
129
129
|
for definition in analysis.property_by_id().values()
|
|
130
130
|
],
|
|
131
131
|
classes=[
|
|
132
|
-
OWLClass.
|
|
133
|
-
for definition in
|
|
132
|
+
OWLClass.from_concept(definition, data_model.metadata.namespace, data_model.prefixes)
|
|
133
|
+
for definition in data_model.concepts
|
|
134
134
|
],
|
|
135
135
|
shapes=[
|
|
136
|
-
SHACLNodeShape.
|
|
137
|
-
|
|
136
|
+
SHACLNodeShape.from_data_model(
|
|
137
|
+
concept_by_suffix[str(concept.suffix)],
|
|
138
138
|
list(properties.values()),
|
|
139
|
-
|
|
139
|
+
data_model.metadata.namespace,
|
|
140
140
|
)
|
|
141
|
-
for
|
|
141
|
+
for concept, properties in analysis.properties_by_id_by_concept().items()
|
|
142
142
|
]
|
|
143
143
|
+ [
|
|
144
|
-
SHACLNodeShape.
|
|
145
|
-
|
|
144
|
+
SHACLNodeShape.from_data_model(
|
|
145
|
+
concept,
|
|
146
146
|
[],
|
|
147
|
-
|
|
147
|
+
data_model.metadata.namespace,
|
|
148
148
|
)
|
|
149
|
-
for
|
|
149
|
+
for concept in concept_by_suffix.values()
|
|
150
150
|
],
|
|
151
|
-
metadata=OWLMetadata(**
|
|
152
|
-
prefixes=
|
|
151
|
+
metadata=OWLMetadata(**data_model.metadata.model_dump()),
|
|
152
|
+
prefixes=data_model.prefixes,
|
|
153
153
|
)
|
|
154
154
|
|
|
155
155
|
def as_shacl(self) -> Graph:
|
|
@@ -259,7 +259,7 @@ class OWLClass(OntologyModel):
|
|
|
259
259
|
namespace: Namespace
|
|
260
260
|
|
|
261
261
|
@classmethod
|
|
262
|
-
def
|
|
262
|
+
def from_concept(cls, definition: Concept, namespace: Namespace, prefixes: dict) -> Self:
|
|
263
263
|
if definition.implements and isinstance(definition.implements, list):
|
|
264
264
|
sub_class_of = []
|
|
265
265
|
for parent_class in definition.implements:
|
|
@@ -271,7 +271,7 @@ class OWLClass(OntologyModel):
|
|
|
271
271
|
sub_class_of = None
|
|
272
272
|
|
|
273
273
|
return cls(
|
|
274
|
-
id_=namespace[str(definition.
|
|
274
|
+
id_=namespace[str(definition.concept.suffix)],
|
|
275
275
|
label=definition.name if definition.name else None,
|
|
276
276
|
comment=definition.description,
|
|
277
277
|
sub_class_of=sub_class_of,
|
|
@@ -338,7 +338,7 @@ class OWLProperty(OntologyModel):
|
|
|
338
338
|
property_ids = {definition.property_ for definition in definitions}
|
|
339
339
|
if len(property_ids) != 1:
|
|
340
340
|
raise PropertyDefinitionDuplicatedError(
|
|
341
|
-
definitions[0].
|
|
341
|
+
definitions[0].concept,
|
|
342
342
|
"class",
|
|
343
343
|
definitions[0].property_,
|
|
344
344
|
frozenset(property_ids),
|
|
@@ -358,11 +358,11 @@ class OWLProperty(OntologyModel):
|
|
|
358
358
|
|
|
359
359
|
if isinstance(definition.value_type, DataType):
|
|
360
360
|
owl_property.range_.add(XSD[definition.value_type.xsd])
|
|
361
|
-
elif isinstance(definition.value_type,
|
|
361
|
+
elif isinstance(definition.value_type, ConceptEntity):
|
|
362
362
|
owl_property.range_.add(namespace[str(definition.value_type.suffix)])
|
|
363
363
|
else:
|
|
364
364
|
raise ValueError(f"Value type {definition.value_type.type_} is not supported")
|
|
365
|
-
owl_property.domain.add(namespace[str(definition.
|
|
365
|
+
owl_property.domain.add(namespace[str(definition.concept.suffix)])
|
|
366
366
|
if definition.name:
|
|
367
367
|
owl_property.label.add(definition.name)
|
|
368
368
|
if definition.description:
|
|
@@ -572,19 +572,19 @@ class SHACLNodeShape(OntologyModel):
|
|
|
572
572
|
)
|
|
573
573
|
|
|
574
574
|
@classmethod
|
|
575
|
-
def
|
|
575
|
+
def from_data_model(
|
|
576
576
|
cls,
|
|
577
|
-
|
|
577
|
+
concept_definition: Concept,
|
|
578
578
|
property_definitions: list[ConceptualProperty],
|
|
579
579
|
namespace: Namespace,
|
|
580
580
|
) -> "SHACLNodeShape":
|
|
581
|
-
if
|
|
582
|
-
parent = [namespace[str(parent.suffix) + "Shape"] for parent in
|
|
581
|
+
if concept_definition.implements:
|
|
582
|
+
parent = [namespace[str(parent.suffix) + "Shape"] for parent in concept_definition.implements]
|
|
583
583
|
else:
|
|
584
584
|
parent = None
|
|
585
585
|
return cls(
|
|
586
|
-
id_=namespace[f"{
|
|
587
|
-
target_class=namespace[str(
|
|
586
|
+
id_=namespace[f"{concept_definition.concept.suffix!s}Shape"],
|
|
587
|
+
target_class=namespace[str(concept_definition.concept.suffix)],
|
|
588
588
|
parent=parent,
|
|
589
589
|
property_shapes=[SHACLPropertyShape.from_property(prop, namespace) for prop in property_definitions],
|
|
590
590
|
namespace=namespace,
|
|
@@ -633,7 +633,7 @@ class SHACLPropertyShape(OntologyModel):
|
|
|
633
633
|
@classmethod
|
|
634
634
|
def from_property(cls, definition: ConceptualProperty, namespace: Namespace) -> "SHACLPropertyShape":
|
|
635
635
|
# TODO requires PR to fix MultiValueType and UnknownValueType
|
|
636
|
-
if isinstance(definition.value_type,
|
|
636
|
+
if isinstance(definition.value_type, ConceptEntity):
|
|
637
637
|
expected_value_type = namespace[f"{definition.value_type.suffix}Shape"]
|
|
638
638
|
elif isinstance(definition.value_type, DataType):
|
|
639
639
|
expected_value_type = XSD[definition.value_type.xsd]
|
|
@@ -5,27 +5,28 @@ from typing import Literal, get_args
|
|
|
5
5
|
|
|
6
6
|
import yaml
|
|
7
7
|
|
|
8
|
-
from cognite.neat.core._data_model._shared import
|
|
8
|
+
from cognite.neat.core._data_model._shared import VerifiedDataModel
|
|
9
9
|
|
|
10
10
|
from ._base import BaseExporter
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
class YAMLExporter(BaseExporter[
|
|
14
|
-
"""Export
|
|
13
|
+
class YAMLExporter(BaseExporter[VerifiedDataModel, str]):
|
|
14
|
+
"""Export data_model (Information, DMS or Domain) to YAML.
|
|
15
15
|
|
|
16
16
|
Args:
|
|
17
17
|
files: The number of files to output. Defaults to "single".
|
|
18
|
-
output: The format to output the
|
|
18
|
+
output: The format to output the data_model. Defaults to "yaml".
|
|
19
19
|
|
|
20
20
|
The following formats are available:
|
|
21
21
|
|
|
22
|
-
- "single": A single YAML file will contain the entire
|
|
22
|
+
- "single": A single YAML file will contain the entire data_model.
|
|
23
23
|
|
|
24
24
|
.. note::
|
|
25
25
|
|
|
26
|
-
YAML files are typically used for storing
|
|
27
|
-
The advantage of using YAML files over
|
|
28
|
-
|
|
26
|
+
YAML files are typically used for storing data_model when checked into version
|
|
27
|
+
control systems, e.g., git-history.The advantage of using YAML files over
|
|
28
|
+
Excel is that tools like git can show the differences between different
|
|
29
|
+
versions of the data_model.
|
|
29
30
|
|
|
30
31
|
"""
|
|
31
32
|
|
|
@@ -47,32 +48,34 @@ class YAMLExporter(BaseExporter[VerifiedRules, str]):
|
|
|
47
48
|
def description(self) -> str:
|
|
48
49
|
return "Export verified model to YAML."
|
|
49
50
|
|
|
50
|
-
def export_to_file(self,
|
|
51
|
-
"""Exports transformation
|
|
51
|
+
def export_to_file(self, data_model: VerifiedDataModel, filepath: Path) -> None:
|
|
52
|
+
"""Exports transformation data_model to YAML/JSON file(s)."""
|
|
52
53
|
if self.files == "single":
|
|
53
54
|
if filepath.suffix != f".{self.output}":
|
|
54
55
|
warnings.warn(f"File extension is not .{self.output}, adding it to the file name", stacklevel=2)
|
|
55
56
|
filepath = filepath.with_suffix(f".{self.output}")
|
|
56
|
-
filepath.write_text(self.export(
|
|
57
|
+
filepath.write_text(self.export(data_model), encoding=self._encoding, newline=self._new_line)
|
|
57
58
|
else:
|
|
58
59
|
raise NotImplementedError(f"Exporting to {self.files} files is not supported")
|
|
59
60
|
|
|
60
|
-
def export(self,
|
|
61
|
-
"""Export
|
|
61
|
+
def export(self, data_model: VerifiedDataModel) -> str:
|
|
62
|
+
"""Export data_model to YAML (or JSON) format.
|
|
62
63
|
|
|
63
|
-
This will export the
|
|
64
|
-
|
|
64
|
+
This will export the data_model to YAML format if the output is
|
|
65
|
+
set to "yaml" and JSON format if the output is set.
|
|
66
|
+
All None and Unset values are excluded from the output
|
|
67
|
+
to keep the output clean, i.e., only the values the user
|
|
65
68
|
has set.
|
|
66
69
|
|
|
67
70
|
Args:
|
|
68
|
-
|
|
71
|
+
data_model: The data_model to be exported.
|
|
69
72
|
|
|
70
73
|
Returns:
|
|
71
|
-
str: The
|
|
74
|
+
str: The data_model in YAML (or JSON) format.
|
|
72
75
|
"""
|
|
73
76
|
# model_dump_json ensures that the output is in JSON format,
|
|
74
77
|
# if we don't do this, we will get Enums and other types that are not serializable to YAML
|
|
75
|
-
json_output =
|
|
78
|
+
json_output = data_model.dump(mode="json", sort=True, exclude_none=True, exclude_unset=True)
|
|
76
79
|
if self.output == "json":
|
|
77
80
|
return json.dumps(json_output)
|
|
78
81
|
elif self.output == "yaml":
|
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
from ._base import BaseImporter
|
|
2
|
-
from .
|
|
3
|
-
from .
|
|
2
|
+
from ._dict2data_model import DictImporter
|
|
3
|
+
from ._dms2data_model import DMSImporter
|
|
4
|
+
from ._dtdl2data_model import DTDLImporter
|
|
4
5
|
from ._rdf import IMFImporter, InferenceImporter, OWLImporter, SubclassInferenceImporter
|
|
5
|
-
from .
|
|
6
|
-
from ._yaml2rules import YAMLImporter
|
|
6
|
+
from ._spreadsheet2data_model import ExcelImporter
|
|
7
7
|
|
|
8
8
|
__all__ = [
|
|
9
9
|
"BaseImporter",
|
|
10
10
|
"DMSImporter",
|
|
11
11
|
"DTDLImporter",
|
|
12
|
+
"DictImporter",
|
|
12
13
|
"ExcelImporter",
|
|
13
14
|
"IMFImporter",
|
|
14
15
|
"InferenceImporter",
|
|
15
16
|
"OWLImporter",
|
|
16
17
|
"SubclassInferenceImporter",
|
|
17
|
-
"YAMLImporter",
|
|
18
18
|
]
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
DataModelImporters = (
|
|
21
21
|
OWLImporter
|
|
22
22
|
| IMFImporter
|
|
23
23
|
| DMSImporter
|
|
24
24
|
| ExcelImporter
|
|
25
25
|
| DTDLImporter
|
|
26
|
-
|
|
|
26
|
+
| DictImporter
|
|
27
27
|
| InferenceImporter
|
|
28
28
|
| SubclassInferenceImporter
|
|
29
29
|
)
|
|
@@ -47,5 +47,5 @@ def _repr_html_() -> str:
|
|
|
47
47
|
|
|
48
48
|
return (
|
|
49
49
|
"<strong>Importer</strong> An importer reads data/schema/data model from a source"
|
|
50
|
-
f" and converts it into Neat's representation of a data model
|
|
50
|
+
f" and converts it into Neat's representation of a data model.<br />{table}"
|
|
51
51
|
)
|
|
@@ -6,21 +6,24 @@ from typing import TYPE_CHECKING, Any, Generic
|
|
|
6
6
|
from rdflib import URIRef
|
|
7
7
|
|
|
8
8
|
from cognite.neat.core._constants import DEFAULT_NAMESPACE
|
|
9
|
-
from cognite.neat.core._data_model._shared import
|
|
9
|
+
from cognite.neat.core._data_model._shared import (
|
|
10
|
+
ImportedDataModel,
|
|
11
|
+
T_UnverifiedDataModel,
|
|
12
|
+
)
|
|
10
13
|
from cognite.neat.core._utils.auxiliary import class_html_doc
|
|
11
14
|
|
|
12
15
|
if TYPE_CHECKING:
|
|
13
16
|
from cognite.neat.core._store._provenance import Agent as ProvenanceAgent
|
|
14
17
|
|
|
15
18
|
|
|
16
|
-
class BaseImporter(ABC, Generic[
|
|
19
|
+
class BaseImporter(ABC, Generic[T_UnverifiedDataModel]):
|
|
17
20
|
"""
|
|
18
|
-
BaseImporter class which all importers inherit from.
|
|
21
|
+
BaseImporter class which all data model importers inherit from.
|
|
19
22
|
"""
|
|
20
23
|
|
|
21
24
|
@abstractmethod
|
|
22
|
-
def
|
|
23
|
-
"""Creates `
|
|
25
|
+
def to_data_model(self) -> ImportedDataModel[T_UnverifiedDataModel]:
|
|
26
|
+
"""Creates `DataModel` object from the data for target role."""
|
|
24
27
|
raise NotImplementedError()
|
|
25
28
|
|
|
26
29
|
def _default_metadata(self) -> dict[str, Any]:
|
|
@@ -31,7 +34,6 @@ class BaseImporter(ABC, Generic[T_InputRules]):
|
|
|
31
34
|
creator = getpass.getuser()
|
|
32
35
|
|
|
33
36
|
return {
|
|
34
|
-
"schema": "partial",
|
|
35
37
|
"space": "neat",
|
|
36
38
|
"external_id": "NeatImportedDataModel",
|
|
37
39
|
"version": "0.1.0",
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Protocol, TypeVar
|
|
3
|
+
|
|
4
|
+
from cognite.neat.core._issues import NeatIssue
|
|
5
|
+
from cognite.neat.core._issues.errors import (
|
|
6
|
+
FileNotAFileError,
|
|
7
|
+
FileNotFoundNeatError,
|
|
8
|
+
FileTypeUnexpectedError,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
T = TypeVar("T")
|
|
12
|
+
DataT = TypeVar("DataT", covariant=True)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FileReader(Protocol[DataT]):
|
|
16
|
+
"""Protocol for file readers that parse files into structured data."""
|
|
17
|
+
|
|
18
|
+
def read_file(self, filepath: Path) -> tuple[DataT, list[NeatIssue]]:
|
|
19
|
+
"""Read a file and return the data with any issues encountered.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
filepath: Path to the file to read
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Tuple of (parsed_data, issues_list)
|
|
26
|
+
"""
|
|
27
|
+
...
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class BaseFileReader:
|
|
31
|
+
"""Base implementation with common file validation logic."""
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def validate_file(filepath: Path, allowed_extensions: frozenset[str]) -> list[NeatIssue]:
|
|
35
|
+
"""Validate that a file exists and has the correct extension.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
filepath: Path to the file to validate
|
|
39
|
+
allowed_extensions: Set of allowed file extensions
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
List of issues found during validation, empty if valid
|
|
43
|
+
"""
|
|
44
|
+
# Check if file exists
|
|
45
|
+
if not filepath.exists():
|
|
46
|
+
return [FileNotFoundNeatError(filepath)]
|
|
47
|
+
|
|
48
|
+
# Check if it's a file
|
|
49
|
+
if not filepath.is_file():
|
|
50
|
+
return [FileNotAFileError(filepath)]
|
|
51
|
+
|
|
52
|
+
# Check file extension
|
|
53
|
+
if filepath.suffix not in allowed_extensions:
|
|
54
|
+
return [FileTypeUnexpectedError(filepath, allowed_extensions)]
|
|
55
|
+
|
|
56
|
+
return []
|
|
@@ -3,32 +3,62 @@ from typing import Any, cast
|
|
|
3
3
|
|
|
4
4
|
import yaml
|
|
5
5
|
|
|
6
|
-
from cognite.neat.core._data_model._shared import
|
|
7
|
-
|
|
6
|
+
from cognite.neat.core._data_model._shared import (
|
|
7
|
+
ImportedDataModel,
|
|
8
|
+
T_UnverifiedDataModel,
|
|
9
|
+
)
|
|
10
|
+
from cognite.neat.core._data_model.models import (
|
|
11
|
+
UNVERIFIED_DATA_MODEL_BY_ROLE,
|
|
12
|
+
RoleTypes,
|
|
13
|
+
)
|
|
8
14
|
from cognite.neat.core._issues import IssueList, MultiValueError, NeatIssue
|
|
9
15
|
from cognite.neat.core._issues.errors import (
|
|
10
16
|
FileMissingRequiredFieldError,
|
|
11
|
-
FileNotAFileError,
|
|
12
|
-
FileNotFoundNeatError,
|
|
13
17
|
FileReadError,
|
|
14
|
-
FileTypeUnexpectedError,
|
|
15
18
|
)
|
|
16
19
|
from cognite.neat.core._issues.warnings import NeatValueWarning
|
|
17
20
|
|
|
18
21
|
from ._base import BaseImporter
|
|
22
|
+
from ._base_file_reader import BaseFileReader
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class YAMLReader:
|
|
26
|
+
"""Handles reading and parsing YAML files with error handling."""
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def read_file(
|
|
30
|
+
filepath: Path, allowed_extensions: frozenset[str] = frozenset([".yaml", ".yml"])
|
|
31
|
+
) -> tuple[dict[str, Any], list[NeatIssue]]:
|
|
32
|
+
"""Read a YAML file and return the data with any issues encountered."""
|
|
33
|
+
issues = BaseFileReader.validate_file(filepath, allowed_extensions)
|
|
34
|
+
if issues:
|
|
35
|
+
return {}, issues
|
|
36
|
+
# Try to load the YAML
|
|
37
|
+
try:
|
|
38
|
+
data = yaml.safe_load(filepath.read_text())
|
|
39
|
+
if not isinstance(data, dict):
|
|
40
|
+
issues.append(FileReadError(filepath, "YAML content is not a dictionary"))
|
|
41
|
+
return {}, issues
|
|
42
|
+
return data, issues
|
|
43
|
+
except yaml.YAMLError as exc:
|
|
44
|
+
return {}, [FileReadError(filepath, f"Invalid YAML: {exc!s}")]
|
|
45
|
+
except Exception as exc:
|
|
46
|
+
return {}, [FileReadError(filepath, f"Error reading file: {exc!s}")]
|
|
19
47
|
|
|
20
48
|
|
|
21
|
-
class
|
|
22
|
-
"""Imports the
|
|
49
|
+
class DictImporter(BaseImporter[T_UnverifiedDataModel]):
|
|
50
|
+
"""Imports the data model from a YAML file.
|
|
23
51
|
|
|
24
52
|
Args:
|
|
25
53
|
raw_data: The raw data to be imported.
|
|
26
54
|
|
|
27
55
|
.. note::
|
|
28
56
|
|
|
29
|
-
YAML files are typically used for storing
|
|
30
|
-
|
|
31
|
-
|
|
57
|
+
YAML files are typically used for storing data model when checked into version
|
|
58
|
+
control systems, e.g., git-history.
|
|
59
|
+
The advantage of using YAML files over Excel is that tools like git can
|
|
60
|
+
show the differences between different
|
|
61
|
+
versions of the data model.
|
|
32
62
|
|
|
33
63
|
"""
|
|
34
64
|
|
|
@@ -49,21 +79,16 @@ class YAMLImporter(BaseImporter[T_InputRules]):
|
|
|
49
79
|
return f"YAML file {self._source_name} read as unverified data model"
|
|
50
80
|
|
|
51
81
|
@classmethod
|
|
52
|
-
def
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return cls({}, [FileTypeUnexpectedError(filepath, frozenset([".yaml", ".yml"]))])
|
|
59
|
-
try:
|
|
60
|
-
data = yaml.safe_load(filepath.read_text())
|
|
61
|
-
except yaml.YAMLError as exc:
|
|
62
|
-
return cls({}, [FileReadError(filepath, f"Invalid YAML: {exc!s}")])
|
|
82
|
+
def from_yaml_file(cls, filepath: Path, source_name: str = "Unknown") -> "DictImporter":
|
|
83
|
+
"""Create a DictImporter from a YAML file."""
|
|
84
|
+
data, issues = YAMLReader.read_file(filepath)
|
|
85
|
+
|
|
86
|
+
if issues:
|
|
87
|
+
return cls({}, issues)
|
|
63
88
|
|
|
64
89
|
return cls(data, filepaths=[filepath], source_name=source_name)
|
|
65
90
|
|
|
66
|
-
def
|
|
91
|
+
def to_data_model(self) -> ImportedDataModel[T_UnverifiedDataModel]:
|
|
67
92
|
if self._read_issues.has_errors or not self.raw_data:
|
|
68
93
|
self._read_issues.trigger_warnings()
|
|
69
94
|
raise MultiValueError(self._read_issues.errors)
|
|
@@ -95,12 +120,12 @@ class YAMLImporter(BaseImporter[T_InputRules]):
|
|
|
95
120
|
|
|
96
121
|
role_input = RoleTypes(metadata["role"])
|
|
97
122
|
role_enum = RoleTypes(role_input)
|
|
98
|
-
|
|
123
|
+
data_model_cls = UNVERIFIED_DATA_MODEL_BY_ROLE[role_enum]
|
|
99
124
|
|
|
100
|
-
|
|
125
|
+
data_model = cast(T_UnverifiedDataModel, data_model_cls.load(self.raw_data))
|
|
101
126
|
|
|
102
127
|
issue_list.trigger_warnings()
|
|
103
128
|
if self._read_issues.has_errors:
|
|
104
129
|
raise MultiValueError(self._read_issues.errors)
|
|
105
130
|
|
|
106
|
-
return
|
|
131
|
+
return ImportedDataModel[T_UnverifiedDataModel](data_model, {})
|