ezKit 1.11.14__py3-none-any.whl → 1.11.15__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
ezKit/bottle.py CHANGED
@@ -14,6 +14,7 @@ License: MIT (see LICENSE for details)
14
14
  """
15
15
 
16
16
  from __future__ import print_function
17
+
17
18
  import sys
18
19
 
19
20
  __author__ = 'Marcel Hellkamp'
@@ -62,48 +63,66 @@ def _cli_patch(cli_args): # pragma: no coverage
62
63
  eventlet.monkey_patch()
63
64
 
64
65
 
65
- if __name__ == '__main__':
66
+ if __name__ == "__main__":
66
67
  _cli_patch(sys.argv)
67
68
 
68
69
  ###############################################################################
69
70
  # Imports and Python 2/3 unification ##########################################
70
71
  ###############################################################################
71
72
 
72
- import base64, calendar, email.utils, functools, hmac, itertools,\
73
- mimetypes, os, re, tempfile, threading, time, warnings, weakref, hashlib
74
-
75
- from types import FunctionType
76
- from datetime import date as datedate, datetime, timedelta
73
+ import base64
74
+ import calendar
75
+ import email.utils
76
+ import functools
77
+ import hashlib
78
+ import hmac
79
+ import itertools
80
+ import mimetypes
81
+ import os
82
+ import re
83
+ import tempfile
84
+ import threading
85
+ import time
86
+ import warnings
87
+ import weakref
88
+ from datetime import date as datedate
89
+ from datetime import datetime, timedelta
77
90
  from tempfile import NamedTemporaryFile
78
91
  from traceback import format_exc, print_exc
92
+ from types import FunctionType
79
93
  from unicodedata import normalize
80
94
 
81
95
  try:
82
- from ujson import dumps as json_dumps, loads as json_lds
96
+ from ujson import dumps as json_dumps
97
+ from ujson import loads as json_lds
83
98
  except ImportError:
84
- from json import dumps as json_dumps, loads as json_lds
99
+ from json import dumps as json_dumps
100
+ from json import loads as json_lds
85
101
 
86
102
  py = sys.version_info
87
103
  py3k = py.major > 2
88
104
 
89
105
  # Lots of stdlib and builtin differences.
90
106
  if py3k:
91
- import http.client as httplib
92
107
  import _thread as thread
93
- from urllib.parse import urljoin, SplitResult as UrlSplitResult
94
- from urllib.parse import urlencode, quote as urlquote, unquote as urlunquote
108
+ import http.client as httplib
109
+ from urllib.parse import SplitResult as UrlSplitResult
110
+ from urllib.parse import quote as urlquote
111
+ from urllib.parse import unquote as urlunquote
112
+ from urllib.parse import urlencode, urljoin
95
113
  urlunquote = functools.partial(urlunquote, encoding='latin1')
96
- from http.cookies import SimpleCookie, Morsel, CookieError
97
- from collections.abc import MutableMapping as DictMixin
98
- from types import ModuleType as new_module
99
- import pickle
100
- from io import BytesIO
101
114
  import configparser
115
+ import pickle
116
+ from collections.abc import MutableMapping as DictMixin
102
117
  from datetime import timezone
118
+ from http.cookies import CookieError, Morsel, SimpleCookie
119
+ from io import BytesIO
120
+ from types import ModuleType as new_module
103
121
  UTC = timezone.utc
104
122
  # getfullargspec was deprecated in 3.5 and un-deprecated in 3.6
105
123
  # getargspec was deprecated in 3.0 and removed in 3.11
106
124
  from inspect import getfullargspec
125
+
107
126
  def getargspec(func):
108
127
  spec = getfullargspec(func)
109
128
  kwargs = makelist(spec[0]) + makelist(spec.kwonlyargs)
@@ -111,27 +130,31 @@ if py3k:
111
130
 
112
131
  basestring = str
113
132
  unicode = str
114
- json_loads = lambda s: json_lds(touni(s))
115
- callable = lambda x: hasattr(x, '__call__')
133
+ def json_loads(s): return json_lds(touni(s))
134
+ def callable(x): return hasattr(x, '__call__')
116
135
  imap = map
117
136
 
118
137
  def _raise(*a):
119
138
  raise a[0](a[1]).with_traceback(a[2])
120
139
  else: # 2.x
121
140
  warnings.warn("Python 2 support will be dropped in Bottle 0.14", DeprecationWarning)
122
- import httplib
123
- import thread
124
- from urlparse import urljoin, SplitResult as UrlSplitResult
125
- from urllib import urlencode, quote as urlquote, unquote as urlunquote
126
- from Cookie import SimpleCookie, Morsel, CookieError
141
+ from collections import MutableMapping as DictMixin
142
+ from datetime import tzinfo
143
+ from imp import new_module
144
+ from inspect import getargspec
127
145
  from itertools import imap
146
+ from urllib import quote as urlquote
147
+ from urllib import unquote as urlunquote
148
+ from urllib import urlencode
149
+
150
+ import ConfigParser as configparser
128
151
  import cPickle as pickle
129
- from imp import new_module
152
+ import httplib
153
+ import thread
154
+ from Cookie import CookieError, Morsel, SimpleCookie
130
155
  from StringIO import StringIO as BytesIO
131
- import ConfigParser as configparser
132
- from collections import MutableMapping as DictMixin
133
- from inspect import getargspec
134
- from datetime import tzinfo
156
+ from urlparse import SplitResult as UrlSplitResult
157
+ from urlparse import urljoin
135
158
 
136
159
  class _UTC(tzinfo):
137
160
  def utcoffset(self, dt): return timedelta(0)
@@ -164,7 +187,7 @@ def _stderr(*args):
164
187
  try:
165
188
  print(*args, file=sys.stderr)
166
189
  except (IOError, AttributeError):
167
- pass # Some environments do not allow printing (mod_wsgi)
190
+ pass # Some environments do not allow printing (mod_wsgi)
168
191
 
169
192
 
170
193
  # A bug in functools causes it to break if the wrapper is an instance method
@@ -209,17 +232,21 @@ class DictProperty(object):
209
232
  return self
210
233
 
211
234
  def __get__(self, obj, cls):
212
- if obj is None: return self
235
+ if obj is None:
236
+ return self
213
237
  key, storage = self.key, getattr(obj, self.attr)
214
- if key not in storage: storage[key] = self.getter(obj)
238
+ if key not in storage:
239
+ storage[key] = self.getter(obj)
215
240
  return storage[key]
216
241
 
217
242
  def __set__(self, obj, value):
218
- if self.read_only: raise AttributeError("Read-Only property.")
243
+ if self.read_only:
244
+ raise AttributeError("Read-Only property.")
219
245
  getattr(obj, self.attr)[self.key] = value
220
246
 
221
247
  def __delete__(self, obj):
222
- if self.read_only: raise AttributeError("Read-Only property.")
248
+ if self.read_only:
249
+ raise AttributeError("Read-Only property.")
223
250
  del getattr(obj, self.attr)[self.key]
224
251
 
225
252
 
@@ -233,7 +260,8 @@ class cached_property(object):
233
260
  self.func = func
234
261
 
235
262
  def __get__(self, obj, cls):
236
- if obj is None: return self
263
+ if obj is None:
264
+ return self
237
265
  value = obj.__dict__[self.func.__name__] = self.func(obj)
238
266
  return value
239
267
 
@@ -339,9 +367,9 @@ class Router(object):
339
367
  self.filters[name] = func
340
368
 
341
369
  rule_syntax = re.compile('(\\\\*)'
342
- '(?:(?::([a-zA-Z_][a-zA-Z_0-9]*)?()(?:#(.*?)#)?)'
343
- '|(?:<([a-zA-Z_][a-zA-Z_0-9]*)?(?::([a-zA-Z_]*)'
344
- '(?::((?:\\\\.|[^\\\\>])+)?)?)?>))')
370
+ '(?:(?::([a-zA-Z_][a-zA-Z_0-9]*)?()(?:#(.*?)#)?)'
371
+ '|(?:<([a-zA-Z_][a-zA-Z_0-9]*)?(?::([a-zA-Z_]*)'
372
+ '(?::((?:\\\\.|[^\\\\>])+)?)?)?>))')
345
373
 
346
374
  def _itertokens(self, rule):
347
375
  offset, prefix = 0, ''
@@ -376,7 +404,8 @@ class Router(object):
376
404
  for key, mode, conf in self._itertokens(rule):
377
405
  if mode:
378
406
  is_static = False
379
- if mode == 'default': mode = self.default_filter
407
+ if mode == 'default':
408
+ mode = self.default_filter
380
409
  mask, in_filter, out_filter = self.filters[mode](conf)
381
410
  if not key:
382
411
  pattern += '(?:%s)' % mask
@@ -385,14 +414,16 @@ class Router(object):
385
414
  else:
386
415
  pattern += '(?P<%s>%s)' % (key, mask)
387
416
  keys.append(key)
388
- if in_filter: filters.append((key, in_filter))
417
+ if in_filter:
418
+ filters.append((key, in_filter))
389
419
  builder.append((key, out_filter or str))
390
420
  elif key:
391
421
  pattern += re.escape(key)
392
422
  builder.append((None, key))
393
423
 
394
424
  self.builder[rule] = builder
395
- if name: self.builder[name] = builder
425
+ if name:
426
+ self.builder[name] = builder
396
427
 
397
428
  if is_static and not self.strict_order:
398
429
  self.static.setdefault(method, {})
@@ -548,11 +579,15 @@ class Route(object):
548
579
  """ Yield all Plugins affecting this route. """
549
580
  unique = set()
550
581
  for p in reversed(self.app.plugins + self.plugins):
551
- if True in self.skiplist: break
582
+ if True in self.skiplist:
583
+ break
552
584
  name = getattr(p, 'name', False)
553
- if name and (name in self.skiplist or name in unique): continue
554
- if p in self.skiplist or type(p) in self.skiplist: continue
555
- if name: unique.add(name)
585
+ if name and (name in self.skiplist or name in unique):
586
+ continue
587
+ if p in self.skiplist or type(p) in self.skiplist:
588
+ continue
589
+ if name:
590
+ unique.add(name)
556
591
  yield p
557
592
 
558
593
  def _make_callback(self):
@@ -815,7 +850,8 @@ class Bottle(object):
815
850
  applied to all routes of this application. A plugin may be a simple
816
851
  decorator or an object that implements the :class:`Plugin` API.
817
852
  """
818
- if hasattr(plugin, 'setup'): plugin.setup(self)
853
+ if hasattr(plugin, 'setup'):
854
+ plugin.setup(self)
819
855
  if not callable(plugin) and not hasattr(plugin, 'apply'):
820
856
  raise TypeError("Plugins must be callable or implement .apply()")
821
857
  self.plugins.append(plugin)
@@ -830,20 +866,25 @@ class Bottle(object):
830
866
  removed, remove = [], plugin
831
867
  for i, plugin in list(enumerate(self.plugins))[::-1]:
832
868
  if remove is True or remove is plugin or remove is type(plugin) \
833
- or getattr(plugin, 'name', True) == remove:
869
+ or getattr(plugin, 'name', True) == remove:
834
870
  removed.append(plugin)
835
871
  del self.plugins[i]
836
- if hasattr(plugin, 'close'): plugin.close()
837
- if removed: self.reset()
872
+ if hasattr(plugin, 'close'):
873
+ plugin.close()
874
+ if removed:
875
+ self.reset()
838
876
  return removed
839
877
 
840
878
  def reset(self, route=None):
841
879
  """ Reset all routes (force plugins to be re-applied) and clear all
842
880
  caches. If an ID or route object is given, only that specific route
843
881
  is affected. """
844
- if route is None: routes = self.routes
845
- elif isinstance(route, Route): routes = [route]
846
- else: routes = [self.routes[route]]
882
+ if route is None:
883
+ routes = self.routes
884
+ elif isinstance(route, Route):
885
+ routes = [route]
886
+ else:
887
+ routes = [self.routes[route]]
847
888
  for route in routes:
848
889
  route.reset()
849
890
  if DEBUG:
@@ -854,7 +895,8 @@ class Bottle(object):
854
895
  def close(self):
855
896
  """ Close the application and all installed plugins. """
856
897
  for plugin in self.plugins:
857
- if hasattr(plugin, 'close'): plugin.close()
898
+ if hasattr(plugin, 'close'):
899
+ plugin.close()
858
900
 
859
901
  def run(self, **kwargs):
860
902
  """ Calls :func:`run` with the same parameters. """
@@ -877,7 +919,8 @@ class Bottle(object):
877
919
  attribute."""
878
920
  self.routes.append(route)
879
921
  self.router.add(route.rule, route.method, route, name=route.name)
880
- if DEBUG: route.prepare()
922
+ if DEBUG:
923
+ route.prepare()
881
924
 
882
925
  def route(self,
883
926
  path=None,
@@ -911,12 +954,14 @@ class Bottle(object):
911
954
  Any additional keyword arguments are stored as route-specific
912
955
  configuration and passed to plugins (see :meth:`Plugin.apply`).
913
956
  """
914
- if callable(path): path, callback = None, path
957
+ if callable(path):
958
+ path, callback = None, path
915
959
  plugins = makelist(apply)
916
960
  skiplist = makelist(skip)
917
961
 
918
962
  def decorator(callback):
919
- if isinstance(callback, basestring): callback = load(callback)
963
+ if isinstance(callback, basestring):
964
+ callback = load(callback)
920
965
  for rule in makelist(path) or yieldroutes(callback):
921
966
  for verb in makelist(method):
922
967
  verb = verb.upper()
@@ -965,7 +1010,8 @@ class Bottle(object):
965
1010
  """
966
1011
 
967
1012
  def decorator(callback):
968
- if isinstance(callback, basestring): callback = load(callback)
1013
+ if isinstance(callback, basestring):
1014
+ callback = load(callback)
969
1015
  self.error_handler[int(code)] = callback
970
1016
  return callback
971
1017
 
@@ -984,7 +1030,7 @@ class Bottle(object):
984
1030
  response.bind()
985
1031
 
986
1032
  try:
987
- while True: # Remove in 0.14 together with RouteReset
1033
+ while True: # Remove in 0.14 together with RouteReset
988
1034
  out = None
989
1035
  try:
990
1036
  self.trigger_hook('before_request')
@@ -1014,7 +1060,8 @@ class Bottle(object):
1014
1060
  except (KeyboardInterrupt, SystemExit, MemoryError):
1015
1061
  raise
1016
1062
  except Exception as E:
1017
- if not self.catchall: raise
1063
+ if not self.catchall:
1064
+ raise
1018
1065
  stacktrace = format_exc()
1019
1066
  environ['wsgi.errors'].write(stacktrace)
1020
1067
  environ['wsgi.errors'].flush()
@@ -1038,7 +1085,7 @@ class Bottle(object):
1038
1085
  return []
1039
1086
  # Join lists of byte or unicode strings. Mixed lists are NOT supported
1040
1087
  if isinstance(out, (tuple, list))\
1041
- and isinstance(out[0], (bytes, unicode)):
1088
+ and isinstance(out[0], (bytes, unicode)):
1042
1089
  out = out[0][0:0].join(out) # b'abc'[0:0] -> b''
1043
1090
  # Encode unicode strings
1044
1091
  if isinstance(out, unicode):
@@ -1079,7 +1126,8 @@ class Bottle(object):
1079
1126
  except (KeyboardInterrupt, SystemExit, MemoryError):
1080
1127
  raise
1081
1128
  except Exception as error:
1082
- if not self.catchall: raise
1129
+ if not self.catchall:
1130
+ raise
1083
1131
  first = HTTPError(500, 'Unhandled exception', error, format_exc())
1084
1132
 
1085
1133
  # These are the inner types allowed in iterator or generator objects.
@@ -1088,7 +1136,7 @@ class Bottle(object):
1088
1136
  elif isinstance(first, bytes):
1089
1137
  new_iter = itertools.chain([first], iout)
1090
1138
  elif isinstance(first, unicode):
1091
- encoder = lambda x: x.encode(response.charset)
1139
+ def encoder(x): return x.encode(response.charset)
1092
1140
  new_iter = imap(encoder, itertools.chain([first], iout))
1093
1141
  else:
1094
1142
  msg = 'Unsupported response type: %s' % type(first)
@@ -1103,8 +1151,9 @@ class Bottle(object):
1103
1151
  out = self._cast(self._handle(environ))
1104
1152
  # rfc2616 section 4.3
1105
1153
  if response._status_code in (100, 101, 204, 304)\
1106
- or environ['REQUEST_METHOD'] == 'HEAD':
1107
- if hasattr(out, 'close'): out.close()
1154
+ or environ['REQUEST_METHOD'] == 'HEAD':
1155
+ if hasattr(out, 'close'):
1156
+ out.close()
1108
1157
  out = []
1109
1158
  exc_info = environ.get('bottle.exc_info')
1110
1159
  if exc_info is not None:
@@ -1114,7 +1163,8 @@ class Bottle(object):
1114
1163
  except (KeyboardInterrupt, SystemExit, MemoryError):
1115
1164
  raise
1116
1165
  except Exception as E:
1117
- if not self.catchall: raise
1166
+ if not self.catchall:
1167
+ raise
1118
1168
  err = '<h1>Critical error while processing request: %s</h1>' \
1119
1169
  % html_escape(environ.get('PATH_INFO', '/'))
1120
1170
  if DEBUG:
@@ -1303,7 +1353,8 @@ class BaseRequest(object):
1303
1353
  maxread = max(0, self.content_length)
1304
1354
  while maxread:
1305
1355
  part = read(min(maxread, bufsize))
1306
- if not part: break
1356
+ if not part:
1357
+ break
1307
1358
  yield part
1308
1359
  maxread -= len(part)
1309
1360
 
@@ -1316,20 +1367,24 @@ class BaseRequest(object):
1316
1367
  while header[-2:] != rn:
1317
1368
  c = read(1)
1318
1369
  header += c
1319
- if not c: raise err
1320
- if len(header) > bufsize: raise err
1370
+ if not c:
1371
+ raise err
1372
+ if len(header) > bufsize:
1373
+ raise err
1321
1374
  size, _, _ = header.partition(sem)
1322
1375
  try:
1323
1376
  maxread = int(tonat(size.strip()), 16)
1324
1377
  except ValueError:
1325
1378
  raise err
1326
- if maxread == 0: break
1379
+ if maxread == 0:
1380
+ break
1327
1381
  buff = bs
1328
1382
  while maxread > 0:
1329
1383
  if not buff:
1330
1384
  buff = read(min(maxread, bufsize))
1331
1385
  part, buff = buff[:maxread], buff[maxread:]
1332
- if not part: raise err
1386
+ if not part:
1387
+ raise err
1333
1388
  yield part
1334
1389
  maxread -= len(part)
1335
1390
  if read(2) != rn:
@@ -1408,15 +1463,15 @@ class BaseRequest(object):
1408
1463
  if not boundary:
1409
1464
  raise MultipartError("Invalid content type header, missing boundary")
1410
1465
  parser = _MultipartParser(self.body, boundary, self.content_length,
1411
- mem_limit=self.MEMFILE_MAX, memfile_limit=self.MEMFILE_MAX,
1412
- charset=charset)
1466
+ mem_limit=self.MEMFILE_MAX, memfile_limit=self.MEMFILE_MAX,
1467
+ charset=charset)
1413
1468
 
1414
1469
  for part in parser.parse():
1415
1470
  if not part.filename and part.is_buffered():
1416
1471
  post[part.name] = tonat(part.value, 'utf8')
1417
1472
  else:
1418
1473
  post[part.name] = FileUpload(part.file, part.name,
1419
- part.filename, part.headerlist)
1474
+ part.filename, part.headerlist)
1420
1475
 
1421
1476
  return post
1422
1477
 
@@ -1436,7 +1491,7 @@ class BaseRequest(object):
1436
1491
  server. """
1437
1492
  env = self.environ
1438
1493
  http = env.get('HTTP_X_FORWARDED_PROTO') \
1439
- or env.get('wsgi.url_scheme', 'http')
1494
+ or env.get('wsgi.url_scheme', 'http')
1440
1495
  host = env.get('HTTP_X_FORWARDED_HOST') or env.get('HTTP_HOST')
1441
1496
  if not host:
1442
1497
  # HTTP 1.1 requires a Host-header. This is for HTTP/1.0 clients.
@@ -1511,9 +1566,11 @@ class BaseRequest(object):
1511
1566
  the user field is looked up from the ``REMOTE_USER`` environ
1512
1567
  variable. On any errors, None is returned. """
1513
1568
  basic = parse_auth(self.environ.get('HTTP_AUTHORIZATION', ''))
1514
- if basic: return basic
1569
+ if basic:
1570
+ return basic
1515
1571
  ruser = self.environ.get('REMOTE_USER')
1516
- if ruser: return (ruser, None)
1572
+ if ruser:
1573
+ return (ruser, None)
1517
1574
  return None
1518
1575
 
1519
1576
  @property
@@ -1523,7 +1580,8 @@ class BaseRequest(object):
1523
1580
  work if all proxies support the ```X-Forwarded-For`` header. Note
1524
1581
  that this information can be forged by malicious clients. """
1525
1582
  proxy = self.environ.get('HTTP_X_FORWARDED_FOR')
1526
- if proxy: return [ip.strip() for ip in proxy.split(',')]
1583
+ if proxy:
1584
+ return [ip.strip() for ip in proxy.split(',')]
1527
1585
  remote = self.environ.get('REMOTE_ADDR')
1528
1586
  return [remote] if remote else []
1529
1587
 
@@ -1589,7 +1647,8 @@ class BaseRequest(object):
1589
1647
 
1590
1648
  def __setattr__(self, name, value):
1591
1649
  """ Define new attributes that are local to the bound request environment. """
1592
- if name == 'environ': return object.__setattr__(self, name, value)
1650
+ if name == 'environ':
1651
+ return object.__setattr__(self, name, value)
1593
1652
  key = 'bottle.request.ext.%s' % name
1594
1653
  if hasattr(self, name):
1595
1654
  raise AttributeError("Attribute already defined: %s" % name)
@@ -1622,7 +1681,8 @@ class HeaderProperty(object):
1622
1681
  self.__doc__ = 'Current value of the %r header.' % name.title()
1623
1682
 
1624
1683
  def __get__(self, obj, _):
1625
- if obj is None: return self
1684
+ if obj is None:
1685
+ return self
1626
1686
  value = obj.get_header(self.name, self.default)
1627
1687
  return self.reader(value) if self.reader else value
1628
1688
 
@@ -1649,8 +1709,8 @@ class BaseResponse(object):
1649
1709
  bad_headers = {
1650
1710
  204: frozenset(('Content-Type', 'Content-Length')),
1651
1711
  304: frozenset(('Allow', 'Content-Encoding', 'Content-Language',
1652
- 'Content-Length', 'Content-Range', 'Content-Type',
1653
- 'Content-Md5', 'Last-Modified'))
1712
+ 'Content-Length', 'Content-Range', 'Content-Type',
1713
+ 'Content-Md5', 'Last-Modified'))
1654
1714
  }
1655
1715
 
1656
1716
  def __init__(self, body='', status=None, headers=None, **more_headers):
@@ -1686,9 +1746,9 @@ class BaseResponse(object):
1686
1746
  copy._headers = dict((k, v[:]) for (k, v) in self._headers.items())
1687
1747
  if self._cookies:
1688
1748
  cookies = copy._cookies = SimpleCookie()
1689
- for k,v in self._cookies.items():
1749
+ for k, v in self._cookies.items():
1690
1750
  cookies[k] = v.value
1691
- cookies[k].update(v) # also copy cookie attributes
1751
+ cookies[k].update(v) # also copy cookie attributes
1692
1752
  return copy
1693
1753
 
1694
1754
  def __iter__(self):
@@ -1879,13 +1939,13 @@ class BaseResponse(object):
1879
1939
  self._cookies[name] = value
1880
1940
 
1881
1941
  for key, value in options.items():
1882
- if key in ('max_age', 'maxage'): # 'maxage' variant added in 0.13
1942
+ if key in ('max_age', 'maxage'): # 'maxage' variant added in 0.13
1883
1943
  key = 'max-age'
1884
1944
  if isinstance(value, timedelta):
1885
1945
  value = value.seconds + value.days * 24 * 3600
1886
1946
  if key == 'expires':
1887
1947
  value = http_date(value)
1888
- if key in ('same_site', 'samesite'): # 'samesite' variant added in 0.13
1948
+ if key in ('same_site', 'samesite'): # 'samesite' variant added in 0.13
1889
1949
  key, value = 'samesite', (value or "none").lower()
1890
1950
  if value not in ('lax', 'strict', 'none'):
1891
1951
  raise CookieError("Invalid value for SameSite")
@@ -2005,19 +2065,20 @@ class JSONPlugin(object):
2005
2065
 
2006
2066
  def setup(self, app):
2007
2067
  app.config._define('json.enable', default=True, validate=bool,
2008
- help="Enable or disable automatic dict->json filter.")
2068
+ help="Enable or disable automatic dict->json filter.")
2009
2069
  app.config._define('json.ascii', default=False, validate=bool,
2010
- help="Use only 7-bit ASCII characters in output.")
2070
+ help="Use only 7-bit ASCII characters in output.")
2011
2071
  app.config._define('json.indent', default=True, validate=bool,
2012
- help="Add whitespace to make json more readable.")
2072
+ help="Add whitespace to make json more readable.")
2013
2073
  app.config._define('json.dump_func', default=None,
2014
- help="If defined, use this function to transform"
2015
- " dict into json. The other options no longer"
2016
- " apply.")
2074
+ help="If defined, use this function to transform"
2075
+ " dict into json. The other options no longer"
2076
+ " apply.")
2017
2077
 
2018
2078
  def apply(self, callback, route):
2019
2079
  dumps = self.json_dumps
2020
- if not self.json_dumps: return callback
2080
+ if not self.json_dumps:
2081
+ return callback
2021
2082
 
2022
2083
  @functools.wraps(callback)
2023
2084
  def wrapper(*a, **ka):
@@ -2077,24 +2138,29 @@ class _ImportRedirect(object):
2077
2138
  sys.meta_path.append(self)
2078
2139
 
2079
2140
  def find_spec(self, fullname, path, target=None):
2080
- if '.' not in fullname: return
2081
- if fullname.rsplit('.', 1)[0] != self.name: return
2141
+ if '.' not in fullname:
2142
+ return
2143
+ if fullname.rsplit('.', 1)[0] != self.name:
2144
+ return
2082
2145
  from importlib.util import spec_from_loader
2083
2146
  return spec_from_loader(fullname, self)
2084
2147
 
2085
2148
  def find_module(self, fullname, path=None):
2086
- if '.' not in fullname: return
2087
- if fullname.rsplit('.', 1)[0] != self.name: return
2149
+ if '.' not in fullname:
2150
+ return
2151
+ if fullname.rsplit('.', 1)[0] != self.name:
2152
+ return
2088
2153
  return self
2089
2154
 
2090
2155
  def create_module(self, spec):
2091
2156
  return self.load_module(spec.name)
2092
2157
 
2093
2158
  def exec_module(self, module):
2094
- pass # This probably breaks importlib.reload() :/
2159
+ pass # This probably breaks importlib.reload() :/
2095
2160
 
2096
2161
  def load_module(self, fullname):
2097
- if fullname in sys.modules: return sys.modules[fullname]
2162
+ if fullname in sys.modules:
2163
+ return sys.modules[fullname]
2098
2164
  modname = fullname.rsplit('.', 1)[1]
2099
2165
  realname = self.impmask % modname
2100
2166
  __import__(realname)
@@ -2263,7 +2329,8 @@ class HeaderDict(MultiDict):
2263
2329
 
2264
2330
  def __init__(self, *a, **ka):
2265
2331
  self.dict = {}
2266
- if a or ka: self.update(*a, **ka)
2332
+ if a or ka:
2333
+ self.update(*a, **ka)
2267
2334
 
2268
2335
  def __contains__(self, key):
2269
2336
  return _hkey(key) in self.dict
@@ -2354,6 +2421,7 @@ class WSGIHeaderDict(DictMixin):
2354
2421
  def __contains__(self, key):
2355
2422
  return self._ekey(key) in self.environ
2356
2423
 
2424
+
2357
2425
  _UNSET = object()
2358
2426
 
2359
2427
  class ConfigDict(dict):
@@ -2535,7 +2603,7 @@ class ConfigDict(dict):
2535
2603
 
2536
2604
  def meta_set(self, key, metafield, value):
2537
2605
  """ Set the meta field for a key to a new value.
2538
-
2606
+
2539
2607
  Meta-fields are shared between all members of an overlay tree.
2540
2608
  """
2541
2609
  self._meta.setdefault(key, {})[metafield] = value
@@ -2595,8 +2663,6 @@ class ConfigDict(dict):
2595
2663
  return overlay
2596
2664
 
2597
2665
 
2598
-
2599
-
2600
2666
  class AppStack(list):
2601
2667
  """ A stack-like list. Calling it returns the head of the stack. """
2602
2668
 
@@ -2624,7 +2690,8 @@ class WSGIFileWrapper(object):
2624
2690
  def __init__(self, fp, buffer_size=1024 * 64):
2625
2691
  self.fp, self.buffer_size = fp, buffer_size
2626
2692
  for attr in 'fileno', 'close', 'read', 'readlines', 'tell', 'seek':
2627
- if hasattr(fp, attr): setattr(self, attr, getattr(fp, attr))
2693
+ if hasattr(fp, attr):
2694
+ setattr(self, attr, getattr(fp, attr))
2628
2695
 
2629
2696
  def __iter__(self):
2630
2697
  buff, read = self.buffer_size, self.read
@@ -2706,11 +2773,14 @@ class ResourceManager(object):
2706
2773
  search = self.path[:]
2707
2774
  while search:
2708
2775
  path = search.pop()
2709
- if not os.path.isdir(path): continue
2776
+ if not os.path.isdir(path):
2777
+ continue
2710
2778
  for name in os.listdir(path):
2711
2779
  full = os.path.join(path, name)
2712
- if os.path.isdir(full): search.append(full)
2713
- else: yield full
2780
+ if os.path.isdir(full):
2781
+ search.append(full)
2782
+ else:
2783
+ yield full
2714
2784
 
2715
2785
  def lookup(self, name):
2716
2786
  """ Search for a resource and return an absolute file path, or `None`.
@@ -2732,7 +2802,8 @@ class ResourceManager(object):
2732
2802
  def open(self, name, mode='r', *args, **kwargs):
2733
2803
  """ Find a resource and return a file object, or raise IOError. """
2734
2804
  fname = self.lookup(name)
2735
- if not fname: raise IOError("Resource %r not found." % name)
2805
+ if not fname:
2806
+ raise IOError("Resource %r not found." % name)
2736
2807
  return self.opener(fname, mode=mode, *args, **kwargs)
2737
2808
 
2738
2809
 
@@ -2779,7 +2850,8 @@ class FileUpload(object):
2779
2850
  read, write, offset = self.file.read, fp.write, self.file.tell()
2780
2851
  while 1:
2781
2852
  buf = read(chunk_size)
2782
- if not buf: break
2853
+ if not buf:
2854
+ break
2783
2855
  write(buf)
2784
2856
  self.file.seek(offset)
2785
2857
 
@@ -2889,11 +2961,11 @@ def static_file(filename, root,
2889
2961
  mimetype, encoding = mimetypes.guess_type(name)
2890
2962
  if encoding == 'gzip':
2891
2963
  mimetype = 'application/gzip'
2892
- elif encoding: # e.g. bzip2 -> application/x-bzip2
2964
+ elif encoding: # e.g. bzip2 -> application/x-bzip2
2893
2965
  mimetype = 'application/x-' + encoding
2894
2966
 
2895
2967
  if charset and mimetype and 'charset=' not in mimetype \
2896
- and (mimetype[:5] == 'text/' or mimetype == 'application/javascript'):
2968
+ and (mimetype[:5] == 'text/' or mimetype == 'application/javascript'):
2897
2969
  mimetype += '; charset=%s' % charset
2898
2970
 
2899
2971
  if mimetype:
@@ -2903,7 +2975,7 @@ def static_file(filename, root,
2903
2975
  download = os.path.basename(filename)
2904
2976
 
2905
2977
  if download:
2906
- download = download.replace('"','')
2978
+ download = download.replace('"', '')
2907
2979
  headers['Content-Disposition'] = 'attachment; filename="%s"' % download
2908
2980
 
2909
2981
  stats = os.stat(filename)
@@ -2940,7 +3012,8 @@ def static_file(filename, root,
2940
3012
  rlen = end - offset
2941
3013
  headers["Content-Range"] = "bytes %d-%d/%d" % (offset, end - 1, clen)
2942
3014
  headers["Content-Length"] = str(rlen)
2943
- if body: body = _closeiter(_rangeiter(body, offset, rlen), body.close)
3015
+ if body:
3016
+ body = _closeiter(_rangeiter(body, offset, rlen), body.close)
2944
3017
  return HTTPResponse(body, status=206, **headers)
2945
3018
  return HTTPResponse(body, **headers)
2946
3019
 
@@ -2953,7 +3026,8 @@ def debug(mode=True):
2953
3026
  """ Change the debug level.
2954
3027
  There is only one debug level supported at the moment."""
2955
3028
  global DEBUG
2956
- if mode: warnings.simplefilter('default')
3029
+ if mode:
3030
+ warnings.simplefilter('default')
2957
3031
  DEBUG = bool(mode)
2958
3032
 
2959
3033
 
@@ -2996,7 +3070,8 @@ def parse_auth(header):
2996
3070
  def parse_range_header(header, maxlen=0):
2997
3071
  """ Yield (start, end) ranges parsed from a HTTP Range header. Skip
2998
3072
  unsatisfiable ranges. The end index is non-inclusive."""
2999
- if not header or header[:6] != 'bytes=': return
3073
+ if not header or header[:6] != 'bytes=':
3074
+ return
3000
3075
  ranges = [r.split('-', 1) for r in header[6:].split(',') if '-' in r]
3001
3076
  for start, end in ranges:
3002
3077
  try:
@@ -3051,9 +3126,11 @@ def _parse_http_header(h):
3051
3126
  def _parse_qsl(qs):
3052
3127
  r = []
3053
3128
  for pair in qs.split('&'):
3054
- if not pair: continue
3129
+ if not pair:
3130
+ continue
3055
3131
  nv = pair.split('=', 1)
3056
- if len(nv) != 2: nv.append('')
3132
+ if len(nv) != 2:
3133
+ nv.append('')
3057
3134
  key = urlunquote(nv[0].replace('+', ' '))
3058
3135
  value = urlunquote(nv[1].replace('+', ' '))
3059
3136
  r.append((key, value))
@@ -3107,7 +3184,7 @@ def html_escape(string):
3107
3184
  def html_quote(string):
3108
3185
  """ Escape and quote a string to be used as an HTTP attribute."""
3109
3186
  return '"%s"' % html_escape(string).replace('\n', '&#10;')\
3110
- .replace('\r', '&#13;').replace('\t', '&#9;')
3187
+ .replace('\r', '&#13;').replace('\t', '&#9;')
3111
3188
 
3112
3189
 
3113
3190
  def yieldroutes(func):
@@ -3139,11 +3216,14 @@ def path_shift(script_name, path_info, shift=1):
3139
3216
  :param shift: The number of path fragments to shift. May be negative to
3140
3217
  change the shift direction. (default: 1)
3141
3218
  """
3142
- if shift == 0: return script_name, path_info
3219
+ if shift == 0:
3220
+ return script_name, path_info
3143
3221
  pathlist = path_info.strip('/').split('/')
3144
3222
  scriptlist = script_name.strip('/').split('/')
3145
- if pathlist and pathlist[0] == '': pathlist = []
3146
- if scriptlist and scriptlist[0] == '': scriptlist = []
3223
+ if pathlist and pathlist[0] == '':
3224
+ pathlist = []
3225
+ if scriptlist and scriptlist[0] == '':
3226
+ scriptlist = []
3147
3227
  if 0 < shift <= len(pathlist):
3148
3228
  moved = pathlist[:shift]
3149
3229
  scriptlist = scriptlist + moved
@@ -3157,7 +3237,8 @@ def path_shift(script_name, path_info, shift=1):
3157
3237
  raise AssertionError("Cannot shift. Nothing left from %s" % empty)
3158
3238
  new_script_name = '/' + '/'.join(scriptlist)
3159
3239
  new_path_info = '/' + '/'.join(pathlist)
3160
- if path_info.endswith('/') and pathlist: new_path_info += '/'
3240
+ if path_info.endswith('/') and pathlist:
3241
+ new_path_info += '/'
3161
3242
  return new_script_name, new_path_info
3162
3243
 
3163
3244
 
@@ -3194,18 +3275,18 @@ def make_default_app_wrapper(name):
3194
3275
  return wrapper
3195
3276
 
3196
3277
 
3197
- route = make_default_app_wrapper('route')
3198
- get = make_default_app_wrapper('get')
3199
- post = make_default_app_wrapper('post')
3200
- put = make_default_app_wrapper('put')
3201
- delete = make_default_app_wrapper('delete')
3202
- patch = make_default_app_wrapper('patch')
3203
- error = make_default_app_wrapper('error')
3204
- mount = make_default_app_wrapper('mount')
3205
- hook = make_default_app_wrapper('hook')
3206
- install = make_default_app_wrapper('install')
3278
+ route = make_default_app_wrapper('route')
3279
+ get = make_default_app_wrapper('get')
3280
+ post = make_default_app_wrapper('post')
3281
+ put = make_default_app_wrapper('put')
3282
+ delete = make_default_app_wrapper('delete')
3283
+ patch = make_default_app_wrapper('patch')
3284
+ error = make_default_app_wrapper('error')
3285
+ mount = make_default_app_wrapper('mount')
3286
+ hook = make_default_app_wrapper('hook')
3287
+ install = make_default_app_wrapper('install')
3207
3288
  uninstall = make_default_app_wrapper('uninstall')
3208
- url = make_default_app_wrapper('get_url')
3289
+ url = make_default_app_wrapper('get_url')
3209
3290
 
3210
3291
 
3211
3292
  ###############################################################################
@@ -3276,7 +3357,7 @@ class _MultipartParser(object):
3276
3357
  if i >= 0:
3277
3358
  yield chunk[scanpos:i], b'\r\n'
3278
3359
  scanpos = i + 2
3279
- else: # CRLF not found
3360
+ else: # CRLF not found
3280
3361
  partial = chunk[scanpos:] if scanpos else chunk
3281
3362
  break
3282
3363
 
@@ -3418,7 +3499,7 @@ class _MultipartPart(object):
3418
3499
  if "filename" in self.options:
3419
3500
  self.filename = self.options.get("filename")
3420
3501
  if self.filename[1:3] == ":\\" or self.filename[:2] == "\\\\":
3421
- self.filename = self.filename.split("\\")[-1] # ie6 bug
3502
+ self.filename = self.filename.split("\\")[-1] # ie6 bug
3422
3503
 
3423
3504
  self.content_type, options = _parse_http_header(content_type)[0] if content_type else (None, {})
3424
3505
  self.charset = options.get("charset") or self.charset
@@ -3477,7 +3558,7 @@ class ServerAdapter(object):
3477
3558
 
3478
3559
  def __repr__(self):
3479
3560
  args = ', '.join('%s=%s' % (k, repr(v))
3480
- for k, v in self.options.items())
3561
+ for k, v in self.options.items())
3481
3562
  return "%s(%s)" % (self.__class__.__name__, args)
3482
3563
 
3483
3564
 
@@ -3503,9 +3584,8 @@ class FlupFCGIServer(ServerAdapter):
3503
3584
 
3504
3585
  class WSGIRefServer(ServerAdapter):
3505
3586
  def run(self, app): # pragma: no cover
3506
- from wsgiref.simple_server import make_server
3507
- from wsgiref.simple_server import WSGIRequestHandler, WSGIServer
3508
3587
  import socket
3588
+ from wsgiref.simple_server import WSGIRequestHandler, WSGIServer, make_server
3509
3589
 
3510
3590
  class FixedHandler(WSGIRequestHandler):
3511
3591
  def address_string(self): # Prevent reverse DNS lookups please.
@@ -3539,7 +3619,7 @@ class CherryPyServer(ServerAdapter):
3539
3619
  depr(0, 13, "The wsgi server part of cherrypy was split into a new "
3540
3620
  "project called 'cheroot'.", "Use the 'cheroot' server "
3541
3621
  "adapter instead of cherrypy.")
3542
- from cherrypy import wsgiserver # This will fail for CherryPy >= 9
3622
+ from cherrypy import wsgiserver # This will fail for CherryPy >= 9
3543
3623
 
3544
3624
  self.options['bind_addr'] = (self.host, self.port)
3545
3625
  self.options['wsgi_app'] = handler
@@ -3564,7 +3644,7 @@ class CherryPyServer(ServerAdapter):
3564
3644
 
3565
3645
 
3566
3646
  class CherootServer(ServerAdapter):
3567
- def run(self, handler): # pragma: no cover
3647
+ def run(self, handler): # pragma: no cover
3568
3648
  from cheroot import wsgi
3569
3649
  from cheroot.ssl import builtin
3570
3650
  self.options['bind_addr'] = (self.host, self.port)
@@ -3575,7 +3655,7 @@ class CherootServer(ServerAdapter):
3575
3655
  server = wsgi.Server(**self.options)
3576
3656
  if certfile and keyfile:
3577
3657
  server.ssl_adapter = builtin.BuiltinSSLAdapter(
3578
- certfile, keyfile, chainfile)
3658
+ certfile, keyfile, chainfile)
3579
3659
  try:
3580
3660
  server.start()
3581
3661
  finally:
@@ -3635,7 +3715,9 @@ class TornadoServer(ServerAdapter):
3635
3715
  """ The super hyped asynchronous server by facebook. Untested. """
3636
3716
 
3637
3717
  def run(self, handler): # pragma: no cover
3638
- import tornado.wsgi, tornado.httpserver, tornado.ioloop
3718
+ import tornado.httpserver
3719
+ import tornado.ioloop
3720
+ import tornado.wsgi
3639
3721
  container = tornado.wsgi.WSGIContainer(handler)
3640
3722
  server = tornado.httpserver.HTTPServer(container)
3641
3723
  server.listen(port=self.port, address=self.host)
@@ -3650,6 +3732,7 @@ class AppEngineServer(ServerAdapter):
3650
3732
  depr(0, 13, "AppEngineServer no longer required",
3651
3733
  "Configure your application directly in your app.yaml")
3652
3734
  from google.appengine.ext.webapp import util
3735
+
3653
3736
  # A main() function in the handler script enables 'App Caching'.
3654
3737
  # Lets makes sure it is there. This _really_ improves performance.
3655
3738
  module = sys.modules.get('__main__')
@@ -3662,9 +3745,9 @@ class TwistedServer(ServerAdapter):
3662
3745
  """ Untested. """
3663
3746
 
3664
3747
  def run(self, handler):
3665
- from twisted.web import server, wsgi
3666
- from twisted.python.threadpool import ThreadPool
3667
3748
  from twisted.internet import reactor
3749
+ from twisted.python.threadpool import ThreadPool
3750
+ from twisted.web import server, wsgi
3668
3751
  thread_pool = ThreadPool()
3669
3752
  thread_pool.start()
3670
3753
  reactor.addSystemEventTrigger('after', 'shutdown', thread_pool.stop)
@@ -3691,7 +3774,7 @@ class GeventServer(ServerAdapter):
3691
3774
  """
3692
3775
 
3693
3776
  def run(self, handler):
3694
- from gevent import pywsgi, local
3777
+ from gevent import local, pywsgi
3695
3778
  if not isinstance(threading.local(), local.local):
3696
3779
  msg = "Bottle requires gevent.monkey.patch_all() (before import)"
3697
3780
  raise RuntimeError(msg)
@@ -3740,7 +3823,7 @@ class EventletServer(ServerAdapter):
3740
3823
  """
3741
3824
 
3742
3825
  def run(self, handler):
3743
- from eventlet import wsgi, listen, patcher
3826
+ from eventlet import listen, patcher, wsgi
3744
3827
  if not patcher.is_monkey_patched(os):
3745
3828
  msg = "Bottle requires eventlet.monkey_patch() (before import)"
3746
3829
  raise RuntimeError(msg)
@@ -3768,6 +3851,7 @@ class BjoernServer(ServerAdapter):
3768
3851
 
3769
3852
  class AsyncioServerAdapter(ServerAdapter):
3770
3853
  """ Extend ServerAdapter for adding custom event loop """
3854
+
3771
3855
  def get_event_loop(self):
3772
3856
  pass
3773
3857
 
@@ -3783,6 +3867,7 @@ class AiohttpServer(AsyncioServerAdapter):
3783
3867
 
3784
3868
  def run(self, handler):
3785
3869
  import asyncio
3870
+
3786
3871
  from aiohttp_wsgi.wsgi import serve
3787
3872
  self.loop = self.get_event_loop()
3788
3873
  asyncio.set_event_loop(self.loop)
@@ -3798,6 +3883,7 @@ class AiohttpUVLoopServer(AiohttpServer):
3798
3883
  """uvloop
3799
3884
  https://github.com/MagicStack/uvloop
3800
3885
  """
3886
+
3801
3887
  def get_event_loop(self):
3802
3888
  import uvloop
3803
3889
  return uvloop.new_event_loop()
@@ -3855,9 +3941,12 @@ def load(target, **namespace):
3855
3941
  local variables. Example: ``import_string('re:compile(x)', x='[a-z]')``
3856
3942
  """
3857
3943
  module, target = target.split(":", 1) if ':' in target else (target, None)
3858
- if module not in sys.modules: __import__(module)
3859
- if not target: return sys.modules[module]
3860
- if target.isalnum(): return getattr(sys.modules[module], target)
3944
+ if module not in sys.modules:
3945
+ __import__(module)
3946
+ if not target:
3947
+ return sys.modules[module]
3948
+ if target.isalnum():
3949
+ return getattr(sys.modules[module], target)
3861
3950
  package_name = module.split('.')[0]
3862
3951
  namespace[package_name] = sys.modules[package_name]
3863
3952
  return eval('%s.%s' % (module, target), namespace)
@@ -3907,7 +3996,8 @@ def run(app=None,
3907
3996
  :param quiet: Suppress output to stdout and stderr? (default: False)
3908
3997
  :param options: Options passed to the server adapter.
3909
3998
  """
3910
- if NORUN: return
3999
+ if NORUN:
4000
+ return
3911
4001
  if reloader and not os.environ.get('BOTTLE_CHILD'):
3912
4002
  import subprocess
3913
4003
  fd, lockfile = tempfile.mkstemp(prefix='bottle.', suffix='.lock')
@@ -3938,7 +4028,8 @@ def run(app=None,
3938
4028
  return
3939
4029
 
3940
4030
  try:
3941
- if debug is not None: _debug(debug)
4031
+ if debug is not None:
4032
+ _debug(debug)
3942
4033
  app = app or default_app()
3943
4034
  if isinstance(app, basestring):
3944
4035
  app = load_app(app)
@@ -3987,7 +4078,8 @@ def run(app=None,
3987
4078
  except (SystemExit, MemoryError):
3988
4079
  raise
3989
4080
  except:
3990
- if not reloader: raise
4081
+ if not reloader:
4082
+ raise
3991
4083
  if not getattr(server, 'quiet', quiet):
3992
4084
  print_exc()
3993
4085
  time.sleep(interval)
@@ -4007,17 +4099,19 @@ class FileCheckerThread(threading.Thread):
4007
4099
 
4008
4100
  def run(self):
4009
4101
  exists = os.path.exists
4010
- mtime = lambda p: os.stat(p).st_mtime
4102
+ def mtime(p): return os.stat(p).st_mtime
4011
4103
  files = dict()
4012
4104
 
4013
4105
  for module in list(sys.modules.values()):
4014
4106
  path = getattr(module, '__file__', '') or ''
4015
- if path[-4:] in ('.pyo', '.pyc'): path = path[:-1]
4016
- if path and exists(path): files[path] = mtime(path)
4107
+ if path[-4:] in ('.pyo', '.pyc'):
4108
+ path = path[:-1]
4109
+ if path and exists(path):
4110
+ files[path] = mtime(path)
4017
4111
 
4018
4112
  while not self.status:
4019
4113
  if not exists(self.lockfile)\
4020
- or mtime(self.lockfile) < time.time() - self.interval - 5:
4114
+ or mtime(self.lockfile) < time.time() - self.interval - 5:
4021
4115
  self.status = 'error'
4022
4116
  thread.interrupt_main()
4023
4117
  for path, lmtime in list(files.items()):
@@ -4031,7 +4125,8 @@ class FileCheckerThread(threading.Thread):
4031
4125
  self.start()
4032
4126
 
4033
4127
  def __exit__(self, exc_type, *_):
4034
- if not self.status: self.status = 'exit' # silent exit
4128
+ if not self.status:
4129
+ self.status = 'exit' # silent exit
4035
4130
  self.join()
4036
4131
  return exc_type is not None and issubclass(exc_type, KeyboardInterrupt)
4037
4132
 
@@ -4047,8 +4142,8 @@ class TemplateError(BottleException):
4047
4142
  class BaseTemplate(object):
4048
4143
  """ Base class and minimal API for template adapters """
4049
4144
  extensions = ['tpl', 'html', 'thtml', 'stpl']
4050
- settings = {} #used in prepare()
4051
- defaults = {} #used in render()
4145
+ settings = {} # used in prepare()
4146
+ defaults = {} # used in render()
4052
4147
 
4053
4148
  def __init__(self,
4054
4149
  source=None,
@@ -4094,8 +4189,10 @@ class BaseTemplate(object):
4094
4189
  for spath in lookup:
4095
4190
  spath = os.path.abspath(spath) + os.sep
4096
4191
  fname = os.path.abspath(os.path.join(spath, name))
4097
- if not fname.startswith(spath): continue
4098
- if os.path.isfile(fname): return fname
4192
+ if not fname.startswith(spath):
4193
+ continue
4194
+ if os.path.isfile(fname):
4195
+ return fname
4099
4196
  for ext in cls.extensions:
4100
4197
  if os.path.isfile('%s.%s' % (fname, ext)):
4101
4198
  return '%s.%s' % (fname, ext)
@@ -4128,8 +4225,8 @@ class BaseTemplate(object):
4128
4225
 
4129
4226
  class MakoTemplate(BaseTemplate):
4130
4227
  def prepare(self, **options):
4131
- from mako.template import Template
4132
4228
  from mako.lookup import TemplateLookup
4229
+ from mako.template import Template
4133
4230
  options.update({'input_encoding': self.encoding})
4134
4231
  options.setdefault('format_exceptions', bool(DEBUG))
4135
4232
  lookup = TemplateLookup(directories=self.lookup, **options)
@@ -4173,9 +4270,12 @@ class Jinja2Template(BaseTemplate):
4173
4270
  def prepare(self, filters=None, tests=None, globals={}, **kwargs):
4174
4271
  from jinja2 import Environment, FunctionLoader
4175
4272
  self.env = Environment(loader=FunctionLoader(self.loader), **kwargs)
4176
- if filters: self.env.filters.update(filters)
4177
- if tests: self.env.tests.update(tests)
4178
- if globals: self.env.globals.update(globals)
4273
+ if filters:
4274
+ self.env.filters.update(filters)
4275
+ if tests:
4276
+ self.env.tests.update(tests)
4277
+ if globals:
4278
+ self.env.globals.update(globals)
4179
4279
  if self.source:
4180
4280
  self.tpl = self.env.from_string(self.source)
4181
4281
  else:
@@ -4193,7 +4293,8 @@ class Jinja2Template(BaseTemplate):
4193
4293
  fname = name
4194
4294
  else:
4195
4295
  fname = self.search(name, self.lookup)
4196
- if not fname: return
4296
+ if not fname:
4297
+ return
4197
4298
  with open(fname, "rb") as f:
4198
4299
  return (f.read().decode(self.encoding), fname, lambda: False)
4199
4300
 
@@ -4258,7 +4359,7 @@ class SimpleTemplate(BaseTemplate):
4258
4359
  exec(self.co, env)
4259
4360
  if env.get('_rebase'):
4260
4361
  subtpl, rargs = env.pop('_rebase')
4261
- rargs['base'] = ''.join(_stdout) #copy stdout
4362
+ rargs['base'] = ''.join(_stdout) # copy stdout
4262
4363
  del _stdout[:] # clear stdout
4263
4364
  return self._include(env, subtpl, **rargs)
4264
4365
  return env
@@ -4332,7 +4433,6 @@ class StplParser(object):
4332
4433
  _re_tok = '(?mx)' + _re_tok
4333
4434
  _re_inl = '(?mx)' + _re_inl
4334
4435
 
4335
-
4336
4436
  default_syntax = '<% %> % {{ }}'
4337
4437
 
4338
4438
  def __init__(self, source, syntax=None, encoding='utf8'):
@@ -4362,7 +4462,8 @@ class StplParser(object):
4362
4462
  syntax = property(get_syntax, set_syntax)
4363
4463
 
4364
4464
  def translate(self):
4365
- if self.offset: raise RuntimeError('Parser is a one time instance.')
4465
+ if self.offset:
4466
+ raise RuntimeError('Parser is a one time instance.')
4366
4467
  while True:
4367
4468
  m = self.re_split.search(self.source, pos=self.offset)
4368
4469
  if m:
@@ -4423,8 +4524,10 @@ class StplParser(object):
4423
4524
  code_line = _blk2
4424
4525
  self.indent_mod -= 1
4425
4526
  elif _cend: # The end-code-block template token (usually '%>')
4426
- if multiline: multiline = False
4427
- else: code_line += _cend
4527
+ if multiline:
4528
+ multiline = False
4529
+ else:
4530
+ code_line += _cend
4428
4531
  elif _end:
4429
4532
  self.indent -= 1
4430
4533
  self.indent_mod += 1
@@ -4440,19 +4543,23 @@ class StplParser(object):
4440
4543
  def flush_text(self):
4441
4544
  text = ''.join(self.text_buffer)
4442
4545
  del self.text_buffer[:]
4443
- if not text: return
4546
+ if not text:
4547
+ return
4444
4548
  parts, pos, nl = [], 0, '\\\n' + ' ' * self.indent
4445
4549
  for m in self.re_inl.finditer(text):
4446
4550
  prefix, pos = text[pos:m.start()], m.end()
4447
4551
  if prefix:
4448
4552
  parts.append(nl.join(map(repr, prefix.splitlines(True))))
4449
- if prefix.endswith('\n'): parts[-1] += nl
4553
+ if prefix.endswith('\n'):
4554
+ parts[-1] += nl
4450
4555
  parts.append(self.process_inline(m.group(1).strip()))
4451
4556
  if pos < len(text):
4452
4557
  prefix = text[pos:]
4453
4558
  lines = prefix.splitlines(True)
4454
- if lines[-1].endswith('\\\\\n'): lines[-1] = lines[-1][:-3]
4455
- elif lines[-1].endswith('\\\\\r\n'): lines[-1] = lines[-1][:-4]
4559
+ if lines[-1].endswith('\\\\\n'):
4560
+ lines[-1] = lines[-1][:-3]
4561
+ elif lines[-1].endswith('\\\\\r\n'):
4562
+ lines[-1] = lines[-1][:-4]
4456
4563
  parts.append(nl.join(map(repr, lines)))
4457
4564
  code = '_printlist((%s,))' % ', '.join(parts)
4458
4565
  self.lineno += code.count('\n') + 1
@@ -4460,7 +4567,8 @@ class StplParser(object):
4460
4567
 
4461
4568
  @staticmethod
4462
4569
  def process_inline(chunk):
4463
- if chunk[0] == '!': return '_str(%s)' % chunk[1:]
4570
+ if chunk[0] == '!':
4571
+ return '_str(%s)' % chunk[1:]
4464
4572
  return '_escape(%s)' % chunk
4465
4573
 
4466
4574
  def write_code(self, line, comment=''):
@@ -4486,7 +4594,8 @@ def template(*args, **kwargs):
4486
4594
  settings = kwargs.pop('template_settings', {})
4487
4595
  if isinstance(tpl, adapter):
4488
4596
  TEMPLATES[tplid] = tpl
4489
- if settings: TEMPLATES[tplid].prepare(**settings)
4597
+ if settings:
4598
+ TEMPLATES[tplid].prepare(**settings)
4490
4599
  elif "\n" in tpl or "{" in tpl or "%" in tpl or '$' in tpl:
4491
4600
  TEMPLATES[tplid] = adapter(source=tpl, lookup=lookup, **settings)
4492
4601
  else:
@@ -4550,7 +4659,7 @@ HTTP_CODES[418] = "I'm a teapot" # RFC 2324
4550
4659
  HTTP_CODES[428] = "Precondition Required"
4551
4660
  HTTP_CODES[429] = "Too Many Requests"
4552
4661
  HTTP_CODES[431] = "Request Header Fields Too Large"
4553
- HTTP_CODES[451] = "Unavailable For Legal Reasons" # RFC 7725
4662
+ HTTP_CODES[451] = "Unavailable For Legal Reasons" # RFC 7725
4554
4663
  HTTP_CODES[511] = "Network Authentication Required"
4555
4664
  _HTTP_STATUS_LINES = dict((k, '%d %s' % (k, v))
4556
4665
  for (k, v) in HTTP_CODES.items())
@@ -4614,7 +4723,7 @@ apps = app = default_app = AppStack()
4614
4723
 
4615
4724
  #: A virtual package that redirects import statements.
4616
4725
  #: Example: ``import bottle.ext.sqlite`` actually imports `bottle_sqlite`.
4617
- ext = _ImportRedirect('bottle.ext' if __name__ == '__main__' else
4726
+ ext = _ImportRedirect('bottle.ext' if __name__ == "__main__" else
4618
4727
  __name__ + ".ext", 'bottle_%s').module
4619
4728
 
4620
4729
 
@@ -4676,5 +4785,5 @@ def main():
4676
4785
  _main(sys.argv)
4677
4786
 
4678
4787
 
4679
- if __name__ == '__main__': # pragma: no coverage
4788
+ if __name__ == "__main__": # pragma: no coverage
4680
4789
  main()
ezKit/utils.py CHANGED
@@ -1,6 +1,8 @@
1
1
  """Utils"""
2
2
  import csv
3
3
  import hashlib
4
+ import importlib
5
+ import importlib.util
4
6
  import json
5
7
  import os
6
8
  import re
@@ -17,9 +19,11 @@ from typing import Any, Callable
17
19
  from urllib.parse import ParseResult, urlparse
18
20
  from uuid import uuid4
19
21
 
20
- import markdown
21
22
  from loguru import logger
22
23
 
24
+ if importlib.util.find_spec("markdown"):
25
+ import markdown # type: ignore
26
+
23
27
  # --------------------------------------------------------------------------------------------------
24
28
 
25
29
 
@@ -1478,7 +1482,9 @@ def markdown_to_html(markdown_file: str, html_file: str, title: str) -> bool:
1478
1482
 
1479
1483
  # 将 Markdown 转换为 HTML
1480
1484
  logger.info(f"{info} [将 Markdown 转换为 HTML]")
1481
- html_body = markdown.markdown(markdown_content, extensions=['tables'])
1485
+ # pylint: disable=E0606
1486
+ html_body = markdown.markdown(markdown_content, extensions=['tables']) # type: ignore
1487
+ # pylint: enable=E0606
1482
1488
 
1483
1489
  # 构造完整的 HTML
1484
1490
  logger.info(f"{info} [构造完整的 HTML]")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: ezKit
3
- Version: 1.11.14
3
+ Version: 1.11.15
4
4
  Summary: Easy Kit
5
5
  Author: septvean
6
6
  Author-email: septvean@gmail.com
@@ -1,6 +1,6 @@
1
1
  ezKit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  ezKit/_file.py,sha256=0qRZhwYuagTgTGrhm-tzAMvEQT4HTJA_xZKqF2bo0ho,1207
3
- ezKit/bottle.py,sha256=usKK1wVaZw4_D-4VwMYmOIc8jtz4TrpM30nck59HMFw,180178
3
+ ezKit/bottle.py,sha256=43h4v1kzz6qrLvCt5IMN0H-gFtaT0koG9wETqteXsps,181666
4
4
  ezKit/bottle_extensions.py,sha256=CwXKxVKxxtbyfeeOSp2xODUqJBo7ro2C88H9sUOVDJI,1161
5
5
  ezKit/cipher.py,sha256=0T_StbjiNI4zgrjVgcfU-ffKgu1waBA9UDudAnqFcNM,2896
6
6
  ezKit/database.py,sha256=r5YNoEzeOeVTlEWI99xXtHTmPZ73_DopS8DTzZk8Lts,12432
@@ -11,10 +11,10 @@ ezKit/qywx.py,sha256=dGChIIf2V81MwufcPn6hwgSenPuxqK994KRH7ECT-CM,10387
11
11
  ezKit/redis.py,sha256=tdiqfizPYQQTIUumkJGUJsJVlv0zVTSTYGQN0QutYs4,1963
12
12
  ezKit/sendemail.py,sha256=47JTDFoLJKi0YtF3RAp9nFfo0ko2jlde3R_C1wr2E2w,7397
13
13
  ezKit/token.py,sha256=HKREyZj_T2S8-aFoFIrBXTaCKExQq4zE66OHXhGHqQg,1750
14
- ezKit/utils.py,sha256=DKCs4ivoRsp79QfmNFipQwis0OhWECqimYc5xEcnmVE,44061
14
+ ezKit/utils.py,sha256=uOUOCgx6WU6J2lTbHlL78Flk3oCZgdj8rBOFg2i0K7Q,44241
15
15
  ezKit/xftp.py,sha256=izUH9pLH_AzgR3c0g8xSfhLn7LQ9EDcTst3LFjTM6hU,7878
16
- ezKit-1.11.14.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
17
- ezKit-1.11.14.dist-info/METADATA,sha256=iR_YAF1nK4aF_EfNT7RgjVJwcft7bekiF17rgtR6iso,295
18
- ezKit-1.11.14.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
19
- ezKit-1.11.14.dist-info/top_level.txt,sha256=aYLB_1WODsqNTsTFWcKP-BN0KCTKcV-HZJ4zlHkCFw8,6
20
- ezKit-1.11.14.dist-info/RECORD,,
16
+ ezKit-1.11.15.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
17
+ ezKit-1.11.15.dist-info/METADATA,sha256=GyLTqpXZ0J06gOrmQemznFBaU7CNDmJLT7izboiDSWQ,295
18
+ ezKit-1.11.15.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
19
+ ezKit-1.11.15.dist-info/top_level.txt,sha256=aYLB_1WODsqNTsTFWcKP-BN0KCTKcV-HZJ4zlHkCFw8,6
20
+ ezKit-1.11.15.dist-info/RECORD,,