pyfastexcel 0.0.8__tar.gz → 0.0.9__tar.gz
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.
- {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/PKG-INFO +4 -4
- {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/README.md +3 -3
- pyfastexcel-0.0.9/pyfastexcel/_typing.py +25 -0
- {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/pyfastexcel/driver.py +13 -7
- {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/pyfastexcel/pyfastexcel.dll +0 -0
- {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/pyfastexcel/pyfastexcel.so +0 -0
- {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/pyfastexcel/utils.py +64 -2
- {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/pyfastexcel/workbook.py +76 -2
- {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/pyfastexcel/worksheet.py +242 -22
- {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/pyfastexcel.egg-info/PKG-INFO +4 -4
- {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/pyfastexcel.egg-info/SOURCES.txt +1 -0
- {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/setup.cfg +1 -1
- {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/LICENSE +0 -0
- {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/pyfastexcel/__init__.py +0 -0
- {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/pyfastexcel/style.py +0 -0
- {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/pyfastexcel/writer.py +0 -0
- {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/pyfastexcel.egg-info/dependency_links.txt +0 -0
- {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/pyfastexcel.egg-info/not-zip-safe +0 -0
- {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/pyfastexcel.egg-info/requires.txt +0 -0
- {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/pyfastexcel.egg-info/top_level.txt +0 -0
- {pyfastexcel-0.0.8 → pyfastexcel-0.0.9}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pyfastexcel
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.9
|
|
4
4
|
Summary: High performace excel file generation library.
|
|
5
5
|
Home-page: https://github.com/Zncl2222/pyfastexcel
|
|
6
6
|
Author: Zncl2222
|
|
@@ -100,7 +100,7 @@ If you prefer to build the package manually, follow these steps:
|
|
|
100
100
|
|
|
101
101
|
## Usage
|
|
102
102
|
|
|
103
|
-
The index assignment is now avaliable in `Workbook` and the `
|
|
103
|
+
The index assignment is now avaliable in `Workbook` and the `StreamWriter`.
|
|
104
104
|
Here is the example usage:
|
|
105
105
|
|
|
106
106
|
```python
|
|
@@ -146,8 +146,8 @@ if __name__ == '__main__':
|
|
|
146
146
|
|
|
147
147
|
```
|
|
148
148
|
|
|
149
|
-
You can also using the `
|
|
150
|
-
subclass of `Workbook` to write excel row by row, see
|
|
149
|
+
You can also using the `StreamWriter` which was the
|
|
150
|
+
subclass of `Workbook` to write excel row by row, see more in [StreamWriter](https://pyfastexcel.readthedocs.io/en/stable/writer/).
|
|
151
151
|
|
|
152
152
|
## Documentation
|
|
153
153
|
|
|
@@ -76,7 +76,7 @@ If you prefer to build the package manually, follow these steps:
|
|
|
76
76
|
|
|
77
77
|
## Usage
|
|
78
78
|
|
|
79
|
-
The index assignment is now avaliable in `Workbook` and the `
|
|
79
|
+
The index assignment is now avaliable in `Workbook` and the `StreamWriter`.
|
|
80
80
|
Here is the example usage:
|
|
81
81
|
|
|
82
82
|
```python
|
|
@@ -122,8 +122,8 @@ if __name__ == '__main__':
|
|
|
122
122
|
|
|
123
123
|
```
|
|
124
124
|
|
|
125
|
-
You can also using the `
|
|
126
|
-
subclass of `Workbook` to write excel row by row, see
|
|
125
|
+
You can also using the `StreamWriter` which was the
|
|
126
|
+
subclass of `Workbook` to write excel row by row, see more in [StreamWriter](https://pyfastexcel.readthedocs.io/en/stable/writer/).
|
|
127
127
|
|
|
128
128
|
## Documentation
|
|
129
129
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Literal, TypedDict, Optional, List, Union
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CommentTextDict(TypedDict, total=False):
|
|
7
|
+
text: str
|
|
8
|
+
size: Optional[int]
|
|
9
|
+
name: Optional[str]
|
|
10
|
+
bold: Optional[bool]
|
|
11
|
+
italic: Optional[bool]
|
|
12
|
+
underline: Optional[Literal['single', 'double']]
|
|
13
|
+
strike: Optional[bool]
|
|
14
|
+
vert_align: Optional[str]
|
|
15
|
+
color: Optional[str]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SelectionDict(TypedDict, total=False):
|
|
19
|
+
sq_ref: str
|
|
20
|
+
active_cell: str
|
|
21
|
+
pane: str
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
CommentTextStructure = Union[str, List[str], CommentTextDict, List[CommentTextDict]]
|
|
25
|
+
SetPanesSelection = List[SelectionDict]
|
|
@@ -20,10 +20,9 @@ class ExcelDriver:
|
|
|
20
20
|
A driver class to write data to Excel files using custom styles.
|
|
21
21
|
|
|
22
22
|
### Attributes:
|
|
23
|
-
BORDER_TO_INDEX (dict[str, int]): Mapping of border styles to excelize's
|
|
24
|
-
corresponding index.
|
|
25
23
|
_FILE_PROPS (dict[str, str]): Default file properties for the Excel
|
|
26
24
|
file.
|
|
25
|
+
_PROTECT_ALGORITHM (tuple[str]): Algorithm for the workbook protection
|
|
27
26
|
|
|
28
27
|
### Methods:
|
|
29
28
|
__init__(): Initializes the ExcelDriver.
|
|
@@ -58,15 +57,22 @@ class ExcelDriver:
|
|
|
58
57
|
'SHA-512',
|
|
59
58
|
)
|
|
60
59
|
|
|
61
|
-
def __init__(self):
|
|
60
|
+
def __init__(self, pre_allocate: dict[str, int] = None, plain_data: list[list[str]] = None):
|
|
62
61
|
"""
|
|
63
|
-
Initializes the
|
|
62
|
+
Initializes the Workbook with default settings and initializes Sheet1.
|
|
64
63
|
|
|
65
|
-
It initializes the
|
|
66
|
-
current sheet
|
|
64
|
+
It initializes the workbook structure with Sheet1 as the default sheet,
|
|
65
|
+
sets default file properties, initializes current sheet and sheet list,
|
|
66
|
+
and initializes dictionaries for workbook and protection settings.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
pre_allocate (dict[str, int], optional): A dictionary containing 'n_rows' and 'n_cols'
|
|
70
|
+
keys specifying the dimensions for pre-allocating data in Sheet1.
|
|
71
|
+
plain_data (list[list[str]], optional): A 2D list of strings representing initial data
|
|
72
|
+
to populate Sheet1.
|
|
67
73
|
"""
|
|
68
74
|
self.workbook = {
|
|
69
|
-
'Sheet1': WorkSheet(),
|
|
75
|
+
'Sheet1': WorkSheet(pre_allocate=pre_allocate, plain_data=plain_data),
|
|
70
76
|
}
|
|
71
77
|
self.file_props = self._get_default_file_props()
|
|
72
78
|
self.sheet = 'Sheet1'
|
|
Binary file
|
|
Binary file
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import re
|
|
4
4
|
import string
|
|
5
5
|
import warnings
|
|
6
|
+
from dataclasses import dataclass
|
|
6
7
|
from typing import Any, Literal
|
|
7
8
|
|
|
8
9
|
from openpyxl_style_writer import CustomStyle
|
|
@@ -10,6 +11,44 @@ from openpyxl_style_writer import CustomStyle
|
|
|
10
11
|
warnings.simplefilter('always', DeprecationWarning)
|
|
11
12
|
|
|
12
13
|
|
|
14
|
+
@dataclass
|
|
15
|
+
class CommentText:
|
|
16
|
+
text: str
|
|
17
|
+
size: int | None = None
|
|
18
|
+
name: str | None = None
|
|
19
|
+
bold: bool | None = None
|
|
20
|
+
italic: bool | None = None
|
|
21
|
+
underline: Literal['single', 'double'] | None = None
|
|
22
|
+
strike: bool | None = None
|
|
23
|
+
vert_align: str | None = None
|
|
24
|
+
color: str | None = None
|
|
25
|
+
|
|
26
|
+
def to_dict(self):
|
|
27
|
+
result = {
|
|
28
|
+
k[0].upper() + k[1:]: v
|
|
29
|
+
for k, v in self.__dict__.items()
|
|
30
|
+
if v is not None and k != 'text'
|
|
31
|
+
}
|
|
32
|
+
if result.get('Vert_align') is not None:
|
|
33
|
+
result['VertAlign'] = result['Vert_align']
|
|
34
|
+
result['text'] = self.text
|
|
35
|
+
return result
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class Selection:
|
|
40
|
+
sq_ref: str
|
|
41
|
+
active_cell: str
|
|
42
|
+
pane: str
|
|
43
|
+
|
|
44
|
+
def to_dict(self):
|
|
45
|
+
return {
|
|
46
|
+
'sq_ref': self.sq_ref,
|
|
47
|
+
'active_cell': self.active_cell,
|
|
48
|
+
'pane': self.pane,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
13
52
|
def deprecated_warning(msg: str):
|
|
14
53
|
warnings.warn(
|
|
15
54
|
msg,
|
|
@@ -48,6 +87,17 @@ def validate_and_format_value(
|
|
|
48
87
|
return (value, 'DEFAULT_STYLE') if set_default_style else value
|
|
49
88
|
|
|
50
89
|
|
|
90
|
+
def transfer_string_slice_to_slice(string_slice: str) -> slice:
|
|
91
|
+
'''
|
|
92
|
+
Transfer a string slice to a slice object.
|
|
93
|
+
For example, 'A1:B2' will be transfered to slice(1, 2, 1).
|
|
94
|
+
'''
|
|
95
|
+
start, end = string_slice.split(':')
|
|
96
|
+
alpha_start, row_start = _separate_alpha_numeric(start)
|
|
97
|
+
alpha_end, row_end = _separate_alpha_numeric(end)
|
|
98
|
+
return slice(f'{alpha_start}{row_start}', f'{alpha_end}{row_end}')
|
|
99
|
+
|
|
100
|
+
|
|
51
101
|
def _separate_alpha_numeric(input_string: str) -> tuple[str, str]:
|
|
52
102
|
'''
|
|
53
103
|
Separate the alpha and numeric part of a string.
|
|
@@ -61,6 +111,9 @@ def _separate_alpha_numeric(input_string: str) -> tuple[str, str]:
|
|
|
61
111
|
|
|
62
112
|
|
|
63
113
|
def _is_valid_column(column: str) -> bool:
|
|
114
|
+
"""
|
|
115
|
+
Validate the alphabet part of the column.
|
|
116
|
+
"""
|
|
64
117
|
column = column.upper()
|
|
65
118
|
index = 0
|
|
66
119
|
for c in column:
|
|
@@ -69,6 +122,9 @@ def _is_valid_column(column: str) -> bool:
|
|
|
69
122
|
|
|
70
123
|
|
|
71
124
|
def column_to_index(column: str) -> int:
|
|
125
|
+
"""
|
|
126
|
+
Translate the column name to the column index.
|
|
127
|
+
"""
|
|
72
128
|
if not isinstance(column, str):
|
|
73
129
|
raise TypeError(f'Invalid type ({type(column)}). Column should be a string.')
|
|
74
130
|
if len(column) > 3:
|
|
@@ -85,6 +141,9 @@ def column_to_index(column: str) -> int:
|
|
|
85
141
|
|
|
86
142
|
|
|
87
143
|
def index_to_column(index: int) -> str:
|
|
144
|
+
"""
|
|
145
|
+
Translate the index to the column name.
|
|
146
|
+
"""
|
|
88
147
|
if not isinstance(index, int):
|
|
89
148
|
raise TypeError(f'Invalid type ({type(index)}). Index should be a string.')
|
|
90
149
|
if index < 1 or index > 16384:
|
|
@@ -96,14 +155,17 @@ def index_to_column(index: int) -> str:
|
|
|
96
155
|
return name
|
|
97
156
|
|
|
98
157
|
|
|
99
|
-
def
|
|
158
|
+
def cell_reference_to_index(index: str) -> tuple[int, int]:
|
|
159
|
+
"""
|
|
160
|
+
Return the row and column index of the given Excel cell reference.
|
|
161
|
+
"""
|
|
100
162
|
alpha, num = _separate_alpha_numeric(index)
|
|
101
163
|
column = column_to_index(alpha)
|
|
102
164
|
row = int(num)
|
|
103
165
|
return row - 1, column - 1
|
|
104
166
|
|
|
105
167
|
|
|
106
|
-
def
|
|
168
|
+
def _validate_cell_reference(index: str) -> bool:
|
|
107
169
|
alpha, num = _separate_alpha_numeric(index)
|
|
108
170
|
num = int(num)
|
|
109
171
|
if not all(c in string.ascii_uppercase for c in alpha):
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
3
5
|
from pyfastexcel.driver import ExcelDriver, WorkSheet
|
|
4
6
|
from pyfastexcel.utils import deprecated_warning
|
|
5
7
|
|
|
@@ -43,16 +45,26 @@ class Workbook(ExcelDriver):
|
|
|
43
45
|
self._sheet_list = tuple(self.workbook.keys())
|
|
44
46
|
self.sheet = self._sheet_list[0]
|
|
45
47
|
|
|
46
|
-
def create_sheet(
|
|
48
|
+
def create_sheet(
|
|
49
|
+
self,
|
|
50
|
+
sheet_name: str,
|
|
51
|
+
pre_allocate: dict[str, int] = None,
|
|
52
|
+
plain_data: list[list] = None,
|
|
53
|
+
) -> None:
|
|
47
54
|
"""
|
|
48
55
|
Creates a new sheet, and set it as current self.sheet.
|
|
49
56
|
|
|
50
57
|
Args:
|
|
51
58
|
sheet_name (str): The name of the new sheet.
|
|
59
|
+
pre_allocate (dict[str, int], optional): A dictionary containing
|
|
60
|
+
'n_rows' and 'n_cols' keys specifying the dimensions
|
|
61
|
+
for pre-allocating data in new sheet.
|
|
62
|
+
plain_data (list[list[str]], optional): A 2D list of strings
|
|
63
|
+
representing initial data to populate new sheet.
|
|
52
64
|
"""
|
|
53
65
|
if self.workbook.get(sheet_name) is not None:
|
|
54
66
|
raise ValueError(f'Sheet {sheet_name} already exists.')
|
|
55
|
-
self.workbook[sheet_name] = WorkSheet()
|
|
67
|
+
self.workbook[sheet_name] = WorkSheet(pre_allocate=pre_allocate, plain_data=plain_data)
|
|
56
68
|
self.sheet = sheet_name
|
|
57
69
|
self._sheet_list = tuple([x for x in self._sheet_list] + [sheet_name])
|
|
58
70
|
|
|
@@ -122,3 +134,65 @@ class Workbook(ExcelDriver):
|
|
|
122
134
|
def auto_filter(self, sheet: str, target_range: str) -> None:
|
|
123
135
|
self._check_if_sheet_exists(sheet)
|
|
124
136
|
self.workbook[sheet].auto_filter(target_range)
|
|
137
|
+
|
|
138
|
+
def set_panes(
|
|
139
|
+
self,
|
|
140
|
+
sheet: str,
|
|
141
|
+
freeze: bool = False,
|
|
142
|
+
split: bool = False,
|
|
143
|
+
x_split: int = 0,
|
|
144
|
+
y_split: int = 0,
|
|
145
|
+
top_left_cell: str = '',
|
|
146
|
+
active_pane: Literal['bottomLeft', 'bottomRight', 'topLeft', 'topRight', ''] = '',
|
|
147
|
+
selection: list[dict[str, str]] = None,
|
|
148
|
+
) -> None:
|
|
149
|
+
self._check_if_sheet_exists(sheet)
|
|
150
|
+
self.workbook[sheet].set_panes(
|
|
151
|
+
freeze=freeze,
|
|
152
|
+
split=split,
|
|
153
|
+
x_split=x_split,
|
|
154
|
+
y_split=y_split,
|
|
155
|
+
top_left_cell=top_left_cell,
|
|
156
|
+
active_pane=active_pane,
|
|
157
|
+
selection=selection,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
def set_data_validation(
|
|
161
|
+
self,
|
|
162
|
+
sheet: str,
|
|
163
|
+
sq_ref: str = '',
|
|
164
|
+
set_range: list[int | float] = None,
|
|
165
|
+
input_msg: list[str] = None,
|
|
166
|
+
drop_list: list[str] | str = None,
|
|
167
|
+
error_msg: list[str] = None,
|
|
168
|
+
):
|
|
169
|
+
self._check_if_sheet_exists(sheet)
|
|
170
|
+
self.workbook[sheet].set_data_validation(
|
|
171
|
+
sq_ref=sq_ref,
|
|
172
|
+
set_range=set_range,
|
|
173
|
+
input_msg=input_msg,
|
|
174
|
+
drop_list=drop_list,
|
|
175
|
+
error_msg=error_msg,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
def add_comment(
|
|
179
|
+
self,
|
|
180
|
+
sheet: str,
|
|
181
|
+
cell: str,
|
|
182
|
+
author: str,
|
|
183
|
+
text: str | dict[str, str] | list[str | dict[str, str]],
|
|
184
|
+
) -> None:
|
|
185
|
+
"""
|
|
186
|
+
Adds a comment to the specified cell.
|
|
187
|
+
Args:
|
|
188
|
+
sheet (str): The name of the sheet.
|
|
189
|
+
cell (str): The cell location to add the comment.
|
|
190
|
+
author (str): The author of the comment.
|
|
191
|
+
text (str | dict[str, str] | list[str | dict[str, str]]): The text of the comment.
|
|
192
|
+
Raises:
|
|
193
|
+
ValueError: If the cell location is invalid.
|
|
194
|
+
Returns:
|
|
195
|
+
None
|
|
196
|
+
"""
|
|
197
|
+
self._check_if_sheet_exists(sheet)
|
|
198
|
+
self.workbook[sheet].add_comment(cell, author, text)
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import Any, Literal, Optional, List
|
|
4
4
|
|
|
5
5
|
from openpyxl_style_writer import CustomStyle
|
|
6
6
|
|
|
7
7
|
from .style import StyleManager
|
|
8
|
+
from ._typing import CommentTextStructure, SetPanesSelection
|
|
8
9
|
from .utils import (
|
|
10
|
+
CommentText,
|
|
11
|
+
Selection,
|
|
9
12
|
_separate_alpha_numeric,
|
|
10
|
-
|
|
13
|
+
_validate_cell_reference,
|
|
11
14
|
column_to_index,
|
|
12
15
|
deprecated_warning,
|
|
13
|
-
|
|
16
|
+
cell_reference_to_index,
|
|
14
17
|
validate_and_format_value,
|
|
15
18
|
validate_and_register_style,
|
|
19
|
+
transfer_string_slice_to_slice,
|
|
16
20
|
)
|
|
17
21
|
|
|
18
22
|
|
|
@@ -59,13 +63,37 @@ class WorkSheet:
|
|
|
59
63
|
index. Raises TypeError if index_supported is False.
|
|
60
64
|
"""
|
|
61
65
|
|
|
62
|
-
def __init__(
|
|
66
|
+
def __init__(
|
|
67
|
+
self,
|
|
68
|
+
pre_allocate: Optional[dict[str, int]] = None,
|
|
69
|
+
plain_data: Optional[list[list[str]]] = None,
|
|
70
|
+
):
|
|
63
71
|
"""
|
|
64
|
-
Initializes a WorkSheet instance
|
|
72
|
+
Initializes a WorkSheet instance with optional pre-allocation of data or initialization
|
|
73
|
+
with plain data.
|
|
65
74
|
|
|
66
75
|
Args:
|
|
67
|
-
|
|
68
|
-
|
|
76
|
+
pre_allocate (dict[str, int], optional): A dictionary containing 'n_rows' and 'n_cols'
|
|
77
|
+
keys specifying the dimensions for pre-allocating data.
|
|
78
|
+
This can enhancement the performance when you need to write a large excel
|
|
79
|
+
plain_data (list[list[str]], optional): A 2D list of strings representing the
|
|
80
|
+
initial data to populate the worksheet.
|
|
81
|
+
|
|
82
|
+
Notes:
|
|
83
|
+
If both `pre_allocate` and `plain_data` are provided, `plain_data` takes precedence.
|
|
84
|
+
|
|
85
|
+
Attributes:
|
|
86
|
+
sheet (dict): Default sheet settings.
|
|
87
|
+
data (list[list]): The main data structure holding worksheet contents.
|
|
88
|
+
header (list): List of header row items.
|
|
89
|
+
merge_cells (list): List of merged cell coordinates.
|
|
90
|
+
width (dict): Column widths.
|
|
91
|
+
height (dict): Row heights.
|
|
92
|
+
auto_filter_set (set): Set of auto-filter settings.
|
|
93
|
+
|
|
94
|
+
Raises:
|
|
95
|
+
TypeError: If `plain_data` is provided but is not a valid 2D list of strings.
|
|
96
|
+
|
|
69
97
|
"""
|
|
70
98
|
self.sheet = self._get_default_sheet()
|
|
71
99
|
self.data = [[('', 'DEFAULT_STYLE')]]
|
|
@@ -73,7 +101,37 @@ class WorkSheet:
|
|
|
73
101
|
self.merge_cells = []
|
|
74
102
|
self.width = {}
|
|
75
103
|
self.height = {}
|
|
104
|
+
self.panes = {}
|
|
105
|
+
self.comment = []
|
|
76
106
|
self.auto_filter_set = set()
|
|
107
|
+
self.data_validation_set = []
|
|
108
|
+
|
|
109
|
+
if plain_data is not None and pre_allocate is not None:
|
|
110
|
+
raise ValueError(
|
|
111
|
+
"You can only specify either 'pre_allocate' or 'plain_data' at a time, not both.",
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
if pre_allocate is not None:
|
|
115
|
+
if (
|
|
116
|
+
not isinstance(pre_allocate, dict)
|
|
117
|
+
or 'n_rows' not in pre_allocate
|
|
118
|
+
or 'n_cols' not in pre_allocate
|
|
119
|
+
):
|
|
120
|
+
raise TypeError('Invalid pre_allocate dictionary format.')
|
|
121
|
+
if not isinstance(pre_allocate['n_rows'], int) or not isinstance(
|
|
122
|
+
pre_allocate['n_cols'],
|
|
123
|
+
int,
|
|
124
|
+
):
|
|
125
|
+
raise TypeError('n_rows and n_cols must be integers.')
|
|
126
|
+
self.data = [[None] * pre_allocate['n_cols'] for _ in range(pre_allocate['n_rows'])]
|
|
127
|
+
|
|
128
|
+
if plain_data is not None:
|
|
129
|
+
if not isinstance(plain_data, list) or any(
|
|
130
|
+
not isinstance(row, list) for row in plain_data
|
|
131
|
+
):
|
|
132
|
+
raise TypeError('plain_data should be a valid 2D list of strings.')
|
|
133
|
+
self.data = plain_data
|
|
134
|
+
self.sheet['NoStyle'] = True
|
|
77
135
|
|
|
78
136
|
def cell(
|
|
79
137
|
self,
|
|
@@ -135,7 +193,11 @@ class WorkSheet:
|
|
|
135
193
|
style = StyleManager._STYLE_NAME_MAP[style]
|
|
136
194
|
|
|
137
195
|
if isinstance(target, str):
|
|
138
|
-
|
|
196
|
+
if ':' in target:
|
|
197
|
+
target = transfer_string_slice_to_slice(target)
|
|
198
|
+
self._apply_style_to_slice_target(target, style)
|
|
199
|
+
else:
|
|
200
|
+
self._apply_style_to_string_target(target, style)
|
|
139
201
|
elif isinstance(target, slice):
|
|
140
202
|
self._apply_style_to_slice_target(target, style)
|
|
141
203
|
elif isinstance(target, list) and len(target) == 2:
|
|
@@ -144,13 +206,8 @@ class WorkSheet:
|
|
|
144
206
|
raise TypeError('Target should be a string, slice, or list[row, index].')
|
|
145
207
|
|
|
146
208
|
def _apply_style_to_string_target(self, target: str, style: str):
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
self.data[row][col] = (self.data[row][col][0], style)
|
|
150
|
-
else:
|
|
151
|
-
target_slice = target.split(':')
|
|
152
|
-
target = slice(target_slice[0], target_slice[1])
|
|
153
|
-
self._apply_style_to_slice_target(target, style)
|
|
209
|
+
row, col = cell_reference_to_index(target)
|
|
210
|
+
self.data[row][col] = (self.data[row][col][0], style)
|
|
154
211
|
|
|
155
212
|
def _apply_style_to_slice_target(self, target: slice, style: str):
|
|
156
213
|
start_row, start_col, col_stop = self._extract_slice_indices(target)
|
|
@@ -251,13 +308,161 @@ class WorkSheet:
|
|
|
251
308
|
if ':' not in target_range:
|
|
252
309
|
raise ValueError('Invalid target range. Target range should be in the format "A1:B2".')
|
|
253
310
|
target_list = target_range.split(':')
|
|
254
|
-
|
|
255
|
-
|
|
311
|
+
_validate_cell_reference(target_list[0])
|
|
312
|
+
_validate_cell_reference(target_list[1])
|
|
256
313
|
self.auto_filter_set.add(target_range)
|
|
257
314
|
|
|
315
|
+
def set_panes(
|
|
316
|
+
self,
|
|
317
|
+
freeze: bool = False,
|
|
318
|
+
split: bool = False,
|
|
319
|
+
x_split: int = 0,
|
|
320
|
+
y_split: int = 0,
|
|
321
|
+
top_left_cell: str = '',
|
|
322
|
+
active_pane: Literal['bottomLeft', 'bottomRight', 'topLeft', 'topRight', ''] = '',
|
|
323
|
+
selection: Optional[SetPanesSelection | list[Selection]] = None,
|
|
324
|
+
) -> None:
|
|
325
|
+
if x_split < 0 or y_split < 0:
|
|
326
|
+
raise ValueError('Split position should be positive.')
|
|
327
|
+
if top_left_cell != '':
|
|
328
|
+
_validate_cell_reference(top_left_cell)
|
|
329
|
+
if active_pane not in ['bottomLeft', 'bottomRight', 'topLeft', 'topRight', '']:
|
|
330
|
+
raise ValueError(
|
|
331
|
+
'Invalid active pane. The options are bottomLeft, bottomRight, topLeft, topRight.',
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
if selection is None:
|
|
335
|
+
selection = []
|
|
336
|
+
self.panes = {
|
|
337
|
+
'freeze': freeze,
|
|
338
|
+
'split': split,
|
|
339
|
+
'x_split': x_split,
|
|
340
|
+
'y_split': y_split,
|
|
341
|
+
'top_left_cell': top_left_cell,
|
|
342
|
+
'active_pane': active_pane,
|
|
343
|
+
'selection': selection,
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
def set_data_validation(
|
|
347
|
+
self,
|
|
348
|
+
sq_ref: str = '',
|
|
349
|
+
set_range: Optional[list[int | float]] = None,
|
|
350
|
+
input_msg: Optional[list[str]] = None,
|
|
351
|
+
drop_list: Optional[list[str] | str] = None,
|
|
352
|
+
error_msg: Optional[list[str]] = None,
|
|
353
|
+
):
|
|
354
|
+
"""
|
|
355
|
+
Set data validation for the specified range.
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
sq_ref (str): The range to set the data validation.
|
|
359
|
+
set_range (list[int | float]): The range of values to set the data validation.
|
|
360
|
+
input (list[str]): The input message for the data validation.
|
|
361
|
+
drop_list (list[str] | str): The drop list for the data validation.
|
|
362
|
+
error (list[str]): The error message for the data validation.
|
|
363
|
+
|
|
364
|
+
Raises:
|
|
365
|
+
ValueError: If the range is invalid.
|
|
366
|
+
|
|
367
|
+
Returns:
|
|
368
|
+
None
|
|
369
|
+
"""
|
|
370
|
+
if ':' in sq_ref:
|
|
371
|
+
sq_ref_list = sq_ref.split(':')
|
|
372
|
+
_validate_cell_reference(sq_ref_list[0])
|
|
373
|
+
_validate_cell_reference(sq_ref_list[1])
|
|
374
|
+
else:
|
|
375
|
+
_validate_cell_reference(sq_ref)
|
|
376
|
+
|
|
377
|
+
drop_list_key = 'drop_list'
|
|
378
|
+
if isinstance(drop_list, str):
|
|
379
|
+
if ':' not in drop_list:
|
|
380
|
+
raise ValueError(
|
|
381
|
+
'Invalid drop list. Sequential Reference'
|
|
382
|
+
'Drop list should be in the format "A1:B2".',
|
|
383
|
+
)
|
|
384
|
+
drop_list_split = drop_list.split(':')
|
|
385
|
+
_validate_cell_reference(drop_list_split[0])
|
|
386
|
+
_validate_cell_reference(drop_list_split[1])
|
|
387
|
+
drop_list_key = 'sqref_drop_list'
|
|
388
|
+
elif drop_list is not None:
|
|
389
|
+
if not isinstance(drop_list, list):
|
|
390
|
+
raise ValueError('Drop list should be a list of strings.')
|
|
391
|
+
drop_list = [str(x) for x in drop_list]
|
|
392
|
+
|
|
393
|
+
dv = {}
|
|
394
|
+
dv['sq_ref'] = sq_ref
|
|
395
|
+
if set_range is not None:
|
|
396
|
+
if not isinstance(set_range, list) or len(set_range) != 2:
|
|
397
|
+
raise ValueError('Set range should be a list of two elements. Like [1, 10].')
|
|
398
|
+
dv['set_range'] = set_range
|
|
399
|
+
if input_msg is not None:
|
|
400
|
+
if not isinstance(input_msg, list) or len(input_msg) != 2:
|
|
401
|
+
raise ValueError(
|
|
402
|
+
'Input message should be a list of two elements. Like ["Title", "Body"].',
|
|
403
|
+
)
|
|
404
|
+
dv['input_title'] = input_msg[0]
|
|
405
|
+
dv['input_body'] = input_msg[1]
|
|
406
|
+
if drop_list is not None:
|
|
407
|
+
dv[drop_list_key] = drop_list
|
|
408
|
+
if error_msg is not None:
|
|
409
|
+
if not isinstance(error_msg, list) or len(error_msg) != 2:
|
|
410
|
+
raise ValueError(
|
|
411
|
+
'Error message should be a list of two elements. Like ["Title", "Body"].',
|
|
412
|
+
)
|
|
413
|
+
dv['error_title'] = error_msg[0]
|
|
414
|
+
dv['error_body'] = error_msg[1]
|
|
415
|
+
|
|
416
|
+
self.data_validation_set.append(dv)
|
|
417
|
+
|
|
418
|
+
def add_comment(
|
|
419
|
+
self,
|
|
420
|
+
cell: str,
|
|
421
|
+
author: str,
|
|
422
|
+
text: CommentTextStructure | CommentText | List[CommentText],
|
|
423
|
+
) -> None:
|
|
424
|
+
"""
|
|
425
|
+
Adds a comment to the specified cell.
|
|
426
|
+
Args:
|
|
427
|
+
cell (str): The cell location to add the comment.
|
|
428
|
+
author (str): The author of the comment.
|
|
429
|
+
text (str | dict[str, str] | list[str | dict[str, str]]): The text of the comment.
|
|
430
|
+
Raises:
|
|
431
|
+
ValueError: If the cell location is invalid.
|
|
432
|
+
Returns:
|
|
433
|
+
None
|
|
434
|
+
"""
|
|
435
|
+
_validate_cell_reference(cell)
|
|
436
|
+
text = (
|
|
437
|
+
[text]
|
|
438
|
+
if isinstance(text, (str, CommentText))
|
|
439
|
+
else text
|
|
440
|
+
if isinstance(text, list)
|
|
441
|
+
else [text]
|
|
442
|
+
)
|
|
443
|
+
if all(isinstance(item, (dict, str)) for item in text):
|
|
444
|
+
for idx, item in enumerate(text):
|
|
445
|
+
if isinstance(item, str):
|
|
446
|
+
text[idx] = {'text': item}
|
|
447
|
+
else:
|
|
448
|
+
if 'text' not in item:
|
|
449
|
+
raise ValueError('Comment text should contain the key "text".')
|
|
450
|
+
text[idx] = {
|
|
451
|
+
k[0].upper() + k[1:] if k != 'text' else k: v for k, v in item.items()
|
|
452
|
+
}
|
|
453
|
+
elif all(isinstance(item, CommentText) for item in text):
|
|
454
|
+
text = [t.to_dict() for t in text]
|
|
455
|
+
else:
|
|
456
|
+
raise ValueError('Comment text should be a string or a list of dictionaries.')
|
|
457
|
+
self.comment.append({'cell': cell, 'author': author, 'paragraph': text})
|
|
458
|
+
|
|
258
459
|
def _expand_row_and_cols(self, target_row: int, target_col: int):
|
|
259
460
|
data_row_len = len(self.data)
|
|
260
461
|
data_col_len = len(self.data[0])
|
|
462
|
+
|
|
463
|
+
if data_row_len > target_row and len(self.data[target_row]) > target_col:
|
|
464
|
+
return
|
|
465
|
+
|
|
261
466
|
# Case when the memory space of self.data row is enough
|
|
262
467
|
# but the memory space of the target_col is not enough
|
|
263
468
|
if data_row_len > target_row:
|
|
@@ -284,6 +489,10 @@ class WorkSheet:
|
|
|
284
489
|
'Width': self.width,
|
|
285
490
|
'Height': self.height,
|
|
286
491
|
'AutoFilter': self.auto_filter_set,
|
|
492
|
+
'Panes': self.panes,
|
|
493
|
+
'DataValidation': self.data_validation_set,
|
|
494
|
+
'NoStyle': self.sheet['NoStyle'],
|
|
495
|
+
'Comment': self.comment,
|
|
287
496
|
}
|
|
288
497
|
return self.sheet
|
|
289
498
|
|
|
@@ -295,6 +504,10 @@ class WorkSheet:
|
|
|
295
504
|
'Width': {},
|
|
296
505
|
'Height': {},
|
|
297
506
|
'AutoFilter': set(),
|
|
507
|
+
'Panes': {},
|
|
508
|
+
'DataValidation': [],
|
|
509
|
+
'NoStyle': False,
|
|
510
|
+
'Comment': [],
|
|
298
511
|
}
|
|
299
512
|
|
|
300
513
|
def _validate_value_and_set_default(self, value: Any):
|
|
@@ -349,6 +562,9 @@ class WorkSheet:
|
|
|
349
562
|
elif isinstance(key, int):
|
|
350
563
|
return self.data[key]
|
|
351
564
|
elif isinstance(key, str):
|
|
565
|
+
if ':' in key:
|
|
566
|
+
target = transfer_string_slice_to_slice(key)
|
|
567
|
+
return self._get_cell_by_slice(target)
|
|
352
568
|
return self._get_cell_by_location(key)
|
|
353
569
|
|
|
354
570
|
def __setitem__(self, key: str | slice | int, value: Any) -> None:
|
|
@@ -357,7 +573,11 @@ class WorkSheet:
|
|
|
357
573
|
elif isinstance(key, int):
|
|
358
574
|
self._set_row_by_index(key, value)
|
|
359
575
|
elif isinstance(key, str):
|
|
360
|
-
|
|
576
|
+
if ':' in key:
|
|
577
|
+
target = transfer_string_slice_to_slice(key)
|
|
578
|
+
self._set_cell_by_slice(target, value)
|
|
579
|
+
else:
|
|
580
|
+
self._set_cell_by_location(key, value)
|
|
361
581
|
else:
|
|
362
582
|
raise TypeError('Key should be a string or slice.')
|
|
363
583
|
|
|
@@ -369,7 +589,7 @@ class WorkSheet:
|
|
|
369
589
|
return self.data[int(start_row) - 1]
|
|
370
590
|
|
|
371
591
|
def _get_cell_by_location(self, key: str) -> tuple:
|
|
372
|
-
row, col =
|
|
592
|
+
row, col = cell_reference_to_index(key)
|
|
373
593
|
return self.data[row][col]
|
|
374
594
|
|
|
375
595
|
def _extract_slice_indices(self, cell_slice: slice) -> tuple[int, int, int]:
|
|
@@ -377,8 +597,8 @@ class WorkSheet:
|
|
|
377
597
|
_, stop_row = _separate_alpha_numeric(cell_slice.stop)
|
|
378
598
|
if start_row != stop_row:
|
|
379
599
|
raise ValueError('Only support row-wise slicing.')
|
|
380
|
-
start_row, start_col =
|
|
381
|
-
_, col_stop =
|
|
600
|
+
start_row, start_col = cell_reference_to_index(cell_slice.start)
|
|
601
|
+
_, col_stop = cell_reference_to_index(cell_slice.stop)
|
|
382
602
|
self._expand_row_and_cols(start_row, col_stop)
|
|
383
603
|
return start_row, start_col, col_stop
|
|
384
604
|
|
|
@@ -398,7 +618,7 @@ class WorkSheet:
|
|
|
398
618
|
self.data[row] = value
|
|
399
619
|
|
|
400
620
|
def _set_cell_by_location(self, key: str, value: Any) -> None:
|
|
401
|
-
row, col =
|
|
621
|
+
row, col = cell_reference_to_index(key)
|
|
402
622
|
value = self._validate_value_and_set_default(value)
|
|
403
623
|
try:
|
|
404
624
|
self.data[row][col] = value
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pyfastexcel
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.9
|
|
4
4
|
Summary: High performace excel file generation library.
|
|
5
5
|
Home-page: https://github.com/Zncl2222/pyfastexcel
|
|
6
6
|
Author: Zncl2222
|
|
@@ -100,7 +100,7 @@ If you prefer to build the package manually, follow these steps:
|
|
|
100
100
|
|
|
101
101
|
## Usage
|
|
102
102
|
|
|
103
|
-
The index assignment is now avaliable in `Workbook` and the `
|
|
103
|
+
The index assignment is now avaliable in `Workbook` and the `StreamWriter`.
|
|
104
104
|
Here is the example usage:
|
|
105
105
|
|
|
106
106
|
```python
|
|
@@ -146,8 +146,8 @@ if __name__ == '__main__':
|
|
|
146
146
|
|
|
147
147
|
```
|
|
148
148
|
|
|
149
|
-
You can also using the `
|
|
150
|
-
subclass of `Workbook` to write excel row by row, see
|
|
149
|
+
You can also using the `StreamWriter` which was the
|
|
150
|
+
subclass of `Workbook` to write excel row by row, see more in [StreamWriter](https://pyfastexcel.readthedocs.io/en/stable/writer/).
|
|
151
151
|
|
|
152
152
|
## Documentation
|
|
153
153
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|