cognite-neat 0.72.3__py3-none-any.whl → 0.73.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 CHANGED
@@ -1 +1 @@
1
- __version__ = "0.72.3"
1
+ __version__ = "0.73.0"
@@ -11,6 +11,7 @@ from openpyxl.cell import Cell
11
11
  from openpyxl.styles import Alignment, Border, Font, NamedStyle, PatternFill, Side
12
12
  from openpyxl.utils import get_column_letter
13
13
  from openpyxl.worksheet.datavalidation import DataValidation
14
+ from openpyxl.worksheet.worksheet import Worksheet
14
15
  from rdflib import RDF, XSD, Literal, Namespace, URIRef
15
16
 
16
17
  from cognite.neat.graph import exceptions
@@ -321,7 +322,7 @@ def rules2graph_capturing_sheet(
321
322
  workbook.create_sheet(title=class_)
322
323
 
323
324
  # Add header rows
324
- workbook[class_].append(["identifier", *list(properties.keys())])
325
+ cast(Worksheet, workbook[class_]).append(["identifier", *list(properties.keys())])
325
326
 
326
327
  if auto_identifier_type and auto_identifier_type == "index-based": # default, easy to read
327
328
  logging.debug(f"Configuring index-based automatic identifiers for sheet {class_}")
@@ -350,35 +351,35 @@ def _add_index_identifiers(workbook: Workbook, sheet: str, no_rows: int):
350
351
  """Adds index-based auto identifier to a sheet identifier column"""
351
352
  for i in range(no_rows):
352
353
  prefix = to_dms_name(sheet, "class", True)
353
- workbook[sheet][f"A{i+2}"] = f'=IF(ISBLANK(B{i+2}), "","{prefix}-{i+1}")'
354
+ workbook[sheet][f"A{i+2}"] = f'=IF(ISBLANK(B{i+2}), "","{prefix}-{i+1}")' # type: ignore[index]
354
355
 
355
356
 
356
357
  def _add_uuid_identifiers(workbook: Workbook, sheet: str, no_rows: int):
357
358
  """Adds UUID-based auto identifier to a sheet identifier column"""
358
359
  for i in range(no_rows):
359
360
  prefix = to_dms_name(sheet, "class", True)
360
- workbook[sheet][f"A{i+2}"] = f'=IF(ISBLANK(B{i+2}), "","{prefix}-{uuid.uuid4()}")'
361
+ workbook[sheet][f"A{i+2}"] = f'=IF(ISBLANK(B{i+2}), "","{prefix}-{uuid.uuid4()}")' # type: ignore[index]
361
362
 
362
363
 
363
364
  def _add_drop_down_list(workbook: Workbook, sheet: str, column: str, no_rows: int, value_sheet: str, value_column: str):
364
365
  """Adds a drop down list to a column"""
365
366
  drop_down_list = DataValidation(type="list", formula1=f"={value_sheet}!{value_column}$2:{value_column}${no_rows}")
366
367
 
367
- workbook[sheet].add_data_validation(drop_down_list)
368
+ cast(Worksheet, workbook[sheet]).add_data_validation(drop_down_list)
368
369
 
369
370
  for i in range(no_rows):
370
- drop_down_list.add(workbook[sheet][f"{column}{i+2}"])
371
+ drop_down_list.add(workbook[sheet][f"{column}{i+2}"]) # type: ignore[index, misc]
371
372
 
372
373
 
373
374
  def _adjust_column_width(workbook: Workbook):
374
375
  """Adjusts the column width based on the content"""
375
376
  for sheet in workbook.sheetnames:
376
- for cell_tuple in workbook[sheet].columns:
377
+ for cell_tuple in cast(Worksheet, workbook[sheet]).columns:
377
378
  # Wrong type annotation in openpyxl
378
379
  cell = cast(Cell, cell_tuple[0]) # type: ignore[index]
379
380
  if cell.value:
380
381
  adjusted_width = (len(str(cell.value)) + 5) * 1.2
381
- workbook[sheet].column_dimensions[cell.column_letter].width = adjusted_width
382
+ cast(Worksheet, workbook[sheet]).column_dimensions[cell.column_letter].width = adjusted_width
382
383
 
383
384
 
384
385
  def _set_header_style(workbook: Workbook):
@@ -390,12 +391,13 @@ def _set_header_style(workbook: Workbook):
390
391
  workbook.add_named_style(style)
391
392
 
392
393
  for sheet in workbook.sheetnames:
393
- for cell_tuple in workbook[sheet].columns:
394
+ for cell_tuple in cast(Worksheet, workbook[sheet]).columns:
394
395
  # Wrong type annotation in openpyxl
395
396
  cell = cast(Cell, cell_tuple[0]) # type: ignore[index]
396
- workbook[sheet][f"{cell.column_letter}1"].style = style
397
+ worksheet = cast(Worksheet, workbook[sheet])
398
+ worksheet[f"{cell.column_letter}1"].style = style
397
399
  if f"{cell.column_letter}1" == "A1":
398
- workbook[sheet][f"{cell.column_letter}1"].fill = PatternFill("solid", start_color="2FB5F2")
400
+ worksheet[f"{cell.column_letter}1"].fill = PatternFill("solid", start_color="2FB5F2")
399
401
  else:
400
- workbook[sheet][f"{cell.column_letter}1"].fill = PatternFill("solid", start_color="FFB202")
401
- workbook[sheet][f"{cell.column_letter}1"].alignment = Alignment(horizontal="center", vertical="center")
402
+ worksheet[f"{cell.column_letter}1"].fill = PatternFill("solid", start_color="FFB202")
403
+ worksheet[f"{cell.column_letter}1"].alignment = Alignment(horizontal="center", vertical="center")
@@ -53,6 +53,7 @@
53
53
  type:
54
54
  container: null
55
55
  type: direct
56
+ list: false
56
57
  space: workshop
57
58
  usedFor: node
58
59
  - externalId: SubGeographicalRegion
@@ -81,6 +82,7 @@
81
82
  type:
82
83
  container: null
83
84
  type: direct
85
+ list: false
84
86
  space: workshop
85
87
  usedFor: node
86
88
  - externalId: Substation
@@ -109,5 +111,6 @@
109
111
  type:
110
112
  container: null
111
113
  type: direct
114
+ list: false
112
115
  space: workshop
113
116
  usedFor: node
@@ -81,6 +81,7 @@ views:
81
81
  type:
82
82
  container: null
83
83
  type: direct
84
+ list: false
84
85
  source:
85
86
  space: workshop
86
87
  externalId: GeographicalRegion
@@ -132,6 +133,7 @@ views:
132
133
  type:
133
134
  container: null
134
135
  type: direct
136
+ list: false
135
137
  source:
136
138
  space: workshop
137
139
  externalId: SubGeographicalRegion
@@ -195,6 +197,7 @@ views:
195
197
  type:
196
198
  container: null
197
199
  type: direct
200
+ list: false
198
201
  source:
199
202
  space: workshop
200
203
  externalId: Substation
@@ -34,7 +34,6 @@ from cognite.client.data_classes.data_modeling import (
34
34
  ViewApply,
35
35
  ViewId,
36
36
  )
37
- from cognite.client.data_classes.data_modeling.data_types import ListablePropertyType
38
37
  from cognite.client.data_classes.data_modeling.views import (
39
38
  ConnectionDefinitionApply,
40
39
  SingleHopConnectionDefinitionApply,
@@ -284,12 +283,9 @@ class DMSSchemaComponents(BaseModel):
284
283
  if (
285
284
  not isinstance(existing_property.type, DirectRelation)
286
285
  and not isinstance(api_container_property.type, DirectRelation)
287
- and cast(ListablePropertyType, existing_property.type).is_list
288
- != cast(ListablePropertyType, api_container_property.type).is_list
286
+ and existing_property.type.is_list != api_container_property.type.is_list
289
287
  ):
290
- cast(
291
- ListablePropertyType, containers[container_id].properties[container_property_id].type
292
- ).is_list = True
288
+ containers[container_id].properties[container_property_id].type.is_list = True
293
289
 
294
290
  if errors:
295
291
  raise ExceptionGroup("Properties value types have been redefined! This is prohibited! Aborting!", errors)
@@ -302,10 +298,7 @@ class DMSSchemaComponents(BaseModel):
302
298
 
303
299
  # Literal, i.e. Node attribute
304
300
  if property_.property_type is EntityTypes.data_property:
305
- property_type = cast(
306
- type[ListablePropertyType],
307
- cast(ValueTypeMapping, property_.expected_value_type.mapping).dms,
308
- )
301
+ property_type = cast(ValueTypeMapping, property_.expected_value_type.mapping).dms
309
302
  return ContainerProperty(
310
303
  type=property_type(is_list=is_one_to_many),
311
304
  nullable=property_.min_count == 0,
@@ -4,6 +4,7 @@ from typing import cast
4
4
  from openpyxl import Workbook
5
5
  from openpyxl.cell import Cell
6
6
  from openpyxl.styles import Alignment, Border, Font, NamedStyle, PatternFill, Side
7
+ from openpyxl.worksheet.worksheet import Worksheet
7
8
 
8
9
  from cognite.neat.rules.models._base import EntityTypes
9
10
 
@@ -190,7 +191,7 @@ class ExcelExporter(BaseExporter[Workbook]):
190
191
  if sheet == "Metadata":
191
192
  continue
192
193
  if sheet == "Classes" or sheet == "Properties":
193
- sheet_obj = data[sheet]
194
+ sheet_obj = cast(Worksheet, data[sheet])
194
195
  if sheet == "Classes":
195
196
  sheet_obj.freeze_panes = "A3"
196
197
  else:
@@ -207,6 +208,6 @@ class ExcelExporter(BaseExporter[Workbook]):
207
208
  cell.fill = PatternFill("solid", start_color="D5DBD5")
208
209
  cell.alignment = Alignment(horizontal="center", vertical="center")
209
210
  adjusted_width = (len(str(cell.value)) + 5) * 1.2
210
- data[sheet].column_dimensions[cell.column_letter].width = adjusted_width
211
+ cast(Worksheet, data[sheet]).column_dimensions[cell.column_letter].width = adjusted_width
211
212
 
212
213
  return data
@@ -1,16 +1,19 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import itertools
4
+ from datetime import datetime
4
5
  from pathlib import Path
5
6
  from types import GenericAlias
6
- from typing import Any, ClassVar, Literal, get_args
7
+ from typing import Any, ClassVar, Literal, cast, get_args
7
8
 
8
9
  from openpyxl import Workbook
9
10
  from openpyxl.cell import MergedCell
10
11
  from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
12
+ from openpyxl.worksheet.worksheet import Worksheet
11
13
 
12
14
  from cognite.neat.rules._shared import Rules
13
- from cognite.neat.rules.models._rules.base import RoleTypes, SheetEntity
15
+ from cognite.neat.rules.models._rules import DMSRules, DomainRules, InformationRules
16
+ from cognite.neat.rules.models._rules.base import RoleTypes, SchemaCompleteness, SheetEntity
14
17
 
15
18
  from ._base import BaseExporter
16
19
 
@@ -23,6 +26,9 @@ class ExcelExporter(BaseExporter[Workbook]):
23
26
  on the different styles.
24
27
  output_role: The role to use for the exported spreadsheet. If provided, the rules will be converted to
25
28
  this role formate before being written to excel. If not provided, the role from the rules will be used.
29
+ new_model_id: The new model ID to use for the exported spreadsheet. This is only applicable if the input
30
+ rules have 'is_reference' set. If provided, the model ID will be used to automatically create the
31
+ new metadata sheet in the Excel file.
26
32
 
27
33
  The following styles are available:
28
34
 
@@ -43,12 +49,18 @@ class ExcelExporter(BaseExporter[Workbook]):
43
49
  }
44
50
  style_options = get_args(Style)
45
51
 
46
- def __init__(self, styling: Style = "default", output_role: RoleTypes | None = None):
52
+ def __init__(
53
+ self,
54
+ styling: Style = "default",
55
+ output_role: RoleTypes | None = None,
56
+ new_model_id: tuple[str, str, str] | None = None,
57
+ ):
47
58
  if styling not in self.style_options:
48
59
  raise ValueError(f"Invalid styling: {styling}. Valid options are {self.style_options}")
49
60
  self.styling = styling
50
61
  self._styling_level = self.style_options.index(styling)
51
62
  self.output_role = output_role
63
+ self.new_model_id = new_model_id
52
64
 
53
65
  def export_to_file(self, rules: Rules, filepath: Path) -> None:
54
66
  """Exports transformation rules to excel file."""
@@ -70,7 +82,7 @@ class ExcelExporter(BaseExporter[Workbook]):
70
82
  if rules.is_reference:
71
83
  # Writes empty reference sheets
72
84
  dumped_rules = {
73
- "Metadata": {field_alias: None for field_alias in rules.metadata.model_dump(by_alias=True).keys()},
85
+ "Metadata": self._create_metadata_sheet_user_rules(rules),
74
86
  }
75
87
  dumped_rules["Metadata"]["role"] = (
76
88
  self.output_role and self.output_role.value
@@ -155,6 +167,12 @@ class ExcelExporter(BaseExporter[Workbook]):
155
167
  cell.font = Font(bold=True, size=14)
156
168
 
157
169
  def _write_metadata_sheet(self, workbook: Workbook, metadata: dict[str, Any], is_reference: bool = False) -> None:
170
+ # Excel does not support timezone in datetime strings
171
+ if isinstance(metadata.get("created"), datetime):
172
+ metadata["created"] = metadata["created"].replace(tzinfo=None)
173
+ if isinstance(metadata.get("updated"), datetime):
174
+ metadata["updated"] = metadata["updated"].replace(tzinfo=None)
175
+
158
176
  if is_reference:
159
177
  metadata_sheet = workbook.create_sheet("RefMetadata")
160
178
  else:
@@ -181,6 +199,7 @@ class ExcelExporter(BaseExporter[Workbook]):
181
199
  @classmethod
182
200
  def _adjust_column_widths(cls, workbook: Workbook) -> None:
183
201
  for sheet in workbook:
202
+ sheet = cast(Worksheet, sheet)
184
203
  for column_cells in sheet.columns:
185
204
  try:
186
205
  max_length = max(len(str(cell.value)) for cell in column_cells if cell.value is not None)
@@ -194,3 +213,74 @@ class ExcelExporter(BaseExporter[Workbook]):
194
213
  current = sheet.column_dimensions[selected_column.column_letter].width or (max_length + 0.5)
195
214
  sheet.column_dimensions[selected_column.column_letter].width = max(current, max_length + 0.5)
196
215
  return None
216
+
217
+ def _create_metadata_sheet_user_rules(self, rules: Rules) -> dict[str, Any]:
218
+ metadata: dict[str, Any] = {
219
+ field_alias: None for field_alias in rules.metadata.model_dump(by_alias=True).keys()
220
+ }
221
+ if "creator" in metadata:
222
+ metadata["creator"] = "YOUR NAME"
223
+
224
+ if isinstance(rules, DomainRules):
225
+ return metadata
226
+ elif isinstance(rules, DMSRules):
227
+ existing_model_id = (rules.metadata.space, rules.metadata.external_id, rules.metadata.version)
228
+ elif isinstance(rules, InformationRules):
229
+ existing_model_id = (rules.metadata.prefix, rules.metadata.name, rules.metadata.version)
230
+ else:
231
+ raise ValueError(f"Unsupported rules type: {type(rules)}")
232
+ existing_metadata = rules.metadata.model_dump(by_alias=True)
233
+ if isinstance(existing_metadata["created"], datetime):
234
+ metadata["created"] = existing_metadata["created"].replace(tzinfo=None)
235
+ if isinstance(existing_metadata["updated"], datetime):
236
+ metadata["updated"] = existing_metadata["updated"].replace(tzinfo=None)
237
+ # Excel does not support timezone in datetime strings
238
+ now_iso = datetime.now().replace(tzinfo=None).isoformat()
239
+ is_info = isinstance(rules, InformationRules)
240
+ is_dms = not is_info
241
+ is_extension = self.new_model_id is not None
242
+ is_solution = is_extension and self.new_model_id != existing_model_id
243
+
244
+ if is_solution:
245
+ metadata["prefix" if is_info else "space"] = self.new_model_id[0] # type: ignore[index]
246
+ metadata["title" if is_info else "externalId"] = self.new_model_id[1] # type: ignore[index]
247
+ metadata["version"] = self.new_model_id[2] # type: ignore[index]
248
+ else:
249
+ metadata["prefix" if is_info else "space"] = existing_model_id[0]
250
+ metadata["title" if is_info else "externalId"] = existing_model_id[1]
251
+ metadata["version"] = existing_model_id[2]
252
+
253
+ if is_solution and is_info:
254
+ metadata["namespace"] = f"http://purl.org/{self.new_model_id[0]}/" # type: ignore[index]
255
+ elif is_info:
256
+ metadata["namespace"] = existing_metadata["namespace"]
257
+
258
+ if is_solution and is_dms:
259
+ metadata["name"] = self.new_model_id[1] # type: ignore[index]
260
+
261
+ if is_solution:
262
+ metadata["created"] = now_iso
263
+ else:
264
+ metadata["created"] = existing_metadata["created"]
265
+
266
+ if is_solution or is_extension:
267
+ metadata["updated"] = now_iso
268
+ else:
269
+ metadata["updated"] = existing_metadata["updated"]
270
+
271
+ if is_solution:
272
+ metadata["creator"] = "YOUR NAME"
273
+ else:
274
+ metadata["creator"] = existing_metadata["creator"]
275
+
276
+ if not is_solution:
277
+ metadata["description"] = existing_metadata["description"]
278
+
279
+ if is_extension:
280
+ metadata["schema"] = SchemaCompleteness.extended.value
281
+ else:
282
+ metadata["schema"] = SchemaCompleteness.complete.value
283
+
284
+ metadata["extension"] = "addition"
285
+
286
+ return metadata
@@ -13,7 +13,6 @@ from cognite.client.data_classes.data_modeling import (
13
13
  SingleHopConnectionDefinition,
14
14
  View,
15
15
  )
16
- from cognite.client.data_classes.data_modeling.data_types import ListablePropertyType
17
16
  from cognite.client.data_classes.data_modeling.ids import DataModelIdentifier, ViewId
18
17
 
19
18
  from cognite.neat.rules.models.tables import Tables
@@ -132,9 +131,7 @@ class DMSImporter(BaseImporter):
132
131
 
133
132
  max_count: str | float = "1"
134
133
  if isinstance(prop, SingleHopConnectionDefinition) or (
135
- isinstance(prop, MappedProperty)
136
- and isinstance(prop.type, ListablePropertyType)
137
- and prop.type.is_list
134
+ isinstance(prop, MappedProperty) and prop.type.is_list
138
135
  ):
139
136
  max_count = float("nan")
140
137
 
@@ -5,9 +5,10 @@ from cognite.client import CogniteClient
5
5
  from cognite.client import data_modeling as dm
6
6
  from cognite.client.data_classes.data_modeling import DataModelIdentifier
7
7
  from cognite.client.data_classes.data_modeling.containers import BTreeIndex, InvertedIndex
8
- from cognite.client.data_classes.data_modeling.data_types import ListablePropertyType
8
+ from cognite.client.utils import ms_to_datetime
9
9
 
10
10
  from cognite.neat.rules import issues
11
+ from cognite.neat.rules.importers._base import BaseImporter, Rules
11
12
  from cognite.neat.rules.issues import IssueList
12
13
  from cognite.neat.rules.models._rules import DMSRules, DMSSchema, RoleTypes
13
14
  from cognite.neat.rules.models._rules._types import (
@@ -19,6 +20,7 @@ from cognite.neat.rules.models._rules._types import (
19
20
  ViewEntity,
20
21
  ViewPropEntity,
21
22
  )
23
+ from cognite.neat.rules.models._rules.base import ExtensionCategory, SchemaCompleteness
22
24
  from cognite.neat.rules.models._rules.dms_architect_rules import (
23
25
  DMSContainer,
24
26
  DMSMetadata,
@@ -27,16 +29,47 @@ from cognite.neat.rules.models._rules.dms_architect_rules import (
27
29
  SheetList,
28
30
  )
29
31
 
30
- from ._base import BaseImporter, Rules
31
-
32
32
 
33
33
  class DMSImporter(BaseImporter):
34
- def __init__(self, schema: DMSSchema):
34
+ def __init__(self, schema: DMSSchema, metadata: DMSMetadata | None = None):
35
35
  self.schema = schema
36
+ self.metadata = metadata
36
37
 
37
38
  @classmethod
38
39
  def from_data_model_id(cls, client: CogniteClient, data_model_id: DataModelIdentifier) -> "DMSImporter":
39
- return cls(DMSSchema.from_model_id(client, data_model_id))
40
+ """Create a DMSImporter ready to convert the given data model to rules.
41
+
42
+ Args:
43
+ client: Instantiated CogniteClient to retrieve data model.
44
+ data_model_id: Data Model to retrieve.
45
+
46
+ Returns:
47
+ DMSImporter: DMSImporter instance
48
+ """
49
+ data_models = client.data_modeling.data_models.retrieve(data_model_id, inline_views=True)
50
+ if len(data_models) == 0:
51
+ raise ValueError(f"Data model {data_model_id} not found")
52
+ data_model = data_models.latest_version()
53
+ schema = DMSSchema.from_data_model(client, data_model)
54
+ description, creator = DMSMetadata._get_description_and_creator(data_model.description)
55
+
56
+ created = ms_to_datetime(data_model.created_time)
57
+ updated = ms_to_datetime(data_model.last_updated_time)
58
+
59
+ metadata = DMSMetadata(
60
+ schema_=SchemaCompleteness.complete,
61
+ extension=ExtensionCategory.addition,
62
+ space=data_model.space,
63
+ external_id=data_model.external_id,
64
+ name=data_model.name or data_model.external_id,
65
+ version=data_model.version or "0.1.0",
66
+ updated=updated,
67
+ created=created,
68
+ creator=creator,
69
+ description=description,
70
+ default_view_version=data_model.version or "0.1.0",
71
+ )
72
+ return cls(schema, metadata)
40
73
 
41
74
  @classmethod
42
75
  def from_directory(cls, directory: str | Path) -> "DMSImporter":
@@ -138,11 +171,7 @@ class DMSImporter(BaseImporter):
138
171
  description=prop.description,
139
172
  value_type=cast(ViewPropEntity | DMSValueType, container_prop.type._type),
140
173
  nullable=container_prop.nullable,
141
- is_list=(
142
- container_prop.type.is_list
143
- if isinstance(container_prop.type, ListablePropertyType)
144
- else False
145
- ),
174
+ is_list=container_prop.type.is_list,
146
175
  default=container_prop.default_value,
147
176
  container=ContainerEntity.from_id(container.as_id()),
148
177
  container_property=prop.container_property_identifier,
@@ -172,7 +201,7 @@ class DMSImporter(BaseImporter):
172
201
  }
173
202
 
174
203
  dms_rules = DMSRules(
175
- metadata=DMSMetadata.from_data_model(data_model),
204
+ metadata=self.metadata or DMSMetadata.from_data_model(data_model),
176
205
  properties=properties,
177
206
  containers=SheetList[DMSContainer](
178
207
  data=[DMSContainer.from_container(container) for container in self.schema.containers]
@@ -68,7 +68,15 @@ class NeatValidationError(ValidationIssue, ABC):
68
68
 
69
69
  This is intended to be overridden in subclasses to handle specific error types.
70
70
  """
71
- return [DefaultPydanticError.from_pydantic_error(error) for error in errors]
71
+ all_errors = []
72
+ for error in errors:
73
+ if isinstance(ctx := error.get("ctx"), dict) and isinstance(
74
+ multi_error := ctx.get("error"), MultiValueError
75
+ ):
76
+ all_errors.extend(multi_error.errors)
77
+ else:
78
+ all_errors.append(DefaultPydanticError.from_pydantic_error(error))
79
+ return all_errors
72
80
 
73
81
 
74
82
  @dataclass(frozen=True)
@@ -26,6 +26,8 @@ __all__ = [
26
26
  "MultipleReferenceWarning",
27
27
  "HasDataFilterOnNoPropertiesViewWarning",
28
28
  "NodeTypeFilterOnParentViewWarning",
29
+ "ChangingContainerError",
30
+ "ChangingViewError",
29
31
  ]
30
32
 
31
33
 
@@ -225,6 +227,78 @@ class ContainerPropertyUsedMultipleTimesError(DMSSchemaError):
225
227
  return output
226
228
 
227
229
 
230
+ @dataclass(frozen=True)
231
+ class ChangingContainerError(DMSSchemaError):
232
+ description = "You are adding to an existing model. "
233
+ fix = "Keep the container the same"
234
+ error_name: ClassVar[str] = "ChangingContainerError"
235
+ container_id: dm.ContainerId
236
+ changed_properties: list[str] | None = None
237
+ changed_attributes: list[str] | None = None
238
+
239
+ def __post_init__(self):
240
+ # Sorting for deterministic output
241
+ if self.changed_properties:
242
+ self.changed_properties.sort()
243
+ if self.changed_attributes:
244
+ self.changed_attributes.sort()
245
+
246
+ def message(self) -> str:
247
+ if self.changed_properties:
248
+ changed = f" properties {self.changed_properties}."
249
+ elif self.changed_attributes:
250
+ changed = f" attributes {self.changed_attributes}."
251
+ else:
252
+ changed = "."
253
+ return (
254
+ f"The container {self.container_id} has changed{changed}"
255
+ "When extending model with extension set to addition or reshape, the container "
256
+ "properties must remain the same"
257
+ )
258
+
259
+ def dump(self) -> dict[str, Any]:
260
+ output = super().dump()
261
+ output["container_id"] = self.container_id.dump()
262
+ output["changed_properties"] = self.changed_properties
263
+ return output
264
+
265
+
266
+ @dataclass(frozen=True)
267
+ class ChangingViewError(DMSSchemaError):
268
+ description = "You are adding to an existing model. "
269
+ fix = "Keep the view the same"
270
+ error_name: ClassVar[str] = "ChangingViewError"
271
+ view_id: dm.ViewId
272
+ changed_properties: list[str] | None = None
273
+ changed_attributes: list[str] | None = None
274
+
275
+ def __post_init__(self):
276
+ # Sorting for deterministic output
277
+ if self.changed_properties:
278
+ self.changed_properties.sort()
279
+ if self.changed_attributes:
280
+ self.changed_attributes.sort()
281
+
282
+ def message(self) -> str:
283
+ if self.changed_properties:
284
+ changed = f" properties {self.changed_properties}."
285
+ elif self.changed_attributes:
286
+ changed = f" attributes {self.changed_attributes}."
287
+ else:
288
+ changed = "."
289
+
290
+ return (
291
+ f"The view {self.view_id} has changed{changed}"
292
+ "When extending model with extension set to addition, the view properties must remain the same"
293
+ )
294
+
295
+ def dump(self) -> dict[str, Any]:
296
+ output = super().dump()
297
+ output["view_id"] = self.view_id.dump()
298
+ output["difference"] = self.changed_properties
299
+ return output
300
+
301
+
228
302
  @dataclass(frozen=True)
229
303
  class DirectRelationListWarning(DMSSchemaWarning):
230
304
  description = "The container property is set to a direct relation list, which is not supported by the CDF API"
@@ -10,10 +10,21 @@ import types
10
10
  from abc import abstractmethod
11
11
  from collections.abc import Callable, Iterator
12
12
  from functools import wraps
13
- from typing import Any, ClassVar, Generic, TypeAlias, TypeVar
13
+ from typing import Annotated, Any, ClassVar, Generic, TypeAlias, TypeVar
14
14
 
15
15
  import pandas as pd
16
- from pydantic import BaseModel, ConfigDict, Field, HttpUrl, constr, field_validator, model_serializer, model_validator
16
+ from pydantic import (
17
+ BaseModel,
18
+ BeforeValidator,
19
+ ConfigDict,
20
+ Field,
21
+ HttpUrl,
22
+ PlainSerializer,
23
+ constr,
24
+ field_validator,
25
+ model_serializer,
26
+ model_validator,
27
+ )
17
28
  from pydantic.fields import FieldInfo
18
29
 
19
30
  from cognite.neat.rules.models._rules._types import ClassType
@@ -318,3 +329,14 @@ class SheetList(BaseModel, Generic[T_Entity]):
318
329
  def mandatory_fields(cls, use_alias=False) -> set[str]:
319
330
  """Returns a set of mandatory fields for the model."""
320
331
  return _get_required_fields(cls, use_alias)
332
+
333
+
334
+ ExtensionCategoryType = Annotated[
335
+ ExtensionCategory,
336
+ PlainSerializer(
337
+ lambda v: v.value if isinstance(v, ExtensionCategory) else v,
338
+ return_type=str,
339
+ when_used="unless-none",
340
+ ),
341
+ BeforeValidator(lambda v: ExtensionCategory(v) if isinstance(v, str) else v),
342
+ ]
@@ -10,7 +10,6 @@ from typing import TYPE_CHECKING, Any, ClassVar, Literal, cast
10
10
  from cognite.client import data_modeling as dm
11
11
  from cognite.client.data_classes.data_modeling import PropertyType as CognitePropertyType
12
12
  from cognite.client.data_classes.data_modeling.containers import BTreeIndex
13
- from cognite.client.data_classes.data_modeling.data_types import ListablePropertyType
14
13
  from cognite.client.data_classes.data_modeling.views import SingleReverseDirectRelationApply, ViewPropertyApply
15
14
  from pydantic import Field, field_validator, model_serializer, model_validator
16
15
  from pydantic_core.core_schema import SerializationInfo, ValidationInfo
@@ -134,18 +133,21 @@ class DMSMetadata(BaseMetadata):
134
133
  )
135
134
 
136
135
  @classmethod
137
- def from_data_model(cls, data_model: dm.DataModelApply) -> "DMSMetadata":
138
- description = None
139
- if data_model.description and (description_match := re.search(r"Creator: (.+)", data_model.description)):
136
+ def _get_description_and_creator(cls, description_raw: str | None) -> tuple[str | None, list[str]]:
137
+ if description_raw and (description_match := re.search(r"Creator: (.+)", description_raw)):
140
138
  creator = description_match.group(1).split(", ")
141
- data_model.description.replace(f" Creator: {', '.join(creator)}", "")
142
- elif data_model.description:
139
+ description = description_raw.replace(description_match.string, "").strip() or None
140
+ elif description_raw:
143
141
  creator = ["MISSING"]
144
- description = data_model.description
142
+ description = description_raw
145
143
  else:
146
144
  creator = ["MISSING"]
147
- description = "Missing description"
145
+ description = None
146
+ return description, creator
148
147
 
148
+ @classmethod
149
+ def from_data_model(cls, data_model: dm.DataModelApply) -> "DMSMetadata":
150
+ description, creator = cls._get_description_and_creator(data_model.description)
149
151
  return cls(
150
152
  schema_=SchemaCompleteness.complete,
151
153
  space=data_model.space,
@@ -490,12 +492,117 @@ class DMSRules(BaseRules):
490
492
  raise issues.MultiValueError(errors)
491
493
  return self
492
494
 
495
+ @model_validator(mode="after")
496
+ def validate_extension(self) -> "DMSRules":
497
+ if self.metadata.schema_ is not SchemaCompleteness.extended:
498
+ return self
499
+ if not self.reference:
500
+ raise ValueError("The schema is set to 'extended', but no reference rules are provided to validate against")
501
+ is_solution = self.metadata.space != self.reference.metadata.space
502
+ if is_solution:
503
+ return self
504
+ if self.metadata.extension is ExtensionCategory.rebuild:
505
+ # Everything is allowed
506
+ return self
507
+ # Is an extension of an existing model.
508
+ user_schema = self.as_schema()
509
+ ref_schema = self.reference.as_schema()
510
+ new_containers = {container.as_id(): container for container in user_schema.containers}
511
+ existing_containers = {container.as_id(): container for container in ref_schema.containers}
512
+
513
+ errors: list[issues.NeatValidationError] = []
514
+ for container_id, container in new_containers.items():
515
+ existing_container = existing_containers.get(container_id)
516
+ if not existing_container or existing_container == container:
517
+ # No problem
518
+ continue
519
+ new_dumped = container.dump()
520
+ existing_dumped = existing_container.dump()
521
+ changed_attributes, changed_properties = self._changed_attributes_and_properties(
522
+ new_dumped, existing_dumped
523
+ )
524
+ errors.append(
525
+ issues.dms.ChangingContainerError(
526
+ container_id=container_id,
527
+ changed_properties=changed_properties or None,
528
+ changed_attributes=changed_attributes or None,
529
+ )
530
+ )
531
+
532
+ if self.metadata.extension is ExtensionCategory.reshape and errors:
533
+ raise issues.MultiValueError(errors)
534
+ elif self.metadata.extension is ExtensionCategory.reshape:
535
+ # Reshape allows changes to views
536
+ return self
537
+
538
+ new_views = {view.as_id(): view for view in user_schema.views}
539
+ existing_views = {view.as_id(): view for view in ref_schema.views}
540
+ for view_id, view in new_views.items():
541
+ existing_view = existing_views.get(view_id)
542
+ if not existing_view or existing_view == view:
543
+ # No problem
544
+ continue
545
+ changed_attributes, changed_properties = self._changed_attributes_and_properties(
546
+ view.dump(), existing_view.dump()
547
+ )
548
+ errors.append(
549
+ issues.dms.ChangingViewError(
550
+ view_id=view_id,
551
+ changed_properties=changed_properties or None,
552
+ changed_attributes=changed_attributes or None,
553
+ )
554
+ )
555
+
556
+ if errors:
557
+ raise issues.MultiValueError(errors)
558
+ return self
559
+
560
+ @staticmethod
561
+ def _changed_attributes_and_properties(
562
+ new_dumped: dict[str, Any], existing_dumped: dict[str, Any]
563
+ ) -> tuple[list[str], list[str]]:
564
+ """Helper method to find the changed attributes and properties between two containers or views."""
565
+ new_attributes = {key: value for key, value in new_dumped.items() if key != "properties"}
566
+ existing_attributes = {key: value for key, value in existing_dumped.items() if key != "properties"}
567
+ changed_attributes = [key for key in new_attributes if new_attributes[key] != existing_attributes.get(key)]
568
+ new_properties = new_dumped.get("properties", {})
569
+ existing_properties = existing_dumped.get("properties", {})
570
+ changed_properties = [prop for prop in new_properties if new_properties[prop] != existing_properties.get(prop)]
571
+ return changed_attributes, changed_properties
572
+
493
573
  @model_validator(mode="after")
494
574
  def validate_schema(self) -> "DMSRules":
495
- if self.metadata.schema_ is not SchemaCompleteness.complete:
575
+ if self.metadata.schema_ is SchemaCompleteness.partial:
496
576
  return self
577
+ elif self.metadata.schema_ is SchemaCompleteness.complete:
578
+ rules: DMSRules = self
579
+ elif self.metadata.schema_ is SchemaCompleteness.extended:
580
+ if not self.reference:
581
+ raise ValueError(
582
+ "The schema is set to 'extended', but no reference rules are provided to validate against"
583
+ )
584
+ # This is an extension of the reference rules, we need to merge the two
585
+ rules = self.copy(deep=True)
586
+ rules.properties.extend(self.reference.properties.data)
587
+ existing_views = {view.view.as_id(False) for view in rules.views}
588
+ rules.views.extend([view for view in self.reference.views if view.view.as_id(False) not in existing_views])
589
+ if rules.containers and self.reference.containers:
590
+ existing_containers = {
591
+ container.container.as_id(self.metadata.space) for container in rules.containers.data
592
+ }
593
+ rules.containers.extend(
594
+ [
595
+ container
596
+ for container in self.reference.containers
597
+ if container.container.as_id(self.reference.metadata.space) not in existing_containers
598
+ ]
599
+ )
600
+ elif not rules.containers and self.reference.containers:
601
+ rules.containers = self.reference.containers
602
+ else:
603
+ raise ValueError("Unknown schema completeness")
497
604
 
498
- schema = self.as_schema()
605
+ schema = rules.as_schema()
499
606
  errors = schema.validate()
500
607
  if errors:
501
608
  raise issues.MultiValueError(errors)
@@ -553,12 +660,16 @@ class DMSRules(BaseRules):
553
660
  )
554
661
  containers.append(dumped)
555
662
 
556
- return {
663
+ output = {
557
664
  "Metadata" if info.by_alias else "metadata": self.metadata.model_dump(**kwargs),
558
665
  "Properties" if info.by_alias else "properties": properties,
559
666
  "Views" if info.by_alias else "views": views,
560
667
  "Containers" if info.by_alias else "containers": containers,
668
+ "is_reference": self.is_reference,
561
669
  }
670
+ if self.reference is not None:
671
+ output["Reference" if info.by_alias else "reference"] = self.reference.model_dump(**kwargs)
672
+ return output
562
673
 
563
674
  def as_schema(self, include_pipeline: bool = False, instance_space: str | None = None) -> DMSSchema:
564
675
  return _DMSExporter(include_pipeline, instance_space).to_schema(self)
@@ -872,10 +983,7 @@ class _DMSExporter:
872
983
  )
873
984
  else:
874
985
  type_: CognitePropertyType
875
- if issubclass(type_cls, ListablePropertyType):
876
- type_ = type_cls(is_list=prop.is_list or False)
877
- else:
878
- type_ = cast(CognitePropertyType, type_cls())
986
+ type_ = type_cls(is_list=prop.is_list or False)
879
987
  container.properties[prop_name] = dm.ContainerProperty(
880
988
  type=type_,
881
989
  nullable=prop.nullable if prop.nullable is not None else True,
@@ -63,6 +63,10 @@ class DMSSchema:
63
63
  if len(data_models) == 0:
64
64
  raise ValueError(f"Data model {data_model_id} not found")
65
65
  data_model = data_models.latest_version()
66
+ return cls.from_data_model(client, data_model)
67
+
68
+ @classmethod
69
+ def from_data_model(cls, client: CogniteClient, data_model: dm.DataModel) -> "DMSSchema":
66
70
  views = dm.ViewList(data_model.views)
67
71
  container_ids = views.referenced_containers()
68
72
  containers = client.data_modeling.containers.retrieve(list(container_ids))
@@ -267,7 +271,7 @@ class DMSSchema:
267
271
  if isinstance(item, dm.ContainerApply | dm.ViewApply | dm.DataModelApply | dm.NodeApply | RawTableWrite):
268
272
  identifier = item.as_id().as_tuple()
269
273
  if len(identifier) == 3 and identifier[2] is None:
270
- return identifier[:2]
274
+ return identifier[:2] # type: ignore[misc]
271
275
  return cast(tuple[str, str] | tuple[str, str, str], identifier)
272
276
  elif isinstance(item, dm.SpaceApply):
273
277
  return item.space
@@ -1,6 +1,7 @@
1
+ import math
1
2
  from typing import Any, ClassVar
2
3
 
3
- from pydantic import Field, model_serializer
4
+ from pydantic import Field, field_serializer, field_validator, model_serializer
4
5
  from pydantic_core.core_schema import SerializationInfo
5
6
 
6
7
  from ._types import ParentClassType, PropertyType, SemanticValueType, StrOrListType
@@ -24,6 +25,18 @@ class DomainProperty(SheetEntity):
24
25
  min_count: int | None = Field(alias="Min Count", default=None)
25
26
  max_count: int | float | None = Field(alias="Max Count", default=None)
26
27
 
28
+ @field_serializer("max_count", when_used="json-unless-none")
29
+ def serialize_max_count(self, value: int | float | None) -> int | float | None | str:
30
+ if isinstance(value, float) and math.isinf(value):
31
+ return None
32
+ return value
33
+
34
+ @field_validator("max_count", mode="before")
35
+ def parse_max_count(cls, value: int | float | None) -> int | float | None:
36
+ if value is None:
37
+ return float("inf")
38
+ return value
39
+
27
40
 
28
41
  class DomainClass(SheetEntity):
29
42
  description: str | None = Field(None, alias="Description")
@@ -1,3 +1,4 @@
1
+ import math
1
2
  import re
2
3
  import sys
3
4
  import warnings
@@ -5,7 +6,7 @@ from collections import defaultdict
5
6
  from datetime import datetime
6
7
  from typing import TYPE_CHECKING, Any, ClassVar, Literal, cast
7
8
 
8
- from pydantic import Field, field_validator, model_serializer, model_validator
9
+ from pydantic import Field, field_serializer, field_validator, model_serializer, model_validator
9
10
  from pydantic_core.core_schema import SerializationInfo
10
11
  from rdflib import Namespace
11
12
 
@@ -47,6 +48,7 @@ from ._types._base import Unknown
47
48
  from .base import (
48
49
  BaseMetadata,
49
50
  ExtensionCategory,
51
+ ExtensionCategoryType,
50
52
  MatchType,
51
53
  RoleTypes,
52
54
  RuleModel,
@@ -69,7 +71,7 @@ else:
69
71
  class InformationMetadata(BaseMetadata):
70
72
  role: ClassVar[RoleTypes] = RoleTypes.information_architect
71
73
  schema_: SchemaCompleteness = Field(alias="schema")
72
- extension: ExtensionCategory | None = ExtensionCategory.addition
74
+ extension: ExtensionCategoryType | None = ExtensionCategory.addition
73
75
  prefix: PrefixType
74
76
  namespace: NamespaceType
75
77
 
@@ -156,6 +158,18 @@ class InformationProperty(SheetEntity):
156
158
  )
157
159
  comment: str | None = Field(alias="Comment", default=None)
158
160
 
161
+ @field_serializer("max_count", when_used="json-unless-none")
162
+ def serialize_max_count(self, value: int | float | None) -> int | float | None | str:
163
+ if isinstance(value, float) and math.isinf(value):
164
+ return None
165
+ return value
166
+
167
+ @field_validator("max_count", mode="before")
168
+ def parse_max_count(cls, value: int | float | None) -> int | float | None:
169
+ if value is None:
170
+ return float("inf")
171
+ return value
172
+
159
173
  @model_validator(mode="after")
160
174
  def is_valid_rule(self):
161
175
  # TODO: Can we skip rule_type and simply try to parse the rule and if it fails, raise an error?
@@ -1,5 +1,5 @@
1
1
  from dataclasses import dataclass, field
2
- from typing import Literal, overload
2
+ from typing import Literal, cast, overload
3
3
 
4
4
  import pandas as pd
5
5
  from openpyxl import load_workbook
@@ -58,7 +58,7 @@ def read_individual_sheet(
58
58
  expected_headers: list[str] | None = None,
59
59
  ) -> tuple[list[dict], SpreadsheetRead] | list[dict]:
60
60
  if expected_headers:
61
- target_row = _get_row_number(load_workbook(excel_file)[sheet_name], expected_headers)
61
+ target_row = _get_row_number(cast(Worksheet, load_workbook(excel_file)[sheet_name]), expected_headers)
62
62
  skiprows = target_row - 1 if target_row is not None else 0
63
63
  else:
64
64
  skiprows = 0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cognite-neat
3
- Version: 0.72.3
3
+ Version: 0.73.0
4
4
  Summary: Knowledge graph transformation
5
5
  Home-page: https://cognite-neat.readthedocs-hosted.com/
6
6
  License: Apache-2.0
@@ -19,7 +19,7 @@ Provides-Extra: graphql
19
19
  Provides-Extra: oxi
20
20
  Requires-Dist: PyYAML
21
21
  Requires-Dist: backports.strenum (>=1.2,<2.0) ; python_version < "3.11"
22
- Requires-Dist: cognite-sdk (>=7.28.2,<8.0.0)
22
+ Requires-Dist: cognite-sdk (>=7.37.0,<8.0.0)
23
23
  Requires-Dist: deepdiff
24
24
  Requires-Dist: exceptiongroup (>=1.1.3,<2.0.0) ; python_version < "3.11"
25
25
  Requires-Dist: fastapi (>=0.100,<0.101)
@@ -1,5 +1,5 @@
1
1
  cognite/neat/__init__.py,sha256=v-rRiDOgZ3sQSMQKq0vgUQZvpeOkoHFXissAx6Ktg84,61
2
- cognite/neat/_version.py,sha256=AZKRaRERnGxRM4Vpp2s8T1LNhTACd4GALrf4Zjj-jCM,23
2
+ cognite/neat/_version.py,sha256=TEb-1D6cIr_qgLP1HaUQ8hIQZ7HNg_9m8A0caIDWuDQ,23
3
3
  cognite/neat/app/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  cognite/neat/app/api/asgi/metrics.py,sha256=nxFy7L5cChTI0a-zkCiJ59Aq8yLuIJp5c9Dg0wRXtV0,152
5
5
  cognite/neat/app/api/configuration.py,sha256=xnKdBE_dtq1nRvKa79YGA_wimI5UhoSRuBQz4LkLzQw,4606
@@ -50,7 +50,7 @@ cognite/neat/graph/exceptions.py,sha256=G899CjKepLB2dxgwH4585cqqtjvbyH6MLTM2aaqZ
50
50
  cognite/neat/graph/extractor/__init__.py,sha256=wqCiqz-sXhUpTL5LRcrl_KFTNF0yTBRY4EzXT8pyCvA,258
51
51
  cognite/neat/graph/extractor/_base.py,sha256=TOXDnlqske8DgnJwA0THDVRgmR79Acjm56yF0E-2w7I,356
52
52
  cognite/neat/graph/extractor/_dexpi.py,sha256=oq7SzCQF4ocMEoPcWV6zDJ7UhtlhNp55rwcGiplFxVY,11232
53
- cognite/neat/graph/extractor/_graph_capturing_sheet.py,sha256=m07oSLJjZXDUwR70hEuVnb9OEcnMKw309bpSlBrT0uw,17408
53
+ cognite/neat/graph/extractor/_graph_capturing_sheet.py,sha256=JCxzapFxdp4MF8rLMrDfOvHw7YOigLabCk1nv_Lr8so,17652
54
54
  cognite/neat/graph/extractor/_mock_graph_generator.py,sha256=C-6jP4CBtaTWHhk03mSdFEO0eAPPZ8orL5WSt0w-5FA,14872
55
55
  cognite/neat/graph/extractors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
56
  cognite/neat/graph/extractors/_base.py,sha256=TOXDnlqske8DgnJwA0THDVRgmR79Acjm56yF0E-2w7I,356
@@ -79,7 +79,6 @@ cognite/neat/graph/transformation/entity_matcher.py,sha256=RdOz6WQ2VApmFpaztTXNl
79
79
  cognite/neat/graph/transformation/query_generator/__init__.py,sha256=9N7RIlM8Pf-mJXzK8ulBe-eAa3yeHYBsBFQF7geTgWE,73
80
80
  cognite/neat/graph/transformation/query_generator/sparql.py,sha256=-611a0KlanTDfUUFlk2un2pRmN1UkBT0gPtBcoO-StA,18659
81
81
  cognite/neat/graph/transformation/transformer.py,sha256=ED_nCTBLh0kx-jbZMYHDsWj3X-RUqxJXbrogWZCMlnM,14673
82
- cognite/neat/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
83
82
  cognite/neat/rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
84
83
  cognite/neat/rules/_analysis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
85
84
  cognite/neat/rules/_analysis/_base.py,sha256=xzRf52rKf5P1EajWS2MoBhzO_FZG3qjNRKlIffCFMpU,674
@@ -89,9 +88,9 @@ cognite/neat/rules/analysis.py,sha256=UL3cvd5jaUkIOXX0nNE5WQ7zr3VvdJFpW_1q2i869F
89
88
  cognite/neat/rules/examples/Rules-Nordic44-to-TNT.xlsx,sha256=pT2skrX3uWZtw-HIfIipVuPVr3bgb8zOAIqkguVjhCI,58987
90
89
  cognite/neat/rules/examples/Rules-Nordic44-to-graphql.xlsx,sha256=eo6K177Xrz0CKyUFsbsGF8qZJsJI1Qf1JrzozdbmkeU,80226
91
90
  cognite/neat/rules/examples/__init__.py,sha256=rLVPMLdcxpiW6LzfIgJodDKXfpdoYbD4Q60nmK_wAqU,858
92
- cognite/neat/rules/examples/power-grid-containers.yaml,sha256=r5q9t9nTFRQMwmm8ptY7f0yr6VgtEhEC26xhoS4QKC0,2538
91
+ cognite/neat/rules/examples/power-grid-containers.yaml,sha256=5Q_TCM8YuLOEOxoCWqosvYJeB4NYBA1EqlE7pyo0AOA,2598
93
92
  cognite/neat/rules/examples/power-grid-example.xlsx,sha256=gUklgEhawhJ-EquwZpNp7dFvrLGAOzbKIGh8hTObxMg,77055
94
- cognite/neat/rules/examples/power-grid-model.yaml,sha256=e9Z5_HFWn8l_h4nUL3fABhoifoKFaYhus6dtgkCrj5o,5501
93
+ cognite/neat/rules/examples/power-grid-model.yaml,sha256=ttCtAuQYrOkWBHttQo0w5WHWpzbnM0Z7MxG_ubtwTh0,5567
95
94
  cognite/neat/rules/examples/rules-template.xlsx,sha256=buyxUbY5N5H52LgKST-D2FVvpjEMW4jC2chAHToknNI,75865
96
95
  cognite/neat/rules/examples/sheet2cdf-transformation-rules.xlsx,sha256=_a93q8Yu7nv7Bq3I__RHD7pcdbfrv7urlp_AdgP2o3o,52433
97
96
  cognite/neat/rules/examples/skos-rules.xlsx,sha256=XjdBpbfUrJnniTCCQLOBW-8OmJyGpIT1Ig-DHQG3x04,26008
@@ -102,8 +101,8 @@ cognite/neat/rules/exporter/__init__.py,sha256=StzV_2Kx5e1GaZOW8ZGMgKZpdzq12rWsc
102
101
  cognite/neat/rules/exporter/_base.py,sha256=ohF0ri6wvxLMylZD7FNTmXdcjf-9p9vZgOd_Fl1I154,1439
103
102
  cognite/neat/rules/exporter/_core/__init__.py,sha256=XpimbyjA-Tub7ndk2r8yp__aXwdMwtKMuYMNsMKkAL8,102
104
103
  cognite/neat/rules/exporter/_core/rules2labels.py,sha256=oXr-J4-rN5QdnigK45ydWwaXrCEdVBbykkmUx7Aw8tY,764
105
- cognite/neat/rules/exporter/_rules2dms.py,sha256=SOb8AYfj-r-VGDxXgxlR7pQvNXWg8m4Yi79uOvetYCY,37099
106
- cognite/neat/rules/exporter/_rules2excel.py,sha256=WFN56mB6MNmEjUpvDfiQ8P-RpENePixKC4cJl9imvcg,8220
104
+ cognite/neat/rules/exporter/_rules2dms.py,sha256=ytXspz0jl42tQEUcrfrEnogpmOeNyKIqZVrA0K5nqD0,36770
105
+ cognite/neat/rules/exporter/_rules2excel.py,sha256=igThjNoFclPUBd8j_bP9OEPDXNiTgtdTD-ZxvtyZcqE,8305
107
106
  cognite/neat/rules/exporter/_rules2graphql.py,sha256=5MLWDw-7YfRwonUjqlLjxhgZOYzkU_GnyKuOvxAEEh4,6215
108
107
  cognite/neat/rules/exporter/_rules2ontology.py,sha256=WD-Lu-1MPRU1HNCzQ3DjxX9XUtF8675rERGqJAFXD0s,18414
109
108
  cognite/neat/rules/exporter/_rules2pydantic_models.py,sha256=VEjwGvleqkaSmNcve-2YKZ9Z2Np5KnX0tASphmOpZpo,28745
@@ -114,14 +113,14 @@ cognite/neat/rules/exporters/__init__.py,sha256=Gn3CjkVKHJF9Po1ZPH4wAJ-sRW9up7b2
114
113
  cognite/neat/rules/exporters/_base.py,sha256=aRCzRjGDoXILkGvASev2UjVvLXrVCRTFiKgYwzJZqAo,1518
115
114
  cognite/neat/rules/exporters/_models.py,sha256=f_RbFhoyD27z6Dsk4upYMHp7JHZRCfIvZsEzH5JSvtc,1645
116
115
  cognite/neat/rules/exporters/_rules2dms.py,sha256=JPwaD42STCLi3N2zH8fKeKhWTNUsT-KsM3t56Pzap3A,11854
117
- cognite/neat/rules/exporters/_rules2excel.py,sha256=uK-aS9sBwXUCFQLIO1jciBQrB4w7ySF6MafDqG23hrQ,9080
116
+ cognite/neat/rules/exporters/_rules2excel.py,sha256=JdEEutD0y80KLRAeWoo2GB-1-UtAughN8cTS8AJIjr8,13146
118
117
  cognite/neat/rules/exporters/_rules2ontology.py,sha256=PqE62FIPWFWUkivyxzOUVFf9Nx8yVJghZBB9A_7us4Q,19922
119
118
  cognite/neat/rules/exporters/_rules2yaml.py,sha256=HKgFlpgD7U2vWLQC_VdSvkDk19QgeVAd_J8wQ9ZgrN8,3038
120
119
  cognite/neat/rules/exporters/_validation.py,sha256=E6nn1QicaJpDFiXrYRzwTqsJ5VULVzzeqhCBVlRxH4M,4092
121
120
  cognite/neat/rules/importer/__init__.py,sha256=h1owL8pBPEtOmlIFAdkqAABH1A_Op5plh8C53og0uag,636
122
121
  cognite/neat/rules/importer/_base.py,sha256=xNFBe33fwJnq3pp2dKqyKnNinwJG7liXvmEUvjtHqB8,2316
123
122
  cognite/neat/rules/importer/_dict2rules.py,sha256=nZuF26CXG8lX_Si5krYLq-CLCKUtvmvfvsV5fky0EPk,6469
124
- cognite/neat/rules/importer/_dms2rules.py,sha256=SHvwQdascUJUqxup7R3kFOv7xksPhEVk16GYHGIRzPs,7890
123
+ cognite/neat/rules/importer/_dms2rules.py,sha256=jDvKkaztBh0TfaKbOmPsM9I7CwJEd6ZAGTnRVIcho6U,7713
125
124
  cognite/neat/rules/importer/_graph2rules.py,sha256=yZqgmNFKWbG-QXfuumn6MgQrCWc3MMgyFj8uXcVSAOs,12097
126
125
  cognite/neat/rules/importer/_json2rules.py,sha256=ko0sC3-xO3ne_PQn5aF0L5SsA8m48SN2bnqmBgjkylc,1610
127
126
  cognite/neat/rules/importer/_owl2rules/__init__.py,sha256=tdGcrgtozdQyST-pTlxIa4cLBNTLvtk1nNYR4vOdFSw,63
@@ -134,7 +133,7 @@ cognite/neat/rules/importer/_xsd2rules.py,sha256=iKglhTX8u61j1GqNNKh_67qaw8-ZIam
134
133
  cognite/neat/rules/importer/_yaml2rules.py,sha256=BnuEbhatcJrLMfPNgzLFwNWbwbIib-kIArcoC6aRMGI,1628
135
134
  cognite/neat/rules/importers/__init__.py,sha256=zqNbGpvdVhYkLjWx1i9dJ3FXzYGtuQyTydUYsj-BndQ,408
136
135
  cognite/neat/rules/importers/_base.py,sha256=qd9jg9n87lOTzNGdPp5p4taE4vncPAGwTm-1Vfbgj3s,4268
137
- cognite/neat/rules/importers/_dms2rules.py,sha256=E8nhnFzBPtehmPt2gAgDPY6ZVL2mXgUIQ24m-i3VahE,9138
136
+ cognite/neat/rules/importers/_dms2rules.py,sha256=QN7fD-bld9e8eU-xS8R0g4A8cQf1MItLOyhwbAX2yGc,10427
138
137
  cognite/neat/rules/importers/_dtdl2rules/__init__.py,sha256=CNR-sUihs2mnR1bPMKs3j3L4ds3vFTsrl6YycExZTfU,68
139
138
  cognite/neat/rules/importers/_dtdl2rules/_unit_lookup.py,sha256=wW4saKva61Q_i17guY0dc4OseJDQfqHy_QZBtm0OD6g,12134
140
139
  cognite/neat/rules/importers/_dtdl2rules/dtdl_converter.py,sha256=o-n9KBWvA5Y7Fj0nW9KCnR1sz_PLtLKn2MTJPRnTcFc,12554
@@ -148,8 +147,8 @@ cognite/neat/rules/importers/_owl2rules/_owl2rules.py,sha256=xVGOo5wnoXLI3E9OUID
148
147
  cognite/neat/rules/importers/_spreadsheet2rules.py,sha256=QIYTW-3Uc5ffFp3rJNyiqigWvc6x2eZTvhxqrXlGGSs,11481
149
148
  cognite/neat/rules/importers/_yaml2rules.py,sha256=sIaYY3Zo--v1cXSu65n4ZPv47cS-5InvSbpkw3Ahov4,4198
150
149
  cognite/neat/rules/issues/__init__.py,sha256=Ms6jgCxCezc5IgTOwCFtXQPtoVFfOvdcXj84_rs917I,563
151
- cognite/neat/rules/issues/base.py,sha256=dS4lmbW9VQ8awgOIB-Ah9z9LdNbCa-fPd8tbyPb8sM4,5856
152
- cognite/neat/rules/issues/dms.py,sha256=WB8N6MPbLFxdScJLGGY_zdErcrEXJnAsMladMB5aKa4,12722
150
+ cognite/neat/rules/issues/base.py,sha256=qgoZ3qr35XLtdmkWWewf3sCYOW0sCkJrN9VpFvX-WzY,6158
151
+ cognite/neat/rules/issues/dms.py,sha256=O4JBbxOchv9wa9MGnoo1JGtc-H5Jo77izjEo3RaF7Ck,15314
153
152
  cognite/neat/rules/issues/fileread.py,sha256=n-GZaULOJF_MKkBIh1maaOuGZXOvZYw7Y6fDAS0jrBI,4492
154
153
  cognite/neat/rules/issues/formatters.py,sha256=_pSogWtfkt2JK0PZgWQffbj2On8vumFNshxOKAi5fYw,3346
155
154
  cognite/neat/rules/issues/importing.py,sha256=GqUywhBD840Fbc4DD5L2I0oEllJ78MTjpmXogVEjihA,7493
@@ -162,11 +161,11 @@ cognite/neat/rules/models/_rules/_types/__init__.py,sha256=Px0uB5fqk-8qH-HRi0Zvg
162
161
  cognite/neat/rules/models/_rules/_types/_base.py,sha256=okf8ebEcKrXf1rZQNp_cMsTS4pNKdJk2QMz_8-wi_so,16681
163
162
  cognite/neat/rules/models/_rules/_types/_field.py,sha256=dOVAU1jWCupFVnrYYwLfI-nNUC4rv4vXHMzpiObtWiw,10295
164
163
  cognite/neat/rules/models/_rules/_types/_value.py,sha256=ubyWmU6neyNxx17fqcciIjyB-CIpYNUuM97Xh2sVrYo,6308
165
- cognite/neat/rules/models/_rules/base.py,sha256=9DgtdCmpz84sMFxZB_stWkalVbjA4HQKsTMpSjjOVLU,10635
166
- cognite/neat/rules/models/_rules/dms_architect_rules.py,sha256=bsuwCFiVFnDQuoAdwTu_-kp5oJGepuD24kLO9B_5YYc,50424
167
- cognite/neat/rules/models/_rules/dms_schema.py,sha256=-ru40beGY2WJvf9_sd5eO2Wh8x2qLQ2UmgzExloBWac,30229
168
- cognite/neat/rules/models/_rules/domain_rules.py,sha256=mOE4M6wOurmnAehxbnxvP9vIVXsFuKSiyMmD1shXKpA,2051
169
- cognite/neat/rules/models/_rules/information_rules.py,sha256=kETaZ3HsPw0EFXIeU-YhfEZeK4FhAh5RvNQAWbrcE-E,21026
164
+ cognite/neat/rules/models/_rules/base.py,sha256=F79FX1qCIqXlAPg0yAlpC6753dcRqoy9UB5BB3zWbDY,11025
165
+ cognite/neat/rules/models/_rules/dms_architect_rules.py,sha256=K5S9Us4hlaOVkqd4IzxsZXsuYv_4M3H107HWm3M_Sao,55886
166
+ cognite/neat/rules/models/_rules/dms_schema.py,sha256=dOzOr5iZyiOWFROo6706Z8aRvR6FSvPgRxWXSuuje3s,30418
167
+ cognite/neat/rules/models/_rules/domain_rules.py,sha256=HSZWk4NTfnzX_LlOdA5HAMkiHA5r4BZxRkGOWTFp79Y,2566
168
+ cognite/neat/rules/models/_rules/information_rules.py,sha256=sCqWt0OEPT8Ygqc7VfRwYVLcbJMOHUzDuhVWnQ4XO6A,21555
170
169
  cognite/neat/rules/models/raw_rules.py,sha256=Y7ZVKyvLhX0s6WdwztbFiYFj4EijGEfjtBmv0ibRmmg,12368
171
170
  cognite/neat/rules/models/rdfpath.py,sha256=kk1dqxl3n2W_vSQtpSJri2O4nCXnCDZB2lhLsLV1PN0,7344
172
171
  cognite/neat/rules/models/rules.py,sha256=ECKrQEtoiQqtR8W-rKMAWmODpHeHhI8Qzf3TwCa3dy8,51063
@@ -181,7 +180,7 @@ cognite/neat/utils/cdf_loaders/_data_modeling.py,sha256=JQO0E1_PSibUP0JiaX4Kdljw
181
180
  cognite/neat/utils/cdf_loaders/_ingestion.py,sha256=GqIJ7JsFuM4TYK_AFysEwS1e5CaVWfq5NqYPEUnId5s,6319
182
181
  cognite/neat/utils/cdf_loaders/data_classes.py,sha256=0apspfwVlFltYOZfmk_PNknS3Z3SCxVr3kkvXH0bfPA,3844
183
182
  cognite/neat/utils/exceptions.py,sha256=-w4cAcvcoWLf-_ZwAl7QV_NysfqtQzIOd1Ti-mpxJgM,981
184
- cognite/neat/utils/spreadsheet.py,sha256=tp9HSuzMWik69iHRCspw8zUy-U2LLAFYaV4biiKm-zU,2699
183
+ cognite/neat/utils/spreadsheet.py,sha256=e9btpmZ68rrbc2h4ML44cpOyA7PQM0qCqMfxqHaKLr4,2722
185
184
  cognite/neat/utils/text.py,sha256=4bg1_Q0lg7KsoxaDOvXrVyeY78BJN8i-27BlyDzUCls,3082
186
185
  cognite/neat/utils/utils.py,sha256=E7CmZzPEYmfjsEiAxN9thNrCDCvw16r8PKODUgBR2Fk,12826
187
186
  cognite/neat/utils/xml.py,sha256=ppLT3lQKVp8wOP-m8-tFY8uB2P4R76l7R_-kUtsABng,992
@@ -227,8 +226,8 @@ cognite/neat/workflows/steps_registry.py,sha256=PZVoHX4d6Vmjz6XzUFnFFWMCnrVnqkUC
227
226
  cognite/neat/workflows/tasks.py,sha256=dqlJwKAb0jlkl7abbY8RRz3m7MT4SK8-7cntMWkOYjw,788
228
227
  cognite/neat/workflows/triggers.py,sha256=_BLNplzoz0iic367u1mhHMHiUrCwP-SLK6_CZzfODX0,7071
229
228
  cognite/neat/workflows/utils.py,sha256=gKdy3RLG7ctRhbCRwaDIWpL9Mi98zm56-d4jfHDqP1E,453
230
- cognite_neat-0.72.3.dist-info/LICENSE,sha256=W8VmvFia4WHa3Gqxq1Ygrq85McUNqIGDVgtdvzT-XqA,11351
231
- cognite_neat-0.72.3.dist-info/METADATA,sha256=4KC49y0DSL83A7qqb5OkxQv78ryoxQR22uLNhJs24Js,9321
232
- cognite_neat-0.72.3.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
233
- cognite_neat-0.72.3.dist-info/entry_points.txt,sha256=61FPqiWb25vbqB0KI7znG8nsg_ibLHBvTjYnkPvNFso,50
234
- cognite_neat-0.72.3.dist-info/RECORD,,
229
+ cognite_neat-0.73.0.dist-info/LICENSE,sha256=W8VmvFia4WHa3Gqxq1Ygrq85McUNqIGDVgtdvzT-XqA,11351
230
+ cognite_neat-0.73.0.dist-info/METADATA,sha256=M-VcOy7BmI2OfbtquJljKUwpda3GrWrV2P_GkyR5MwE,9321
231
+ cognite_neat-0.73.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
232
+ cognite_neat-0.73.0.dist-info/entry_points.txt,sha256=61FPqiWb25vbqB0KI7znG8nsg_ibLHBvTjYnkPvNFso,50
233
+ cognite_neat-0.73.0.dist-info/RECORD,,
cognite/neat/py.typed DELETED
File without changes