velocity-python 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of velocity-python might be problematic. Click here for more details.
- velocity/__init__.py +3 -0
- velocity/aws/__init__.py +24 -0
- velocity/aws/context.py +0 -0
- velocity/aws/handlers/__init__.py +2 -0
- velocity/aws/handlers/lambda_handler.py +123 -0
- velocity/aws/handlers/sqs_handler.py +48 -0
- velocity/db/__init__.py +5 -0
- velocity/db/core/__init__.py +0 -0
- velocity/db/core/column.py +202 -0
- velocity/db/core/database.py +65 -0
- velocity/db/core/decorators.py +81 -0
- velocity/db/core/engine.py +367 -0
- velocity/db/core/exceptions.py +31 -0
- velocity/db/core/result.py +137 -0
- velocity/db/core/row.py +191 -0
- velocity/db/core/sequence.py +33 -0
- velocity/db/core/table.py +448 -0
- velocity/db/core/transaction.py +178 -0
- velocity/db/servers/__init__.py +0 -0
- velocity/db/servers/mysql.py +575 -0
- velocity/db/servers/postgres.py +1275 -0
- velocity/db/servers/sql.py +558 -0
- velocity/db/servers/sqlite.py +899 -0
- velocity/db/servers/sqlserver.py +821 -0
- velocity/misc/__init__.py +0 -0
- velocity/misc/conv.py +420 -0
- velocity/misc/db.py +85 -0
- velocity/misc/export.py +147 -0
- velocity/misc/format.py +81 -0
- velocity/misc/mail.py +67 -0
- velocity/misc/timer.py +27 -0
- velocity_python-0.0.1.dist-info/LICENSE +19 -0
- velocity_python-0.0.1.dist-info/METADATA +181 -0
- velocity_python-0.0.1.dist-info/RECORD +36 -0
- velocity_python-0.0.1.dist-info/WHEEL +5 -0
- velocity_python-0.0.1.dist-info/top_level.txt +1 -0
|
File without changes
|
velocity/misc/conv.py
ADDED
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import codecs
|
|
3
|
+
import decimal
|
|
4
|
+
from email.utils import parseaddr
|
|
5
|
+
from datetime import datetime, date, time
|
|
6
|
+
import pprint
|
|
7
|
+
|
|
8
|
+
# oconv converts data to be displayed or rendered.
|
|
9
|
+
class oconv(object):
|
|
10
|
+
@staticmethod
|
|
11
|
+
def none(data):
|
|
12
|
+
if data == None:
|
|
13
|
+
return ''
|
|
14
|
+
if data == 'None':
|
|
15
|
+
return ''
|
|
16
|
+
if data == 'null':
|
|
17
|
+
return ''
|
|
18
|
+
return data
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def phone(data):
|
|
22
|
+
if data == None:
|
|
23
|
+
return ''
|
|
24
|
+
if data == 'None':
|
|
25
|
+
return ''
|
|
26
|
+
if not data:
|
|
27
|
+
return data
|
|
28
|
+
data = re.search(r'\d{10}$', re.sub("[^0-9]", "", data)).group()
|
|
29
|
+
return "({}) {}-{}".format(data[:3],data[3:6],data[6:])
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
def day_of_week(data, abbrev=False):
|
|
34
|
+
if isinstance(data, list):
|
|
35
|
+
new = []
|
|
36
|
+
for day in data:
|
|
37
|
+
new.append(oconv.day_of_week(day, abbrev=abbrev))
|
|
38
|
+
return ','.join(new)
|
|
39
|
+
if data == None:
|
|
40
|
+
return ''
|
|
41
|
+
if data == 'None':
|
|
42
|
+
return ''
|
|
43
|
+
if not data:
|
|
44
|
+
return data
|
|
45
|
+
if abbrev:
|
|
46
|
+
return {
|
|
47
|
+
1: 'Mon',
|
|
48
|
+
2: 'Tue',
|
|
49
|
+
3: 'Wed',
|
|
50
|
+
4: 'Thu',
|
|
51
|
+
5: 'Fri',
|
|
52
|
+
6: 'Sat',
|
|
53
|
+
7: 'Sun'
|
|
54
|
+
}[int(data)]
|
|
55
|
+
return {
|
|
56
|
+
1: 'Monday',
|
|
57
|
+
2: 'Tuesday',
|
|
58
|
+
3: 'Wednesday',
|
|
59
|
+
4: 'Thursday',
|
|
60
|
+
5: 'Friday',
|
|
61
|
+
6: 'Saturday',
|
|
62
|
+
7: 'Sunday'
|
|
63
|
+
}[int(data)]
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
def date(*args, **kwds):
|
|
67
|
+
kwds.setdefault('fmt','%Y-%m-%d')
|
|
68
|
+
def _(param):
|
|
69
|
+
if isinstance(param,(datetime, date)):
|
|
70
|
+
return param.strftime( kwds['fmt'] )
|
|
71
|
+
else:
|
|
72
|
+
return param
|
|
73
|
+
if args and args[0]:
|
|
74
|
+
return _(args[0])
|
|
75
|
+
return _
|
|
76
|
+
|
|
77
|
+
@staticmethod
|
|
78
|
+
def time(*args, **kwds):
|
|
79
|
+
kwds.setdefault('fmt','%X')
|
|
80
|
+
def _(param):
|
|
81
|
+
if isinstance(param,(datetime, time)):
|
|
82
|
+
return param.strftime( kwds['fmt'] )
|
|
83
|
+
else:
|
|
84
|
+
return param
|
|
85
|
+
if args and args[0]:
|
|
86
|
+
return _(args[0])
|
|
87
|
+
return _
|
|
88
|
+
|
|
89
|
+
@staticmethod
|
|
90
|
+
def timestamp(*args, **kwds):
|
|
91
|
+
kwds.setdefault('fmt','%c')
|
|
92
|
+
def _(param):
|
|
93
|
+
if isinstance(param,(datetime)):
|
|
94
|
+
return param.strftime( kwds['fmt'] )
|
|
95
|
+
else:
|
|
96
|
+
return param
|
|
97
|
+
if args:
|
|
98
|
+
return _(args[0])
|
|
99
|
+
return _
|
|
100
|
+
|
|
101
|
+
@staticmethod
|
|
102
|
+
def email(data):
|
|
103
|
+
if data == None:
|
|
104
|
+
return ''
|
|
105
|
+
if data == 'None':
|
|
106
|
+
return ''
|
|
107
|
+
return data.lower()
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def pointer(data):
|
|
111
|
+
try:
|
|
112
|
+
return int(data)
|
|
113
|
+
except:
|
|
114
|
+
return ''
|
|
115
|
+
|
|
116
|
+
@staticmethod
|
|
117
|
+
def rot13(data):
|
|
118
|
+
return codecs.decode(data,'rot13')
|
|
119
|
+
|
|
120
|
+
@staticmethod
|
|
121
|
+
def boolean(data):
|
|
122
|
+
if isinstance(data,str):
|
|
123
|
+
if data.lower() in ['false','','f','off','no']:
|
|
124
|
+
return False
|
|
125
|
+
return bool(data)
|
|
126
|
+
|
|
127
|
+
@staticmethod
|
|
128
|
+
def money(data):
|
|
129
|
+
if data in [None,'']:
|
|
130
|
+
return ''
|
|
131
|
+
data = re.sub("[^0-9\.-]", "", str(data))
|
|
132
|
+
return '${:,.2f}'.format(decimal.Decimal(data))
|
|
133
|
+
|
|
134
|
+
@staticmethod
|
|
135
|
+
def round(precision, data=None):
|
|
136
|
+
def function(data):
|
|
137
|
+
data = re.sub("[^0-9\.]", "", str(data))
|
|
138
|
+
if data == '':
|
|
139
|
+
return '0'
|
|
140
|
+
return '{:.{prec}f}'.format(decimal.Decimal(data), prec=precision)
|
|
141
|
+
if data == None:
|
|
142
|
+
return function
|
|
143
|
+
return function(data)
|
|
144
|
+
|
|
145
|
+
@staticmethod
|
|
146
|
+
def ein(data):
|
|
147
|
+
if data == None:
|
|
148
|
+
return ''
|
|
149
|
+
if data == 'None':
|
|
150
|
+
return ''
|
|
151
|
+
if not data:
|
|
152
|
+
return data
|
|
153
|
+
data = re.search(r'\d{9}$', re.sub("[^0-9]", "", data)).group()
|
|
154
|
+
return "{}-{}".format(data[:2],data[2:])
|
|
155
|
+
|
|
156
|
+
@staticmethod
|
|
157
|
+
def list(data):
|
|
158
|
+
if data in (None,'None'):
|
|
159
|
+
return None
|
|
160
|
+
if isinstance(data, list):
|
|
161
|
+
return data
|
|
162
|
+
if isinstance(data, str):
|
|
163
|
+
if data[0] == '[':
|
|
164
|
+
return eval(data)
|
|
165
|
+
return [data]
|
|
166
|
+
|
|
167
|
+
@staticmethod
|
|
168
|
+
def title(data):
|
|
169
|
+
if data == None:
|
|
170
|
+
return ''
|
|
171
|
+
if data == 'None':
|
|
172
|
+
return ''
|
|
173
|
+
return str(data).title()
|
|
174
|
+
|
|
175
|
+
@staticmethod
|
|
176
|
+
def lower(data):
|
|
177
|
+
if data == None:
|
|
178
|
+
return ''
|
|
179
|
+
if data == 'None':
|
|
180
|
+
return ''
|
|
181
|
+
return str(data).lower()
|
|
182
|
+
|
|
183
|
+
@staticmethod
|
|
184
|
+
def upper(data):
|
|
185
|
+
if data == None:
|
|
186
|
+
return ''
|
|
187
|
+
if data == 'None':
|
|
188
|
+
return ''
|
|
189
|
+
return str(data).upper()
|
|
190
|
+
|
|
191
|
+
@staticmethod
|
|
192
|
+
def padding(length, char):
|
|
193
|
+
def inner(data):
|
|
194
|
+
if data is None:
|
|
195
|
+
return ''
|
|
196
|
+
return str(data).rjust(length, char)
|
|
197
|
+
return inner
|
|
198
|
+
|
|
199
|
+
@staticmethod
|
|
200
|
+
def pprint(data):
|
|
201
|
+
try:
|
|
202
|
+
return pprint.pformat(eval(data))
|
|
203
|
+
except:
|
|
204
|
+
return data
|
|
205
|
+
|
|
206
|
+
@staticmethod
|
|
207
|
+
def string(data):
|
|
208
|
+
if data == None:
|
|
209
|
+
return ''
|
|
210
|
+
return str(data)
|
|
211
|
+
|
|
212
|
+
# iconv converts data to be stored in the database.
|
|
213
|
+
# The source is generally user input from html forms.
|
|
214
|
+
class iconv(object):
|
|
215
|
+
@staticmethod
|
|
216
|
+
def none(data):
|
|
217
|
+
if data == '':
|
|
218
|
+
return None
|
|
219
|
+
if data == 'null':
|
|
220
|
+
return None
|
|
221
|
+
if data == '@NULL':
|
|
222
|
+
return None
|
|
223
|
+
return data
|
|
224
|
+
|
|
225
|
+
@staticmethod
|
|
226
|
+
def phone(data):
|
|
227
|
+
if data == 'None':
|
|
228
|
+
return None
|
|
229
|
+
if not data:
|
|
230
|
+
return data
|
|
231
|
+
return re.search(r'\d{10}$', re.sub("[^0-9]", "", data)).group()
|
|
232
|
+
|
|
233
|
+
@staticmethod
|
|
234
|
+
def day_of_week(data):
|
|
235
|
+
if not data:
|
|
236
|
+
return data
|
|
237
|
+
return {
|
|
238
|
+
'monday': 1,
|
|
239
|
+
'tuesday': 2,
|
|
240
|
+
'wednesday': 3,
|
|
241
|
+
'thursday': 4,
|
|
242
|
+
'friday': 5,
|
|
243
|
+
'saturday': 6,
|
|
244
|
+
'sunday': 7,
|
|
245
|
+
'mon': 1,
|
|
246
|
+
'tue': 2,
|
|
247
|
+
'wed': 3,
|
|
248
|
+
'thu': 4,
|
|
249
|
+
'fri': 5,
|
|
250
|
+
'sat': 6,
|
|
251
|
+
'sun': 7
|
|
252
|
+
}[data.lower()]
|
|
253
|
+
|
|
254
|
+
@staticmethod
|
|
255
|
+
def date(*args, **kwds):
|
|
256
|
+
kwds.setdefault('fmt', '%Y-%m-%d')
|
|
257
|
+
def _(param):
|
|
258
|
+
if isinstance(param,str):
|
|
259
|
+
return datetime.strptime(param, kwds['fmt']).date()
|
|
260
|
+
else:
|
|
261
|
+
return param
|
|
262
|
+
if args and args[0]:
|
|
263
|
+
return _(args[0])
|
|
264
|
+
return _
|
|
265
|
+
|
|
266
|
+
@staticmethod
|
|
267
|
+
def time(*args, **kwds):
|
|
268
|
+
kwds.setdefault('fmt','%X')
|
|
269
|
+
def _(param):
|
|
270
|
+
if isinstance(param, str):
|
|
271
|
+
return datetime.strptime(param, kwds['fmt']).time()
|
|
272
|
+
else:
|
|
273
|
+
return param
|
|
274
|
+
if args and args[0]:
|
|
275
|
+
return _(args[0])
|
|
276
|
+
return _
|
|
277
|
+
|
|
278
|
+
@staticmethod
|
|
279
|
+
def timestamp(*args, **kwds):
|
|
280
|
+
kwds.setdefault('fmt','%c')
|
|
281
|
+
def _(param):
|
|
282
|
+
if isinstance(param, str):
|
|
283
|
+
return datetime.strptime(param, kwds['fmt'])
|
|
284
|
+
else:
|
|
285
|
+
return param
|
|
286
|
+
if args and args[0]:
|
|
287
|
+
return _(args[0])
|
|
288
|
+
return _
|
|
289
|
+
|
|
290
|
+
@staticmethod
|
|
291
|
+
def email(data):
|
|
292
|
+
if not data:
|
|
293
|
+
return None
|
|
294
|
+
if data == 'None':
|
|
295
|
+
return None
|
|
296
|
+
data = data.strip().lower()
|
|
297
|
+
if '@' not in data:
|
|
298
|
+
raise Exception()
|
|
299
|
+
email = parseaddr(data)[1]
|
|
300
|
+
mailbox,domain = email.split('@')
|
|
301
|
+
if '.' in domain:
|
|
302
|
+
if len(domain.split('.')[1]) < 1:
|
|
303
|
+
raise Exception()
|
|
304
|
+
else:
|
|
305
|
+
raise Exception()
|
|
306
|
+
return data
|
|
307
|
+
|
|
308
|
+
@staticmethod
|
|
309
|
+
def integer(data):
|
|
310
|
+
return int(re.sub("[^0-9\.-]", "", str(data)))
|
|
311
|
+
|
|
312
|
+
@staticmethod
|
|
313
|
+
def boolean(data):
|
|
314
|
+
if isinstance(data,str):
|
|
315
|
+
if data.lower() in ['false','','f','off','no']:
|
|
316
|
+
return False
|
|
317
|
+
return bool(data)
|
|
318
|
+
|
|
319
|
+
@staticmethod
|
|
320
|
+
def rot13(data):
|
|
321
|
+
return codecs.encode(data,'rot13')
|
|
322
|
+
|
|
323
|
+
@staticmethod
|
|
324
|
+
def pointer(data):
|
|
325
|
+
if data == '@new': return data
|
|
326
|
+
if data == '': return None
|
|
327
|
+
if data == None: return None
|
|
328
|
+
if data == '@NULL': return None
|
|
329
|
+
return int(data)
|
|
330
|
+
|
|
331
|
+
@staticmethod
|
|
332
|
+
def money(data):
|
|
333
|
+
if data == 'None':
|
|
334
|
+
return None
|
|
335
|
+
if not data:
|
|
336
|
+
return data
|
|
337
|
+
return decimal.Decimal(re.sub("[^0-9\.-]", "", str(data)))
|
|
338
|
+
|
|
339
|
+
@staticmethod
|
|
340
|
+
def round(precision, data=None):
|
|
341
|
+
def function(data):
|
|
342
|
+
if data == 'None':
|
|
343
|
+
return None
|
|
344
|
+
if not data:
|
|
345
|
+
return data
|
|
346
|
+
if isinstance(data, str):
|
|
347
|
+
data = re.sub("[^0-9\.-]", "", data)
|
|
348
|
+
return decimal.Decimal(data).quantize(decimal.Decimal(10) ** -precision, rounding=decimal.ROUND_HALF_UP)
|
|
349
|
+
if data == None:
|
|
350
|
+
return function
|
|
351
|
+
return function(data)
|
|
352
|
+
|
|
353
|
+
@staticmethod
|
|
354
|
+
def decimal(data):
|
|
355
|
+
if data == 'None':
|
|
356
|
+
return None
|
|
357
|
+
if not data:
|
|
358
|
+
return data
|
|
359
|
+
return decimal.Decimal(re.sub("[^0-9\.-]", "", str(data)))
|
|
360
|
+
|
|
361
|
+
@staticmethod
|
|
362
|
+
def ein(data):
|
|
363
|
+
if data == 'None':
|
|
364
|
+
return None
|
|
365
|
+
if not data:
|
|
366
|
+
return data
|
|
367
|
+
return re.search(r'^\d{9}$', re.sub("[^0-9]", "", data)).group()
|
|
368
|
+
|
|
369
|
+
@staticmethod
|
|
370
|
+
def list(data):
|
|
371
|
+
if data in (None,'None'):
|
|
372
|
+
return None
|
|
373
|
+
if isinstance(data, str):
|
|
374
|
+
if data[0] == '[':
|
|
375
|
+
return data
|
|
376
|
+
if not isinstance(data, list):
|
|
377
|
+
data = [data]
|
|
378
|
+
return repr(data)
|
|
379
|
+
|
|
380
|
+
@staticmethod
|
|
381
|
+
def title(data):
|
|
382
|
+
if data == None:
|
|
383
|
+
return ''
|
|
384
|
+
if data == 'None':
|
|
385
|
+
return ''
|
|
386
|
+
return str(data).title()
|
|
387
|
+
|
|
388
|
+
@staticmethod
|
|
389
|
+
def lower(data):
|
|
390
|
+
if data == None:
|
|
391
|
+
return ''
|
|
392
|
+
if data == 'None':
|
|
393
|
+
return ''
|
|
394
|
+
return str(data).lower()
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
@staticmethod
|
|
398
|
+
def upper(data):
|
|
399
|
+
if data == None:
|
|
400
|
+
return ''
|
|
401
|
+
if data == 'None':
|
|
402
|
+
return ''
|
|
403
|
+
return str(data).upper()
|
|
404
|
+
|
|
405
|
+
@staticmethod
|
|
406
|
+
def padding(length, char):
|
|
407
|
+
def inner(data):
|
|
408
|
+
if data in [None,'None','']:
|
|
409
|
+
return None
|
|
410
|
+
return str(data).rjust(length, char)
|
|
411
|
+
return inner
|
|
412
|
+
|
|
413
|
+
@staticmethod
|
|
414
|
+
def string(data):
|
|
415
|
+
if data == '':
|
|
416
|
+
return None
|
|
417
|
+
return str(data)
|
|
418
|
+
|
|
419
|
+
if __name__=='__main__':
|
|
420
|
+
print(iconv.money("$123.441.234c"))
|
velocity/misc/db.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import numbers
|
|
2
|
+
import random
|
|
3
|
+
import string
|
|
4
|
+
from functools import wraps
|
|
5
|
+
from velocity.db import exceptions
|
|
6
|
+
|
|
7
|
+
#TBD implement or delete all this code. It is not used anywhere.
|
|
8
|
+
def NotSupported(*args, **kwds):
|
|
9
|
+
raise Exception("Sorry, the driver for this database is not installed")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def NOTNULL(x):
|
|
13
|
+
""" Helper function to filter out NULL values from keys/values functions """
|
|
14
|
+
return len(x) == 2 and x[1] is not None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def pipe(func, primary, secondary, *args, **kwds):
|
|
18
|
+
with primary.transaction() as pri:
|
|
19
|
+
with secondary.transaction() as sec:
|
|
20
|
+
func(pri, sec, *args, **kwds)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class join(object):
|
|
24
|
+
@classmethod
|
|
25
|
+
def _or(cls, *args, **kwargs):
|
|
26
|
+
return '(' + ' or '.join(cls._list(*args, **kwargs)) + ')'
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def _and(cls, *args, **kwargs):
|
|
30
|
+
return '(' + ' and '.join(cls._list(*args, **kwargs)) + ')'
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def _list(cls, *args, **kwargs):
|
|
34
|
+
vals = []
|
|
35
|
+
vals.extend(args)
|
|
36
|
+
for key,val in kwargs.items():
|
|
37
|
+
if isinstance(val, numbers.Number):
|
|
38
|
+
vals.append("{}={}".format(key,val))
|
|
39
|
+
else:
|
|
40
|
+
vals.append("{}='{}'".format(key,val))
|
|
41
|
+
return vals
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def return_default(default=None):
|
|
46
|
+
"""
|
|
47
|
+
Decorator that sets a default value for a function.
|
|
48
|
+
If an exception is raised within the function, the decorator
|
|
49
|
+
catches the exception and returns the default value instead.
|
|
50
|
+
"""
|
|
51
|
+
def decorator(f):
|
|
52
|
+
f.default = default
|
|
53
|
+
@wraps(f)
|
|
54
|
+
def return_default(self, *args, **kwds):
|
|
55
|
+
sp = self.tx.create_savepoint(cursor=self.table.cursor)
|
|
56
|
+
try:
|
|
57
|
+
result = f(self, *args, **kwds)
|
|
58
|
+
except (exceptions.DbApplicationError,
|
|
59
|
+
exceptions.DbTableMissingError,
|
|
60
|
+
exceptions.DbColumnMissingError,
|
|
61
|
+
exceptions.DbTruncationError,
|
|
62
|
+
StopIteration,
|
|
63
|
+
exceptions.DbObjectExistsError):
|
|
64
|
+
self.tx.rollback_savepoint(sp, cursor=self.table.cursor)
|
|
65
|
+
return f.default
|
|
66
|
+
self.tx.release_savepoint(sp, cursor=self.table.cursor)
|
|
67
|
+
return result
|
|
68
|
+
return return_default
|
|
69
|
+
return decorator
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def randomword(length=None):
|
|
73
|
+
"""
|
|
74
|
+
Generate a random word consisting of lowercase letters. This is used to generate random savepoint names.
|
|
75
|
+
The length of the word can be specified, otherwise a random length between 5 and 15 will be used.
|
|
76
|
+
|
|
77
|
+
Parameters:
|
|
78
|
+
length (int, optional): The length of the random word. If not provided, a random length between 5 and 15 will be used.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
str: The randomly generated word.
|
|
82
|
+
"""
|
|
83
|
+
if length is None:
|
|
84
|
+
length = random.randint(5,15)
|
|
85
|
+
return ''.join(random.choice(string.ascii_lowercase) for i in range(length))
|
velocity/misc/export.py
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import openpyxl
|
|
2
|
+
from openpyxl.styles import NamedStyle, Font, Border, Side, Alignment
|
|
3
|
+
from openpyxl.utils import get_column_letter
|
|
4
|
+
from io import BytesIO
|
|
5
|
+
import base64
|
|
6
|
+
|
|
7
|
+
def extract(d, keys):
|
|
8
|
+
return [d[key] for key in keys]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def autosize_columns(ws, fixed={}):
|
|
12
|
+
# Try to autosize the columns (doesn't always work due to dynamic
|
|
13
|
+
# content, font family and font size differences, etc.) There is no
|
|
14
|
+
# easy way to do this when buiding excel files.
|
|
15
|
+
for col in ws.columns:
|
|
16
|
+
max_length = 0
|
|
17
|
+
for cell in col:
|
|
18
|
+
try: # Necessary to avoid error on empty cells
|
|
19
|
+
if len(str(cell.value)) > max_length:
|
|
20
|
+
max_length = len(cell.value)
|
|
21
|
+
except:
|
|
22
|
+
pass
|
|
23
|
+
adjusted_width = (max_length + 2) * 1.2
|
|
24
|
+
try:
|
|
25
|
+
l = get_column_letter(col[0].column)
|
|
26
|
+
if l in fixed:
|
|
27
|
+
adjusted_width = fixed['l']
|
|
28
|
+
ws.column_dimensions[l].width = adjusted_width
|
|
29
|
+
except:
|
|
30
|
+
l = col[0].column
|
|
31
|
+
if l in fixed:
|
|
32
|
+
adjusted_width = fixed['l']
|
|
33
|
+
ws.column_dimensions[l].width = adjusted_width
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def create_spreadsheet(headers,
|
|
37
|
+
rows,
|
|
38
|
+
fileorbuffer,
|
|
39
|
+
styles={},
|
|
40
|
+
merge=[],
|
|
41
|
+
formats={},
|
|
42
|
+
named_styles=[],
|
|
43
|
+
freeze_panes='A2',
|
|
44
|
+
dimensions=None,
|
|
45
|
+
auto_size=True):
|
|
46
|
+
wb = openpyxl.Workbook()
|
|
47
|
+
ws = wb.active
|
|
48
|
+
|
|
49
|
+
local_styles = {}
|
|
50
|
+
|
|
51
|
+
style = NamedStyle(name="col_header")
|
|
52
|
+
style.font = Font(bold=True)
|
|
53
|
+
style.border = Border(bottom=Side(style='medium', color="000000"))
|
|
54
|
+
local_styles[style.name] = style
|
|
55
|
+
|
|
56
|
+
style = NamedStyle(name="sum_total")
|
|
57
|
+
style.border = Border(bottom=Side(style='double', color="000000"))
|
|
58
|
+
local_styles[style.name] = style
|
|
59
|
+
|
|
60
|
+
style = NamedStyle(name="sub_total")
|
|
61
|
+
style.font = Font(bold=True)
|
|
62
|
+
style.border = Border(bottom=Side(style='thin', color="000000"))
|
|
63
|
+
local_styles[style.name] = style
|
|
64
|
+
|
|
65
|
+
style = NamedStyle(name="bold")
|
|
66
|
+
style.font = Font(bold=True)
|
|
67
|
+
local_styles[style.name] = style
|
|
68
|
+
|
|
69
|
+
style = NamedStyle(name="align_right")
|
|
70
|
+
style.font = Font(bold=True)
|
|
71
|
+
style.border = Border(top=Side(style='thin', color="000000"))
|
|
72
|
+
style.alignment = Alignment(horizontal="right", vertical="center")
|
|
73
|
+
local_styles[style.name] = style
|
|
74
|
+
|
|
75
|
+
style = NamedStyle(name="align_left")
|
|
76
|
+
style.font = Font(bold=True)
|
|
77
|
+
style.border = Border(top=Side(style='thin', color="000000"))
|
|
78
|
+
style.alignment = Alignment(horizontal="left", vertical="center")
|
|
79
|
+
local_styles[style.name] = style
|
|
80
|
+
|
|
81
|
+
style = NamedStyle(name="align_right_double")
|
|
82
|
+
style.font = Font(bold=True)
|
|
83
|
+
style.border = Border(top=Side(style='double', color="000000"))
|
|
84
|
+
style.alignment = Alignment(horizontal="right", vertical="center")
|
|
85
|
+
local_styles[style.name] = style
|
|
86
|
+
|
|
87
|
+
style = NamedStyle(name="align_left_double")
|
|
88
|
+
style.font = Font(bold=True)
|
|
89
|
+
style.border = Border(top=Side(style='double', color="000000"))
|
|
90
|
+
style.alignment = Alignment(horizontal="left", vertical="center")
|
|
91
|
+
local_styles[style.name] = style
|
|
92
|
+
|
|
93
|
+
for style in named_styles:
|
|
94
|
+
local_styles[style.name] = style
|
|
95
|
+
|
|
96
|
+
for style in local_styles.values():
|
|
97
|
+
wb.add_named_style(style)
|
|
98
|
+
|
|
99
|
+
ws.append(headers)
|
|
100
|
+
|
|
101
|
+
[ws.append(row) for row in rows]
|
|
102
|
+
|
|
103
|
+
if freeze_panes:
|
|
104
|
+
ws.freeze_panes = freeze_panes
|
|
105
|
+
|
|
106
|
+
if auto_size:
|
|
107
|
+
autosize_columns(ws)
|
|
108
|
+
|
|
109
|
+
if dimensions:
|
|
110
|
+
for key, val in dimensions.get('rows', {}).items():
|
|
111
|
+
ws.row_dimensions[key].height = val
|
|
112
|
+
for key, val in dimensions.get('columns', {}).items():
|
|
113
|
+
ws.column_dimensions[key].width = val
|
|
114
|
+
|
|
115
|
+
for cell, style in styles.items():
|
|
116
|
+
ws[cell].style = style
|
|
117
|
+
|
|
118
|
+
for cell_range in merge:
|
|
119
|
+
ws.merge_cells(cell_range)
|
|
120
|
+
|
|
121
|
+
for cell, format in formats.items():
|
|
122
|
+
ws[cell].number_format = format
|
|
123
|
+
|
|
124
|
+
wb.save(fileorbuffer)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def getDownloadableSpreadsheet(headers,
|
|
128
|
+
rows,
|
|
129
|
+
styles={},
|
|
130
|
+
merge=[],
|
|
131
|
+
formats={},
|
|
132
|
+
named_styles=[],
|
|
133
|
+
freeze_panes='A2',
|
|
134
|
+
dimensions=None,
|
|
135
|
+
auto_size=True):
|
|
136
|
+
buffer = BytesIO()
|
|
137
|
+
create_spreadsheet(headers,
|
|
138
|
+
rows,
|
|
139
|
+
buffer,
|
|
140
|
+
styles,
|
|
141
|
+
merge,
|
|
142
|
+
formats,
|
|
143
|
+
named_styles,
|
|
144
|
+
freeze_panes,
|
|
145
|
+
dimensions,
|
|
146
|
+
auto_size)
|
|
147
|
+
return base64.b64encode(buffer.getvalue()).decode()
|