cognite-neat 0.75.9__py3-none-any.whl → 0.76.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.75.9"
1
+ __version__ = "0.76.0"
@@ -2,6 +2,8 @@ from abc import ABC
2
2
  from dataclasses import dataclass, field
3
3
  from functools import total_ordering
4
4
 
5
+ from cognite.neat.rules.issues import IssueList
6
+
5
7
 
6
8
  @total_ordering
7
9
  @dataclass
@@ -32,6 +34,7 @@ class UploadResult(UploadResultCore):
32
34
  failed_changed: int = 0
33
35
  failed_deleted: int = 0
34
36
  error_messages: list[str] = field(default_factory=list)
37
+ issues: IssueList = field(default_factory=IssueList)
35
38
 
36
39
  @property
37
40
  def total(self) -> int:
@@ -4,10 +4,13 @@ from pathlib import Path
4
4
  from typing import Literal, TypeAlias, cast
5
5
 
6
6
  from cognite.client import CogniteClient
7
- from cognite.client.data_classes._base import CogniteResourceList
7
+ from cognite.client.data_classes._base import CogniteResource, CogniteResourceList
8
+ from cognite.client.data_classes.data_modeling import DataModelApply, DataModelId
8
9
  from cognite.client.exceptions import CogniteAPIError
9
10
 
11
+ from cognite.neat.rules import issues
10
12
  from cognite.neat.rules._shared import Rules
13
+ from cognite.neat.rules.issues import IssueList
11
14
  from cognite.neat.rules.models.rules import InformationRules
12
15
  from cognite.neat.rules.models.rules._base import ExtensionCategory, SheetList
13
16
  from cognite.neat.rules.models.rules._dms_architect_rules import DMSContainer, DMSRules
@@ -43,6 +46,8 @@ class DMSExporter(CDFExporter[DMSSchema]):
43
46
  export_pipeline (bool, optional): Whether to export the pipeline. Defaults to False. This means setting
44
47
  up transformations, RAW databases and tables to populate the data model.
45
48
  instance_space (str, optional): The space to use for the instance. Defaults to None.
49
+ suppress_warnings (bool, optional): Suppress warnings. Defaults to False.
50
+
46
51
  ... note::
47
52
 
48
53
  - "fail": If any component already exists, the export will fail.
@@ -59,12 +64,14 @@ class DMSExporter(CDFExporter[DMSSchema]):
59
64
  existing_handling: Literal["fail", "skip", "update", "force"] = "update",
60
65
  export_pipeline: bool = False,
61
66
  instance_space: str | None = None,
67
+ suppress_warnings: bool = False,
62
68
  ):
63
69
  self.export_components = {export_components} if isinstance(export_components, str) else set(export_components)
64
70
  self.include_space = include_space
65
71
  self.existing_handling = existing_handling
66
72
  self.export_pipeline = export_pipeline
67
73
  self.instance_space = instance_space
74
+ self.suppress_warnings = suppress_warnings
68
75
  self._schema: DMSSchema | None = None
69
76
 
70
77
  def export_to_file(self, rules: Rules, filepath: Path) -> None:
@@ -115,11 +122,11 @@ class DMSExporter(CDFExporter[DMSSchema]):
115
122
  )
116
123
  is_new_model = dms_rules.reference is None
117
124
  if is_new_model or is_solution_model:
118
- return dms_rules.as_schema(self.export_pipeline, self.instance_space)
125
+ return dms_rules.as_schema(False, self.export_pipeline, self.instance_space)
119
126
 
120
127
  # This is an extension of an existing model.
121
128
  reference_rules = cast(DMSRules, dms_rules.reference).model_copy(deep=True)
122
- reference_schema = reference_rules.as_schema(self.export_pipeline)
129
+ reference_schema = reference_rules.as_schema(include_ref=False, include_pipeline=self.export_pipeline)
123
130
 
124
131
  # Todo Move this to an appropriate location
125
132
  # Merging Reference with User Rules
@@ -142,7 +149,7 @@ class DMSExporter(CDFExporter[DMSSchema]):
142
149
  property_.reference = None
143
150
  combined_rules.properties.append(property_)
144
151
 
145
- schema = combined_rules.as_schema(self.export_pipeline, self.instance_space)
152
+ schema = combined_rules.as_schema(True, self.export_pipeline, self.instance_space)
146
153
 
147
154
  if dms_rules.metadata.extension in (ExtensionCategory.addition, ExtensionCategory.reshape):
148
155
  # We do not freeze views as they might be changed, even for addition,
@@ -206,6 +213,7 @@ class DMSExporter(CDFExporter[DMSSchema]):
206
213
  redeploy_data_model = False
207
214
 
208
215
  for all_items, loader in to_export:
216
+ issue_list = IssueList()
209
217
  all_item_ids = loader.get_ids(all_items)
210
218
  skipped = sum(1 for item_id in all_item_ids if item_id in schema.frozen_ids)
211
219
  item_ids = [item_id for item_id in all_item_ids if item_id not in schema.frozen_ids]
@@ -249,6 +257,9 @@ class DMSExporter(CDFExporter[DMSSchema]):
249
257
  else:
250
258
  raise ValueError(f"Unsupported existing_handling {self.existing_handling}")
251
259
 
260
+ warning_list = self._validate(loader, items)
261
+ issue_list.extend(warning_list)
262
+
252
263
  error_messages: list[str] = []
253
264
  if not dry_run:
254
265
  if to_delete:
@@ -284,6 +295,7 @@ class DMSExporter(CDFExporter[DMSSchema]):
284
295
  failed_created=failed_created,
285
296
  failed_changed=failed_changed,
286
297
  error_messages=error_messages,
298
+ issues=issue_list,
287
299
  )
288
300
 
289
301
  if loader.resource_name == "views" and (created or changed) and not redeploy_data_model:
@@ -307,3 +319,33 @@ class DMSExporter(CDFExporter[DMSSchema]):
307
319
  to_export.append((schema.raw_tables, RawTableLoader(client)))
308
320
  to_export.append((schema.transformations, TransformationLoader(client)))
309
321
  return schema, to_export
322
+
323
+ def _validate(self, loader: ResourceLoader, items: list[CogniteResource]) -> IssueList:
324
+ issue_list = IssueList()
325
+ if isinstance(loader, DataModelLoader):
326
+ models = cast(list[DataModelApply], items)
327
+ if other_models := self._exist_other_data_models(loader, models):
328
+ warning = issues.dms.OtherDataModelsInSpaceWarning(models[0].space, other_models)
329
+ if not self.suppress_warnings:
330
+ warnings.warn(warning, stacklevel=2)
331
+ issue_list.append(warning)
332
+
333
+ return issue_list
334
+
335
+ @classmethod
336
+ def _exist_other_data_models(cls, loader: DataModelLoader, models: list[DataModelApply]) -> list[DataModelId]:
337
+ if not models:
338
+ return []
339
+ space = models[0].space
340
+ external_id = models[0].external_id
341
+ try:
342
+ data_models = loader.client.data_modeling.data_models.list(space=space, limit=25, all_versions=False)
343
+ except CogniteAPIError as e:
344
+ warnings.warn(issues.importing.APIWarning(str(e)), stacklevel=2)
345
+ return []
346
+ else:
347
+ return [
348
+ data_model.as_id()
349
+ for data_model in data_models
350
+ if (data_model.space, data_model.external_id) != (space, external_id)
351
+ ]
@@ -120,13 +120,6 @@ class ExcelExporter(BaseExporter[Workbook]):
120
120
  else:
121
121
  sheet = workbook.create_sheet(sheet_name)
122
122
 
123
- # Reorder such that the first column is class + the first field of the subclass
124
- # of sheet entity. This is to make the properties/classes/views/containers sheet more readable.
125
- # For example, for the properties these that means class, property, name, description
126
- # instead of class, name, description, property
127
- move = len(SheetEntity.model_fields) - 1 # -1 is for the class field
128
- headers = headers[:1] + headers[move : move + 1] + headers[1:move] + headers[move + 1 :]
129
-
130
123
  main_header = self._main_header_by_sheet_name[sheet_name]
131
124
  sheet.append([main_header] + [""] * (len(headers) - 1))
132
125
  sheet.merge_cells(start_row=1, start_column=1, end_row=1, end_column=len(headers))
@@ -150,8 +143,6 @@ class ExcelExporter(BaseExporter[Workbook]):
150
143
  cell.border = Border(left=side, right=side, top=side, bottom=side)
151
144
  fill_color = next(fill_colors)
152
145
 
153
- # Need to do the same reordering as for the headers above
154
- row = row[:1] + row[move : move + 1] + row[1:move] + row[move + 1 :]
155
146
  sheet.append(row)
156
147
  if self._styling_level > 2 and is_properties:
157
148
  for cell in sheet[sheet.max_row]:
@@ -60,6 +60,12 @@ class BaseImporter(ABC):
60
60
  else:
61
61
  return output, issues
62
62
 
63
+ @classmethod
64
+ def _return_or_raise(cls, issue_list: IssueList, errors: Literal["raise", "continue"]) -> tuple[None, IssueList]:
65
+ if errors == "raise":
66
+ raise issue_list.as_errors()
67
+ return None, issue_list
68
+
63
69
  def _default_metadata(self):
64
70
  return {
65
71
  "prefix": "neat",