cognite-neat 0.126.0__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 +6 -1
- 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 +74 -5
- cognite/neat/_data_model/importers/_table_importer/reader.py +63 -7
- cognite/neat/_data_model/models/dms/__init__.py +17 -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 +23 -2
- cognite/neat/_version.py +1 -1
- cognite/neat/v0/core/_data_model/importers/_rdf/_shared.py +2 -2
- {cognite_neat-0.126.0.dist-info → cognite_neat-0.126.1.dist-info}/METADATA +1 -1
- {cognite_neat-0.126.0.dist-info → cognite_neat-0.126.1.dist-info}/RECORD +72 -38
- cognite/neat/_data_model/exporters/_table_exporter.py +0 -35
- cognite/neat/_session/_state_machine/__init__.py +0 -23
- cognite/neat/_session/_state_machine/_states.py +0 -150
- {cognite_neat-0.126.0.dist-info → cognite_neat-0.126.1.dist-info}/WHEEL +0 -0
- {cognite_neat-0.126.0.dist-info → cognite_neat-0.126.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
from collections.abc import Mapping, Set
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from functools import lru_cache
|
|
5
|
+
from typing import Literal, cast
|
|
6
|
+
|
|
7
|
+
from openpyxl import Workbook
|
|
8
|
+
from openpyxl.cell import MergedCell
|
|
9
|
+
from openpyxl.styles import Border, Font, PatternFill, Side
|
|
10
|
+
from openpyxl.utils import get_column_letter
|
|
11
|
+
from openpyxl.worksheet.datavalidation import DataValidation
|
|
12
|
+
from openpyxl.worksheet.worksheet import Worksheet
|
|
13
|
+
|
|
14
|
+
from cognite.neat._data_model._constants import (
|
|
15
|
+
CDF_CDM_SPACE,
|
|
16
|
+
CDF_CDM_VERSION,
|
|
17
|
+
COGNITE_CONCEPTS_3D,
|
|
18
|
+
COGNITE_CONCEPTS_ANNOTATIONS,
|
|
19
|
+
COGNITE_CONCEPTS_CONFIGURATIONS,
|
|
20
|
+
COGNITE_CONCEPTS_INTERFACES,
|
|
21
|
+
COGNITE_CONCEPTS_MAIN,
|
|
22
|
+
)
|
|
23
|
+
from cognite.neat._data_model.importers._table_importer.data_classes import DMSContainer, DMSProperty, DMSView, TableDMS
|
|
24
|
+
from cognite.neat._data_model.models.dms import (
|
|
25
|
+
DMS_DATA_TYPES,
|
|
26
|
+
EnumProperty,
|
|
27
|
+
FileCDFExternalIdReference,
|
|
28
|
+
SequenceCDFExternalIdReference,
|
|
29
|
+
TimeseriesCDFExternalIdReference,
|
|
30
|
+
)
|
|
31
|
+
from cognite.neat._utils.useful_types import CellValueType, DataModelTableType
|
|
32
|
+
|
|
33
|
+
MAIN_HEADERS_BY_SHEET_NAME: Mapping[str, str] = {
|
|
34
|
+
"Properties": "Definition of Properties",
|
|
35
|
+
"Views": "Definition of Views",
|
|
36
|
+
"Containers": "Definition of Containers",
|
|
37
|
+
"Nodes": "Definition of Nodes",
|
|
38
|
+
"Enum": "Definition of Enum Collections",
|
|
39
|
+
}
|
|
40
|
+
MAX_COLUMN_WIDTH = 70.0
|
|
41
|
+
HEADER_ROWS = 2
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class WorkbookOptions:
|
|
46
|
+
"""Options for creating an Excel workbook from a data model.
|
|
47
|
+
|
|
48
|
+
Attributes:
|
|
49
|
+
adjust_column_width (bool): Whether to adjust the column widths to fit the content. Default
|
|
50
|
+
is True.
|
|
51
|
+
style_headers (bool): Whether to style the header rows. Default is True.
|
|
52
|
+
row_band_highlighting (bool): Whether to apply row band highlighting to the properties sheet.
|
|
53
|
+
Default is True.
|
|
54
|
+
separate_view_properties (bool): Whether to separate properties by view with an empty row.
|
|
55
|
+
Default is False.
|
|
56
|
+
add_dropdowns (bool): Whether to add drop-down menus for certain columns. Default is True.
|
|
57
|
+
dropdown_implements (Set[Literal["main", "interface", "configuration", "annotation", "3D"]]):
|
|
58
|
+
The types of Cognite concepts to include in the "implements" drop-down menu. Default is
|
|
59
|
+
{"main", "interface"}.
|
|
60
|
+
max_views (int): The maximum number of views to support in the drop-down menus. Default is 100.
|
|
61
|
+
max_containers (int): The maximum number of containers to support in the drop-down menus. Default is 100.
|
|
62
|
+
max_properties_per_view (int): The maximum number of properties per view to support in the
|
|
63
|
+
drop-down menus. Default is 100.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
adjust_column_width: bool = True
|
|
67
|
+
style_headers: bool = True
|
|
68
|
+
row_band_highlighting: bool = True
|
|
69
|
+
separate_view_properties: bool = False
|
|
70
|
+
add_dropdowns: bool = True
|
|
71
|
+
dropdown_implements: Set[Literal["main", "interface", "configuration", "annotation", "3D"]] = frozenset(
|
|
72
|
+
{"main", "interface"}
|
|
73
|
+
)
|
|
74
|
+
max_views: int = 100
|
|
75
|
+
max_containers: int = 100
|
|
76
|
+
max_properties_per_view: int = 100
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class WorkbookCreator:
|
|
80
|
+
# skip types which require special handling (enum) or are surpassed by CDM (CDF references)
|
|
81
|
+
DROPDOWN_DMS_TYPE_EXCLUDE = frozenset(
|
|
82
|
+
{
|
|
83
|
+
# MyPy does not understand that model_fields is in all pydantic classes.
|
|
84
|
+
prop.model_fields["type"].default # type: ignore[attr-defined]
|
|
85
|
+
for prop in [
|
|
86
|
+
EnumProperty,
|
|
87
|
+
TimeseriesCDFExternalIdReference,
|
|
88
|
+
SequenceCDFExternalIdReference,
|
|
89
|
+
FileCDFExternalIdReference,
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# These classer are used to refer to sheets that needs to be explicitly checked for in the
|
|
95
|
+
# workbook creation.
|
|
96
|
+
class Sheets:
|
|
97
|
+
metadata = cast(str, TableDMS.model_fields["metadata"].validation_alias)
|
|
98
|
+
properties = cast(str, TableDMS.model_fields["properties"].validation_alias)
|
|
99
|
+
views = cast(str, TableDMS.model_fields["views"].validation_alias)
|
|
100
|
+
containers = cast(str, TableDMS.model_fields["containers"].validation_alias)
|
|
101
|
+
dropdown_source = "_dropdown_source"
|
|
102
|
+
|
|
103
|
+
# The following classes are used to refer to sheets and columns that are used
|
|
104
|
+
# in dropdown creation.
|
|
105
|
+
class PropertyColumns:
|
|
106
|
+
view = cast(str, DMSProperty.model_fields["view"].validation_alias)
|
|
107
|
+
value_type = cast(str, DMSProperty.model_fields["value_type"].validation_alias)
|
|
108
|
+
immutable = cast(str, DMSProperty.model_fields["immutable"].validation_alias)
|
|
109
|
+
container = cast(str, DMSProperty.model_fields["container"].validation_alias)
|
|
110
|
+
|
|
111
|
+
class ContainerColumns:
|
|
112
|
+
container = cast(str, DMSContainer.model_fields["container"].validation_alias)
|
|
113
|
+
used_for = cast(str, DMSContainer.model_fields["used_for"].validation_alias)
|
|
114
|
+
|
|
115
|
+
class ViewColumns:
|
|
116
|
+
view = cast(str, DMSView.model_fields["view"].validation_alias)
|
|
117
|
+
implements = cast(str, DMSView.model_fields["implements"].validation_alias)
|
|
118
|
+
in_model = cast(str, DMSView.model_fields["in_model"].validation_alias)
|
|
119
|
+
|
|
120
|
+
class DropdownSourceColumns:
|
|
121
|
+
view = 1
|
|
122
|
+
implements = 2
|
|
123
|
+
value_type = 3
|
|
124
|
+
container = 4
|
|
125
|
+
in_model = 5
|
|
126
|
+
immutable = 5
|
|
127
|
+
used_for = 6
|
|
128
|
+
|
|
129
|
+
def __init__(self, options: WorkbookOptions | None = None) -> None:
|
|
130
|
+
options = options or WorkbookOptions()
|
|
131
|
+
self._adjust_column_width = options.adjust_column_width
|
|
132
|
+
self._style_headers = options.style_headers
|
|
133
|
+
self._row_band_highlighting = options.row_band_highlighting
|
|
134
|
+
self._separate_view_properties = options.separate_view_properties
|
|
135
|
+
self._add_dropdowns = options.add_dropdowns
|
|
136
|
+
self._dropdown_implements = options.dropdown_implements
|
|
137
|
+
self._max_views = options.max_views
|
|
138
|
+
self._max_containers = options.max_containers
|
|
139
|
+
self._max_properties_per_view = options.max_properties_per_view
|
|
140
|
+
|
|
141
|
+
def create_workbook(self, tables: DataModelTableType) -> Workbook:
|
|
142
|
+
"""Creates an Excel workbook from the data model.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
tables (DataModelTableType): The data model in table
|
|
146
|
+
"""
|
|
147
|
+
workbook = Workbook()
|
|
148
|
+
# Remove default sheet named "Sheet"
|
|
149
|
+
workbook.remove(workbook["Sheet"])
|
|
150
|
+
|
|
151
|
+
index_by_sheet_name_column: dict[tuple[str, str], int] = {}
|
|
152
|
+
for sheet_name, table in tables.items():
|
|
153
|
+
if not table and sheet_name not in TableDMS.required_sheets():
|
|
154
|
+
continue
|
|
155
|
+
worksheet = workbook.create_sheet(title=sheet_name)
|
|
156
|
+
if sheet_name == self.Sheets.metadata:
|
|
157
|
+
self._write_metadata_to_worksheet(worksheet, table)
|
|
158
|
+
continue
|
|
159
|
+
if table:
|
|
160
|
+
column_headers = list(table[0].keys())
|
|
161
|
+
else:
|
|
162
|
+
column_headers = TableDMS.get_sheet_column_by_name(sheet_name, column_type="all")
|
|
163
|
+
self._write_table_to_worksheet(worksheet, table, MAIN_HEADERS_BY_SHEET_NAME[sheet_name], column_headers)
|
|
164
|
+
for i, column in enumerate(column_headers, 1):
|
|
165
|
+
index_by_sheet_name_column[(sheet_name, column)] = i
|
|
166
|
+
|
|
167
|
+
if self._adjust_column_width:
|
|
168
|
+
self._adjust_column_widths(worksheet)
|
|
169
|
+
|
|
170
|
+
if self._add_dropdowns:
|
|
171
|
+
self._add_drop_downs(workbook, index_by_sheet_name_column)
|
|
172
|
+
return workbook
|
|
173
|
+
|
|
174
|
+
@staticmethod
|
|
175
|
+
def _write_metadata_to_worksheet(worksheet: Worksheet, table: list[dict[str, CellValueType]]) -> None:
|
|
176
|
+
"""Writes Metadata to the given worksheet.
|
|
177
|
+
|
|
178
|
+
Metadata is written as key-value pairs without headers.
|
|
179
|
+
"""
|
|
180
|
+
for row in table:
|
|
181
|
+
worksheet.append(list(row.values()))
|
|
182
|
+
|
|
183
|
+
def _write_table_to_worksheet(
|
|
184
|
+
self, worksheet: Worksheet, table: list[dict[str, CellValueType]], main_header: str, column_headers: list[str]
|
|
185
|
+
) -> None:
|
|
186
|
+
worksheet.append([main_header] + [""] * (len(column_headers) - 1))
|
|
187
|
+
if self._style_headers:
|
|
188
|
+
worksheet.merge_cells(start_row=1, start_column=1, end_row=1, end_column=max(3, len(column_headers)))
|
|
189
|
+
cell = worksheet.cell(row=1, column=1)
|
|
190
|
+
cell.font = Font(bold=True, size=20)
|
|
191
|
+
cell.fill = PatternFill(fgColor="FFC000", patternType="solid")
|
|
192
|
+
|
|
193
|
+
worksheet.append(column_headers)
|
|
194
|
+
header_row = 2 if main_header else 1
|
|
195
|
+
if self._style_headers:
|
|
196
|
+
for col_idx in range(1, len(column_headers) + 1):
|
|
197
|
+
cell = worksheet.cell(row=header_row, column=col_idx)
|
|
198
|
+
cell.font = Font(bold=True, size=14)
|
|
199
|
+
cell.fill = PatternFill(fgColor="FFD966", patternType="solid")
|
|
200
|
+
|
|
201
|
+
self._write_rows_to_worksheet(worksheet, table, column_headers)
|
|
202
|
+
|
|
203
|
+
if self._style_headers:
|
|
204
|
+
# openpyxl is not well typed
|
|
205
|
+
worksheet.freeze_panes = worksheet.cell(row=header_row + 1, column=1) # type: ignore[assignment]
|
|
206
|
+
|
|
207
|
+
def _write_rows_to_worksheet(
|
|
208
|
+
self, worksheet: Worksheet, table: list[dict[str, CellValueType]], headers: list[str]
|
|
209
|
+
) -> None:
|
|
210
|
+
is_properties = worksheet.title == self.Sheets.properties
|
|
211
|
+
fill_colors = itertools.cycle(["CADCFC", "FFFFFF"])
|
|
212
|
+
fill_color = next(fill_colors)
|
|
213
|
+
is_new_view = False
|
|
214
|
+
last_view_value: CellValueType = None
|
|
215
|
+
side = Side(style="thin")
|
|
216
|
+
for row in table:
|
|
217
|
+
if is_properties:
|
|
218
|
+
is_new_view = row[self.PropertyColumns.view] != last_view_value and last_view_value is not None
|
|
219
|
+
if is_new_view and is_properties and self._separate_view_properties:
|
|
220
|
+
worksheet.append([None] * len(headers)) # Add an empty row between views
|
|
221
|
+
if self._row_band_highlighting:
|
|
222
|
+
for cell in worksheet[worksheet.max_row]:
|
|
223
|
+
cell.border = Border(left=side, right=side, top=side, bottom=side)
|
|
224
|
+
|
|
225
|
+
worksheet.append(list(row.values()))
|
|
226
|
+
if self._row_band_highlighting and is_new_view and is_properties:
|
|
227
|
+
fill_color = next(fill_colors)
|
|
228
|
+
|
|
229
|
+
if self._row_band_highlighting and is_properties:
|
|
230
|
+
for cell in worksheet[worksheet.max_row]:
|
|
231
|
+
cell.fill = PatternFill(fgColor=fill_color, fill_type="solid")
|
|
232
|
+
cell.border = Border(left=side, right=side, top=side, bottom=side)
|
|
233
|
+
|
|
234
|
+
if is_properties:
|
|
235
|
+
last_view_value = row[self.PropertyColumns.view]
|
|
236
|
+
|
|
237
|
+
@classmethod
|
|
238
|
+
def _adjust_column_widths(cls, worksheet: Worksheet) -> None:
|
|
239
|
+
for column_cells in worksheet.columns:
|
|
240
|
+
try:
|
|
241
|
+
max_length = max(len(str(cell.value)) for cell in column_cells if cell.value is not None)
|
|
242
|
+
except ValueError:
|
|
243
|
+
max_length = 0
|
|
244
|
+
|
|
245
|
+
selected_column = column_cells[0]
|
|
246
|
+
if isinstance(selected_column, MergedCell):
|
|
247
|
+
selected_column = column_cells[1]
|
|
248
|
+
|
|
249
|
+
current = worksheet.column_dimensions[selected_column.column_letter].width or (max_length + 0.5) # type: ignore[union-attr]
|
|
250
|
+
worksheet.column_dimensions[selected_column.column_letter].width = min( # type: ignore[union-attr]
|
|
251
|
+
max(current, max_length + 0.5), MAX_COLUMN_WIDTH
|
|
252
|
+
)
|
|
253
|
+
return None
|
|
254
|
+
|
|
255
|
+
def _add_drop_downs(self, workbook: Workbook, index_by_sheet_name_column: dict[tuple[str, str], int]) -> None:
|
|
256
|
+
"""Adds drop down menus to specific columns for fast and accurate data entry
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
workbook: Workbook representation of the Excel file.
|
|
260
|
+
index_by_sheet_name_column: A mapping of (sheet name, column name) to column index.
|
|
261
|
+
"""
|
|
262
|
+
|
|
263
|
+
self._create_dropdown_source_sheet(workbook)
|
|
264
|
+
|
|
265
|
+
property_sheet = workbook[self.Sheets.properties]
|
|
266
|
+
self._add_validation(
|
|
267
|
+
property_sheet,
|
|
268
|
+
self.DropdownSourceColumns.view,
|
|
269
|
+
self._max_views,
|
|
270
|
+
index_by_sheet_name_column[(self.Sheets.properties, self.PropertyColumns.view)],
|
|
271
|
+
self._max_views,
|
|
272
|
+
)
|
|
273
|
+
self._add_validation(
|
|
274
|
+
property_sheet,
|
|
275
|
+
self.DropdownSourceColumns.value_type,
|
|
276
|
+
len(DMS_DATA_TYPES) - len(self.DROPDOWN_DMS_TYPE_EXCLUDE) + self._max_views,
|
|
277
|
+
index_by_sheet_name_column[(self.Sheets.properties, self.PropertyColumns.value_type)],
|
|
278
|
+
self._max_views * self._max_properties_per_view,
|
|
279
|
+
)
|
|
280
|
+
self._add_validation(
|
|
281
|
+
property_sheet,
|
|
282
|
+
self.DropdownSourceColumns.immutable,
|
|
283
|
+
2, # True, False
|
|
284
|
+
index_by_sheet_name_column[(self.Sheets.properties, self.PropertyColumns.immutable)],
|
|
285
|
+
self._max_views * self._max_properties_per_view,
|
|
286
|
+
)
|
|
287
|
+
self._add_validation(
|
|
288
|
+
property_sheet,
|
|
289
|
+
self.DropdownSourceColumns.container,
|
|
290
|
+
self._max_containers,
|
|
291
|
+
index_by_sheet_name_column[(self.Sheets.properties, self.PropertyColumns.container)],
|
|
292
|
+
self._max_views * self._max_properties_per_view,
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
view_sheet = workbook[self.Sheets.views]
|
|
296
|
+
self._add_validation(
|
|
297
|
+
view_sheet,
|
|
298
|
+
self.DropdownSourceColumns.implements,
|
|
299
|
+
self._max_views + len(self._get_cognite_concepts()),
|
|
300
|
+
index_by_sheet_name_column[(self.Sheets.views, self.ViewColumns.implements)],
|
|
301
|
+
self._max_views,
|
|
302
|
+
)
|
|
303
|
+
self._add_validation(
|
|
304
|
+
view_sheet,
|
|
305
|
+
self.DropdownSourceColumns.in_model,
|
|
306
|
+
3, # True, False, None
|
|
307
|
+
index_by_sheet_name_column[(self.Sheets.views, self.ViewColumns.in_model)],
|
|
308
|
+
self._max_views,
|
|
309
|
+
)
|
|
310
|
+
container_sheet = workbook[self.Sheets.containers]
|
|
311
|
+
self._add_validation(
|
|
312
|
+
container_sheet,
|
|
313
|
+
self.DropdownSourceColumns.used_for,
|
|
314
|
+
3, # node, edge, all
|
|
315
|
+
index_by_sheet_name_column[(self.Sheets.containers, self.ContainerColumns.used_for)],
|
|
316
|
+
self._max_containers,
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
def _add_validation(
|
|
320
|
+
self, sheet: Worksheet, column_index: int, row_range: int, sheet_column_index: int, sheet_row_range: int
|
|
321
|
+
) -> None:
|
|
322
|
+
"""Adds data validation to a specific column in a sheet.
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
sheet: The worksheet to add the data validation to.
|
|
326
|
+
column_index: The column index in the dropdown source sheet to use as the source for the drop-down.
|
|
327
|
+
row_range: The number of rows in the dropdown source sheet to use as the source for the drop-down.
|
|
328
|
+
sheet_column_index: The column index in the target sheet to add the data validation to.
|
|
329
|
+
sheet_row_range: The number of rows in the target sheet to add the data validation to.
|
|
330
|
+
"""
|
|
331
|
+
letter = get_column_letter(column_index)
|
|
332
|
+
data_validation = DataValidation(
|
|
333
|
+
type="list", formula1=f"={self.Sheets.dropdown_source}!${letter}$1:${letter}${row_range}"
|
|
334
|
+
)
|
|
335
|
+
sheet.add_data_validation(data_validation)
|
|
336
|
+
target_letter = get_column_letter(sheet_column_index)
|
|
337
|
+
data_validation.add(f"{target_letter}{HEADER_ROWS + 1}:{target_letter}{HEADER_ROWS + sheet_row_range}")
|
|
338
|
+
|
|
339
|
+
def _create_dropdown_source_sheet(self, workbook: Workbook) -> None:
|
|
340
|
+
"""This methods creates a hidden sheet in the workbook which contains
|
|
341
|
+
the source data for the drop-down menus.
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
workbook: Workbook representation of the Excel file.
|
|
345
|
+
|
|
346
|
+
"""
|
|
347
|
+
dropdown_sheet = workbook.create_sheet(title=self.Sheets.dropdown_source)
|
|
348
|
+
exclude = self.DROPDOWN_DMS_TYPE_EXCLUDE
|
|
349
|
+
for no, dms_type in enumerate([type for type in DMS_DATA_TYPES.keys() if type not in exclude], 1):
|
|
350
|
+
dropdown_sheet.cell(row=no, column=self.DropdownSourceColumns.value_type, value=dms_type)
|
|
351
|
+
|
|
352
|
+
cognite_concepts = self._get_cognite_concepts()
|
|
353
|
+
|
|
354
|
+
for i, concept in enumerate(cognite_concepts, 1):
|
|
355
|
+
dropdown_sheet.cell(
|
|
356
|
+
row=i,
|
|
357
|
+
column=self.DropdownSourceColumns.implements,
|
|
358
|
+
value=f"{CDF_CDM_SPACE}:{concept}(version={CDF_CDM_VERSION})",
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
dms_type_count = len(DMS_DATA_TYPES) - len(exclude)
|
|
362
|
+
core_concept_count = len(cognite_concepts)
|
|
363
|
+
for i in range(1, self._max_views + 1):
|
|
364
|
+
source_row = i + HEADER_ROWS
|
|
365
|
+
view_reference = f'=IF(ISBLANK({self.Sheets.views}!A{source_row}), "", {self.Sheets.views}!A{source_row})'
|
|
366
|
+
dropdown_sheet.cell(row=i, column=self.DropdownSourceColumns.view, value=view_reference)
|
|
367
|
+
dropdown_sheet.cell(
|
|
368
|
+
row=i + core_concept_count, column=self.DropdownSourceColumns.implements, value=view_reference
|
|
369
|
+
)
|
|
370
|
+
dropdown_sheet.cell(
|
|
371
|
+
row=i + dms_type_count, column=self.DropdownSourceColumns.value_type, value=view_reference
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
for i in range(1, self._max_containers + 1):
|
|
375
|
+
source_row = i + HEADER_ROWS
|
|
376
|
+
container_reference = (
|
|
377
|
+
f'=IF(ISBLANK({self.Sheets.containers}!A{source_row}), "", {self.Sheets.containers}!A{source_row})'
|
|
378
|
+
)
|
|
379
|
+
dropdown_sheet.cell(row=i, column=self.DropdownSourceColumns.container, value=container_reference)
|
|
380
|
+
|
|
381
|
+
for i, value in enumerate([True, False, None], 1):
|
|
382
|
+
dropdown_sheet.cell(row=i, column=self.DropdownSourceColumns.in_model, value=value)
|
|
383
|
+
|
|
384
|
+
for i, value in enumerate(["node", "edge", "all"], 1):
|
|
385
|
+
dropdown_sheet.cell(row=i, column=self.DropdownSourceColumns.used_for, value=value)
|
|
386
|
+
|
|
387
|
+
dropdown_sheet.sheet_state = "hidden"
|
|
388
|
+
|
|
389
|
+
def _get_cognite_concepts(self) -> list[str]:
|
|
390
|
+
"""Gets the cognite concepts based on the dropdown_implements setting."""
|
|
391
|
+
return _get_cognite_concepts(self._dropdown_implements)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
@lru_cache(maxsize=1)
|
|
395
|
+
def _get_cognite_concepts(
|
|
396
|
+
dropdown_implements: Set[Literal["main", "interface", "configuration", "annotation", "3D"]],
|
|
397
|
+
) -> list[str]:
|
|
398
|
+
"""Gets the cognite concepts based on the dropdown_implements setting.
|
|
399
|
+
|
|
400
|
+
This is moved outside of the class to enable caching.
|
|
401
|
+
"""
|
|
402
|
+
cognite_concepts: list[str] = []
|
|
403
|
+
|
|
404
|
+
if "main" in dropdown_implements:
|
|
405
|
+
cognite_concepts.extend(COGNITE_CONCEPTS_MAIN)
|
|
406
|
+
if "interface" in dropdown_implements:
|
|
407
|
+
cognite_concepts.extend(COGNITE_CONCEPTS_INTERFACES)
|
|
408
|
+
if "configuration" in dropdown_implements:
|
|
409
|
+
cognite_concepts.extend(COGNITE_CONCEPTS_CONFIGURATIONS)
|
|
410
|
+
if "annotation" in dropdown_implements:
|
|
411
|
+
cognite_concepts.extend(COGNITE_CONCEPTS_ANNOTATIONS)
|
|
412
|
+
if "3D" in dropdown_implements:
|
|
413
|
+
cognite_concepts.extend(COGNITE_CONCEPTS_3D)
|
|
414
|
+
return cognite_concepts
|