velocity-python 0.0.31__py3-none-any.whl → 0.0.32__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 velocity-python might be problematic. Click here for more details.
- velocity/__init__.py +1 -1
- velocity/aws/handlers/response.py +203 -52
- velocity/misc/conv/iconv.py +106 -143
- velocity/misc/conv/oconv.py +96 -134
- velocity/misc/export.py +102 -99
- velocity/misc/format.py +44 -47
- velocity/misc/mail.py +44 -40
- velocity/misc/merge.py +33 -17
- velocity/misc/timer.py +33 -10
- {velocity_python-0.0.31.dist-info → velocity_python-0.0.32.dist-info}/METADATA +1 -1
- {velocity_python-0.0.31.dist-info → velocity_python-0.0.32.dist-info}/RECORD +14 -14
- {velocity_python-0.0.31.dist-info → velocity_python-0.0.32.dist-info}/LICENSE +0 -0
- {velocity_python-0.0.31.dist-info → velocity_python-0.0.32.dist-info}/WHEEL +0 -0
- {velocity_python-0.0.31.dist-info → velocity_python-0.0.32.dist-info}/top_level.txt +0 -0
velocity/misc/conv/oconv.py
CHANGED
|
@@ -3,46 +3,31 @@ import codecs
|
|
|
3
3
|
import decimal
|
|
4
4
|
from datetime import datetime, date, time
|
|
5
5
|
from pprint import pformat
|
|
6
|
+
from typing import Optional, Union, List, Callable
|
|
6
7
|
|
|
8
|
+
# Convert SQL data to JS format for display
|
|
7
9
|
|
|
8
|
-
def none(data):
|
|
9
|
-
if data == None:
|
|
10
|
-
return ""
|
|
11
|
-
if data == "None":
|
|
12
|
-
return ""
|
|
13
|
-
if data == "null":
|
|
14
|
-
return ""
|
|
15
|
-
return data
|
|
16
10
|
|
|
11
|
+
def none(data: Optional[str]) -> str:
|
|
12
|
+
"""Converts various 'null' representations to an empty string."""
|
|
13
|
+
return "" if data in (None, "None", "null", "@NULL") else data
|
|
17
14
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if data
|
|
15
|
+
|
|
16
|
+
def phone(data: Optional[str]) -> str:
|
|
17
|
+
"""Formats a 10-digit phone number as (XXX) XXX-XXXX or returns an empty string if invalid."""
|
|
18
|
+
if data in (None, "None", ""):
|
|
22
19
|
return ""
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
digits = re.sub(r"[^0-9]", "", data)
|
|
21
|
+
match = re.search(r"\d{10}$", digits)
|
|
22
|
+
if match:
|
|
23
|
+
num = match.group()
|
|
24
|
+
return f"({num[:3]}) {num[3:6]}-{num[6:]}"
|
|
25
|
+
return ""
|
|
27
26
|
|
|
28
27
|
|
|
29
|
-
def day_of_week(data, abbrev=False):
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
for day in data:
|
|
33
|
-
new.append(day_of_week(day, abbrev=abbrev))
|
|
34
|
-
return ",".join(new)
|
|
35
|
-
if data == None:
|
|
36
|
-
return ""
|
|
37
|
-
if data == "None":
|
|
38
|
-
return ""
|
|
39
|
-
if not data:
|
|
40
|
-
return data
|
|
41
|
-
if abbrev:
|
|
42
|
-
return {1: "Mon", 2: "Tue", 3: "Wed", 4: "Thu", 5: "Fri", 6: "Sat", 7: "Sun"}[
|
|
43
|
-
int(data)
|
|
44
|
-
]
|
|
45
|
-
return {
|
|
28
|
+
def day_of_week(data: Union[int, str, List], abbrev: bool = False) -> str:
|
|
29
|
+
"""Converts a day number (1-7) to a day name, abbreviated if specified. Supports lists."""
|
|
30
|
+
days_full = {
|
|
46
31
|
1: "Monday",
|
|
47
32
|
2: "Tuesday",
|
|
48
33
|
3: "Wednesday",
|
|
@@ -50,159 +35,136 @@ def day_of_week(data, abbrev=False):
|
|
|
50
35
|
5: "Friday",
|
|
51
36
|
6: "Saturday",
|
|
52
37
|
7: "Sunday",
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def date(*args, **kwds):
|
|
57
|
-
kwds.setdefault("fmt", "%Y-%m-%d")
|
|
58
|
-
|
|
59
|
-
def _(param):
|
|
60
|
-
if isinstance(param, (datetime, date)):
|
|
61
|
-
return param.strftime(kwds["fmt"])
|
|
62
|
-
else:
|
|
63
|
-
return param
|
|
64
|
-
|
|
65
|
-
if args and args[0]:
|
|
66
|
-
return _(args[0])
|
|
67
|
-
return _
|
|
38
|
+
}
|
|
39
|
+
days_abbrev = {1: "Mon", 2: "Tue", 3: "Wed", 4: "Thu", 5: "Fri", 6: "Sat", 7: "Sun"}
|
|
40
|
+
days = days_abbrev if abbrev else days_full
|
|
68
41
|
|
|
42
|
+
if isinstance(data, list):
|
|
43
|
+
return ",".join(day_of_week(day, abbrev) for day in data if day in days)
|
|
69
44
|
|
|
70
|
-
|
|
71
|
-
|
|
45
|
+
try:
|
|
46
|
+
return days[int(data)]
|
|
47
|
+
except (ValueError, KeyError, TypeError):
|
|
48
|
+
return ""
|
|
72
49
|
|
|
73
|
-
def _(param):
|
|
74
|
-
if isinstance(param, (datetime, time)):
|
|
75
|
-
return param.strftime(kwds["fmt"])
|
|
76
|
-
else:
|
|
77
|
-
return param
|
|
78
50
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return
|
|
51
|
+
def date(data: Union[datetime, date, str], fmt: str = "%Y-%m-%d") -> str:
|
|
52
|
+
"""Formats a date object as a string according to the specified format."""
|
|
53
|
+
return data.strftime(fmt) if isinstance(data, (datetime, date)) else str(data)
|
|
82
54
|
|
|
83
55
|
|
|
84
|
-
def
|
|
85
|
-
|
|
56
|
+
def time(data: Union[datetime, time, str], fmt: str = "%X") -> str:
|
|
57
|
+
"""Formats a time object as a string according to the specified format."""
|
|
58
|
+
return data.strftime(fmt) if isinstance(data, (datetime, time)) else str(data)
|
|
86
59
|
|
|
87
|
-
def _(param):
|
|
88
|
-
if isinstance(param, (datetime)):
|
|
89
|
-
return param.strftime(kwds["fmt"])
|
|
90
|
-
else:
|
|
91
|
-
return param
|
|
92
60
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
return
|
|
61
|
+
def timestamp(data: Union[datetime, str], fmt: str = "%c") -> str:
|
|
62
|
+
"""Formats a datetime object as a string according to the specified format."""
|
|
63
|
+
return data.strftime(fmt) if isinstance(data, datetime) else str(data)
|
|
96
64
|
|
|
97
65
|
|
|
98
|
-
def email(data):
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if data == "None":
|
|
102
|
-
return ""
|
|
103
|
-
return data.lower()
|
|
66
|
+
def email(data: Optional[str]) -> str:
|
|
67
|
+
"""Returns a lowercase email address or an empty string if invalid."""
|
|
68
|
+
return "" if data in (None, "None") else data.lower()
|
|
104
69
|
|
|
105
70
|
|
|
106
|
-
def pointer(data):
|
|
71
|
+
def pointer(data: Union[str, int]) -> Union[int, str]:
|
|
72
|
+
"""Converts a string to an integer, or returns an empty string if conversion fails."""
|
|
107
73
|
try:
|
|
108
74
|
return int(data)
|
|
109
|
-
except:
|
|
75
|
+
except (ValueError, TypeError):
|
|
110
76
|
return ""
|
|
111
77
|
|
|
112
78
|
|
|
113
|
-
def rot13(data):
|
|
79
|
+
def rot13(data: str) -> str:
|
|
80
|
+
"""Encodes a string using ROT13."""
|
|
114
81
|
return codecs.decode(data, "rot13")
|
|
115
82
|
|
|
116
83
|
|
|
117
|
-
def boolean(data):
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
84
|
+
def boolean(data: Union[str, bool]) -> bool:
|
|
85
|
+
"""Converts various representations to a boolean."""
|
|
86
|
+
if isinstance(data, str) and data.lower() in ["false", "", "f", "off", "no"]:
|
|
87
|
+
return False
|
|
121
88
|
return bool(data)
|
|
122
89
|
|
|
123
90
|
|
|
124
|
-
def money(data):
|
|
91
|
+
def money(data: str) -> str:
|
|
92
|
+
"""Formats a numeric string as currency."""
|
|
125
93
|
if data in [None, ""]:
|
|
126
94
|
return ""
|
|
127
|
-
|
|
128
|
-
return "${
|
|
95
|
+
cleaned_data = re.sub(r"[^0-9\.-]", "", str(data))
|
|
96
|
+
return f"${decimal.Decimal(cleaned_data):,.2f}"
|
|
129
97
|
|
|
130
98
|
|
|
131
|
-
def
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
return "0"
|
|
136
|
-
return "{:.{prec}f}".format(decimal.Decimal(data), prec=precision)
|
|
99
|
+
def round_to(
|
|
100
|
+
precision: int, data: Optional[Union[str, float, decimal.Decimal]] = None
|
|
101
|
+
) -> Union[Callable, str]:
|
|
102
|
+
"""Rounds a number to the specified precision."""
|
|
137
103
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
104
|
+
def function(value):
|
|
105
|
+
cleaned_value = re.sub(r"[^0-9\.-]", "", str(value))
|
|
106
|
+
return (
|
|
107
|
+
f"{decimal.Decimal(cleaned_value):.{precision}f}" if cleaned_value else "0"
|
|
108
|
+
)
|
|
141
109
|
|
|
110
|
+
return function if data is None else function(data)
|
|
142
111
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if data
|
|
112
|
+
|
|
113
|
+
def ein(data: str) -> str:
|
|
114
|
+
"""Formats a 9-digit EIN as XX-XXXXXXX or returns an empty string if invalid."""
|
|
115
|
+
if data in (None, "None", ""):
|
|
147
116
|
return ""
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
return "{}-{}".format(data[:2], data[2:])
|
|
117
|
+
cleaned_data = re.sub(r"[^0-9]", "", data)
|
|
118
|
+
match = re.fullmatch(r"\d{9}", cleaned_data)
|
|
119
|
+
return f"{cleaned_data[:2]}-{cleaned_data[2:]}" if match else ""
|
|
152
120
|
|
|
153
121
|
|
|
154
|
-
def
|
|
122
|
+
def to_list(data: Union[str, List]) -> Optional[List]:
|
|
123
|
+
"""Converts a single element or JSON-like list string to a list."""
|
|
155
124
|
if data in (None, "None"):
|
|
156
125
|
return None
|
|
157
126
|
if isinstance(data, list):
|
|
158
127
|
return data
|
|
159
|
-
if isinstance(data, str):
|
|
160
|
-
|
|
161
|
-
return eval(data)
|
|
128
|
+
if isinstance(data, str) and data.startswith("["):
|
|
129
|
+
try:
|
|
130
|
+
return eval(data) # Be cautious with eval; only use if data is trusted
|
|
131
|
+
except (SyntaxError, NameError):
|
|
132
|
+
return None
|
|
162
133
|
return [data]
|
|
163
134
|
|
|
164
135
|
|
|
165
|
-
def title(data):
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
if data == "None":
|
|
169
|
-
return ""
|
|
170
|
-
return str(data).title()
|
|
136
|
+
def title(data: Optional[str]) -> str:
|
|
137
|
+
"""Converts a string to title case."""
|
|
138
|
+
return "" if data in (None, "None") else str(data).title()
|
|
171
139
|
|
|
172
140
|
|
|
173
|
-
def lower(data):
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if data == "None":
|
|
177
|
-
return ""
|
|
178
|
-
return str(data).lower()
|
|
141
|
+
def lower(data: Optional[str]) -> str:
|
|
142
|
+
"""Converts a string to lowercase."""
|
|
143
|
+
return "" if data in (None, "None") else str(data).lower()
|
|
179
144
|
|
|
180
145
|
|
|
181
|
-
def upper(data):
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
return ""
|
|
186
|
-
return str(data).upper()
|
|
146
|
+
def upper(data: Optional[str]) -> str:
|
|
147
|
+
"""Converts a string to uppercase."""
|
|
148
|
+
return "" if data in (None, "None") else str(data).upper()
|
|
149
|
+
|
|
187
150
|
|
|
151
|
+
def padding(length: int, char: str) -> Callable[[str], str]:
|
|
152
|
+
"""Returns a function that pads a string to the specified length with the given character."""
|
|
188
153
|
|
|
189
|
-
def
|
|
190
|
-
|
|
191
|
-
if data is None:
|
|
192
|
-
return ""
|
|
193
|
-
return str(data).rjust(length, char)
|
|
154
|
+
def inner(data: str) -> str:
|
|
155
|
+
return str(data).rjust(length, char) if data not in (None, "None", "") else ""
|
|
194
156
|
|
|
195
157
|
return inner
|
|
196
158
|
|
|
197
159
|
|
|
198
|
-
def pprint(data):
|
|
160
|
+
def pprint(data: str) -> str:
|
|
161
|
+
"""Pretty-prints a JSON-like string representation of data."""
|
|
199
162
|
try:
|
|
200
163
|
return pformat(eval(data))
|
|
201
|
-
except:
|
|
164
|
+
except (SyntaxError, NameError):
|
|
202
165
|
return data
|
|
203
166
|
|
|
204
167
|
|
|
205
|
-
def string(data):
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
return str(data)
|
|
168
|
+
def string(data: Optional[str]) -> str:
|
|
169
|
+
"""Converts a None value to an empty string; otherwise returns the string itself."""
|
|
170
|
+
return "" if data is None else str(data)
|
velocity/misc/export.py
CHANGED
|
@@ -1,143 +1,146 @@
|
|
|
1
|
+
import decimal
|
|
2
|
+
import json
|
|
3
|
+
from datetime import datetime, date, time, timedelta
|
|
4
|
+
from typing import Union, List, Dict
|
|
5
|
+
from io import BytesIO
|
|
6
|
+
import base64
|
|
1
7
|
import openpyxl
|
|
2
8
|
from openpyxl.styles import NamedStyle, Font, Border, Side, Alignment
|
|
3
9
|
from openpyxl.utils import get_column_letter
|
|
4
|
-
from io import BytesIO
|
|
5
|
-
import base64
|
|
6
10
|
|
|
7
11
|
|
|
8
|
-
def extract(d, keys):
|
|
9
|
-
|
|
12
|
+
def extract(d: dict, keys: List[str]) -> List:
|
|
13
|
+
"""Extract values from a dictionary based on a list of keys."""
|
|
14
|
+
return [d.get(key) for key in keys]
|
|
10
15
|
|
|
11
16
|
|
|
12
|
-
def autosize_columns(ws, fixed={}):
|
|
13
|
-
|
|
14
|
-
# content, font family and font size differences, etc.) There is no
|
|
15
|
-
# easy way to do this when buiding excel files.
|
|
17
|
+
def autosize_columns(ws, fixed: Dict[str, float] = {}):
|
|
18
|
+
"""Autosize columns in the worksheet based on content length."""
|
|
16
19
|
for col in ws.columns:
|
|
17
20
|
max_length = 0
|
|
18
21
|
for cell in col:
|
|
19
|
-
try:
|
|
20
|
-
if len(str(cell.value)) > max_length:
|
|
21
|
-
max_length = len(cell.value)
|
|
22
|
-
except:
|
|
23
|
-
|
|
22
|
+
try:
|
|
23
|
+
if cell.value and len(str(cell.value)) > max_length:
|
|
24
|
+
max_length = len(str(cell.value))
|
|
25
|
+
except Exception:
|
|
26
|
+
continue
|
|
24
27
|
adjusted_width = (max_length + 2) * 1.2
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if l in fixed:
|
|
28
|
-
adjusted_width = fixed["l"]
|
|
29
|
-
ws.column_dimensions[l].width = adjusted_width
|
|
30
|
-
except:
|
|
31
|
-
l = col[0].column
|
|
32
|
-
if l in fixed:
|
|
33
|
-
adjusted_width = fixed["l"]
|
|
34
|
-
ws.column_dimensions[l].width = adjusted_width
|
|
28
|
+
col_letter = get_column_letter(col[0].column)
|
|
29
|
+
ws.column_dimensions[col_letter].width = fixed.get(col_letter, adjusted_width)
|
|
35
30
|
|
|
36
31
|
|
|
37
32
|
def create_spreadsheet(
|
|
38
|
-
headers,
|
|
39
|
-
rows,
|
|
33
|
+
headers: List[str],
|
|
34
|
+
rows: List[List],
|
|
40
35
|
fileorbuffer,
|
|
41
|
-
styles={},
|
|
42
|
-
merge=[],
|
|
43
|
-
formats={},
|
|
44
|
-
named_styles=[],
|
|
45
|
-
freeze_panes="A2",
|
|
46
|
-
dimensions=None,
|
|
47
|
-
auto_size=True,
|
|
36
|
+
styles: Dict[str, str] = {},
|
|
37
|
+
merge: List[str] = [],
|
|
38
|
+
formats: Dict[str, str] = {},
|
|
39
|
+
named_styles: List[NamedStyle] = [],
|
|
40
|
+
freeze_panes: str = "A2",
|
|
41
|
+
dimensions: dict = None,
|
|
42
|
+
auto_size: bool = True,
|
|
48
43
|
):
|
|
44
|
+
"""Create an Excel spreadsheet with specified headers, rows, and styles."""
|
|
49
45
|
wb = openpyxl.Workbook()
|
|
50
46
|
ws = wb.active
|
|
51
47
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
48
|
+
# Define default named styles
|
|
49
|
+
def get_named_styles():
|
|
50
|
+
named_styles = {
|
|
51
|
+
"col_header": NamedStyle(
|
|
52
|
+
name="col_header",
|
|
53
|
+
font=Font(bold=True),
|
|
54
|
+
border=Border(bottom=Side(style="medium", color="000000")),
|
|
55
|
+
),
|
|
56
|
+
"sum_total": NamedStyle(
|
|
57
|
+
name="sum_total",
|
|
58
|
+
border=Border(bottom=Side(style="double", color="000000")),
|
|
59
|
+
),
|
|
60
|
+
"sub_total": NamedStyle(
|
|
61
|
+
name="sub_total",
|
|
62
|
+
font=Font(bold=True),
|
|
63
|
+
border=Border(bottom=Side(style="thin", color="000000")),
|
|
64
|
+
),
|
|
65
|
+
"bold": NamedStyle(name="bold", font=Font(bold=True)),
|
|
66
|
+
"align_right": NamedStyle(
|
|
67
|
+
name="align_right",
|
|
68
|
+
font=Font(bold=True),
|
|
69
|
+
border=Border(top=Side(style="thin", color="000000")),
|
|
70
|
+
alignment=Alignment(horizontal="right", vertical="center"),
|
|
71
|
+
),
|
|
72
|
+
"align_left": NamedStyle(
|
|
73
|
+
name="align_left",
|
|
74
|
+
font=Font(bold=True),
|
|
75
|
+
border=Border(top=Side(style="thin", color="000000")),
|
|
76
|
+
alignment=Alignment(horizontal="left", vertical="center"),
|
|
77
|
+
),
|
|
78
|
+
"align_right_double": NamedStyle(
|
|
79
|
+
name="align_right_double",
|
|
80
|
+
font=Font(bold=True),
|
|
81
|
+
border=Border(top=Side(style="double", color="000000")),
|
|
82
|
+
alignment=Alignment(horizontal="right", vertical="center"),
|
|
83
|
+
),
|
|
84
|
+
"align_left_double": NamedStyle(
|
|
85
|
+
name="align_left_double",
|
|
86
|
+
font=Font(bold=True),
|
|
87
|
+
border=Border(top=Side(style="double", color="000000")),
|
|
88
|
+
alignment=Alignment(horizontal="left", vertical="center"),
|
|
89
|
+
),
|
|
90
|
+
}
|
|
91
|
+
return named_styles
|
|
92
|
+
|
|
93
|
+
# Add default and user-defined styles
|
|
94
|
+
local_styles = get_named_styles()
|
|
96
95
|
for style in named_styles:
|
|
97
96
|
local_styles[style.name] = style
|
|
98
|
-
|
|
99
97
|
for style in local_styles.values():
|
|
100
98
|
wb.add_named_style(style)
|
|
101
99
|
|
|
100
|
+
# Add headers and rows
|
|
102
101
|
ws.append(headers)
|
|
102
|
+
for row in rows:
|
|
103
|
+
ws.append(row)
|
|
103
104
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if freeze_panes:
|
|
107
|
-
ws.freeze_panes = freeze_panes
|
|
105
|
+
# Set freeze panes
|
|
106
|
+
ws.freeze_panes = freeze_panes
|
|
108
107
|
|
|
108
|
+
# Auto-size columns if enabled
|
|
109
109
|
if auto_size:
|
|
110
|
-
autosize_columns(ws)
|
|
110
|
+
autosize_columns(ws, fixed={})
|
|
111
111
|
|
|
112
|
+
# Set row and column dimensions if provided
|
|
112
113
|
if dimensions:
|
|
113
114
|
for key, val in dimensions.get("rows", {}).items():
|
|
114
115
|
ws.row_dimensions[key].height = val
|
|
115
116
|
for key, val in dimensions.get("columns", {}).items():
|
|
116
117
|
ws.column_dimensions[key].width = val
|
|
117
118
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
119
|
+
# Apply cell styles, merges, and formats
|
|
120
|
+
for cell, style_name in styles.items():
|
|
121
|
+
if style_name in local_styles:
|
|
122
|
+
ws[cell].style = local_styles[style_name]
|
|
121
123
|
for cell_range in merge:
|
|
122
124
|
ws.merge_cells(cell_range)
|
|
125
|
+
for cell, format_code in formats.items():
|
|
126
|
+
ws[cell].number_format = format_code
|
|
123
127
|
|
|
124
|
-
|
|
125
|
-
ws[cell].number_format = format
|
|
126
|
-
|
|
128
|
+
# Save workbook to the provided file or buffer
|
|
127
129
|
wb.save(fileorbuffer)
|
|
128
130
|
|
|
129
131
|
|
|
130
|
-
def
|
|
131
|
-
headers,
|
|
132
|
-
rows,
|
|
133
|
-
styles={},
|
|
134
|
-
merge=[],
|
|
135
|
-
formats={},
|
|
136
|
-
named_styles=[],
|
|
137
|
-
freeze_panes="A2",
|
|
138
|
-
dimensions=None,
|
|
139
|
-
auto_size=True,
|
|
140
|
-
):
|
|
132
|
+
def get_downloadable_spreadsheet(
|
|
133
|
+
headers: List[str],
|
|
134
|
+
rows: List[List],
|
|
135
|
+
styles: Dict[str, str] = {},
|
|
136
|
+
merge: List[str] = [],
|
|
137
|
+
formats: Dict[str, str] = {},
|
|
138
|
+
named_styles: List[NamedStyle] = [],
|
|
139
|
+
freeze_panes: str = "A2",
|
|
140
|
+
dimensions: dict = None,
|
|
141
|
+
auto_size: bool = True,
|
|
142
|
+
) -> str:
|
|
143
|
+
"""Generate a downloadable spreadsheet encoded in base64."""
|
|
141
144
|
buffer = BytesIO()
|
|
142
145
|
create_spreadsheet(
|
|
143
146
|
headers,
|