pyfastexcel 0.1.0__tar.gz → 0.1.2__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.1.0 → pyfastexcel-0.1.2}/PKG-INFO +1 -1
- {pyfastexcel-0.1.0 → pyfastexcel-0.1.2}/pyfastexcel/__init__.py +2 -0
- {pyfastexcel-0.1.0 → pyfastexcel-0.1.2}/pyfastexcel/_typing.py +5 -1
- {pyfastexcel-0.1.0 → pyfastexcel-0.1.2}/pyfastexcel/driver.py +44 -9
- pyfastexcel-0.1.2/pyfastexcel/logformatter.py +38 -0
- {pyfastexcel-0.1.0 → pyfastexcel-0.1.2}/pyfastexcel/pyfastexcel.dll +0 -0
- {pyfastexcel-0.1.0 → pyfastexcel-0.1.2}/pyfastexcel/pyfastexcel.so +0 -0
- {pyfastexcel-0.1.0 → pyfastexcel-0.1.2}/pyfastexcel/style.py +14 -10
- {pyfastexcel-0.1.0 → pyfastexcel-0.1.2}/pyfastexcel/utils.py +5 -0
- {pyfastexcel-0.1.0 → pyfastexcel-0.1.2}/pyfastexcel/workbook.py +56 -6
- {pyfastexcel-0.1.0 → pyfastexcel-0.1.2}/pyfastexcel/worksheet.py +405 -289
- pyfastexcel-0.1.2/pyfastexcel/writer.py +150 -0
- {pyfastexcel-0.1.0 → pyfastexcel-0.1.2}/pyfastexcel.egg-info/PKG-INFO +1 -1
- {pyfastexcel-0.1.0 → pyfastexcel-0.1.2}/pyfastexcel.egg-info/SOURCES.txt +1 -0
- {pyfastexcel-0.1.0 → pyfastexcel-0.1.2}/setup.cfg +2 -1
- pyfastexcel-0.1.0/pyfastexcel/writer.py +0 -60
- {pyfastexcel-0.1.0 → pyfastexcel-0.1.2}/LICENSE +0 -0
- {pyfastexcel-0.1.0 → pyfastexcel-0.1.2}/README.md +0 -0
- {pyfastexcel-0.1.0 → pyfastexcel-0.1.2}/pyfastexcel.egg-info/dependency_links.txt +0 -0
- {pyfastexcel-0.1.0 → pyfastexcel-0.1.2}/pyfastexcel.egg-info/not-zip-safe +0 -0
- {pyfastexcel-0.1.0 → pyfastexcel-0.1.2}/pyfastexcel.egg-info/requires.txt +0 -0
- {pyfastexcel-0.1.0 → pyfastexcel-0.1.2}/pyfastexcel.egg-info/top_level.txt +0 -0
- {pyfastexcel-0.1.0 → pyfastexcel-0.1.2}/setup.py +0 -0
|
@@ -2,6 +2,7 @@ from openpyxl_style_writer import CustomStyle, DefaultStyle
|
|
|
2
2
|
|
|
3
3
|
from pyfastexcel.workbook import Workbook
|
|
4
4
|
from pyfastexcel.writer import StreamWriter
|
|
5
|
+
from pyfastexcel.utils import set_debug_level
|
|
5
6
|
|
|
6
7
|
__all__ = [
|
|
7
8
|
'Workbook',
|
|
@@ -10,4 +11,5 @@ __all__ = [
|
|
|
10
11
|
# convinent usage.
|
|
11
12
|
'CustomStyle',
|
|
12
13
|
'DefaultStyle',
|
|
14
|
+
'set_debug_level',
|
|
13
15
|
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Literal, TypedDict, Optional, List, Union
|
|
3
|
+
from typing import Literal, Protocol, TypedDict, Optional, List, Union
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class CommentTextDict(TypedDict, total=False):
|
|
@@ -21,5 +21,9 @@ class SelectionDict(TypedDict, total=False):
|
|
|
21
21
|
pane: str
|
|
22
22
|
|
|
23
23
|
|
|
24
|
+
class Writable(Protocol):
|
|
25
|
+
def write(self, content: str) -> None: ...
|
|
26
|
+
|
|
27
|
+
|
|
24
28
|
CommentTextStructure = Union[str, List[str], CommentTextDict, List[CommentTextDict]]
|
|
25
29
|
SetPanesSelection = List[SelectionDict]
|
|
@@ -2,20 +2,31 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import base64
|
|
4
4
|
import ctypes
|
|
5
|
+
import logging
|
|
5
6
|
import sys
|
|
6
7
|
from datetime import datetime, timezone
|
|
7
8
|
from io import BytesIO
|
|
8
9
|
from pathlib import Path
|
|
10
|
+
from typing import overload
|
|
9
11
|
|
|
10
12
|
import msgspec
|
|
11
13
|
from openpyxl import load_workbook
|
|
12
14
|
from openpyxl_style_writer import CustomStyle
|
|
13
15
|
|
|
16
|
+
from .logformatter import formatter
|
|
14
17
|
from .style import StyleManager
|
|
15
18
|
from .worksheet import WorkSheet
|
|
19
|
+
from ._typing import Writable
|
|
16
20
|
|
|
17
21
|
BASE_DIR = Path(__file__).resolve().parent
|
|
18
22
|
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
style_formatter = logging.StreamHandler()
|
|
25
|
+
style_formatter.setFormatter(formatter)
|
|
26
|
+
|
|
27
|
+
logger.addHandler(style_formatter)
|
|
28
|
+
logger.propagate = False
|
|
29
|
+
|
|
19
30
|
|
|
20
31
|
class ExcelDriver:
|
|
21
32
|
"""
|
|
@@ -85,14 +96,37 @@ class ExcelDriver:
|
|
|
85
96
|
|
|
86
97
|
@property
|
|
87
98
|
def sheet_list(self):
|
|
88
|
-
return self._sheet_list
|
|
99
|
+
return list(self._sheet_list)
|
|
100
|
+
|
|
101
|
+
@overload
|
|
102
|
+
def save(self, file: Writable) -> None:
|
|
103
|
+
"""
|
|
104
|
+
Saves the workbook to a writable object.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
file (Writable): Writable object that has .write() function.
|
|
108
|
+
"""
|
|
109
|
+
...
|
|
89
110
|
|
|
90
|
-
|
|
111
|
+
@overload
|
|
112
|
+
def save(self, path: str) -> None:
|
|
113
|
+
"""
|
|
114
|
+
Saves the workbook to a file.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
path (str): A path to save the file.
|
|
118
|
+
"""
|
|
119
|
+
...
|
|
120
|
+
|
|
121
|
+
def save(self, file_or_path: Writable | str) -> None:
|
|
91
122
|
if not hasattr(self, 'decoded_bytes'):
|
|
92
123
|
self.read_lib_and_create_excel()
|
|
93
124
|
|
|
94
|
-
|
|
95
|
-
|
|
125
|
+
if isinstance(file_or_path, str):
|
|
126
|
+
with open(file_or_path, 'wb') as file:
|
|
127
|
+
file.write(self.decoded_bytes)
|
|
128
|
+
else:
|
|
129
|
+
file_or_path.write(self.decoded_bytes)
|
|
96
130
|
|
|
97
131
|
def __getitem__(self, key: str) -> WorkSheet:
|
|
98
132
|
return self.workbook[key]
|
|
@@ -120,11 +154,11 @@ class ExcelDriver:
|
|
|
120
154
|
use_openpyxl = False
|
|
121
155
|
for sheet in self._sheet_list:
|
|
122
156
|
self._dict_wb[sheet] = self.workbook[sheet]._transfer_to_dict()
|
|
123
|
-
if self.workbook[sheet].
|
|
157
|
+
if self.workbook[sheet]._engine == 'openpyxl':
|
|
124
158
|
use_openpyxl = True
|
|
125
|
-
if len(self.workbook[sheet].
|
|
159
|
+
if len(self.workbook[sheet]._grouped_columns_list) != 0:
|
|
126
160
|
set_group_columns = True
|
|
127
|
-
if len(self.workbook[sheet].
|
|
161
|
+
if len(self.workbook[sheet]._grouped_rows_list) != 0:
|
|
128
162
|
set_row_columns = True
|
|
129
163
|
|
|
130
164
|
# Set writer (if some of the function that excelize StremWriter is not support, and
|
|
@@ -155,6 +189,7 @@ class ExcelDriver:
|
|
|
155
189
|
# Due to Streaming API of Excelize can't group column currently
|
|
156
190
|
# So implement this function by openpyxl
|
|
157
191
|
if (set_group_columns or set_row_columns) and use_openpyxl is True:
|
|
192
|
+
logger.info('Using openpyxl to group columns and rows...')
|
|
158
193
|
self.decoded_bytes = self._set_group_columns_and_group_rows()
|
|
159
194
|
|
|
160
195
|
return self.decoded_bytes
|
|
@@ -162,7 +197,7 @@ class ExcelDriver:
|
|
|
162
197
|
def _set_group_columns_and_group_rows(self):
|
|
163
198
|
wb = load_workbook(BytesIO(self.decoded_bytes))
|
|
164
199
|
for sheet in self._sheet_list:
|
|
165
|
-
grouped_columns = self.workbook[sheet].
|
|
200
|
+
grouped_columns = self.workbook[sheet]._grouped_columns_list
|
|
166
201
|
ws = wb[sheet]
|
|
167
202
|
for col in grouped_columns:
|
|
168
203
|
ws.column_dimensions.group(
|
|
@@ -171,7 +206,7 @@ class ExcelDriver:
|
|
|
171
206
|
outline_level=col['outline_level'],
|
|
172
207
|
hidden=col['hidden'],
|
|
173
208
|
)
|
|
174
|
-
grouped_rows = self.workbook[sheet].
|
|
209
|
+
grouped_rows = self.workbook[sheet]._grouped_rows_list
|
|
175
210
|
for row in grouped_rows:
|
|
176
211
|
ws.row_dimensions.group(
|
|
177
212
|
row['start_row'],
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import warnings
|
|
3
|
+
|
|
4
|
+
NORMAL_FORMAT = '\033[%(log_color)s%(asctime)s - %(name)s - %(levelname)s - %(message)s\033[0m'
|
|
5
|
+
LOG_COLORS = {
|
|
6
|
+
'DEBUG': '\033[90m', # Grey
|
|
7
|
+
'INFO': '\033[92m', # Green
|
|
8
|
+
'WARNING': '\033[93m', # Yellow
|
|
9
|
+
'ERROR': '\033[91m', # Red
|
|
10
|
+
'CRITICAL': '\033[91m', # Red
|
|
11
|
+
}
|
|
12
|
+
LOG_CACHE = {}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def custom_warning_format(message, *args):
|
|
16
|
+
start_color = '\033[93m'
|
|
17
|
+
reset_color = '\033[0m'
|
|
18
|
+
return f'{start_color}DeprecationWarning: {message}{reset_color}\n'
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def log_warning(logger: logging.Logger, message: str):
|
|
22
|
+
if not LOG_CACHE.get(message):
|
|
23
|
+
logger.warning(message)
|
|
24
|
+
LOG_CACHE[message] = True
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ColoredFormatter(logging.Formatter):
|
|
28
|
+
def __init__(self, fmt):
|
|
29
|
+
super().__init__(fmt)
|
|
30
|
+
|
|
31
|
+
def format(self, record):
|
|
32
|
+
record.log_color = LOG_COLORS[record.levelname]
|
|
33
|
+
return super().format(record)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
warnings.formatwarning = custom_warning_format
|
|
37
|
+
warnings.simplefilter('always', DeprecationWarning)
|
|
38
|
+
formatter = ColoredFormatter(NORMAL_FORMAT)
|
|
Binary file
|
|
Binary file
|
|
@@ -5,20 +5,16 @@ from pathlib import Path
|
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
7
|
from openpyxl_style_writer import CustomStyle
|
|
8
|
+
from .logformatter import formatter, log_warning
|
|
8
9
|
|
|
9
10
|
BASE_DIR = Path(__file__).resolve().parent
|
|
10
11
|
|
|
11
12
|
logger = logging.getLogger(__name__)
|
|
12
|
-
|
|
13
|
+
style_formatter = logging.StreamHandler()
|
|
14
|
+
style_formatter.setFormatter(formatter)
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
18
|
-
ch.setFormatter(formatter)
|
|
19
|
-
|
|
20
|
-
if not logger.hasHandlers():
|
|
21
|
-
logger.addHandler(ch)
|
|
16
|
+
logger.addHandler(style_formatter)
|
|
17
|
+
logger.propagate = False
|
|
22
18
|
|
|
23
19
|
# TODO: Implement a CustomStyle without the dependency of openpyxl_style_writer
|
|
24
20
|
|
|
@@ -75,6 +71,11 @@ class StyleManager:
|
|
|
75
71
|
|
|
76
72
|
@classmethod
|
|
77
73
|
def set_custom_style(cls, name: str, custom_style: CustomStyle):
|
|
74
|
+
if cls.REGISTERED_STYLES.get(name):
|
|
75
|
+
log_warning(
|
|
76
|
+
logger,
|
|
77
|
+
f'{name} has already existed. Overiding the style settings.',
|
|
78
|
+
)
|
|
78
79
|
cls.REGISTERED_STYLES[name] = custom_style
|
|
79
80
|
cls._STYLE_NAME_MAP[custom_style] = name
|
|
80
81
|
|
|
@@ -104,7 +105,10 @@ class StyleManager:
|
|
|
104
105
|
|
|
105
106
|
def _update_style_map(self, style_name: str, custom_style: CustomStyle) -> None:
|
|
106
107
|
if self._style_map.get(style_name):
|
|
107
|
-
|
|
108
|
+
log_warning(
|
|
109
|
+
logger,
|
|
110
|
+
f'{style_name} has already existed. Overiding the style settings.',
|
|
111
|
+
)
|
|
108
112
|
self._style_map[style_name] = self._get_default_style()
|
|
109
113
|
self._style_map[style_name]['Font'] = self._get_font_style(custom_style)
|
|
110
114
|
self._style_map[style_name]['Fill'] = self._get_fill_style(custom_style)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
import re
|
|
4
5
|
import string
|
|
5
6
|
import warnings
|
|
@@ -49,6 +50,10 @@ class Selection:
|
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
|
|
53
|
+
def set_debug_level(level: int):
|
|
54
|
+
logging.basicConfig(level=level)
|
|
55
|
+
|
|
56
|
+
|
|
52
57
|
def deprecated_warning(msg: str):
|
|
53
58
|
warnings.warn(
|
|
54
59
|
msg,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Literal, Optional
|
|
3
|
+
from typing import Literal, Optional, overload
|
|
4
4
|
|
|
5
5
|
from pyfastexcel.driver import ExcelDriver, WorkSheet
|
|
6
6
|
from pyfastexcel.utils import deprecated_warning
|
|
@@ -16,6 +16,8 @@ class Workbook(ExcelDriver):
|
|
|
16
16
|
Methods:
|
|
17
17
|
remove_sheet(sheet: str) -> None:
|
|
18
18
|
Removes a sheet from the Excel data.
|
|
19
|
+
rename_sheet(self, old_sheet_name: str, new_sheet_name: str) -> None:
|
|
20
|
+
Rename a sheet.
|
|
19
21
|
create_sheet(sheet_name: str) -> None:
|
|
20
22
|
Creates a new sheet.
|
|
21
23
|
switch_sheet(sheet_name: str) -> None:
|
|
@@ -45,6 +47,24 @@ class Workbook(ExcelDriver):
|
|
|
45
47
|
self._sheet_list = tuple(self.workbook.keys())
|
|
46
48
|
self.sheet = self._sheet_list[0]
|
|
47
49
|
|
|
50
|
+
def rename_sheet(self, old_sheet_name: str, new_sheet_name: str) -> None:
|
|
51
|
+
"""
|
|
52
|
+
Renames a sheet in the Excel data.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
old_sheet_name (str): The name of the sheet to rename.
|
|
56
|
+
new_sheet_name (str): The new name for the sheet.
|
|
57
|
+
"""
|
|
58
|
+
if self.workbook.get(old_sheet_name) is None:
|
|
59
|
+
raise IndexError(f'Sheet {old_sheet_name} does not exist.')
|
|
60
|
+
if self.workbook.get(new_sheet_name) is not None:
|
|
61
|
+
raise ValueError(f'Sheet {new_sheet_name} already exists.')
|
|
62
|
+
self.workbook[new_sheet_name] = self.workbook.pop(old_sheet_name)
|
|
63
|
+
self._sheet_list = tuple(
|
|
64
|
+
[new_sheet_name if x == old_sheet_name else x for x in self._sheet_list]
|
|
65
|
+
)
|
|
66
|
+
self.sheet = new_sheet_name
|
|
67
|
+
|
|
48
68
|
def create_sheet(
|
|
49
69
|
self,
|
|
50
70
|
sheet_name: str,
|
|
@@ -121,15 +141,45 @@ class Workbook(ExcelDriver):
|
|
|
121
141
|
self._check_if_sheet_exists(sheet)
|
|
122
142
|
self.workbook[sheet].set_cell_height(row, value)
|
|
123
143
|
|
|
124
|
-
|
|
144
|
+
@overload
|
|
145
|
+
def set_merge_cell(
|
|
146
|
+
self,
|
|
147
|
+
sheet: str,
|
|
148
|
+
top_lef_cell: Optional[str],
|
|
149
|
+
bottom_right_cell: Optional[str],
|
|
150
|
+
) -> None: ...
|
|
151
|
+
|
|
152
|
+
@overload
|
|
153
|
+
def set_merge_cell(
|
|
154
|
+
self,
|
|
155
|
+
sheet: str,
|
|
156
|
+
cell_range: Optional[str],
|
|
157
|
+
) -> None: ...
|
|
158
|
+
|
|
159
|
+
def set_merge_cell(self, sheet, *args) -> None:
|
|
125
160
|
deprecated_warning(
|
|
126
|
-
"
|
|
161
|
+
"wb.set_merge_cell is going to deprecated in v1.0.0. Please use 'wb.merge_cell' instead",
|
|
127
162
|
)
|
|
128
|
-
self.merge_cell(sheet,
|
|
163
|
+
self.merge_cell(sheet, *args)
|
|
164
|
+
|
|
165
|
+
@overload
|
|
166
|
+
def merge_cell(
|
|
167
|
+
self,
|
|
168
|
+
sheet: str,
|
|
169
|
+
top_lef_cell: Optional[str],
|
|
170
|
+
bottom_right_cell: Optional[str],
|
|
171
|
+
) -> None: ...
|
|
172
|
+
|
|
173
|
+
@overload
|
|
174
|
+
def merge_cell(
|
|
175
|
+
self,
|
|
176
|
+
sheet: str,
|
|
177
|
+
cell_range: Optional[str],
|
|
178
|
+
) -> None: ...
|
|
129
179
|
|
|
130
|
-
def merge_cell(self, sheet: str,
|
|
180
|
+
def merge_cell(self, sheet: str, *args) -> None:
|
|
131
181
|
self._check_if_sheet_exists(sheet)
|
|
132
|
-
self.workbook[sheet].set_merge_cell(
|
|
182
|
+
self.workbook[sheet].set_merge_cell(*args)
|
|
133
183
|
|
|
134
184
|
def auto_filter(self, sheet: str, target_range: str) -> None:
|
|
135
185
|
self._check_if_sheet_exists(sheet)
|