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.
- cognite/neat/_client/__init__.py +4 -0
- cognite/neat/_client/api.py +8 -0
- cognite/neat/_client/client.py +19 -0
- cognite/neat/_client/config.py +40 -0
- cognite/neat/_client/containers_api.py +73 -0
- cognite/neat/_client/data_classes.py +10 -0
- cognite/neat/_client/data_model_api.py +63 -0
- cognite/neat/_client/spaces_api.py +67 -0
- cognite/neat/_client/views_api.py +82 -0
- cognite/neat/_data_model/_analysis.py +127 -0
- cognite/neat/_data_model/_constants.py +59 -0
- cognite/neat/_data_model/_shared.py +46 -0
- cognite/neat/_data_model/deployer/__init__.py +0 -0
- cognite/neat/_data_model/deployer/_differ.py +113 -0
- cognite/neat/_data_model/deployer/_differ_container.py +354 -0
- cognite/neat/_data_model/deployer/_differ_data_model.py +29 -0
- cognite/neat/_data_model/deployer/_differ_space.py +9 -0
- cognite/neat/_data_model/deployer/_differ_view.py +194 -0
- cognite/neat/_data_model/deployer/data_classes.py +176 -0
- cognite/neat/_data_model/exporters/__init__.py +4 -0
- cognite/neat/_data_model/exporters/_base.py +22 -0
- cognite/neat/_data_model/exporters/_table_exporter/__init__.py +0 -0
- cognite/neat/_data_model/exporters/_table_exporter/exporter.py +106 -0
- cognite/neat/_data_model/exporters/_table_exporter/workbook.py +414 -0
- cognite/neat/_data_model/exporters/_table_exporter/writer.py +391 -0
- cognite/neat/_data_model/importers/__init__.py +2 -1
- cognite/neat/_data_model/importers/_api_importer.py +88 -0
- cognite/neat/_data_model/importers/_table_importer/data_classes.py +48 -8
- cognite/neat/_data_model/importers/_table_importer/importer.py +102 -6
- cognite/neat/_data_model/importers/_table_importer/reader.py +860 -0
- cognite/neat/_data_model/models/dms/__init__.py +19 -1
- cognite/neat/_data_model/models/dms/_base.py +12 -8
- cognite/neat/_data_model/models/dms/_constants.py +1 -1
- cognite/neat/_data_model/models/dms/_constraints.py +2 -1
- cognite/neat/_data_model/models/dms/_container.py +5 -5
- cognite/neat/_data_model/models/dms/_data_model.py +3 -3
- cognite/neat/_data_model/models/dms/_data_types.py +8 -1
- cognite/neat/_data_model/models/dms/_http.py +18 -0
- cognite/neat/_data_model/models/dms/_indexes.py +2 -1
- cognite/neat/_data_model/models/dms/_references.py +17 -4
- cognite/neat/_data_model/models/dms/_space.py +11 -7
- cognite/neat/_data_model/models/dms/_view_property.py +7 -4
- cognite/neat/_data_model/models/dms/_views.py +16 -6
- cognite/neat/_data_model/validation/__init__.py +0 -0
- cognite/neat/_data_model/validation/_base.py +16 -0
- cognite/neat/_data_model/validation/dms/__init__.py +9 -0
- cognite/neat/_data_model/validation/dms/_orchestrator.py +68 -0
- cognite/neat/_data_model/validation/dms/_validators.py +139 -0
- cognite/neat/_exceptions.py +15 -3
- cognite/neat/_issues.py +39 -6
- cognite/neat/_session/__init__.py +3 -0
- cognite/neat/_session/_physical.py +88 -0
- cognite/neat/_session/_session.py +34 -25
- cognite/neat/_session/_wrappers.py +61 -0
- cognite/neat/_state_machine/__init__.py +10 -0
- cognite/neat/{_session/_state_machine → _state_machine}/_base.py +11 -1
- cognite/neat/_state_machine/_states.py +53 -0
- cognite/neat/_store/__init__.py +3 -0
- cognite/neat/_store/_provenance.py +55 -0
- cognite/neat/_store/_store.py +124 -0
- cognite/neat/_utils/_reader.py +194 -0
- cognite/neat/_utils/http_client/__init__.py +14 -20
- cognite/neat/_utils/http_client/_client.py +22 -61
- cognite/neat/_utils/http_client/_data_classes.py +167 -268
- cognite/neat/_utils/text.py +6 -0
- cognite/neat/_utils/useful_types.py +26 -2
- cognite/neat/_version.py +1 -1
- cognite/neat/v0/core/_data_model/importers/_rdf/_shared.py +2 -2
- cognite/neat/v0/core/_data_model/importers/_spreadsheet2data_model.py +2 -2
- cognite/neat/v0/core/_data_model/models/entities/_single_value.py +1 -1
- cognite/neat/v0/core/_data_model/models/physical/_unverified.py +1 -1
- cognite/neat/v0/core/_data_model/models/physical/_validation.py +2 -2
- cognite/neat/v0/core/_data_model/models/physical/_verified.py +3 -3
- cognite/neat/v0/core/_data_model/transformers/_converters.py +1 -1
- {cognite_neat-0.125.1.dist-info → cognite_neat-0.126.1.dist-info}/METADATA +1 -1
- {cognite_neat-0.125.1.dist-info → cognite_neat-0.126.1.dist-info}/RECORD +78 -40
- cognite/neat/_session/_state_machine/__init__.py +0 -23
- cognite/neat/_session/_state_machine/_states.py +0 -150
- {cognite_neat-0.125.1.dist-info → cognite_neat-0.126.1.dist-info}/WHEEL +0 -0
- {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
|
|
14
|
+
from cognite.neat._exceptions import DataModelImportException
|
|
11
15
|
from cognite.neat._issues import ModelSyntaxError
|
|
12
|
-
from cognite.neat._utils.
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|