cognite-neat 0.90.2__py3-none-any.whl → 0.92.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 (41) hide show
  1. cognite/neat/_version.py +1 -1
  2. cognite/neat/graph/extractors/__init__.py +5 -0
  3. cognite/neat/graph/extractors/_classic_cdf/_assets.py +8 -8
  4. cognite/neat/graph/extractors/_classic_cdf/_base.py +26 -1
  5. cognite/neat/graph/extractors/_classic_cdf/_classic.py +208 -0
  6. cognite/neat/graph/extractors/_classic_cdf/_data_sets.py +110 -0
  7. cognite/neat/graph/extractors/_classic_cdf/_events.py +30 -5
  8. cognite/neat/graph/extractors/_classic_cdf/_files.py +33 -8
  9. cognite/neat/graph/extractors/_classic_cdf/_labels.py +14 -6
  10. cognite/neat/graph/extractors/_classic_cdf/_relationships.py +38 -7
  11. cognite/neat/graph/extractors/_classic_cdf/_sequences.py +30 -5
  12. cognite/neat/graph/extractors/_classic_cdf/_timeseries.py +30 -5
  13. cognite/neat/graph/extractors/_dexpi.py +4 -4
  14. cognite/neat/graph/extractors/_iodd.py +160 -0
  15. cognite/neat/issues/_base.py +6 -2
  16. cognite/neat/rules/exporters/_rules2excel.py +3 -3
  17. cognite/neat/rules/exporters/_rules2yaml.py +5 -1
  18. cognite/neat/rules/models/__init__.py +2 -2
  19. cognite/neat/rules/models/_base_input.py +2 -2
  20. cognite/neat/rules/models/_base_rules.py +142 -142
  21. cognite/neat/rules/models/asset/_rules.py +1 -34
  22. cognite/neat/rules/models/dms/_rules.py +127 -46
  23. cognite/neat/rules/models/dms/_validation.py +2 -2
  24. cognite/neat/rules/models/domain.py +16 -19
  25. cognite/neat/rules/models/entities/_single_value.py +25 -11
  26. cognite/neat/rules/models/entities/_types.py +0 -10
  27. cognite/neat/rules/models/information/_rules.py +68 -43
  28. cognite/neat/rules/models/information/_validation.py +5 -5
  29. cognite/neat/rules/transformers/_converters.py +6 -8
  30. cognite/neat/rules/transformers/_pipelines.py +8 -4
  31. cognite/neat/store/_base.py +1 -1
  32. cognite/neat/utils/collection_.py +4 -3
  33. cognite/neat/utils/xml_.py +27 -12
  34. {cognite_neat-0.90.2.dist-info → cognite_neat-0.92.0.dist-info}/METADATA +1 -1
  35. {cognite_neat-0.90.2.dist-info → cognite_neat-0.92.0.dist-info}/RECORD +38 -38
  36. cognite/neat/rules/models/asset/_serializer.py +0 -73
  37. cognite/neat/rules/models/dms/_serializer.py +0 -157
  38. cognite/neat/rules/models/information/_serializer.py +0 -73
  39. {cognite_neat-0.90.2.dist-info → cognite_neat-0.92.0.dist-info}/LICENSE +0 -0
  40. {cognite_neat-0.90.2.dist-info → cognite_neat-0.92.0.dist-info}/WHEEL +0 -0
  41. {cognite_neat-0.90.2.dist-info → cognite_neat-0.92.0.dist-info}/entry_points.txt +0 -0
@@ -1,7 +1,7 @@
1
- from collections.abc import Callable, Set
1
+ from collections import defaultdict
2
+ from collections.abc import Callable, Iterable, Set
2
3
  from datetime import datetime, timezone
3
4
  from pathlib import Path
4
- from urllib.parse import quote
5
5
 
6
6
  from cognite.client import CogniteClient
7
7
  from cognite.client.data_classes import Relationship, RelationshipList
@@ -10,10 +10,11 @@ from rdflib import RDF, Literal, Namespace
10
10
  from cognite.neat.graph.models import Triple
11
11
  from cognite.neat.utils.auxiliary import create_sha256_hash
12
12
 
13
- from ._base import DEFAULT_SKIP_METADATA_VALUES, ClassicCDFExtractor
13
+ from ._base import DEFAULT_SKIP_METADATA_VALUES, ClassicCDFBaseExtractor, InstanceIdPrefix
14
+ from ._labels import LabelsExtractor
14
15
 
15
16
 
16
- class RelationshipsExtractor(ClassicCDFExtractor[Relationship]):
17
+ class RelationshipsExtractor(ClassicCDFBaseExtractor[Relationship]):
17
18
  """Extract data from Cognite Data Fusions Relationships into Neat.
18
19
 
19
20
  Args:
@@ -34,6 +35,31 @@ class RelationshipsExtractor(ClassicCDFExtractor[Relationship]):
34
35
 
35
36
  _default_rdf_type = "Relationship"
36
37
 
38
+ def __init__(
39
+ self,
40
+ items: Iterable[Relationship],
41
+ namespace: Namespace | None = None,
42
+ to_type: Callable[[Relationship], str | None] | None = None,
43
+ total: int | None = None,
44
+ limit: int | None = None,
45
+ unpack_metadata: bool = True,
46
+ skip_metadata_values: Set[str] | None = DEFAULT_SKIP_METADATA_VALUES,
47
+ ):
48
+ super().__init__(
49
+ items,
50
+ namespace=namespace,
51
+ to_type=to_type,
52
+ total=total,
53
+ limit=limit,
54
+ unpack_metadata=unpack_metadata,
55
+ skip_metadata_values=skip_metadata_values,
56
+ )
57
+ # This is used by the ClassicExtractor to log the target nodes, such
58
+ # that it can extract them.
59
+ # It is private to avoid exposing it to the user.
60
+ self._log_target_nodes = False
61
+ self._target_external_ids_by_type: dict[InstanceIdPrefix, set[str]] = defaultdict(set)
62
+
37
63
  @classmethod
38
64
  def from_dataset(
39
65
  cls,
@@ -79,8 +105,13 @@ class RelationshipsExtractor(ClassicCDFExtractor[Relationship]):
79
105
  """Converts an asset to triples."""
80
106
 
81
107
  if relationship.external_id and relationship.source_external_id and relationship.target_external_id:
108
+ if self._log_target_nodes and relationship.target_type and relationship.target_external_id:
109
+ self._target_external_ids_by_type[InstanceIdPrefix.from_str(relationship.target_type)].add(
110
+ relationship.target_external_id
111
+ )
112
+
82
113
  # relationships do not have an internal id, so we generate one
83
- id_ = self.namespace[f"Relationship_{create_sha256_hash(relationship.external_id)}"]
114
+ id_ = self.namespace[f"{InstanceIdPrefix.relationship}{create_sha256_hash(relationship.external_id)}"]
84
115
 
85
116
  type_ = self._get_rdf_type(relationship)
86
117
  # Set rdf type
@@ -178,7 +209,7 @@ class RelationshipsExtractor(ClassicCDFExtractor[Relationship]):
178
209
  (
179
210
  id_,
180
211
  self.namespace.label,
181
- self.namespace[f"Label_{quote(label.dump()['externalId'])}"],
212
+ self.namespace[f"{InstanceIdPrefix.label}{LabelsExtractor._label_id(label)}"],
182
213
  )
183
214
  )
184
215
 
@@ -188,7 +219,7 @@ class RelationshipsExtractor(ClassicCDFExtractor[Relationship]):
188
219
  (
189
220
  id_,
190
221
  self.namespace.dataset,
191
- self.namespace[f"Dataset_{relationship.data_set_id}"],
222
+ self.namespace[f"{InstanceIdPrefix.data_set}{relationship.data_set_id}"],
192
223
  )
193
224
  )
194
225
 
@@ -8,10 +8,10 @@ from rdflib import RDF, Literal, Namespace
8
8
 
9
9
  from cognite.neat.graph.models import Triple
10
10
 
11
- from ._base import DEFAULT_SKIP_METADATA_VALUES, ClassicCDFExtractor
11
+ from ._base import DEFAULT_SKIP_METADATA_VALUES, ClassicCDFBaseExtractor, InstanceIdPrefix
12
12
 
13
13
 
14
- class SequencesExtractor(ClassicCDFExtractor[Sequence]):
14
+ class SequencesExtractor(ClassicCDFBaseExtractor[Sequence]):
15
15
  """Extract data from Cognite Data Fusions Sequences into Neat.
16
16
 
17
17
  Args:
@@ -56,6 +56,31 @@ class SequencesExtractor(ClassicCDFExtractor[Sequence]):
56
56
  skip_metadata_values=skip_metadata_values,
57
57
  )
58
58
 
59
+ @classmethod
60
+ def from_hierarchy(
61
+ cls,
62
+ client: CogniteClient,
63
+ root_asset_external_id: str,
64
+ namespace: Namespace | None = None,
65
+ to_type: Callable[[Sequence], str | None] | None = None,
66
+ limit: int | None = None,
67
+ unpack_metadata: bool = True,
68
+ skip_metadata_values: Set[str] | None = DEFAULT_SKIP_METADATA_VALUES,
69
+ ):
70
+ total = client.sequences.aggregate_count(
71
+ filter=SequenceFilter(asset_subtree_ids=[{"externalId": root_asset_external_id}])
72
+ )
73
+
74
+ return cls(
75
+ client.sequences(asset_subtree_external_ids=[root_asset_external_id]),
76
+ namespace,
77
+ to_type,
78
+ total,
79
+ limit,
80
+ unpack_metadata=unpack_metadata,
81
+ skip_metadata_values=skip_metadata_values,
82
+ )
83
+
59
84
  @classmethod
60
85
  def from_file(
61
86
  cls,
@@ -78,7 +103,7 @@ class SequencesExtractor(ClassicCDFExtractor[Sequence]):
78
103
  )
79
104
 
80
105
  def _item2triples(self, sequence: Sequence) -> list[Triple]:
81
- id_ = self.namespace[f"Sequence_{sequence.id}"]
106
+ id_ = self.namespace[f"{InstanceIdPrefix.sequence}{sequence.id}"]
82
107
 
83
108
  type_ = self._get_rdf_type(sequence)
84
109
  # Set rdf type
@@ -121,7 +146,7 @@ class SequencesExtractor(ClassicCDFExtractor[Sequence]):
121
146
  (
122
147
  id_,
123
148
  self.namespace.data_set_id,
124
- self.namespace[f"Dataset_{sequence.data_set_id}"],
149
+ self.namespace[f"{InstanceIdPrefix.data_set}{sequence.data_set_id}"],
125
150
  )
126
151
  )
127
152
 
@@ -130,7 +155,7 @@ class SequencesExtractor(ClassicCDFExtractor[Sequence]):
130
155
  (
131
156
  id_,
132
157
  self.namespace.asset,
133
- self.namespace[f"Asset_{sequence.asset_id}"],
158
+ self.namespace[f"{InstanceIdPrefix.asset}{sequence.asset_id}"],
134
159
  )
135
160
  )
136
161
 
@@ -9,10 +9,10 @@ from rdflib import RDF, Literal, Namespace, URIRef
9
9
 
10
10
  from cognite.neat.graph.models import Triple
11
11
 
12
- from ._base import DEFAULT_SKIP_METADATA_VALUES, ClassicCDFExtractor
12
+ from ._base import DEFAULT_SKIP_METADATA_VALUES, ClassicCDFBaseExtractor, InstanceIdPrefix
13
13
 
14
14
 
15
- class TimeSeriesExtractor(ClassicCDFExtractor[TimeSeries]):
15
+ class TimeSeriesExtractor(ClassicCDFBaseExtractor[TimeSeries]):
16
16
  """Extract data from Cognite Data Fusions TimeSeries into Neat.
17
17
 
18
18
  Args:
@@ -58,6 +58,31 @@ class TimeSeriesExtractor(ClassicCDFExtractor[TimeSeries]):
58
58
  skip_metadata_values=skip_metadata_values,
59
59
  )
60
60
 
61
+ @classmethod
62
+ def from_hierarchy(
63
+ cls,
64
+ client: CogniteClient,
65
+ root_asset_external_id: str,
66
+ namespace: Namespace | None = None,
67
+ to_type: Callable[[TimeSeries], str | None] | None = None,
68
+ limit: int | None = None,
69
+ unpack_metadata: bool = True,
70
+ skip_metadata_values: Set[str] | None = DEFAULT_SKIP_METADATA_VALUES,
71
+ ):
72
+ total = client.time_series.aggregate_count(
73
+ filter=TimeSeriesFilter(asset_subtree_ids=[{"externalId": root_asset_external_id}])
74
+ )
75
+
76
+ return cls(
77
+ client.time_series(asset_external_ids=[root_asset_external_id]),
78
+ namespace,
79
+ to_type,
80
+ total,
81
+ limit,
82
+ unpack_metadata=unpack_metadata,
83
+ skip_metadata_values=skip_metadata_values,
84
+ )
85
+
61
86
  @classmethod
62
87
  def from_file(
63
88
  cls,
@@ -80,7 +105,7 @@ class TimeSeriesExtractor(ClassicCDFExtractor[TimeSeries]):
80
105
  )
81
106
 
82
107
  def _item2triples(self, timeseries: TimeSeries) -> list[Triple]:
83
- id_ = self.namespace[f"TimeSeries_{timeseries.id}"]
108
+ id_ = self.namespace[f"{InstanceIdPrefix.time_series}{timeseries.id}"]
84
109
 
85
110
  # Set rdf type
86
111
  type_ = self._get_rdf_type(timeseries)
@@ -158,7 +183,7 @@ class TimeSeriesExtractor(ClassicCDFExtractor[TimeSeries]):
158
183
  (
159
184
  id_,
160
185
  self.namespace.dataset,
161
- self.namespace[f"Dataset_{timeseries.data_set_id}"],
186
+ self.namespace[f"{InstanceIdPrefix.data_set}{timeseries.data_set_id}"],
162
187
  )
163
188
  )
164
189
 
@@ -167,7 +192,7 @@ class TimeSeriesExtractor(ClassicCDFExtractor[TimeSeries]):
167
192
  (
168
193
  id_,
169
194
  self.namespace.asset,
170
- self.namespace[f"Asset_{timeseries.asset_id}"],
195
+ self.namespace[f"{InstanceIdPrefix.asset}{timeseries.asset_id}"],
171
196
  )
172
197
  )
173
198
 
@@ -202,13 +202,13 @@ class DexpiExtractor(BaseExtractor):
202
202
 
203
203
  @classmethod
204
204
  def _get_element_label(cls, element: Element) -> str | None:
205
- if children := get_children(element, "Label", 1):
206
- if grandchildren := get_children(children[0], "Text", 1):
205
+ if children := get_children(element, "Label", no_children=1):
206
+ if grandchildren := get_children(children[0], "Text", no_children=1):
207
207
  if "String" in grandchildren[0].attrib:
208
208
  return grandchildren[0].attrib["String"]
209
209
 
210
210
  # extension for schema version 3.3, where text is used to "label" without a <label> parent
211
- elif children := get_children(element, "Text", 1):
211
+ elif children := get_children(element, "Text", no_children=1):
212
212
  if "String" in children[0].attrib:
213
213
  return children[0].attrib["String"]
214
214
 
@@ -219,7 +219,7 @@ class DexpiExtractor(BaseExtractor):
219
219
  # TODO: This requires more work as there are multiple groupings of GenericAttributes
220
220
 
221
221
  attributes = defaultdict(list)
222
- if children := get_children(element, "GenericAttributes", 1):
222
+ if children := get_children(element, "GenericAttributes", no_children=1):
223
223
  if grandchildren := get_children(children[0], "GenericAttribute"):
224
224
  for generic_attribute in grandchildren:
225
225
  # extension for schema version 3.3, where "AttributeURI" is not included
@@ -0,0 +1,160 @@
1
+ import xml.etree.ElementTree as ET
2
+ from pathlib import Path
3
+ from typing import ClassVar
4
+ from xml.etree.ElementTree import Element
5
+
6
+ from rdflib import RDF, Literal, Namespace, URIRef
7
+
8
+ from cognite.neat.constants import DEFAULT_NAMESPACE
9
+ from cognite.neat.graph.extractors._base import BaseExtractor
10
+ from cognite.neat.graph.models import Triple
11
+ from cognite.neat.utils.rdf_ import as_neat_compliant_uri
12
+ from cognite.neat.utils.text import to_camel
13
+ from cognite.neat.utils.xml_ import get_children
14
+
15
+ IODD = Namespace("http://www.io-link.com/IODD/2010/10/")
16
+ XSI = Namespace("http://www.w3.org/2001/XMLSchema-instance/")
17
+
18
+
19
+ class IODDExtractor(BaseExtractor):
20
+ """
21
+ IODD-XML extractor of RDF triples
22
+
23
+ Each IODD sheet describes an IODD device. This extractor extracts rdf triples that describes the device, and the
24
+ sensors connected to the device.
25
+ This data is described under the elements "DeviceIdentity" and "ProcessDataCollection".
26
+ In addition, triples extacted from "DeviceIdentity" and
27
+ "ProcessDataCollection" may reference "Text" elements which are found under "ExternalTextCollection". Edges to
28
+ these Text element nodes are also extracted.
29
+
30
+ Args:
31
+ root: XML root element of IODD XML file.
32
+ namespace: Optional custom namespace to use for extracted triples that define data
33
+ model instances. Defaults to DEFAULT_NAMESPACE.
34
+ """
35
+
36
+ device_elements_with_text_nodes: ClassVar[list[str]] = ["VendorText", "VendorUrl", "DeviceName", "DeviceFamily"]
37
+
38
+ def __init__(self, root: Element, namespace: Namespace | None = None):
39
+ self.root = root
40
+ self.namespace = namespace or DEFAULT_NAMESPACE
41
+
42
+ @classmethod
43
+ def from_file(cls, filepath: str | Path, namespace: Namespace | None = None):
44
+ return cls(ET.parse(filepath).getroot(), namespace)
45
+
46
+ @classmethod
47
+ def _from_root2triples(cls, root: Element, namespace: Namespace) -> list[Triple]:
48
+ """Loops through the relevant elements of the IODD XML sheet to create rdf triples that describes the IODD
49
+ device by starting at the root element.
50
+ """
51
+ triples: list[Triple] = []
52
+
53
+ # Extract DeviceIdentity triples
54
+ if di_root := get_children(
55
+ root, "DeviceIdentity", ignore_namespace=True, include_nested_children=True, no_children=1
56
+ ):
57
+ triples.extend(cls._iodd_device_identity2triples(di_root[0], namespace))
58
+
59
+ # Extract ProcessDataCollection triples -
60
+ # this element holds the information about the sensors with data coming from MQTT
61
+ if pc_root := get_children(
62
+ root, "ProcessDataCollection", ignore_namespace=True, include_nested_children=True, no_children=1
63
+ ):
64
+ triples.extend(cls._process_data_collection2triples(pc_root[0], namespace))
65
+
66
+ if et_root := get_children(
67
+ root, "ExternalTextCollection", ignore_namespace=True, include_nested_children=True, no_children=1
68
+ ):
69
+ triples.extend(cls._text_elements2triples(et_root[0], namespace))
70
+
71
+ return triples
72
+
73
+ @classmethod
74
+ def _device_2text_elements_edges(cls, di_root: Element, id: URIRef, namespace: Namespace) -> list[Triple]:
75
+ """
76
+ Create edges from the device node to text nodes.
77
+ """
78
+ triples: list[Triple] = []
79
+
80
+ for element_tag in cls.device_elements_with_text_nodes:
81
+ if child := get_children(
82
+ di_root, child_tag=element_tag, ignore_namespace=True, include_nested_children=True, no_children=1
83
+ ):
84
+ if text_id := child[0].attrib.get("textId"):
85
+ # Create connection from device to textId node
86
+ element_tag = to_camel(element_tag)
87
+ triples.append((id, IODD[element_tag], namespace[text_id]))
88
+
89
+ return triples
90
+
91
+ @classmethod
92
+ def _text_elements2triples(cls, et_root: Element, namespace: Namespace) -> list[Triple]:
93
+ """
94
+ This method extracts all text item triples under the ExternalTextCollection element. This will create a node
95
+ for each text item, and add the text value as a property to the node.
96
+ """
97
+ triples: list[Triple] = []
98
+
99
+ if text_elements := get_children(
100
+ et_root, child_tag="Text", ignore_namespace=True, include_nested_children=True
101
+ ):
102
+ for element in text_elements:
103
+ if id := element.attrib.get("id"):
104
+ text_id = namespace[id]
105
+
106
+ # Create Text node
107
+ triples.append((text_id, RDF.type, as_neat_compliant_uri(IODD["Text"])))
108
+
109
+ # Resolve text value related to the text item
110
+ if value := element.attrib.get("value"):
111
+ triples.append((text_id, IODD.value, Literal(value)))
112
+
113
+ return triples
114
+
115
+ # TODO
116
+ @classmethod
117
+ def _process_data_collection2triples(cls, pc_root: Element, namespace: Namespace) -> list[Triple]:
118
+ """
119
+ ProcessDataCollection contains both ProcessDataIn and ProcessDataOut. Here, we are interested in the elements
120
+ that corresponds to actual sensor values that we will rececive from the MQTT connection.
121
+
122
+ Most likely this is ProcessDataOut elements (to be verified).
123
+ """
124
+ triples: list[Triple] = []
125
+
126
+ return triples
127
+
128
+ @classmethod
129
+ def _iodd_device_identity2triples(cls, di_root: Element, namespace: Namespace) -> list[Triple]:
130
+ """
131
+ Properties and metadata related to the IO Device are described under the 'DeviceIdentity' element in the XML.
132
+ This method extracts the triples that describe the device's identity which is found under the
133
+ DeviceIdentity element and its child elements.
134
+
135
+ """
136
+ triples: list[Triple] = []
137
+
138
+ if device_id := di_root.attrib.get("deviceId"):
139
+ id_ = namespace[device_id]
140
+
141
+ for attribute_name, attribute_value in di_root.attrib.items():
142
+ if attribute_name == "deviceId":
143
+ # Create rdf type triple for IODD
144
+ triples.append(
145
+ (
146
+ id_,
147
+ RDF.type,
148
+ as_neat_compliant_uri(IODD["IoddDevice"]),
149
+ )
150
+ )
151
+ else:
152
+ # Collect attributes at root element
153
+ triples.append((id_, IODD[attribute_name], Literal(attribute_value)))
154
+
155
+ triples.extend(cls._device_2text_elements_edges(di_root, id_, namespace))
156
+ return triples
157
+
158
+ def extract(self) -> list[Triple]:
159
+ # Extract triples from IODD device XML
160
+ return self._from_root2triples(self.root, self.namespace)
@@ -190,6 +190,10 @@ class NeatError(NeatIssue, Exception):
190
190
  read_info_by_sheet = kwargs.get("read_info_by_sheet")
191
191
 
192
192
  for error in errors:
193
+ if error["type"] == "is_instance_of" and error["loc"][1] == "is-instance[SheetList]":
194
+ # Skip the error for SheetList, as it is not relevant for the user. This is an
195
+ # internal class used to have helper methods for a lists as .to_pandas()
196
+ continue
193
197
  ctx = error.get("ctx")
194
198
  if isinstance(ctx, dict) and isinstance(multi_error := ctx.get("error"), MultiValueError):
195
199
  if read_info_by_sheet:
@@ -363,10 +367,10 @@ class NeatIssueList(UserList[T_NeatIssue], Sequence[T_NeatIssue], ABC):
363
367
  """Return all the warnings in this list."""
364
368
  return type(self)([issue for issue in self if isinstance(issue, NeatWarning)]) # type: ignore[misc]
365
369
 
366
- def as_errors(self) -> ExceptionGroup:
370
+ def as_errors(self, operation: str = "Operation failed") -> ExceptionGroup:
367
371
  """Return an ExceptionGroup with all the errors in this list."""
368
372
  return ExceptionGroup(
369
- "Operation failed",
373
+ operation,
370
374
  [issue for issue in self if isinstance(issue, NeatError)],
371
375
  )
372
376
 
@@ -17,7 +17,7 @@ from cognite.neat.rules.models import (
17
17
  DataModelType,
18
18
  ExtensionCategory,
19
19
  SchemaCompleteness,
20
- SheetEntity,
20
+ SheetRow,
21
21
  )
22
22
  from cognite.neat.rules.models.dms import DMSMetadata
23
23
  from cognite.neat.rules.models.domain import DomainMetadata
@@ -210,14 +210,14 @@ class ExcelExporter(BaseExporter[VerifiedRules, Workbook]):
210
210
  cell.font = Font(bold=True, size=12)
211
211
 
212
212
  @classmethod
213
- def _get_item_class(cls, annotation: GenericAlias) -> type[SheetEntity]:
213
+ def _get_item_class(cls, annotation: GenericAlias) -> type[SheetRow]:
214
214
  if not isinstance(annotation, GenericAlias):
215
215
  raise ValueError(f"Expected annotation to be a GenericAlias, but got {type(annotation)}")
216
216
  args = get_args(annotation)
217
217
  if len(args) != 1:
218
218
  raise ValueError(f"Expected annotation to have exactly one argument, but got {len(args)}")
219
219
  arg = args[0]
220
- if not issubclass(arg, SheetEntity):
220
+ if not issubclass(arg, SheetRow):
221
221
  raise ValueError(f"Expected annotation to have a BaseModel argument, but got {type(arg)}")
222
222
  return arg
223
223
 
@@ -58,6 +58,10 @@ class YAMLExporter(BaseExporter[VerifiedRules, str]):
58
58
  def export(self, rules: VerifiedRules) -> str:
59
59
  """Export rules to YAML (or JSON) format.
60
60
 
61
+ This will export the rules to YAML format if the output is set to "yaml" and JSON format if the output is set.
62
+ All None and Unset values are excluded from the output to keep the output clean, i.e., only the values the user
63
+ has set.
64
+
61
65
  Args:
62
66
  rules: The rules to be exported.
63
67
 
@@ -66,7 +70,7 @@ class YAMLExporter(BaseExporter[VerifiedRules, str]):
66
70
  """
67
71
  # model_dump_json ensures that the output is in JSON format,
68
72
  # if we don't do this, we will get Enums and other types that are not serializable to YAML
69
- json_output = rules.dump(mode="json")
73
+ json_output = rules.dump(mode="json", exclude_none=True, exclude_unset=True)
70
74
  if self.output == "json":
71
75
  return json.dumps(json_output)
72
76
  elif self.output == "yaml":
@@ -4,7 +4,7 @@ from cognite.neat.rules.models.domain import DomainInputRules, DomainRules
4
4
  from cognite.neat.rules.models.information._rules import InformationRules
5
5
  from cognite.neat.rules.models.information._rules_input import InformationInputRules
6
6
 
7
- from ._base_rules import DataModelType, ExtensionCategory, RoleTypes, SchemaCompleteness, SheetEntity, SheetList
7
+ from ._base_rules import DataModelType, ExtensionCategory, RoleTypes, SchemaCompleteness, SheetList, SheetRow
8
8
  from .dms._rules import DMSRules
9
9
  from .dms._rules_input import DMSInputRules
10
10
  from .dms._schema import DMSSchema
@@ -42,5 +42,5 @@ __all__ = [
42
42
  "ExtensionCategory",
43
43
  "DataModelType",
44
44
  "SheetList",
45
- "SheetEntity",
45
+ "SheetRow",
46
46
  ]
@@ -18,7 +18,7 @@ from dataclasses import Field, dataclass, fields, is_dataclass
18
18
  from types import GenericAlias, UnionType
19
19
  from typing import Any, Generic, TypeVar, Union, cast, get_args, get_origin, overload
20
20
 
21
- from ._base_rules import BaseRules, RuleModel
21
+ from ._base_rules import BaseRules, SchemaModel
22
22
 
23
23
  if sys.version_info >= (3, 11):
24
24
  from typing import Self
@@ -26,7 +26,7 @@ else:
26
26
  from typing_extensions import Self
27
27
 
28
28
  T_BaseRules = TypeVar("T_BaseRules", bound=BaseRules)
29
- T_RuleModel = TypeVar("T_RuleModel", bound=RuleModel)
29
+ T_RuleModel = TypeVar("T_RuleModel", bound=SchemaModel)
30
30
 
31
31
 
32
32
  @dataclass