cognite-neat 0.88.3__py3-none-any.whl → 0.89.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 (75) hide show
  1. cognite/neat/_version.py +1 -1
  2. cognite/neat/constants.py +3 -0
  3. cognite/neat/graph/extractors/_mock_graph_generator.py +2 -1
  4. cognite/neat/issues/_base.py +2 -1
  5. cognite/neat/issues/errors/__init__.py +2 -1
  6. cognite/neat/issues/errors/_general.py +7 -0
  7. cognite/neat/issues/warnings/_models.py +1 -1
  8. cognite/neat/issues/warnings/user_modeling.py +1 -1
  9. cognite/neat/rules/_shared.py +49 -6
  10. cognite/neat/rules/analysis/_base.py +1 -1
  11. cognite/neat/rules/exporters/_base.py +7 -18
  12. cognite/neat/rules/exporters/_rules2dms.py +8 -18
  13. cognite/neat/rules/exporters/_rules2excel.py +5 -12
  14. cognite/neat/rules/exporters/_rules2ontology.py +9 -19
  15. cognite/neat/rules/exporters/_rules2yaml.py +3 -6
  16. cognite/neat/rules/importers/_base.py +7 -52
  17. cognite/neat/rules/importers/_dms2rules.py +171 -115
  18. cognite/neat/rules/importers/_dtdl2rules/dtdl_converter.py +26 -18
  19. cognite/neat/rules/importers/_dtdl2rules/dtdl_importer.py +14 -30
  20. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2classes.py +7 -3
  21. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2metadata.py +3 -3
  22. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2properties.py +18 -11
  23. cognite/neat/rules/importers/_rdf/_imf2rules/_imf2rules.py +9 -18
  24. cognite/neat/rules/importers/_rdf/_inference2rules.py +10 -33
  25. cognite/neat/rules/importers/_rdf/_owl2rules/_owl2rules.py +9 -20
  26. cognite/neat/rules/importers/_rdf/_shared.py +1 -1
  27. cognite/neat/rules/importers/_spreadsheet2rules.py +22 -86
  28. cognite/neat/rules/importers/_yaml2rules.py +14 -41
  29. cognite/neat/rules/models/__init__.py +21 -5
  30. cognite/neat/rules/models/_base_input.py +162 -0
  31. cognite/neat/rules/models/{_base.py → _base_rules.py} +1 -12
  32. cognite/neat/rules/models/asset/__init__.py +5 -2
  33. cognite/neat/rules/models/asset/_rules.py +2 -20
  34. cognite/neat/rules/models/asset/_rules_input.py +40 -115
  35. cognite/neat/rules/models/asset/_validation.py +1 -1
  36. cognite/neat/rules/models/data_types.py +150 -44
  37. cognite/neat/rules/models/dms/__init__.py +19 -7
  38. cognite/neat/rules/models/dms/_exporter.py +72 -26
  39. cognite/neat/rules/models/dms/_rules.py +42 -155
  40. cognite/neat/rules/models/dms/_rules_input.py +186 -254
  41. cognite/neat/rules/models/dms/_serializer.py +44 -3
  42. cognite/neat/rules/models/dms/_validation.py +3 -4
  43. cognite/neat/rules/models/domain.py +52 -1
  44. cognite/neat/rules/models/entities/__init__.py +63 -0
  45. cognite/neat/rules/models/entities/_constants.py +73 -0
  46. cognite/neat/rules/models/entities/_loaders.py +76 -0
  47. cognite/neat/rules/models/entities/_multi_value.py +67 -0
  48. cognite/neat/rules/models/{entities.py → entities/_single_value.py} +74 -232
  49. cognite/neat/rules/models/entities/_types.py +86 -0
  50. cognite/neat/rules/models/{wrapped_entities.py → entities/_wrapped.py} +1 -1
  51. cognite/neat/rules/models/information/__init__.py +10 -2
  52. cognite/neat/rules/models/information/_rules.py +3 -14
  53. cognite/neat/rules/models/information/_rules_input.py +57 -204
  54. cognite/neat/rules/models/information/_validation.py +1 -1
  55. cognite/neat/rules/transformers/__init__.py +21 -0
  56. cognite/neat/rules/transformers/_base.py +69 -3
  57. cognite/neat/rules/{models/information/_converter.py → transformers/_converters.py} +216 -20
  58. cognite/neat/rules/transformers/_map_onto.py +97 -0
  59. cognite/neat/rules/transformers/_pipelines.py +61 -0
  60. cognite/neat/rules/transformers/_verification.py +136 -0
  61. cognite/neat/store/_provenance.py +10 -1
  62. cognite/neat/utils/cdf/data_classes.py +20 -0
  63. cognite/neat/utils/regex_patterns.py +6 -0
  64. cognite/neat/workflows/steps/lib/current/rules_exporter.py +106 -37
  65. cognite/neat/workflows/steps/lib/current/rules_importer.py +24 -22
  66. {cognite_neat-0.88.3.dist-info → cognite_neat-0.89.0.dist-info}/METADATA +1 -1
  67. {cognite_neat-0.88.3.dist-info → cognite_neat-0.89.0.dist-info}/RECORD +71 -66
  68. cognite/neat/rules/models/_constants.py +0 -2
  69. cognite/neat/rules/models/_types/__init__.py +0 -19
  70. cognite/neat/rules/models/asset/_converter.py +0 -4
  71. cognite/neat/rules/models/dms/_converter.py +0 -143
  72. /cognite/neat/rules/models/{_types/_field.py → _types.py} +0 -0
  73. {cognite_neat-0.88.3.dist-info → cognite_neat-0.89.0.dist-info}/LICENSE +0 -0
  74. {cognite_neat-0.88.3.dist-info → cognite_neat-0.89.0.dist-info}/WHEEL +0 -0
  75. {cognite_neat-0.88.3.dist-info → cognite_neat-0.89.0.dist-info}/entry_points.txt +0 -0
@@ -37,16 +37,20 @@ def parse_imf_to_classes(graph: Graph, language: str = "en") -> list[dict]:
37
37
  query = """
38
38
  SELECT ?class ?name ?description ?parentClass ?reference ?match ?comment
39
39
  WHERE {
40
- #Finding IMF - elements
40
+ # Finding IMF - elements
41
41
  VALUES ?type { imf:BlockType imf:TerminalType imf:AttributeType }
42
42
  ?imfClass a ?type .
43
43
  OPTIONAL {?imfClass rdfs:subClassOf ?parent }.
44
44
  OPTIONAL {?imfClass rdfs:label | skos:prefLabel ?name }.
45
- OPTIONAL {?imfClass rdfs:comment | skos:description ?description} .
45
+
46
+ # Note: Bug in PCA has lead to the use non-existing term skos:description. This will be replaced
47
+ # with the correct skos:definition in the near future, so both terms are included here.
48
+ OPTIONAL {?imfClass rdfs:comment | skos:definition | skos:description ?description} .
46
49
 
47
50
  # Finding the last segment of the class IRI
48
51
  BIND(STR(?imfClass) AS ?classString)
49
- BIND(REPLACE(?classString, "^.*[/#]([^/#]*)$", "$1") AS ?classSegment)
52
+ BIND(REPLACE(?classString, "^.*[/#]([^/#]*)$", "$1") AS ?tempSegment)
53
+ BIND(REPLACE(?tempSegment, "-", "_") AS ?classSegment)
50
54
  BIND(IF(CONTAINS(?classString, "imf/"), CONCAT("IMF_", ?classSegment) , ?classSegment) AS ?class)
51
55
 
52
56
  # Add imf:Attribute as parent class
@@ -19,12 +19,12 @@ def parse_imf_metadata() -> dict:
19
19
  raw_metadata = {
20
20
  "role": RoleTypes.information,
21
21
  "schema": SchemaCompleteness.partial,
22
- "prefix": "pca-imf",
23
- "namespace": Namespace("http://posccaesar.org/imf/"),
22
+ "prefix": "pcaimf",
23
+ "namespace": Namespace("https://posccaesar.org/imf/"),
24
24
  "version": None,
25
25
  "created": None,
26
26
  "updated": None,
27
- "title": "IMF - types",
27
+ "title": "IMF_types",
28
28
  "description": "IMF - types",
29
29
  "creator": None,
30
30
  "rights": None,
@@ -36,7 +36,7 @@ def parse_imf_to_properties(graph: Graph, language: str = "en") -> list[dict]:
36
36
  """
37
37
 
38
38
  query = """
39
- SELECT ?class ?property ?name ?description ?valueType ?minCount ?maxCount ?default ?reference
39
+ SELECT DISTINCT ?class ?property ?name ?description ?valueType ?minCount ?maxCount ?default ?reference
40
40
  ?match ?comment ?propertyType
41
41
  WHERE
42
42
  {
@@ -48,7 +48,7 @@ def parse_imf_to_properties(graph: Graph, language: str = "en") -> list[dict]:
48
48
  ?propertyShape sh:path ?imfProperty .
49
49
 
50
50
  OPTIONAL { ?imfProperty skos:prefLabel ?name . }
51
- OPTIONAL { ?imfProperty skos:description ?description . }
51
+ OPTIONAL { ?imfProperty skos:definition | skos:description ?description . }
52
52
  OPTIONAL { ?imfProperty rdfs:range ?range . }
53
53
  OPTIONAL { ?imfProperty a ?type . }
54
54
  OPTIONAL { ?propertyShape sh:minCount ?minCardinality} .
@@ -62,22 +62,24 @@ def parse_imf_to_properties(graph: Graph, language: str = "en") -> list[dict]:
62
62
  ?imfClass a imf:AttributeType ;
63
63
  ?imfProperty ?default .
64
64
 
65
- # imf:predicate is required
66
- BIND(IF(?imfProperty = <http://ns.imfid.org/imf#predicate>, 1, 0) AS ?minCardinality)
67
-
68
65
  # The following information is used to describe the attribute when it is connected to a block or a terminal
69
66
  # and not duplicated here.
70
- FILTER(?imfProperty != rdf:type && ?imfProperty != skos:prefLabel && ?imfProperty != skos:description)
67
+ # Note: Bug in PCA has lead to the use non-existing term skos:description. This will be replaced
68
+ # with the correct skos:definition in the near future, so both terms are included here.
69
+ FILTER(?imfProperty != rdf:type && ?imfProperty != skos:prefLabel &&
70
+ ?imfProperty != skos:defintion && ?imfProperty != skos:description)
71
71
  }
72
72
 
73
73
  # Finding the last segment of the class IRI
74
74
  BIND(STR(?imfClass) AS ?classString)
75
- BIND(REPLACE(?classString, "^.*[/#]([^/#]*)$", "$1") AS ?classSegment)
75
+ BIND(REPLACE(?classString, "^.*[/#]([^/#]*)$", "$1") AS ?tempClassSegment)
76
+ BIND(REPLACE(?tempClassSegment, "-", "_") AS ?classSegment)
76
77
  BIND(IF(CONTAINS(?classString, "imf/"), CONCAT("IMF_", ?classSegment) , ?classSegment) AS ?class)
77
78
 
78
79
  # Finding the last segment of the property IRI
79
80
  BIND(STR(?imfProperty) AS ?propertyString)
80
- BIND(REPLACE(?propertyString, "^.*[/#]([^/#]*)$", "$1") AS ?propertySegment)
81
+ BIND(REPLACE(?propertyString, "^.*[/#]([^/#]*)$", "$1") AS ?tempPropertySegment)
82
+ BIND(REPLACE(?tempPropertySegment, "-", "_") AS ?propertySegment)
81
83
  BIND(IF(CONTAINS(?propertyString, "imf/"), CONCAT("IMF_", ?propertySegment) , ?propertySegment) AS ?property)
82
84
 
83
85
  # Set the value type for the property based on sh:class, sh:qualifiedValueType or rdfs:range
@@ -85,12 +87,17 @@ def parse_imf_to_properties(graph: Graph, language: str = "en") -> list[dict]:
85
87
 
86
88
  # Finding the last segment of value types
87
89
  BIND(STR(?valueIriType) AS ?valueTypeString)
88
- BIND(REPLACE(?valueTypeString, "^.*[/#]([^/#]*)$", "$1") AS ?valueTypeSegment)
90
+ BIND(REPLACE(?valueTypeString, "^.*[/#]([^/#]*)$", "$1") AS ?tempValueTypeSegment)
91
+ BIND(REPLACE(?tempValueTypeSegment, "-", "_") AS ?valueTypeSegment)
89
92
  BIND(IF(CONTAINS(?valueTypeString, "imf/"), CONCAT("IMF_", ?valueTypeSegment) , ?valueTypeSegment)
90
93
  AS ?valueType)
91
94
 
92
- # Helper variable to set property type if value type is not already set.
93
- BIND(IF(BOUND(?type) && ?type = owl:DatatypeProperty, ?type , owl:ObjectProperty) AS ?propertyType)
95
+ # Helper variable to set owl datatype- or object-property if this is not already set.
96
+ BIND(IF( EXISTS {?imfProperty a ?tempPropertyType . FILTER(?tempPropertyType = owl:DatatypeProperty) },
97
+ owl:DatatypeProperty,
98
+ owl:ObjectProperty
99
+ )
100
+ AS ?propertyType)
94
101
 
95
102
  # Assert cardinality values if they do not exist
96
103
  BIND(IF(BOUND(?minCardinality), ?minCardinality, 0) AS ?minCount)
@@ -2,13 +2,14 @@
2
2
  there are loaders to TransformationRules pydantic class."""
3
3
 
4
4
  from pathlib import Path
5
- from typing import Literal, overload
6
5
 
7
6
  from rdflib import DC, DCTERMS, OWL, RDF, RDFS, SH, SKOS, Graph
8
7
 
9
8
  from cognite.neat.issues import IssueList
10
- from cognite.neat.rules.importers._base import BaseImporter, VerifiedRules
11
- from cognite.neat.rules.models import InformationRules, RoleTypes
9
+ from cognite.neat.issues.errors import FileReadError
10
+ from cognite.neat.rules._shared import ReadRules
11
+ from cognite.neat.rules.importers._base import BaseImporter
12
+ from cognite.neat.rules.models import InformationInputRules
12
13
  from cognite.neat.rules.models.data_types import _XSD_TYPES
13
14
 
14
15
  from ._imf2classes import parse_imf_to_classes
@@ -16,7 +17,7 @@ from ._imf2metadata import parse_imf_metadata
16
17
  from ._imf2properties import parse_imf_to_properties
17
18
 
18
19
 
19
- class IMFImporter(BaseImporter):
20
+ class IMFImporter(BaseImporter[InformationInputRules]):
20
21
  """Convert SHACL shapes to tables/ transformation rules / Excel file.
21
22
 
22
23
  Args:
@@ -38,24 +39,14 @@ class IMFImporter(BaseImporter):
38
39
  def __init__(self, filepath: Path):
39
40
  self.filepath = filepath
40
41
 
41
- @overload
42
- def to_rules(self, errors: Literal["raise"], role: RoleTypes | None = None) -> VerifiedRules: ...
43
-
44
- @overload
45
- def to_rules(
46
- self, errors: Literal["continue"] = "continue", role: RoleTypes | None = None
47
- ) -> tuple[VerifiedRules | None, IssueList]: ...
48
-
49
42
  def to_rules(
50
43
  self,
51
- errors: Literal["raise", "continue"] = "continue",
52
- role: RoleTypes | None = None,
53
- ) -> tuple[VerifiedRules | None, IssueList] | VerifiedRules:
44
+ ) -> ReadRules[InformationInputRules]:
54
45
  graph = Graph()
55
46
  try:
56
47
  graph.parse(self.filepath)
57
48
  except Exception as e:
58
- raise Exception(f"Could not parse owl file: {e}") from e
49
+ return ReadRules(None, IssueList([FileReadError(self.filepath, f"Could not parse owl file: {e}")]), {})
59
50
 
60
51
  # bind key namespaces
61
52
  graph.bind("owl", OWL)
@@ -76,8 +67,8 @@ class IMFImporter(BaseImporter):
76
67
 
77
68
  components = make_components_compliant(components)
78
69
 
79
- rules = InformationRules.model_validate(components)
80
- return self._to_output(rules, IssueList(), errors, role)
70
+ rules = InformationInputRules.load(components)
71
+ return ReadRules(rules, IssueList(), {})
81
72
 
82
73
 
83
74
  def make_components_compliant(components: dict) -> dict:
@@ -1,7 +1,7 @@
1
1
  from collections import Counter, defaultdict
2
2
  from datetime import datetime
3
3
  from pathlib import Path
4
- from typing import Literal, cast, overload
4
+ from typing import cast
5
5
 
6
6
  from rdflib import Graph, Namespace, URIRef
7
7
  from rdflib import Literal as RdfLiteral
@@ -9,12 +9,12 @@ from rdflib import Literal as RdfLiteral
9
9
  from cognite.neat.constants import DEFAULT_NAMESPACE, get_default_prefixes
10
10
  from cognite.neat.issues import IssueList
11
11
  from cognite.neat.issues.errors import FileReadError
12
- from cognite.neat.rules.importers._base import BaseImporter, VerifiedRules, _handle_issues
13
- from cognite.neat.rules.models import InformationRules, RoleTypes
14
- from cognite.neat.rules.models._base import MatchType
12
+ from cognite.neat.rules._shared import ReadRules
13
+ from cognite.neat.rules.importers._base import BaseImporter
14
+ from cognite.neat.rules.models._base_rules import MatchType
15
15
  from cognite.neat.rules.models.information import (
16
+ InformationInputRules,
16
17
  InformationMetadata,
17
- InformationRulesInput,
18
18
  )
19
19
  from cognite.neat.store import NeatGraphStore
20
20
  from cognite.neat.utils.rdf_ import get_namespace, remove_namespace_from_uri, uri_to_short_form
@@ -44,7 +44,7 @@ INSTANCE_PROPERTIES_DEFINITION = """SELECT ?property (count(?property) as ?occur
44
44
  GROUP BY ?property ?dataType ?objectType"""
45
45
 
46
46
 
47
- class InferenceImporter(BaseImporter):
47
+ class InferenceImporter(BaseImporter[InformationInputRules]):
48
48
  """Infers rules from a triple store.
49
49
 
50
50
  Rules inference through analysis of knowledge graph provided in various formats.
@@ -147,44 +147,21 @@ class InferenceImporter(BaseImporter):
147
147
  ) -> "InferenceImporter":
148
148
  raise NotImplementedError("JSON file format is not supported yet.")
149
149
 
150
- @overload
151
- def to_rules(self, errors: Literal["raise"], role: RoleTypes | None = None) -> VerifiedRules: ...
152
-
153
- @overload
154
- def to_rules(
155
- self,
156
- errors: Literal["continue"] = "continue",
157
- role: RoleTypes | None = None,
158
- ) -> tuple[VerifiedRules | None, IssueList]: ...
159
-
160
150
  def to_rules(
161
151
  self,
162
- errors: Literal["raise", "continue"] = "continue",
163
- role: RoleTypes | None = None,
164
- ) -> tuple[VerifiedRules | None, IssueList] | VerifiedRules:
152
+ ) -> ReadRules[InformationInputRules]:
165
153
  """
166
154
  Creates `Rules` object from the data for target role.
167
155
  """
168
156
 
169
157
  if self.issue_list.has_errors:
170
158
  # In case there were errors during the import, the to_rules method will return None
171
- return self._return_or_raise(self.issue_list, errors)
159
+ return ReadRules(None, self.issue_list, {})
172
160
 
173
161
  rules_dict = self._to_rules_components()
174
162
 
175
- with _handle_issues(self.issue_list) as future:
176
- rules: InformationRules
177
- rules = InformationRulesInput.load(rules_dict).as_rules()
178
-
179
- if future.result == "failure" or self.issue_list.has_errors:
180
- return self._return_or_raise(self.issue_list, errors)
181
-
182
- return self._to_output(
183
- rules,
184
- self.issue_list,
185
- errors=errors,
186
- role=role,
187
- )
163
+ rules = InformationInputRules.load(rules_dict)
164
+ return ReadRules(rules, self.issue_list, {})
188
165
 
189
166
  def _to_rules_components(
190
167
  self,
@@ -2,21 +2,22 @@
2
2
  there are loaders to TransformationRules pydantic class."""
3
3
 
4
4
  from pathlib import Path
5
- from typing import Literal, overload
6
5
 
7
6
  from rdflib import DC, DCTERMS, OWL, RDF, RDFS, SKOS, Graph
8
7
 
9
8
  from cognite.neat.issues import IssueList
10
- from cognite.neat.rules.importers._base import BaseImporter, VerifiedRules
9
+ from cognite.neat.issues.errors import FileReadError
10
+ from cognite.neat.rules._shared import ReadRules
11
+ from cognite.neat.rules.importers._base import BaseImporter
11
12
  from cognite.neat.rules.importers._rdf._shared import make_components_compliant
12
- from cognite.neat.rules.models import InformationRules, RoleTypes
13
+ from cognite.neat.rules.models import InformationInputRules
13
14
 
14
15
  from ._owl2classes import parse_owl_classes
15
16
  from ._owl2metadata import parse_owl_metadata
16
17
  from ._owl2properties import parse_owl_properties
17
18
 
18
19
 
19
- class OWLImporter(BaseImporter):
20
+ class OWLImporter(BaseImporter[InformationInputRules]):
20
21
  """Convert OWL ontology to tables/ transformation rules / Excel file.
21
22
 
22
23
  Args:
@@ -37,24 +38,12 @@ class OWLImporter(BaseImporter):
37
38
  def __init__(self, filepath: Path):
38
39
  self.owl_filepath = filepath
39
40
 
40
- @overload
41
- def to_rules(self, errors: Literal["raise"], role: RoleTypes | None = None) -> VerifiedRules: ...
42
-
43
- @overload
44
- def to_rules(
45
- self, errors: Literal["continue"] = "continue", role: RoleTypes | None = None
46
- ) -> tuple[VerifiedRules | None, IssueList]: ...
47
-
48
- def to_rules(
49
- self,
50
- errors: Literal["raise", "continue"] = "continue",
51
- role: RoleTypes | None = None,
52
- ) -> tuple[VerifiedRules | None, IssueList] | VerifiedRules:
41
+ def to_rules(self) -> ReadRules[InformationInputRules]:
53
42
  graph = Graph()
54
43
  try:
55
44
  graph.parse(self.owl_filepath)
56
45
  except Exception as e:
57
- raise Exception(f"Could not parse owl file: {e}") from e
46
+ return ReadRules(None, IssueList([FileReadError(self.owl_filepath, f"Could not parse owl file: {e}")]), {})
58
47
 
59
48
  # bind key namespaces
60
49
  graph.bind("owl", OWL)
@@ -72,5 +61,5 @@ class OWLImporter(BaseImporter):
72
61
 
73
62
  components = make_components_compliant(components)
74
63
 
75
- rules = InformationRules.model_validate(components)
76
- return self._to_output(rules, IssueList(), errors, role)
64
+ rules = InformationInputRules.load(components)
65
+ return ReadRules(rules, IssueList(), {})
@@ -4,7 +4,7 @@ import numpy as np
4
4
  import pandas as pd
5
5
  from rdflib import OWL, Literal, Namespace
6
6
 
7
- from cognite.neat.rules.models._base import MatchType
7
+ from cognite.neat.rules.models._base_rules import MatchType
8
8
  from cognite.neat.rules.models.data_types import _XSD_TYPES
9
9
  from cognite.neat.utils.rdf_ import remove_namespace_from_uri
10
10
  from cognite.neat.utils.regex_patterns import PATTERNS
@@ -6,35 +6,30 @@ generating a list of rules based on which nodes that form the graph are made.
6
6
  from collections import UserDict, defaultdict
7
7
  from dataclasses import dataclass
8
8
  from pathlib import Path
9
- from typing import Literal, cast, overload
9
+ from typing import Literal, cast
10
10
 
11
11
  import pandas as pd
12
+ from cognite.client.utils._importing import local_import
12
13
  from pandas import ExcelFile
13
14
 
14
- from cognite.neat.issues import IssueList, NeatError
15
+ from cognite.neat.issues import IssueList
15
16
  from cognite.neat.issues.errors import (
16
17
  FileMissingRequiredFieldError,
17
18
  FileNotFoundNeatError,
18
19
  FileReadError,
19
20
  PropertyDefinitionDuplicatedError,
20
21
  )
22
+ from cognite.neat.rules._shared import ReadRules, T_InputRules
21
23
  from cognite.neat.rules.models import (
22
- RULES_PER_ROLE,
23
- AssetRules,
24
- DMSRules,
25
- DomainRules,
26
- InformationRules,
24
+ INPUT_RULES_BY_ROLE,
25
+ VERIFIED_RULES_BY_ROLE,
27
26
  RoleTypes,
28
27
  SchemaCompleteness,
29
28
  )
30
- from cognite.neat.rules.models.asset import AssetRulesInput
31
- from cognite.neat.rules.models.dms import DMSRulesInput
32
- from cognite.neat.rules.models.information import InformationRulesInput
33
- from cognite.neat.utils.auxiliary import local_import
34
29
  from cognite.neat.utils.spreadsheet import SpreadsheetRead, read_individual_sheet
35
30
  from cognite.neat.utils.text import humanize_collection
36
31
 
37
- from ._base import BaseImporter, VerifiedRules, _handle_issues
32
+ from ._base import BaseImporter
38
33
 
39
34
  SOURCE_SHEET__TARGET_FIELD__HEADERS = [
40
35
  (
@@ -53,7 +48,7 @@ SOURCE_SHEET__TARGET_FIELD__HEADERS = [
53
48
  ]
54
49
 
55
50
  MANDATORY_SHEETS_BY_ROLE: dict[RoleTypes, set[str]] = {
56
- role_type: {str(sheet_name) for sheet_name in RULES_PER_ROLE[role_type].mandatory_fields(use_alias=True)}
51
+ role_type: {str(sheet_name) for sheet_name in VERIFIED_RULES_BY_ROLE[role_type].mandatory_fields(use_alias=True)}
57
52
  for role_type in RoleTypes.__members__.values()
58
53
  }
59
54
 
@@ -207,7 +202,7 @@ class SpreadsheetReader:
207
202
  return sheets, read_info_by_sheet
208
203
 
209
204
 
210
- class ExcelImporter(BaseImporter):
205
+ class ExcelImporter(BaseImporter[T_InputRules]):
211
206
  """Import rules from an Excel file.
212
207
 
213
208
  Args:
@@ -217,30 +212,18 @@ class ExcelImporter(BaseImporter):
217
212
  def __init__(self, filepath: Path):
218
213
  self.filepath = filepath
219
214
 
220
- @overload
221
- def to_rules(self, errors: Literal["raise"], role: RoleTypes | None = None) -> VerifiedRules: ...
222
-
223
- @overload
224
- def to_rules(
225
- self,
226
- errors: Literal["continue"] = "continue",
227
- role: RoleTypes | None = None,
228
- ) -> tuple[VerifiedRules | None, IssueList]: ...
229
-
230
- def to_rules(
231
- self, errors: Literal["raise", "continue"] = "continue", role: RoleTypes | None = None
232
- ) -> tuple[VerifiedRules | None, IssueList] | VerifiedRules:
215
+ def to_rules(self) -> ReadRules[T_InputRules]:
233
216
  issue_list = IssueList(title=f"'{self.filepath.name}'")
234
217
  if not self.filepath.exists():
235
218
  issue_list.append(FileNotFoundNeatError(self.filepath))
236
- return self._return_or_raise(issue_list, errors)
219
+ return ReadRules(None, issue_list, {})
237
220
 
238
221
  with pd.ExcelFile(self.filepath) as excel_file:
239
222
  user_reader = SpreadsheetReader(issue_list)
240
223
 
241
224
  user_read = user_reader.read(excel_file, self.filepath)
242
225
  if user_read is None or issue_list.has_errors:
243
- return self._return_or_raise(issue_list, errors)
226
+ return ReadRules(None, issue_list, {})
244
227
 
245
228
  last_read: ReadResult | None = None
246
229
  if any(sheet_name.startswith("Last") for sheet_name in user_reader.seen_sheets):
@@ -252,7 +235,7 @@ class ExcelImporter(BaseImporter):
252
235
  reference_read = SpreadsheetReader(issue_list, sheet_prefix="Ref").read(excel_file, self.filepath)
253
236
 
254
237
  if issue_list.has_errors:
255
- return self._return_or_raise(issue_list, errors)
238
+ return ReadRules(None, issue_list, {})
256
239
 
257
240
  if reference_read and user_read.role != reference_read.role:
258
241
  issue_list.append(
@@ -265,7 +248,7 @@ class ExcelImporter(BaseImporter):
265
248
  "sheet",
266
249
  )
267
250
  )
268
- return self._return_or_raise(issue_list, errors)
251
+ return ReadRules(None, issue_list, {})
269
252
 
270
253
  sheets = user_read.sheets
271
254
  original_role = user_read.role
@@ -280,34 +263,12 @@ class ExcelImporter(BaseImporter):
280
263
  sheets["reference"] = reference_read.sheets
281
264
  read_info_by_sheet.update(reference_read.read_info_by_sheet)
282
265
 
283
- rules_cls = RULES_PER_ROLE[original_role]
284
- with _handle_issues(
285
- issue_list,
286
- error_cls=NeatError,
287
- error_args={"read_info_by_sheet": read_info_by_sheet},
288
- ) as future:
289
- rules: VerifiedRules
290
- if rules_cls is DMSRules:
291
- rules = DMSRulesInput.load(sheets).as_rules()
292
- elif rules_cls is InformationRules:
293
- rules = InformationRulesInput.load(sheets).as_rules()
294
- elif rules_cls is AssetRules:
295
- rules = AssetRulesInput.load(sheets).as_rules()
296
- else:
297
- rules = rules_cls.model_validate(sheets) # type: ignore[attr-defined]
298
-
299
- if future.result == "failure" or issue_list.has_errors:
300
- return self._return_or_raise(issue_list, errors)
266
+ rules_cls = INPUT_RULES_BY_ROLE[original_role]
267
+ rules = cast(T_InputRules, rules_cls.load(sheets))
268
+ return ReadRules(rules, issue_list, {"read_info_by_sheet": read_info_by_sheet})
301
269
 
302
- return self._to_output(
303
- rules,
304
- issue_list,
305
- errors=errors,
306
- role=role,
307
- )
308
270
 
309
-
310
- class GoogleSheetImporter(BaseImporter):
271
+ class GoogleSheetImporter(BaseImporter[T_InputRules]):
311
272
  """Import rules from a Google Sheet.
312
273
 
313
274
  .. warning::
@@ -323,38 +284,13 @@ class GoogleSheetImporter(BaseImporter):
323
284
  self.sheet_id = sheet_id
324
285
  self.skiprows = skiprows
325
286
 
326
- @overload
327
- def to_rules(self, errors: Literal["raise"], role: RoleTypes | None = None) -> VerifiedRules: ...
328
-
329
- @overload
330
- def to_rules(
331
- self, errors: Literal["continue"] = "continue", role: RoleTypes | None = None
332
- ) -> tuple[VerifiedRules | None, IssueList]: ...
287
+ def to_rules(self) -> ReadRules[T_InputRules]:
288
+ raise NotImplementedError("Google Sheet Importer is not yet implemented.")
333
289
 
334
- def to_rules(
335
- self, errors: Literal["raise", "continue"] = "continue", role: RoleTypes | None = None
336
- ) -> tuple[VerifiedRules | None, IssueList] | VerifiedRules:
290
+ def _get_sheets(self) -> dict[str, pd.DataFrame]:
337
291
  local_import("gspread", "google")
338
292
  import gspread # type: ignore[import]
339
293
 
340
- role = role or RoleTypes.domain_expert
341
- rules_model = cast(DomainRules | InformationRules | AssetRules | DMSRules, RULES_PER_ROLE[role])
342
-
343
294
  client_google = gspread.service_account()
344
295
  google_sheet = client_google.open_by_key(self.sheet_id)
345
- sheets = {worksheet.title: pd.DataFrame(worksheet.get_all_records()) for worksheet in google_sheet.worksheets()}
346
- sheet_names = {str(name).lower() for name in sheets.keys()}
347
-
348
- if missing_sheets := rules_model.mandatory_fields().difference(sheet_names):
349
- raise ValueError(f"Missing mandatory sheets: {missing_sheets}")
350
-
351
- if role == RoleTypes.domain_expert:
352
- output = rules_model.model_validate(sheets)
353
- elif role == RoleTypes.information:
354
- output = rules_model.model_validate(sheets)
355
- elif role == RoleTypes.dms:
356
- output = rules_model.model_validate(sheets)
357
- else:
358
- raise ValueError(f"Role {role} is not valid.")
359
-
360
- return self._to_output(output, IssueList(), errors=errors, role=role)
296
+ return {worksheet.title: pd.DataFrame(worksheet.get_all_records()) for worksheet in google_sheet.worksheets()}
@@ -1,5 +1,5 @@
1
1
  from pathlib import Path
2
- from typing import Any, Literal, overload
2
+ from typing import Any, cast
3
3
 
4
4
  import yaml
5
5
 
@@ -11,13 +11,13 @@ from cognite.neat.issues.errors import (
11
11
  FileTypeUnexpectedError,
12
12
  )
13
13
  from cognite.neat.issues.warnings import NeatValueWarning
14
- from cognite.neat.rules.models import RULES_PER_ROLE, DMSRules, RoleTypes
15
- from cognite.neat.rules.models.dms import DMSRulesInput
14
+ from cognite.neat.rules._shared import ReadRules, T_InputRules
15
+ from cognite.neat.rules.models import INPUT_RULES_BY_ROLE, RoleTypes
16
16
 
17
- from ._base import BaseImporter, VerifiedRules, _handle_issues
17
+ from ._base import BaseImporter
18
18
 
19
19
 
20
- class YAMLImporter(BaseImporter):
20
+ class YAMLImporter(BaseImporter[T_InputRules]):
21
21
  """Imports the rules from a YAML file.
22
22
 
23
23
  Args:
@@ -51,21 +51,9 @@ class YAMLImporter(BaseImporter):
51
51
  return cls({}, [FileTypeUnexpectedError(filepath, frozenset([".yaml", ".yml"]))])
52
52
  return cls(yaml.safe_load(filepath.read_text()), filepaths=[filepath])
53
53
 
54
- @overload
55
- def to_rules(self, errors: Literal["raise"], role: RoleTypes | None = None) -> VerifiedRules: ...
56
-
57
- @overload
58
- def to_rules(
59
- self, errors: Literal["continue"] = "continue", role: RoleTypes | None = None
60
- ) -> tuple[VerifiedRules | None, IssueList]: ...
61
-
62
- def to_rules(
63
- self, errors: Literal["raise", "continue"] = "continue", role: RoleTypes | None = None
64
- ) -> tuple[VerifiedRules | None, IssueList] | VerifiedRules:
54
+ def to_rules(self) -> ReadRules[T_InputRules]:
65
55
  if self._read_issues.has_errors or not self.raw_data:
66
- if errors == "raise":
67
- raise self._read_issues.as_errors()
68
- return None, self._read_issues
56
+ return ReadRules(None, self._read_issues, {})
69
57
  issue_list = IssueList(title="YAML Importer", issues=self._read_issues)
70
58
 
71
59
  if not self._filepaths:
@@ -81,33 +69,18 @@ class YAMLImporter(BaseImporter):
81
69
 
82
70
  if "metadata" not in self.raw_data:
83
71
  self._read_issues.append(FileMissingRequiredFieldError(metadata_file, "section", "metadata"))
84
- if errors == "raise":
85
- raise self._read_issues.as_errors()
86
- return None, self._read_issues
72
+ return ReadRules(None, self._read_issues, {})
87
73
 
88
74
  metadata = self.raw_data["metadata"]
89
75
 
90
76
  if "role" not in metadata:
91
77
  self._read_issues.append(FileMissingRequiredFieldError(metadata, "metadata", "role"))
92
- if errors == "raise":
93
- raise self._read_issues.as_errors()
94
- return None, self._read_issues
78
+ return ReadRules(None, self._read_issues, {})
95
79
 
96
80
  role_input = RoleTypes(metadata["role"])
97
81
  role_enum = RoleTypes(role_input)
98
- rules_model = RULES_PER_ROLE[role_enum]
99
-
100
- with _handle_issues(issue_list) as future:
101
- rules: VerifiedRules
102
- if rules_model is DMSRules:
103
- rules = DMSRulesInput.load(self.raw_data).as_rules()
104
- else:
105
- rules = rules_model.model_validate(self.raw_data)
106
-
107
- if future.result == "failure":
108
- if errors == "continue":
109
- return None, issue_list
110
- else:
111
- raise issue_list.as_errors()
112
-
113
- return self._to_output(rules, issue_list, errors, role)
82
+ rules_cls = INPUT_RULES_BY_ROLE[role_enum]
83
+
84
+ rules = cast(T_InputRules, rules_cls.load(self.raw_data))
85
+
86
+ return ReadRules(rules, issue_list, {})
@@ -1,12 +1,25 @@
1
- from cognite.neat.rules.models.asset import AssetRules
2
- from cognite.neat.rules.models.domain import DomainRules
1
+ from cognite.neat.rules.models.asset._rules import AssetRules
2
+ from cognite.neat.rules.models.asset._rules_input import AssetInputRules
3
+ from cognite.neat.rules.models.domain import DomainInputRules, DomainRules
3
4
  from cognite.neat.rules.models.information._rules import InformationRules
5
+ from cognite.neat.rules.models.information._rules_input import InformationInputRules
4
6
 
5
- from ._base import DataModelType, ExtensionCategory, RoleTypes, SchemaCompleteness, SheetEntity, SheetList
7
+ from ._base_rules import DataModelType, ExtensionCategory, RoleTypes, SchemaCompleteness, SheetEntity, SheetList
6
8
  from .dms._rules import DMSRules
9
+ from .dms._rules_input import DMSInputRules
7
10
  from .dms._schema import DMSSchema
8
11
 
9
- RULES_PER_ROLE: dict[RoleTypes, type[DomainRules] | type[InformationRules] | type[AssetRules] | type[DMSRules]] = {
12
+ INPUT_RULES_BY_ROLE: dict[
13
+ RoleTypes, type[InformationInputRules] | type[AssetInputRules] | type[DMSInputRules] | type[DomainInputRules]
14
+ ] = {
15
+ RoleTypes.domain_expert: DomainInputRules,
16
+ RoleTypes.information: InformationInputRules,
17
+ RoleTypes.asset: AssetInputRules,
18
+ RoleTypes.dms: DMSInputRules,
19
+ }
20
+ VERIFIED_RULES_BY_ROLE: dict[
21
+ RoleTypes, type[InformationRules] | type[AssetRules] | type[DMSRules] | type[DomainRules]
22
+ ] = {
10
23
  RoleTypes.domain_expert: DomainRules,
11
24
  RoleTypes.information: InformationRules,
12
25
  RoleTypes.asset: AssetRules,
@@ -16,10 +29,13 @@ RULES_PER_ROLE: dict[RoleTypes, type[DomainRules] | type[InformationRules] | typ
16
29
 
17
30
  __all__ = [
18
31
  "DomainRules",
32
+ "DMSInputRules",
33
+ "InformationInputRules",
34
+ "AssetInputRules",
19
35
  "InformationRules",
20
36
  "AssetRules",
21
37
  "DMSRules",
22
- "RULES_PER_ROLE",
38
+ "INPUT_RULES_BY_ROLE",
23
39
  "DMSSchema",
24
40
  "RoleTypes",
25
41
  "SchemaCompleteness",