pyfastexcel 0.1.8__py3-none-win_amd64.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.
@@ -0,0 +1,21 @@
1
+ from openpyxl_style_writer import CustomStyle, DefaultStyle
2
+
3
+ from pyfastexcel.workbook import Workbook
4
+ from pyfastexcel.writer import StreamWriter
5
+ from pyfastexcel.utils import set_debug_level
6
+ from pyfastexcel.enums import ChartType, ChartDataLabelPosition, ChartLineType, MarkerSymbol
7
+
8
+ __all__ = [
9
+ 'Workbook',
10
+ 'StreamWriter',
11
+ # Temporary link the CustomStyle from openpyxl_style_writer for
12
+ # convinent usage.
13
+ 'CustomStyle',
14
+ 'DefaultStyle',
15
+ 'set_debug_level',
16
+ # Constants for chart creation.
17
+ 'ChartType',
18
+ 'ChartDataLabelPosition',
19
+ 'ChartLineType',
20
+ 'MarkerSymbol',
21
+ ]
pyfastexcel/_typing.py ADDED
@@ -0,0 +1,31 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Literal, Protocol, Optional, List, Union, TypeVar
4
+ from typing_extensions import TypedDict
5
+
6
+
7
+ class CommentTextDict(TypedDict, total=False):
8
+ text: str
9
+ size: Optional[int]
10
+ name: Optional[str]
11
+ bold: Optional[bool]
12
+ italic: Optional[bool]
13
+ underline: Optional[Literal['single', 'double']]
14
+ strike: Optional[bool]
15
+ vert_align: Optional[str]
16
+ color: Optional[str]
17
+
18
+
19
+ class SelectionDict(TypedDict, total=False):
20
+ sq_ref: str
21
+ active_cell: str
22
+ pane: str
23
+
24
+
25
+ class Writable(Protocol):
26
+ def write(self, content: str) -> None: ...
27
+
28
+
29
+ Self = TypeVar('Self')
30
+ CommentTextStructure = Union[str, List[str], CommentTextDict, List[CommentTextDict]]
31
+ SetPanesSelection = List[SelectionDict]
pyfastexcel/chart.py ADDED
@@ -0,0 +1,346 @@
1
+ from __future__ import annotations
2
+
3
+ from pydantic import BaseModel, Field, field_serializer
4
+ from typing import List, Literal, Optional
5
+
6
+ from .enums import ChartDataLabelPosition, ChartLineType, ChartType, MarkerSymbol
7
+
8
+
9
+ class Font(BaseModel):
10
+ """
11
+ Defines font settings for text elements in a chart.
12
+
13
+ Attributes:
14
+ bold (Optional[bool]): Specifies if the text is bold.
15
+ color (Optional[str]): The color of the text.
16
+ family (Optional[str]): The font family for the text.
17
+ italic (Optional[bool]): Specifies if the text is italic.
18
+ size (Optional[float]): The font size for the text.
19
+ strike (Optional[bool]): Specifies if the text has a strikethrough.
20
+ underline (Optional[str]): The style of underline for the text.
21
+ vert_align (Optional[str]): Vertical alignment for the text, such as
22
+ "baseline", "superscript" or "subscript".
23
+ """
24
+
25
+ bold: Optional[bool] = Field(None, serialization_alias='Bold')
26
+ color: Optional[str] = Field(None, serialization_alias='Color')
27
+ family: Optional[str] = Field(None, serialization_alias='Family')
28
+ italic: Optional[bool] = Field(None, serialization_alias='Italic')
29
+ size: Optional[float] = Field(None, serialization_alias='Size')
30
+ strike: Optional[bool] = Field(None, serialization_alias='Strike')
31
+ underline: Optional[str] = Field(None, serialization_alias='Underline')
32
+ vert_align: Optional[str] = Field(None, serialization_alias='VertAlign')
33
+
34
+
35
+ class Fill(BaseModel):
36
+ """
37
+ Describes the fill settings.
38
+
39
+ Attributes:
40
+ ftype (Optional[Literal['pattern', 'gradient']]): The type of fill, either
41
+ 'pattern' or 'gradient'.
42
+ pattern (Optional[int]): The pattern index for fill (between 0 and 18).
43
+ color (Optional[str]): The fill color (Only support hex color value).
44
+ shading (Optional[int]): The shading index for the fill (between 0 and 5).
45
+ """
46
+
47
+ ftype: Optional[Literal['pattern', 'gradient']] = Field('pattern', serialization_alias='Type')
48
+ pattern: Optional[int] = Field(None, serialization_alias='Pattern', gt=-1, lt=19)
49
+ color: Optional[str] = Field(None, serialization_alias='Color')
50
+ shading: Optional[int] = Field(None, serialization_alias='Shading', gt=-1, lt=6)
51
+
52
+
53
+ class Marker(BaseModel):
54
+ """
55
+ Defines the appearance and style of markers used in charts.
56
+
57
+ Attributes:
58
+ fill (Optional[Fill]): Fill settings for the marker.
59
+ symbol (Optional[str | MarkerSymbol]): The symbol used for the marker.
60
+ size (Optional[int]): The size of the marker.
61
+ """
62
+
63
+ fill: Optional[Fill] = Field(None, serialization_alias='Fill')
64
+ symbol: Optional[str | MarkerSymbol] = Field(None, serialization_alias='Symbol')
65
+ size: Optional[int] = Field(None, serialization_alias='Size')
66
+
67
+ @field_serializer('symbol')
68
+ @classmethod
69
+ def marker_symbol_serializer(cls, symbol: str | MarkerSymbol | None) -> str | None:
70
+ if symbol is None:
71
+ return None
72
+ if isinstance(symbol, MarkerSymbol):
73
+ return symbol.value
74
+ return MarkerSymbol.get_enum(symbol).value
75
+
76
+
77
+ class Line(BaseModel):
78
+ """
79
+ Represents line settings for chart elements.
80
+
81
+ Attributes:
82
+ ltype (Optional[str | ChartLineType]): The type of line.
83
+ smooth (Optional[bool]): Specifies if the line should be smoothed.
84
+ width (Optional[float]): The width of the line.
85
+ show_marker_line (Optional[bool]): Indicates if the line should be shown on markers.
86
+ """
87
+
88
+ ltype: Optional[str | ChartLineType] = Field(None, serialization_alias='Type')
89
+ smooth: Optional[bool] = Field(None, serialization_alias='Smooth')
90
+ width: Optional[float] = Field(None, serialization_alias='Width')
91
+ show_marker_line: Optional[bool] = Field(None, serialization_alias='ShowMarkerLine')
92
+
93
+ @field_serializer('ltype')
94
+ @classmethod
95
+ def line_type_validator(cls, ltype: str | ChartLineType | None) -> int:
96
+ if ltype is None:
97
+ return None
98
+ if isinstance(ltype, ChartLineType):
99
+ return ltype.value
100
+ return ChartLineType.get_enum(ltype).value
101
+
102
+
103
+ class ChartLegend(BaseModel):
104
+ """
105
+ Defines settings for the chart legend.
106
+
107
+ Attributes:
108
+ position (Optional[Literal['none', 'top', 'bottom', 'left', 'right', 'top_right']]):
109
+ The position of the legend.
110
+ show_legend_key (Optional[bool]): Specifies if the legend key should be shown.
111
+ """
112
+
113
+ position: Optional[Literal['none', 'top', 'bottom', 'left', 'right', 'top_right']] = Field(
114
+ None, serialization_alias='Position'
115
+ )
116
+ show_legend_key: Optional[bool] = Field(None, serialization_alias='ShowLegendKey')
117
+
118
+
119
+ class RichTextRun(BaseModel):
120
+ """
121
+ Represents a text run with rich text formatting.
122
+
123
+ Attributes:
124
+ text (str): The text content.
125
+ font (Optional[Font]): Font settings for the text.
126
+ """
127
+
128
+ text: str = Field(..., serialization_alias='Text')
129
+ font: Optional[Font] = Field(None, serialization_alias='Font')
130
+
131
+
132
+ class ChartCustomNumFmt(BaseModel):
133
+ """
134
+ Custom number formatting for chart elements.
135
+
136
+ Attributes:
137
+ num_fmt (Optional[str]): The custom number format.
138
+ source_linked (Optional[bool]): Specifies if the format is linked to the source.
139
+ """
140
+
141
+ num_fmt: Optional[str] = Field(None, serialization_alias='CustomNumFmt')
142
+ source_linked: Optional[bool] = Field(None, serialization_alias='SourceLinked')
143
+
144
+
145
+ class ChartAxis(BaseModel):
146
+ """
147
+ Defines the settings for chart axes.
148
+
149
+ Attributes:
150
+ none (Optional[bool]): Specifies if the axis should be hidden.
151
+ font (Optional[Font]): Font settings for the axis labels.
152
+ major_grid_lines (Optional[bool]): Specifies if major grid lines should be displayed.
153
+ minor_grid_lines (Optional[bool]): Specifies if minor grid lines should be displayed.
154
+ major_unit (Optional[float]): The interval between major grid lines.
155
+ tick_label_skip (Optional[int]): Specifies the number of tick labels to skip between
156
+ each drawn label.
157
+ reverse_order (Optional[bool]): Indicates if the axis order should be reversed.
158
+ secondary (Optional[bool]): Specifies if this is a secondary axis.
159
+ maximum (Optional[float]): The maximum value for the axis.
160
+ minimum (Optional[float]): The minimum value for the axis.
161
+ log_base (Optional[float]): The logarithmic base for the axis scale.
162
+ num_fmt (Optional[ChartCustomNumFmt]): Custom number format for the axis.
163
+ title (Optional[List[RichTextRun]]): The title of the axis.
164
+ """
165
+
166
+ none: Optional[bool] = Field(None, serialization_alias='None')
167
+ font: Optional[Font] = Field(Font(), serialization_alias='Font')
168
+ major_grid_lines: Optional[bool] = Field(None, serialization_alias='MajorGridLines')
169
+ minor_grid_lines: Optional[bool] = Field(None, serialization_alias='MinorGridLines')
170
+ major_unit: Optional[float] = Field(None, serialization_alias='MajorUnit')
171
+ tick_label_skip: Optional[int] = Field(None, serialization_alias='TickLabelSkip')
172
+ reverse_order: Optional[bool] = Field(None, serialization_alias='ReverseOrder')
173
+ secondary: Optional[bool] = Field(None, serialization_alias='Secondary')
174
+ maximum: Optional[float] = Field(None, serialization_alias='Maximum')
175
+ minimum: Optional[float] = Field(None, serialization_alias='Minimum')
176
+ log_base: Optional[float] = Field(None, serialization_alias='LogBase')
177
+ num_fmt: Optional[ChartCustomNumFmt] = Field(ChartCustomNumFmt(), serialization_alias='NumFmt')
178
+ title: Optional[List[RichTextRun]] = Field(None, serialization_alias='Title')
179
+
180
+
181
+ class ChartPlotArea(BaseModel):
182
+ """
183
+ Represents the plot area of a chart.
184
+
185
+ Attributes:
186
+ second_plot_values (Optional[int]): The number of values in a secondary plot
187
+ (Only for pieOfPie and barOfPie chart).
188
+ show_bubble_size (Optional[bool]): Indicates if bubble sizes should be displayed.
189
+ show_cat_name (Optional[bool]): Specifies if category names should be shown.
190
+ show_leader_lines (Optional[bool]): Indicates if leader lines should be shown in
191
+ the data label.
192
+ show_percent (Optional[bool]): Specifies if percentages should be shown in the data label.
193
+ show_ser_name (Optional[bool]): Indicates if series names should be displayed in the
194
+ data label.
195
+ show_val (Optional[bool]): Specifies if values should be shown in the data label.
196
+ fill (Optional[Fill]): Fill settings for the plot area.
197
+ num_fmt (Optional[ChartCustomNumFmt]): Custom number format for the plot area.
198
+ """
199
+
200
+ second_plot_values: Optional[int] = Field(None, serialization_alias='SecondPlotValues')
201
+ show_bubble_size: Optional[bool] = Field(None, serialization_alias='ShowBubbleSize')
202
+ show_cat_name: Optional[bool] = Field(None, serialization_alias='ShowCatName')
203
+ show_leader_lines: Optional[bool] = Field(None, serialization_alias='ShowLeaderLines')
204
+ show_percent: Optional[bool] = Field(None, serialization_alias='ShowPercent')
205
+ show_ser_name: Optional[bool] = Field(None, serialization_alias='ShowSerName')
206
+ show_val: Optional[bool] = Field(None, serialization_alias='ShowVal')
207
+ fill: Optional[Fill] = Field(None, serialization_alias='Fill')
208
+ num_fmt: Optional[ChartCustomNumFmt] = Field(ChartCustomNumFmt(), serialization_alias='NumFmt')
209
+
210
+
211
+ class GraphicOptions(BaseModel):
212
+ """
213
+ Defines various graphical options for chart elements.
214
+
215
+ Attributes:
216
+ alt_text (Optional[str]): Alternative text for accessibility.
217
+ print_object (Optional[bool]): Indicates if the object should be printed.
218
+ locked (Optional[bool]): Specifies if the object is locked.
219
+ lock_aspect_ratio (Optional[bool]): Indicates if the aspect ratio should be locked.
220
+ auto_fit (Optional[bool]): Specifies if the object should automatically fit its content.
221
+ offset_x (Optional[int]): The horizontal offset of the object.
222
+ offset_y (Optional[int]): The vertical offset of the object.
223
+ scale_x (Optional[float]): The horizontal scale factor.
224
+ scale_y (Optional[float]): The vertical scale factor.
225
+ hyperlink (Optional[str]): A hyperlink associated with the object.
226
+ hyperlink_type (Optional[str]): The type of hyperlink.
227
+ positioning (Optional[str]): The positioning mode for the object.
228
+ """
229
+
230
+ alt_text: Optional[str] = Field(None, serialization_alias='AltText')
231
+ print_object: Optional[bool] = Field(None, serialization_alias='PrintObject')
232
+ locked: Optional[bool] = Field(None, serialization_alias='Locked')
233
+ lock_aspect_ratio: Optional[bool] = Field(None, serialization_alias='LockAspectRatio')
234
+ auto_fit: Optional[bool] = Field(None, serialization_alias='AutoFit')
235
+ offset_x: Optional[int] = Field(None, serialization_alias='OffsetX')
236
+ offset_y: Optional[int] = Field(None, serialization_alias='OffsetY')
237
+ scale_x: Optional[float] = Field(None, serialization_alias='ScaleX')
238
+ scale_y: Optional[float] = Field(None, serialization_alias='ScaleY')
239
+ hyperlink: Optional[str] = Field(None, serialization_alias='Hyperlink')
240
+ hyperlink_type: Optional[str] = Field(None, serialization_alias='HyperlinkType')
241
+ positioning: Optional[str] = Field(None, serialization_alias='Positioning')
242
+
243
+
244
+ class ChartDimension(BaseModel):
245
+ """
246
+ Specifies the dimensions of a chart.
247
+
248
+ Attributes:
249
+ width (Optional[int]): The width of the chart.
250
+ height (Optional[int]): The height of the chart.
251
+ """
252
+
253
+ width: Optional[int] = Field(None, serialization_alias='Width')
254
+ height: Optional[int] = Field(None, serialization_alias='Height')
255
+
256
+
257
+ class ChartSeries(BaseModel):
258
+ """
259
+ Represents a data series in a chart.
260
+
261
+ Attributes:
262
+ name (str): The name of the series (Legend).
263
+ categories (str): The categories for the series (X value).
264
+ values (str): The values for the series (Y value).
265
+ sizes (Optional[str]): The sizes for bubble charts.
266
+ fill (Optional[Fill]): Fill settings for the series.
267
+ line (Optional[Line]): Line settings for the series.
268
+ marker (Optional[Marker]): Marker settings for the series.
269
+ data_label_position (Optional[str | ChartDataLabelPosition]): The position
270
+ of data labels for the series.
271
+ """
272
+
273
+ name: str = Field(..., serialization_alias='Name')
274
+ categories: str = Field(..., serialization_alias='Categories')
275
+ values: str = Field(..., serialization_alias='Values')
276
+ sizes: Optional[str] = Field(None, serialization_alias='Sizes')
277
+ fill: Optional[Fill] = Field(Fill(), serialization_alias='Fill')
278
+ line: Optional[Line] = Field(Line(), serialization_alias='Line')
279
+ marker: Optional[Marker] = Field(Marker(), serialization_alias='Marker')
280
+ data_label_position: Optional[str | ChartDataLabelPosition] = Field(
281
+ None, serialization_alias='DataLabelPosition'
282
+ )
283
+
284
+ @field_serializer('data_label_position')
285
+ @classmethod
286
+ def data_label_position_validator(cls, label: str | ChartDataLabelPosition | None) -> int:
287
+ if label is None:
288
+ return ChartDataLabelPosition.Unset.value
289
+ if isinstance(label, ChartDataLabelPosition):
290
+ return label.value
291
+ return ChartDataLabelPosition.get_enum(label).value
292
+
293
+
294
+ class Chart(BaseModel):
295
+ """
296
+ Defines the configuration for a chart.
297
+
298
+ Attributes:
299
+ chart_type (str | ChartType): The type of chart, such as 'bar', 'line', etc.
300
+ series (List[ChartSeries] | ChartSeries): The data series to be plotted
301
+ in the chart.
302
+ graph_format (Optional[GraphicOptions]): Graphical options for the chart.
303
+ title (Optional[List[RichTextRun]]): The title of the chart.
304
+ legend (Optional[ChartLegend]): The legend settings for the chart.
305
+ dimension (Optional[ChartDimension]): The dimensions of the chart.
306
+ vary_colors (Optional[bool]): Specifies if colors should vary by data point.
307
+ x_axis (Optional[ChartAxis]): The configuration of the X-axis.
308
+ y_axis (Optional[ChartAxis]): The configuration of the Y-axis.
309
+ plot_area (Optional[ChartPlotArea]): The configuration of the plot area.
310
+ fill (Optional[Fill]): The fill settings for the chart.
311
+ border (Optional[Line]): The border settings for the chart.
312
+ show_blanks_as (Optional[str]): Specifies how blanks should be shown in the chart.
313
+ bubble_size (Optional[int]): The size of bubbles in a bubble chart.
314
+ hole_size (Optional[int]): The size of the hole in a doughnut chart.
315
+ order (Optional[int]): The order of the series in the chart.
316
+ """
317
+
318
+ chart_type: str | ChartType = Field(..., serialization_alias='Type')
319
+ series: List[ChartSeries] | ChartSeries = Field(None, serialization_alias='Series')
320
+ graph_format: Optional[GraphicOptions] = Field(GraphicOptions(), serialization_alias='Format')
321
+ title: Optional[List[RichTextRun]] = Field(None, serialization_alias='Title')
322
+ legend: Optional[ChartLegend] = Field(ChartLegend(), serialization_alias='Legend')
323
+ dimension: Optional[ChartDimension] = Field(ChartDimension(), serialization_alias='Dimension')
324
+ vary_colors: Optional[bool] = Field(None, serialization_alias='VaryColors')
325
+ x_axis: Optional[ChartAxis] = Field(ChartAxis(), serialization_alias='XAxis')
326
+ y_axis: Optional[ChartAxis] = Field(ChartAxis(), serialization_alias='YAxis')
327
+ plot_area: Optional[ChartPlotArea] = Field(ChartPlotArea(), serialization_alias='PlotArea')
328
+ fill: Optional[Fill] = Field(Fill(), serialization_alias='Fill')
329
+ border: Optional[Line] = Field(Line(), serialization_alias='Border')
330
+ show_blanks_as: Optional[str] = Field(None, serialization_alias='ShowBlanksAs')
331
+ bubble_size: Optional[int] = Field(None, serialization_alias='BubbleSize')
332
+ hole_size: Optional[int] = Field(None, serialization_alias='HoleSize')
333
+ order: Optional[int] = Field(None, serialization_alias='order')
334
+
335
+ @field_serializer('series')
336
+ @classmethod
337
+ def series_serializer(cls, series: List[ChartSeries] | ChartSeries) -> List[ChartSeries]:
338
+ series = [series] if isinstance(series, ChartSeries) else series
339
+ return series
340
+
341
+ @field_serializer('chart_type')
342
+ @classmethod
343
+ def chart_type_validator(cls, chart_type: str | ChartType) -> int:
344
+ if isinstance(chart_type, ChartType):
345
+ return chart_type.value
346
+ return ChartType.get_enum(chart_type).value
pyfastexcel/driver.py ADDED
@@ -0,0 +1,290 @@
1
+ from __future__ import annotations
2
+
3
+ import base64
4
+ import ctypes
5
+ import logging
6
+ import sys
7
+ from datetime import datetime, timezone
8
+ from io import BytesIO
9
+ from pathlib import Path
10
+ from typing import overload
11
+
12
+ import msgspec
13
+ from openpyxl import load_workbook
14
+ from openpyxl_style_writer import CustomStyle
15
+
16
+ from .logformatter import formatter
17
+ from .style import StyleManager
18
+ from .worksheet import WorkSheet
19
+ from .validators import TableFinalValidation
20
+ from ._typing import Writable
21
+
22
+ BASE_DIR = Path(__file__).resolve().parent
23
+
24
+ logger = logging.getLogger(__name__)
25
+ style_formatter = logging.StreamHandler()
26
+ style_formatter.setFormatter(formatter)
27
+
28
+ logger.addHandler(style_formatter)
29
+ logger.propagate = False
30
+
31
+
32
+ class ExcelDriver:
33
+ """
34
+ A driver class to write data to Excel files using custom styles.
35
+
36
+ ### Attributes:
37
+ _FILE_PROPS (dict[str, str]): Default file properties for the Excel
38
+ file.
39
+ _PROTECT_ALGORITHM (tuple[str]): Algorithm for the workbook protection
40
+
41
+ ### Methods:
42
+ __init__(): Initializes the ExcelDriver.
43
+ _read_lib(lib_path: str): Reads a library for Excel manipulation.
44
+ read_lib_and_create_excel(lib_path: str = None): Reads the library and
45
+ creates the Excel file.
46
+ """
47
+
48
+ _FILE_PROPS = {
49
+ 'Category': '',
50
+ 'ContentStatus': '',
51
+ 'Created': '',
52
+ 'Creator': 'pyfastexcel',
53
+ 'Description': '',
54
+ 'Identifier': 'xlsx',
55
+ 'Keywords': 'spreadsheet',
56
+ 'LastModifiedBy': 'pyfastexcel',
57
+ 'Modified': '',
58
+ 'Revision': '0',
59
+ 'Subject': '',
60
+ 'Title': '',
61
+ 'Language': 'en-Us',
62
+ 'Version': '',
63
+ }
64
+ _PROTECT_ALGORITHM = (
65
+ 'XOR',
66
+ 'MD4',
67
+ 'MD5',
68
+ 'SHA-1',
69
+ 'SHA-256',
70
+ 'SHA-384',
71
+ 'SHA-512',
72
+ )
73
+ DEBUG = False
74
+
75
+ def __init__(self, pre_allocate: dict[str, int] = None, plain_data: list[list[str]] = None):
76
+ """
77
+ Initializes the Workbook with default settings and initializes Sheet1.
78
+
79
+ It initializes the workbook structure with Sheet1 as the default sheet,
80
+ sets default file properties, initializes current sheet and sheet list,
81
+ and initializes dictionaries for workbook and protection settings.
82
+
83
+ Args:
84
+ pre_allocate (dict[str, int], optional): A dictionary containing 'n_rows' and 'n_cols'
85
+ keys specifying the dimensions for pre-allocating data in Sheet1.
86
+ plain_data (list[list[str]], optional): A 2D list of strings representing initial data
87
+ to populate Sheet1.
88
+ """
89
+ self.workbook = {
90
+ 'Sheet1': WorkSheet(pre_allocate=pre_allocate, plain_data=plain_data),
91
+ }
92
+ self.file_props = self._get_default_file_props()
93
+ self.sheet = 'Sheet1'
94
+ self._sheet_list = tuple(['Sheet1'])
95
+ self._dict_wb = {}
96
+ self.protection = {}
97
+ self.style = StyleManager()
98
+
99
+ @property
100
+ def sheet_list(self):
101
+ return list(self._sheet_list)
102
+
103
+ @overload
104
+ def save(self, file: Writable) -> None:
105
+ """
106
+ Saves the workbook to a writable object.
107
+
108
+ Args:
109
+ file (Writable): Writable object that has .write() function.
110
+ """
111
+ ...
112
+
113
+ @overload
114
+ def save(self, path: str) -> None:
115
+ """
116
+ Saves the workbook to a file.
117
+
118
+ Args:
119
+ path (str): A path to save the file.
120
+ """
121
+ ...
122
+
123
+ def save(self, file_or_path: Writable | str) -> None:
124
+ if not hasattr(self, 'decoded_bytes'):
125
+ self.read_lib_and_create_excel()
126
+
127
+ if isinstance(file_or_path, str):
128
+ with open(file_or_path, 'wb') as file:
129
+ file.write(self.decoded_bytes)
130
+ else:
131
+ file_or_path.write(self.decoded_bytes)
132
+
133
+ def __getitem__(self, key: str) -> WorkSheet:
134
+ return self.workbook[key]
135
+
136
+ def _check_if_sheet_exists(self, sheet_name: str) -> None:
137
+ if sheet_name not in self.sheet_list:
138
+ raise KeyError(f'{sheet_name} Sheet Does Not Exist.')
139
+
140
+ def read_lib_and_create_excel(
141
+ self, lib_path: str = None, ignore_go_panic: bool = True
142
+ ) -> bytes:
143
+ """
144
+ Reads the library and creates the Excel file.
145
+
146
+ Args:
147
+ lib_path (str, optional): The path to the library. Defaults to None.
148
+
149
+ Returns:
150
+ bytes: The byte data of the created Excel file.
151
+ """
152
+ ignore_go_panic = 0 if ignore_go_panic is False else 1
153
+ pyfastexcel = self._read_lib(lib_path)
154
+ self._create_style()
155
+
156
+ # Transfer all WorkSheet Object to the sheet dictionary in the workbook.
157
+ set_group_columns = False
158
+ set_row_columns = False
159
+ use_openpyxl = False
160
+ for sheet in self._sheet_list:
161
+ self._dict_wb[sheet] = self.workbook[sheet]._transfer_to_dict()
162
+ if self.workbook[sheet]._excel_engine == 'openpyxl':
163
+ use_openpyxl = True
164
+ if len(self.workbook[sheet]._grouped_columns_list) != 0:
165
+ set_group_columns = True
166
+ if len(self.workbook[sheet]._grouped_rows_list) != 0:
167
+ set_row_columns = True
168
+ if len(self.workbook[sheet]._table_list) != 0:
169
+ TableFinalValidation(
170
+ data=self.workbook[sheet]._data,
171
+ table_list=self.workbook[sheet]._table_list,
172
+ )
173
+
174
+ results = {
175
+ 'content': self._dict_wb,
176
+ 'file_props': self.file_props,
177
+ 'style': self.style._style_map,
178
+ 'protection': self.protection,
179
+ 'sheet_order': self._sheet_list,
180
+ }
181
+ json_data = msgspec.json.encode(results)
182
+ create_excel = pyfastexcel.Export
183
+ free_pointer = pyfastexcel.FreeCPointer
184
+ free_pointer.argtypes = [ctypes.c_void_p, ctypes.c_int64]
185
+ create_excel.argtypes = [ctypes.c_char_p, ctypes.c_int64]
186
+ create_excel.restype = ctypes.c_void_p
187
+ byte_data = create_excel(json_data, ignore_go_panic)
188
+ self.decoded_bytes = base64.b64decode(ctypes.cast(byte_data, ctypes.c_char_p).value)
189
+ free_pointer(byte_data, 1 if self.DEBUG else 0)
190
+ StyleManager.reset_style_configs()
191
+
192
+ # Due to Streaming API of Excelize can't group column currently
193
+ # So implement this function by openpyxl
194
+ if (set_group_columns or set_row_columns) and use_openpyxl is True:
195
+ logger.info('Using openpyxl to group columns and rows...')
196
+ self.decoded_bytes = self._set_group_columns_and_group_rows()
197
+
198
+ return self.decoded_bytes
199
+
200
+ def _set_group_columns_and_group_rows(self):
201
+ wb = load_workbook(BytesIO(self.decoded_bytes))
202
+ for sheet in self._sheet_list:
203
+ grouped_columns = self.workbook[sheet]._grouped_columns_list
204
+ ws = wb[sheet]
205
+ for col in grouped_columns:
206
+ ws.column_dimensions.group(
207
+ col['start_col'],
208
+ col['end_col'],
209
+ outline_level=col['outline_level'],
210
+ hidden=col['hidden'],
211
+ )
212
+ grouped_rows = self.workbook[sheet]._grouped_rows_list
213
+ for row in grouped_rows:
214
+ ws.row_dimensions.group(
215
+ row['start_row'],
216
+ row['end_row'],
217
+ outline_level=row['outline_level'],
218
+ hidden=row['hidden'],
219
+ )
220
+
221
+ # Save the workbook to a BytesIO stream
222
+ excel_stream = BytesIO()
223
+ wb.save(excel_stream)
224
+ decoded_bytes = excel_stream.getvalue()
225
+ excel_stream.close()
226
+ return decoded_bytes
227
+
228
+ def _read_lib(self, lib_path: str) -> ctypes.CDLL: # pragma: no cover
229
+ """
230
+ Reads a shared-library for writing Excel.
231
+
232
+ Args:
233
+ lib_path (str): The path to the library.
234
+
235
+ Returns:
236
+ ctypes.CDLL: The library object.
237
+ """
238
+ if lib_path is None:
239
+ if sys.platform.startswith('linux'):
240
+ lib_path = str(list(BASE_DIR.glob('**/*.so'))[0])
241
+ elif sys.platform.startswith('win32'):
242
+ lib_path = str(list(BASE_DIR.glob('**/*.dll'))[0])
243
+ elif sys.platform.startswith('darwin'):
244
+ lib_path = str(list(BASE_DIR.glob('**/*.dylib'))[0])
245
+
246
+ # On macOS, there is no winmode parameter, so we should not pass it
247
+ if sys.platform.startswith('win32') or sys.platform.startswith('linux'):
248
+ lib = ctypes.CDLL(lib_path, winmode=0)
249
+ else:
250
+ lib = ctypes.CDLL(lib_path)
251
+ return lib
252
+
253
+ def _get_default_file_props(self) -> dict[str, str]:
254
+ now = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
255
+ file_props = self._FILE_PROPS.copy()
256
+ file_props['Created'] = now
257
+ file_props['Modified'] = now
258
+ return file_props
259
+
260
+ def _get_style_collections(self) -> dict[str, CustomStyle]:
261
+ """
262
+ Gets collections of custom styles.
263
+
264
+ Returns:
265
+ dict[str, CustomStyle]: A dictionary containing custom style
266
+ collections.
267
+ """
268
+ return {
269
+ attr: getattr(self, attr)
270
+ for attr in dir(self)
271
+ if not callable(getattr(self, attr)) and isinstance(getattr(self, attr), CustomStyle)
272
+ }
273
+
274
+ def _create_style(self) -> None:
275
+ """
276
+ Creates custom styles for the Excel file.
277
+
278
+ This method initializes custom styles for the Excel file based on
279
+ predefined attributes.
280
+ """
281
+ style_collections = self._get_style_collections()
282
+ self.style._STYLE_NAME_MAP.update({val: key for key, val in style_collections.items()})
283
+
284
+ # Set the CustomStyle from the pre-defined class attributes.
285
+ for key, val in style_collections.items():
286
+ self.style._update_style_map(key, val)
287
+
288
+ # Set the CustomStyle from the REGISTERED method.
289
+ for key, val in self.style.REGISTERED_STYLES.items():
290
+ self.style._update_style_map(key, val)