cognite-neat 0.125.1__py3-none-any.whl → 0.126.1__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 (80) hide show
  1. cognite/neat/_client/__init__.py +4 -0
  2. cognite/neat/_client/api.py +8 -0
  3. cognite/neat/_client/client.py +19 -0
  4. cognite/neat/_client/config.py +40 -0
  5. cognite/neat/_client/containers_api.py +73 -0
  6. cognite/neat/_client/data_classes.py +10 -0
  7. cognite/neat/_client/data_model_api.py +63 -0
  8. cognite/neat/_client/spaces_api.py +67 -0
  9. cognite/neat/_client/views_api.py +82 -0
  10. cognite/neat/_data_model/_analysis.py +127 -0
  11. cognite/neat/_data_model/_constants.py +59 -0
  12. cognite/neat/_data_model/_shared.py +46 -0
  13. cognite/neat/_data_model/deployer/__init__.py +0 -0
  14. cognite/neat/_data_model/deployer/_differ.py +113 -0
  15. cognite/neat/_data_model/deployer/_differ_container.py +354 -0
  16. cognite/neat/_data_model/deployer/_differ_data_model.py +29 -0
  17. cognite/neat/_data_model/deployer/_differ_space.py +9 -0
  18. cognite/neat/_data_model/deployer/_differ_view.py +194 -0
  19. cognite/neat/_data_model/deployer/data_classes.py +176 -0
  20. cognite/neat/_data_model/exporters/__init__.py +4 -0
  21. cognite/neat/_data_model/exporters/_base.py +22 -0
  22. cognite/neat/_data_model/exporters/_table_exporter/__init__.py +0 -0
  23. cognite/neat/_data_model/exporters/_table_exporter/exporter.py +106 -0
  24. cognite/neat/_data_model/exporters/_table_exporter/workbook.py +414 -0
  25. cognite/neat/_data_model/exporters/_table_exporter/writer.py +391 -0
  26. cognite/neat/_data_model/importers/__init__.py +2 -1
  27. cognite/neat/_data_model/importers/_api_importer.py +88 -0
  28. cognite/neat/_data_model/importers/_table_importer/data_classes.py +48 -8
  29. cognite/neat/_data_model/importers/_table_importer/importer.py +102 -6
  30. cognite/neat/_data_model/importers/_table_importer/reader.py +860 -0
  31. cognite/neat/_data_model/models/dms/__init__.py +19 -1
  32. cognite/neat/_data_model/models/dms/_base.py +12 -8
  33. cognite/neat/_data_model/models/dms/_constants.py +1 -1
  34. cognite/neat/_data_model/models/dms/_constraints.py +2 -1
  35. cognite/neat/_data_model/models/dms/_container.py +5 -5
  36. cognite/neat/_data_model/models/dms/_data_model.py +3 -3
  37. cognite/neat/_data_model/models/dms/_data_types.py +8 -1
  38. cognite/neat/_data_model/models/dms/_http.py +18 -0
  39. cognite/neat/_data_model/models/dms/_indexes.py +2 -1
  40. cognite/neat/_data_model/models/dms/_references.py +17 -4
  41. cognite/neat/_data_model/models/dms/_space.py +11 -7
  42. cognite/neat/_data_model/models/dms/_view_property.py +7 -4
  43. cognite/neat/_data_model/models/dms/_views.py +16 -6
  44. cognite/neat/_data_model/validation/__init__.py +0 -0
  45. cognite/neat/_data_model/validation/_base.py +16 -0
  46. cognite/neat/_data_model/validation/dms/__init__.py +9 -0
  47. cognite/neat/_data_model/validation/dms/_orchestrator.py +68 -0
  48. cognite/neat/_data_model/validation/dms/_validators.py +139 -0
  49. cognite/neat/_exceptions.py +15 -3
  50. cognite/neat/_issues.py +39 -6
  51. cognite/neat/_session/__init__.py +3 -0
  52. cognite/neat/_session/_physical.py +88 -0
  53. cognite/neat/_session/_session.py +34 -25
  54. cognite/neat/_session/_wrappers.py +61 -0
  55. cognite/neat/_state_machine/__init__.py +10 -0
  56. cognite/neat/{_session/_state_machine → _state_machine}/_base.py +11 -1
  57. cognite/neat/_state_machine/_states.py +53 -0
  58. cognite/neat/_store/__init__.py +3 -0
  59. cognite/neat/_store/_provenance.py +55 -0
  60. cognite/neat/_store/_store.py +124 -0
  61. cognite/neat/_utils/_reader.py +194 -0
  62. cognite/neat/_utils/http_client/__init__.py +14 -20
  63. cognite/neat/_utils/http_client/_client.py +22 -61
  64. cognite/neat/_utils/http_client/_data_classes.py +167 -268
  65. cognite/neat/_utils/text.py +6 -0
  66. cognite/neat/_utils/useful_types.py +26 -2
  67. cognite/neat/_version.py +1 -1
  68. cognite/neat/v0/core/_data_model/importers/_rdf/_shared.py +2 -2
  69. cognite/neat/v0/core/_data_model/importers/_spreadsheet2data_model.py +2 -2
  70. cognite/neat/v0/core/_data_model/models/entities/_single_value.py +1 -1
  71. cognite/neat/v0/core/_data_model/models/physical/_unverified.py +1 -1
  72. cognite/neat/v0/core/_data_model/models/physical/_validation.py +2 -2
  73. cognite/neat/v0/core/_data_model/models/physical/_verified.py +3 -3
  74. cognite/neat/v0/core/_data_model/transformers/_converters.py +1 -1
  75. {cognite_neat-0.125.1.dist-info → cognite_neat-0.126.1.dist-info}/METADATA +1 -1
  76. {cognite_neat-0.125.1.dist-info → cognite_neat-0.126.1.dist-info}/RECORD +78 -40
  77. cognite/neat/_session/_state_machine/__init__.py +0 -23
  78. cognite/neat/_session/_state_machine/_states.py +0 -150
  79. {cognite_neat-0.125.1.dist-info → cognite_neat-0.126.1.dist-info}/WHEEL +0 -0
  80. {cognite_neat-0.125.1.dist-info → cognite_neat-0.126.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,18 +1,25 @@
1
1
  from collections.abc import Mapping
2
+ from pathlib import Path
2
3
  from typing import ClassVar, cast
3
4
 
5
+ import yaml
6
+ from openpyxl import load_workbook
7
+ from openpyxl.worksheet.worksheet import Worksheet
4
8
  from pydantic import ValidationError
5
9
 
6
10
  from cognite.neat._data_model.importers._base import DMSImporter
7
11
  from cognite.neat._data_model.models.dms import (
8
12
  RequestSchema,
9
13
  )
10
- from cognite.neat._exceptions import DataModelImportError
14
+ from cognite.neat._exceptions import DataModelImportException
11
15
  from cognite.neat._issues import ModelSyntaxError
12
- from cognite.neat._utils.useful_types import CellValueType
16
+ from cognite.neat._utils.text import humanize_collection
17
+ from cognite.neat._utils.useful_types import CellValueType, DataModelTableType
13
18
  from cognite.neat._utils.validation import as_json_path, humanize_validation_error
14
19
 
15
- from .data_classes import TableDMS
20
+ from .data_classes import MetadataValue, TableDMS
21
+ from .reader import DMSTableReader
22
+ from .source import SpreadsheetReadContext, TableSource
16
23
 
17
24
 
18
25
  class DMSTableImporter(DMSImporter):
@@ -29,12 +36,45 @@ class DMSTableImporter(DMSImporter):
29
36
  REQUIRED_SHEET_MESSAGES: ClassVar[Mapping[str, str]] = {
30
37
  f"Missing required column: {sheet!r}": f"Missing required sheet: {sheet!r}" for sheet in REQUIRED_SHEETS
31
38
  }
39
+ MetadataSheet = cast(str, TableDMS.model_fields["metadata"].validation_alias)
32
40
 
33
- def __init__(self, tables: dict[str, list[dict[str, CellValueType]]]) -> None:
41
+ def __init__(self, tables: DataModelTableType, source: TableSource | None = None) -> None:
34
42
  self._table = tables
43
+ self._source = source or TableSource("Unknown")
35
44
 
36
45
  def to_data_model(self) -> RequestSchema:
37
- raise NotImplementedError()
46
+ tables = self._read_tables()
47
+
48
+ space, version = self._read_defaults(tables.metadata)
49
+ reader = DMSTableReader(space, version, self._source)
50
+ return reader.read_tables(tables)
51
+
52
+ @classmethod
53
+ def from_yaml(cls, yaml_file: Path) -> "DMSTableImporter":
54
+ """Create a DMSTableImporter from a YAML file."""
55
+ source = cls._display_name(yaml_file)
56
+ return cls(yaml.safe_load(yaml_file.read_text()), TableSource(source.as_posix()))
57
+
58
+ @classmethod
59
+ def from_excel(cls, excel_file: Path) -> "DMSTableImporter":
60
+ """Create a DMSTableImporter from an Excel file."""
61
+ tables: DataModelTableType = {}
62
+ source = TableSource(cls._display_name(excel_file).as_posix())
63
+ workbook = load_workbook(excel_file, read_only=True, data_only=True, rich_text=False)
64
+ try:
65
+ for column_id, column_info in TableDMS.model_fields.items():
66
+ sheet_name = cast(str, column_info.validation_alias)
67
+ if sheet_name not in workbook.sheetnames:
68
+ continue
69
+ required_columns = TableDMS.get_sheet_columns(column_id, column_info, column_type="required")
70
+ sheet = workbook[sheet_name]
71
+ context = SpreadsheetReadContext()
72
+ table_rows = cls._read_rows(sheet, required_columns, context)
73
+ tables[sheet_name] = table_rows
74
+ source.table_read[sheet_name] = context
75
+ return cls(tables, source)
76
+ finally:
77
+ workbook.close()
38
78
 
39
79
  def _read_tables(self) -> TableDMS:
40
80
  try:
@@ -42,7 +82,7 @@ class DMSTableImporter(DMSImporter):
42
82
  table = TableDMS.model_validate(self._table)
43
83
  except ValidationError as e:
44
84
  errors = self._create_error_messages(e)
45
- raise DataModelImportError(errors) from None
85
+ raise DataModelImportException(errors) from None
46
86
  return table
47
87
 
48
88
  def _create_error_messages(self, error: ValidationError) -> list[ModelSyntaxError]:
@@ -74,3 +114,59 @@ class DMSTableImporter(DMSImporter):
74
114
  return f"{loc[0]} sheet row {loc[1] + 1} column {loc[2]!r}"
75
115
  # This should be unreachable as the TableDMS model only has 2 levels.
76
116
  return as_json_path(loc)
117
+
118
+ @staticmethod
119
+ def _read_defaults(metadata: list[MetadataValue]) -> tuple[str, str]:
120
+ """Reads the space and version from the metadata table."""
121
+ default_space: str | None = None
122
+ default_version: str | None = None
123
+ missing = {"space", "version"}
124
+ for meta in metadata:
125
+ if meta.key == "space":
126
+ default_space = str(meta.value)
127
+ missing.remove("space")
128
+ elif meta.key == "version":
129
+ default_version = str(meta.value)
130
+ missing.remove("version")
131
+ if missing:
132
+ error = ModelSyntaxError(message=f"In Metadata missing required values: {humanize_collection(missing)}")
133
+ # If space or version is missing, we cannot continue parsing the model as these are used as defaults.
134
+ raise DataModelImportException([error]) from None
135
+ return str(default_space), str(default_version)
136
+
137
+ @classmethod
138
+ def _display_name(cls, filepath: Path) -> Path:
139
+ """Get a display-friendly version of the file path."""
140
+ cwd = Path.cwd()
141
+ source = filepath
142
+ if filepath.is_relative_to(cwd):
143
+ source = filepath.relative_to(cwd)
144
+ return source
145
+
146
+ @classmethod
147
+ def _read_rows(
148
+ cls, sheet: Worksheet, required_columns: list[str], context: SpreadsheetReadContext
149
+ ) -> list[dict[str, CellValueType]]:
150
+ table_rows: list[dict[str, CellValueType]] = []
151
+ # Metadata sheet is just a key-value pair of the first two columns.
152
+ # For other sheets, we need to find the column header row first.
153
+ columns: list[str] = [] if sheet.title != cls.MetadataSheet else required_columns
154
+ for row_no, row in enumerate(sheet.iter_rows(values_only=True)):
155
+ if columns:
156
+ # We have found the column header row, read the data rows.
157
+ if all(cell is None for cell in row):
158
+ context.empty_rows.append(row_no)
159
+ else:
160
+ record = dict(zip(columns, row, strict=False))
161
+ # MyPy complains as it thinks DataTableFormula | ArrayFormula could be cell values,
162
+ # but as we used values_only=True, this is not the case.
163
+ table_rows.append(record) # type: ignore[arg-type]
164
+ else:
165
+ # Look for the column header row.
166
+ row_values = [str(cell) for cell in row]
167
+ if set(row_values).intersection(required_columns):
168
+ columns = row_values
169
+ context.header_row = row_no
170
+ else:
171
+ context.skipped_rows.append(row_no)
172
+ return table_rows