comparison web/bottle.py @ 248:317a1738f15c

Update to bottle 0.12.8
author Matt Johnston <matt@ucc.asn.au>
date Wed, 27 May 2015 22:17:39 +0800
parents dbbd503119ba
children
comparison
equal deleted inserted replaced
247:a19496c95be5 248:317a1738f15c
7 template engines - all in a single file and with no dependencies other than the 7 template engines - all in a single file and with no dependencies other than the
8 Python Standard Library. 8 Python Standard Library.
9 9
10 Homepage and documentation: http://bottlepy.org/ 10 Homepage and documentation: http://bottlepy.org/
11 11
12 Copyright (c) 2011, Marcel Hellkamp. 12 Copyright (c) 2013, Marcel Hellkamp.
13 License: MIT (see LICENSE for details) 13 License: MIT (see LICENSE for details)
14 """ 14 """
15 15
16 from __future__ import with_statement 16 from __future__ import with_statement
17 17
18 __author__ = 'Marcel Hellkamp' 18 __author__ = 'Marcel Hellkamp'
19 __version__ = '0.11.dev' 19 __version__ = '0.12.8'
20 __license__ = 'MIT' 20 __license__ = 'MIT'
21 21
22 # The gevent server adapter needs to patch some modules before they are imported 22 # The gevent server adapter needs to patch some modules before they are imported
23 # This is why we parse the commandline parameters here but handle them later 23 # This is why we parse the commandline parameters here but handle them later
24 if __name__ == '__main__': 24 if __name__ == '__main__':
34 _cmd_options, _cmd_args = _cmd_parser.parse_args() 34 _cmd_options, _cmd_args = _cmd_parser.parse_args()
35 if _cmd_options.server and _cmd_options.server.startswith('gevent'): 35 if _cmd_options.server and _cmd_options.server.startswith('gevent'):
36 import gevent.monkey; gevent.monkey.patch_all() 36 import gevent.monkey; gevent.monkey.patch_all()
37 37
38 import base64, cgi, email.utils, functools, hmac, imp, itertools, mimetypes,\ 38 import base64, cgi, email.utils, functools, hmac, imp, itertools, mimetypes,\
39 os, re, subprocess, sys, tempfile, threading, time, urllib, warnings 39 os, re, subprocess, sys, tempfile, threading, time, warnings
40 40
41 from datetime import date as datedate, datetime, timedelta 41 from datetime import date as datedate, datetime, timedelta
42 from tempfile import TemporaryFile 42 from tempfile import TemporaryFile
43 from traceback import format_exc, print_exc 43 from traceback import format_exc, print_exc
44 44 from inspect import getargspec
45 try: from json import dumps as json_dumps, loads as json_lds 45 from unicodedata import normalize
46
47
48 try: from simplejson import dumps as json_dumps, loads as json_lds
46 except ImportError: # pragma: no cover 49 except ImportError: # pragma: no cover
47 try: from simplejson import dumps as json_dumps, loads as json_lds 50 try: from json import dumps as json_dumps, loads as json_lds
48 except ImportError: 51 except ImportError:
49 try: from django.utils.simplejson import dumps as json_dumps, loads as json_lds 52 try: from django.utils.simplejson import dumps as json_dumps, loads as json_lds
50 except ImportError: 53 except ImportError:
51 def json_dumps(data): 54 def json_dumps(data):
52 raise ImportError("JSON support requires Python 2.6 or simplejson.") 55 raise ImportError("JSON support requires Python 2.6 or simplejson.")
56 59
57 # We now try to fix 2.5/2.6/3.1/3.2 incompatibilities. 60 # We now try to fix 2.5/2.6/3.1/3.2 incompatibilities.
58 # It ain't pretty but it works... Sorry for the mess. 61 # It ain't pretty but it works... Sorry for the mess.
59 62
60 py = sys.version_info 63 py = sys.version_info
61 py3k = py >= (3,0,0) 64 py3k = py >= (3, 0, 0)
62 py25 = py < (2,6,0) 65 py25 = py < (2, 6, 0)
66 py31 = (3, 1, 0) <= py < (3, 2, 0)
63 67
64 # Workaround for the missing "as" keyword in py3k. 68 # Workaround for the missing "as" keyword in py3k.
65 def _e(): return sys.exc_info()[1] 69 def _e(): return sys.exc_info()[1]
66 70
67 # Workaround for the "print is a keyword/function" Python 2/3 dilemma 71 # Workaround for the "print is a keyword/function" Python 2/3 dilemma
74 78
75 # Lots of stdlib and builtin differences. 79 # Lots of stdlib and builtin differences.
76 if py3k: 80 if py3k:
77 import http.client as httplib 81 import http.client as httplib
78 import _thread as thread 82 import _thread as thread
79 from urllib.parse import urljoin, parse_qsl, SplitResult as UrlSplitResult 83 from urllib.parse import urljoin, SplitResult as UrlSplitResult
80 from urllib.parse import urlencode, quote as urlquote, unquote as urlunquote 84 from urllib.parse import urlencode, quote as urlquote, unquote as urlunquote
85 urlunquote = functools.partial(urlunquote, encoding='latin1')
81 from http.cookies import SimpleCookie 86 from http.cookies import SimpleCookie
82 from collections import MutableMapping as DictMixin 87 from collections import MutableMapping as DictMixin
83 import pickle 88 import pickle
84 from io import BytesIO 89 from io import BytesIO
90 from configparser import ConfigParser
85 basestring = str 91 basestring = str
86 unicode = str 92 unicode = str
87 json_loads = lambda s: json_lds(touni(s)) 93 json_loads = lambda s: json_lds(touni(s))
88 callable = lambda x: hasattr(x, '__call__') 94 callable = lambda x: hasattr(x, '__call__')
89 imap = map 95 imap = map
96 def _raise(*a): raise a[0](a[1]).with_traceback(a[2])
90 else: # 2.x 97 else: # 2.x
91 import httplib 98 import httplib
92 import thread 99 import thread
93 from urlparse import urljoin, SplitResult as UrlSplitResult 100 from urlparse import urljoin, SplitResult as UrlSplitResult
94 from urllib import urlencode, quote as urlquote, unquote as urlunquote 101 from urllib import urlencode, quote as urlquote, unquote as urlunquote
95 from Cookie import SimpleCookie 102 from Cookie import SimpleCookie
96 from itertools import imap 103 from itertools import imap
97 import cPickle as pickle 104 import cPickle as pickle
98 from StringIO import StringIO as BytesIO 105 from StringIO import StringIO as BytesIO
106 from ConfigParser import SafeConfigParser as ConfigParser
99 if py25: 107 if py25:
100 msg = "Python 2.5 support may be dropped in future versions of Bottle." 108 msg = "Python 2.5 support may be dropped in future versions of Bottle."
101 warnings.warn(msg, DeprecationWarning) 109 warnings.warn(msg, DeprecationWarning)
102 from cgi import parse_qsl
103 from UserDict import DictMixin 110 from UserDict import DictMixin
104 def next(it): return it.next() 111 def next(it): return it.next()
105 bytes = str 112 bytes = str
106 else: # 2.6, 2.7 113 else: # 2.6, 2.7
107 from urlparse import parse_qsl
108 from collections import MutableMapping as DictMixin 114 from collections import MutableMapping as DictMixin
115 unicode = unicode
109 json_loads = json_lds 116 json_loads = json_lds
117 eval(compile('def _raise(*a): raise a[0], a[1], a[2]', '<py3fix>', 'exec'))
110 118
111 # Some helpers for string/byte handling 119 # Some helpers for string/byte handling
112 def tob(s, enc='utf8'): 120 def tob(s, enc='utf8'):
113 return s.encode(enc) if isinstance(s, unicode) else bytes(s) 121 return s.encode(enc) if isinstance(s, unicode) else bytes(s)
114 def touni(s, enc='utf8', err='strict'): 122 def touni(s, enc='utf8', err='strict'):
115 return s.decode(enc, err) if isinstance(s, bytes) else unicode(s) 123 return s.decode(enc, err) if isinstance(s, bytes) else unicode(s)
116 tonat = touni if py3k else tob 124 tonat = touni if py3k else tob
117 125
118 # 3.2 fixes cgi.FieldStorage to accept bytes (which makes a lot of sense). 126 # 3.2 fixes cgi.FieldStorage to accept bytes (which makes a lot of sense).
119 # 3.1 needs a workaround. 127 # 3.1 needs a workaround.
120 NCTextIOWrapper = None 128 if py31:
121 if (3,0,0) < py < (3,2,0):
122 from io import TextIOWrapper 129 from io import TextIOWrapper
123 class NCTextIOWrapper(TextIOWrapper): 130 class NCTextIOWrapper(TextIOWrapper):
124 def close(self): pass # Keep wrapped buffer open. 131 def close(self): pass # Keep wrapped buffer open.
132
125 133
126 # A bug in functools causes it to break if the wrapper is an instance method 134 # A bug in functools causes it to break if the wrapper is an instance method
127 def update_wrapper(wrapper, wrapped, *a, **ka): 135 def update_wrapper(wrapper, wrapped, *a, **ka):
128 try: functools.update_wrapper(wrapper, wrapped, *a, **ka) 136 try: functools.update_wrapper(wrapper, wrapped, *a, **ka)
129 except AttributeError: pass 137 except AttributeError: pass
131 139
132 140
133 # These helpers are used at module level and need to be defined first. 141 # These helpers are used at module level and need to be defined first.
134 # And yes, I know PEP-8, but sometimes a lower-case classname makes more sense. 142 # And yes, I know PEP-8, but sometimes a lower-case classname makes more sense.
135 143
136 def depr(message): 144 def depr(message, hard=False):
137 warnings.warn(message, DeprecationWarning, stacklevel=3) 145 warnings.warn(message, DeprecationWarning, stacklevel=3)
138 146
139 def makelist(data): # This is just to handy 147 def makelist(data): # This is just to handy
140 if isinstance(data, (tuple, list, set, dict)): return list(data) 148 if isinstance(data, (tuple, list, set, dict)): return list(data)
141 elif data: return [data] 149 elif data: return [data]
171 ''' A property that is only computed once per instance and then replaces 179 ''' A property that is only computed once per instance and then replaces
172 itself with an ordinary attribute. Deleting the attribute resets the 180 itself with an ordinary attribute. Deleting the attribute resets the
173 property. ''' 181 property. '''
174 182
175 def __init__(self, func): 183 def __init__(self, func):
184 self.__doc__ = getattr(func, '__doc__')
176 self.func = func 185 self.func = func
177 186
178 def __get__(self, obj, cls): 187 def __get__(self, obj, cls):
179 if obj is None: return self 188 if obj is None: return self
180 value = obj.__dict__[self.func.__name__] = self.func(obj) 189 value = obj.__dict__[self.func.__name__] = self.func(obj)
205 class BottleException(Exception): 214 class BottleException(Exception):
206 """ A base class for exceptions used by bottle. """ 215 """ A base class for exceptions used by bottle. """
207 pass 216 pass
208 217
209 218
210 #TODO: This should subclass BaseRequest
211 class HTTPResponse(BottleException):
212 """ Used to break execution and immediately finish the response """
213 def __init__(self, output='', status=200, header=None):
214 super(BottleException, self).__init__("HTTP Response %d" % status)
215 self.status = int(status)
216 self.output = output
217 self.headers = HeaderDict(header) if header else None
218
219 def apply(self, response):
220 if self.headers:
221 for key, value in self.headers.allitems():
222 response.headers[key] = value
223 response.status = self.status
224
225
226 class HTTPError(HTTPResponse):
227 """ Used to generate an error page """
228 def __init__(self, code=500, output='Unknown Error', exception=None,
229 traceback=None, header=None):
230 super(HTTPError, self).__init__(output, code, header)
231 self.exception = exception
232 self.traceback = traceback
233
234 def __repr__(self):
235 return tonat(template(ERROR_PAGE_TEMPLATE, e=self))
236
237
238 219
239 220
240 221
241 222
242 ############################################################################### 223 ###############################################################################
254 235
255 class RouterUnknownModeError(RouteError): pass 236 class RouterUnknownModeError(RouteError): pass
256 237
257 238
258 class RouteSyntaxError(RouteError): 239 class RouteSyntaxError(RouteError):
259 """ The route parser found something not supported by this router """ 240 """ The route parser found something not supported by this router. """
260 241
261 242
262 class RouteBuildError(RouteError): 243 class RouteBuildError(RouteError):
263 """ The route could not been built """ 244 """ The route could not be built. """
245
246
247 def _re_flatten(p):
248 ''' Turn all capturing groups in a regular expression pattern into
249 non-capturing groups. '''
250 if '(' not in p: return p
251 return re.sub(r'(\\*)(\(\?P<[^>]+>|\((?!\?))',
252 lambda m: m.group(0) if len(m.group(1)) % 2 else m.group(1) + '(?:', p)
264 253
265 254
266 class Router(object): 255 class Router(object):
267 ''' A Router is an ordered collection of route->target pairs. It is used to 256 ''' A Router is an ordered collection of route->target pairs. It is used to
268 efficiently match WSGI requests against a number of routes and return 257 efficiently match WSGI requests against a number of routes and return
274 path that contains wildcards (e.g. `/wiki/<page>`). The wildcard syntax 263 path that contains wildcards (e.g. `/wiki/<page>`). The wildcard syntax
275 and details on the matching order are described in docs:`routing`. 264 and details on the matching order are described in docs:`routing`.
276 ''' 265 '''
277 266
278 default_pattern = '[^/]+' 267 default_pattern = '[^/]+'
279 default_filter = 're' 268 default_filter = 're'
280 #: Sorry for the mess. It works. Trust me. 269
281 rule_syntax = re.compile('(\\\\*)'\ 270 #: The current CPython regexp implementation does not allow more
282 '(?:(?::([a-zA-Z_][a-zA-Z_0-9]*)?()(?:#(.*?)#)?)'\ 271 #: than 99 matching groups per regular expression.
283 '|(?:<([a-zA-Z_][a-zA-Z_0-9]*)?(?::([a-zA-Z_]*)'\ 272 _MAX_GROUPS_PER_PATTERN = 99
284 '(?::((?:\\\\.|[^\\\\>]+)+)?)?)?>))')
285 273
286 def __init__(self, strict=False): 274 def __init__(self, strict=False):
287 self.rules = {} # A {rule: Rule} mapping 275 self.rules = [] # All rules in order
288 self.builder = {} # A rule/name->build_info mapping 276 self._groups = {} # index of regexes to find them in dyna_routes
289 self.static = {} # Cache for static routes: {path: {method: target}} 277 self.builder = {} # Data structure for the url builder
290 self.dynamic = [] # Cache for dynamic routes. See _compile() 278 self.static = {} # Search structure for static routes
279 self.dyna_routes = {}
280 self.dyna_regexes = {} # Search structure for dynamic routes
291 #: If true, static routes are no longer checked first. 281 #: If true, static routes are no longer checked first.
292 self.strict_order = strict 282 self.strict_order = strict
293 self.filters = {'re': self.re_filter, 'int': self.int_filter, 283 self.filters = {
294 'float': self.float_filter, 'path': self.path_filter} 284 're': lambda conf:
295 285 (_re_flatten(conf or self.default_pattern), None, None),
296 def re_filter(self, conf): 286 'int': lambda conf: (r'-?\d+', int, lambda x: str(int(x))),
297 return conf or self.default_pattern, None, None 287 'float': lambda conf: (r'-?[\d.]+', float, lambda x: str(float(x))),
298 288 'path': lambda conf: (r'.+?', None, None)}
299 def int_filter(self, conf):
300 return r'-?\d+', int, lambda x: str(int(x))
301
302 def float_filter(self, conf):
303 return r'-?[\d.]+', float, lambda x: str(float(x))
304
305 def path_filter(self, conf):
306 return r'.+?', None, None
307 289
308 def add_filter(self, name, func): 290 def add_filter(self, name, func):
309 ''' Add a filter. The provided function is called with the configuration 291 ''' Add a filter. The provided function is called with the configuration
310 string as parameter and must return a (regexp, to_python, to_url) tuple. 292 string as parameter and must return a (regexp, to_python, to_url) tuple.
311 The first element is a string, the last two are callables or None. ''' 293 The first element is a string, the last two are callables or None. '''
312 self.filters[name] = func 294 self.filters[name] = func
313 295
314 def parse_rule(self, rule): 296 rule_syntax = re.compile('(\\\\*)'\
315 ''' Parses a rule into a (name, filter, conf) token stream. If mode is 297 '(?:(?::([a-zA-Z_][a-zA-Z_0-9]*)?()(?:#(.*?)#)?)'\
316 None, name contains a static rule part. ''' 298 '|(?:<([a-zA-Z_][a-zA-Z_0-9]*)?(?::([a-zA-Z_]*)'\
299 '(?::((?:\\\\.|[^\\\\>]+)+)?)?)?>))')
300
301 def _itertokens(self, rule):
317 offset, prefix = 0, '' 302 offset, prefix = 0, ''
318 for match in self.rule_syntax.finditer(rule): 303 for match in self.rule_syntax.finditer(rule):
319 prefix += rule[offset:match.start()] 304 prefix += rule[offset:match.start()]
320 g = match.groups() 305 g = match.groups()
321 if len(g[0])%2: # Escaped wildcard 306 if len(g[0])%2: # Escaped wildcard
322 prefix += match.group(0)[len(g[0]):] 307 prefix += match.group(0)[len(g[0]):]
323 offset = match.end() 308 offset = match.end()
324 continue 309 continue
325 if prefix: yield prefix, None, None 310 if prefix:
326 name, filtr, conf = g[1:4] if not g[2] is None else g[4:7] 311 yield prefix, None, None
327 if not filtr: filtr = self.default_filter 312 name, filtr, conf = g[4:7] if g[2] is None else g[1:4]
328 yield name, filtr, conf or None 313 yield name, filtr or 'default', conf or None
329 offset, prefix = match.end(), '' 314 offset, prefix = match.end(), ''
330 if offset <= len(rule) or prefix: 315 if offset <= len(rule) or prefix:
331 yield prefix+rule[offset:], None, None 316 yield prefix+rule[offset:], None, None
332 317
333 def add(self, rule, method, target, name=None): 318 def add(self, rule, method, target, name=None):
334 ''' Add a new route or replace the target for an existing route. ''' 319 ''' Add a new rule or replace the target for an existing rule. '''
335 if rule in self.rules: 320 anons = 0 # Number of anonymous wildcards found
336 self.rules[rule][method] = target 321 keys = [] # Names of keys
337 if name: self.builder[name] = self.builder[rule] 322 pattern = '' # Regular expression pattern with named groups
338 return 323 filters = [] # Lists of wildcard input filters
339 324 builder = [] # Data structure for the URL builder
340 target = self.rules[rule] = {method: target}
341
342 # Build pattern and other structures for dynamic routes
343 anons = 0 # Number of anonymous wildcards
344 pattern = '' # Regular expression pattern
345 filters = [] # Lists of wildcard input filters
346 builder = [] # Data structure for the URL builder
347 is_static = True 325 is_static = True
348 for key, mode, conf in self.parse_rule(rule): 326
327 for key, mode, conf in self._itertokens(rule):
349 if mode: 328 if mode:
350 is_static = False 329 is_static = False
330 if mode == 'default': mode = self.default_filter
351 mask, in_filter, out_filter = self.filters[mode](conf) 331 mask, in_filter, out_filter = self.filters[mode](conf)
352 if key: 332 if not key:
333 pattern += '(?:%s)' % mask
334 key = 'anon%d' % anons
335 anons += 1
336 else:
353 pattern += '(?P<%s>%s)' % (key, mask) 337 pattern += '(?P<%s>%s)' % (key, mask)
354 else: 338 keys.append(key)
355 pattern += '(?:%s)' % mask
356 key = 'anon%d' % anons; anons += 1
357 if in_filter: filters.append((key, in_filter)) 339 if in_filter: filters.append((key, in_filter))
358 builder.append((key, out_filter or str)) 340 builder.append((key, out_filter or str))
359 elif key: 341 elif key:
360 pattern += re.escape(key) 342 pattern += re.escape(key)
361 builder.append((None, key)) 343 builder.append((None, key))
344
362 self.builder[rule] = builder 345 self.builder[rule] = builder
363 if name: self.builder[name] = builder 346 if name: self.builder[name] = builder
364 347
365 if is_static and not self.strict_order: 348 if is_static and not self.strict_order:
366 self.static[self.build(rule)] = target 349 self.static.setdefault(method, {})
350 self.static[method][self.build(rule)] = (target, None)
367 return 351 return
368 352
369 def fpat_sub(m):
370 return m.group(0) if len(m.group(1)) % 2 else m.group(1) + '(?:'
371 flat_pattern = re.sub(r'(\\*)(\(\?P<[^>]*>|\((?!\?))', fpat_sub, pattern)
372
373 try: 353 try:
374 re_match = re.compile('^(%s)$' % pattern).match 354 re_pattern = re.compile('^(%s)$' % pattern)
355 re_match = re_pattern.match
375 except re.error: 356 except re.error:
376 raise RouteSyntaxError("Could not add Route: %s (%s)" % (rule, _e())) 357 raise RouteSyntaxError("Could not add Route: %s (%s)" % (rule, _e()))
377 358
378 def match(path): 359 if filters:
379 """ Return an url-argument dictionary. """ 360 def getargs(path):
380 url_args = re_match(path).groupdict() 361 url_args = re_match(path).groupdict()
381 for name, wildcard_filter in filters: 362 for name, wildcard_filter in filters:
382 try: 363 try:
383 url_args[name] = wildcard_filter(url_args[name]) 364 url_args[name] = wildcard_filter(url_args[name])
384 except ValueError: 365 except ValueError:
385 raise HTTPError(400, 'Path has wrong format.') 366 raise HTTPError(400, 'Path has wrong format.')
386 return url_args 367 return url_args
387 368 elif re_pattern.groupindex:
388 try: 369 def getargs(path):
389 combined = '%s|(^%s$)' % (self.dynamic[-1][0].pattern, flat_pattern) 370 return re_match(path).groupdict()
390 self.dynamic[-1] = (re.compile(combined), self.dynamic[-1][1]) 371 else:
391 self.dynamic[-1][1].append((match, target)) 372 getargs = None
392 except (AssertionError, IndexError): # AssertionError: Too many groups 373
393 self.dynamic.append((re.compile('(^%s$)' % flat_pattern), 374 flatpat = _re_flatten(pattern)
394 [(match, target)])) 375 whole_rule = (rule, flatpat, target, getargs)
395 return match 376
377 if (flatpat, method) in self._groups:
378 if DEBUG:
379 msg = 'Route <%s %s> overwrites a previously defined route'
380 warnings.warn(msg % (method, rule), RuntimeWarning)
381 self.dyna_routes[method][self._groups[flatpat, method]] = whole_rule
382 else:
383 self.dyna_routes.setdefault(method, []).append(whole_rule)
384 self._groups[flatpat, method] = len(self.dyna_routes[method]) - 1
385
386 self._compile(method)
387
388 def _compile(self, method):
389 all_rules = self.dyna_routes[method]
390 comborules = self.dyna_regexes[method] = []
391 maxgroups = self._MAX_GROUPS_PER_PATTERN
392 for x in range(0, len(all_rules), maxgroups):
393 some = all_rules[x:x+maxgroups]
394 combined = (flatpat for (_, flatpat, _, _) in some)
395 combined = '|'.join('(^%s$)' % flatpat for flatpat in combined)
396 combined = re.compile(combined).match
397 rules = [(target, getargs) for (_, _, target, getargs) in some]
398 comborules.append((combined, rules))
396 399
397 def build(self, _name, *anons, **query): 400 def build(self, _name, *anons, **query):
398 ''' Build an URL by filling the wildcards in a rule. ''' 401 ''' Build an URL by filling the wildcards in a rule. '''
399 builder = self.builder.get(_name) 402 builder = self.builder.get(_name)
400 if not builder: raise RouteBuildError("No route with that name.", _name) 403 if not builder: raise RouteBuildError("No route with that name.", _name)
405 except KeyError: 408 except KeyError:
406 raise RouteBuildError('Missing URL argument: %r' % _e().args[0]) 409 raise RouteBuildError('Missing URL argument: %r' % _e().args[0])
407 410
408 def match(self, environ): 411 def match(self, environ):
409 ''' Return a (target, url_agrs) tuple or raise HTTPError(400/404/405). ''' 412 ''' Return a (target, url_agrs) tuple or raise HTTPError(400/404/405). '''
410 path, targets, urlargs = environ['PATH_INFO'] or '/', None, {} 413 verb = environ['REQUEST_METHOD'].upper()
411 if path in self.static: 414 path = environ['PATH_INFO'] or '/'
412 targets = self.static[path] 415 target = None
416 if verb == 'HEAD':
417 methods = ['PROXY', verb, 'GET', 'ANY']
413 else: 418 else:
414 for combined, rules in self.dynamic: 419 methods = ['PROXY', verb, 'ANY']
415 match = combined.match(path) 420
416 if not match: continue 421 for method in methods:
417 getargs, targets = rules[match.lastindex - 1] 422 if method in self.static and path in self.static[method]:
418 urlargs = getargs(path) if getargs else {} 423 target, getargs = self.static[method][path]
419 break 424 return target, getargs(path) if getargs else {}
420 425 elif method in self.dyna_regexes:
421 if not targets: 426 for combined, rules in self.dyna_regexes[method]:
422 raise HTTPError(404, "Not found: " + repr(environ['PATH_INFO'])) 427 match = combined(path)
423 method = environ['REQUEST_METHOD'].upper() 428 if match:
424 if method in targets: 429 target, getargs = rules[match.lastindex - 1]
425 return targets[method], urlargs 430 return target, getargs(path) if getargs else {}
426 if method == 'HEAD' and 'GET' in targets: 431
427 return targets['GET'], urlargs 432 # No matching route found. Collect alternative methods for 405 response
428 if 'ANY' in targets: 433 allowed = set([])
429 return targets['ANY'], urlargs 434 nocheck = set(methods)
430 allowed = [verb for verb in targets if verb != 'ANY'] 435 for method in set(self.static) - nocheck:
431 if 'GET' in allowed and 'HEAD' not in allowed: 436 if path in self.static[method]:
432 allowed.append('HEAD') 437 allowed.add(verb)
433 raise HTTPError(405, "Method not allowed.", 438 for method in set(self.dyna_regexes) - allowed - nocheck:
434 header=[('Allow',",".join(allowed))]) 439 for combined, rules in self.dyna_regexes[method]:
440 match = combined(path)
441 if match:
442 allowed.add(method)
443 if allowed:
444 allow_header = ",".join(sorted(allowed))
445 raise HTTPError(405, "Method not allowed.", Allow=allow_header)
446
447 # No matching route and no alternative method found. We give up
448 raise HTTPError(404, "Not found: " + repr(path))
449
450
451
452
435 453
436 454
437 class Route(object): 455 class Route(object):
438 ''' This class wraps a route callback along with route specific metadata and 456 ''' This class wraps a route callback along with route specific metadata and
439 configuration and applies Plugins on demand. It is also responsible for 457 configuration and applies Plugins on demand. It is also responsible for
457 #: A list of plugins to not apply to this route (see :meth:`Bottle.route`). 475 #: A list of plugins to not apply to this route (see :meth:`Bottle.route`).
458 self.skiplist = skiplist or [] 476 self.skiplist = skiplist or []
459 #: Additional keyword arguments passed to the :meth:`Bottle.route` 477 #: Additional keyword arguments passed to the :meth:`Bottle.route`
460 #: decorator are stored in this dictionary. Used for route-specific 478 #: decorator are stored in this dictionary. Used for route-specific
461 #: plugin configuration and meta-data. 479 #: plugin configuration and meta-data.
462 self.config = ConfigDict(config) 480 self.config = ConfigDict().load_dict(config, make_namespaces=True)
463 481
464 def __call__(self, *a, **ka): 482 def __call__(self, *a, **ka):
465 depr("Some APIs changed to return Route() instances instead of"\ 483 depr("Some APIs changed to return Route() instances instead of"\
466 " callables. Make sure to use the Route.call method and not to"\ 484 " callables. Make sure to use the Route.call method and not to"\
467 " call Route instances directly.") 485 " call Route instances directly.") #0.12
468 return self.call(*a, **ka) 486 return self.call(*a, **ka)
469 487
470 @cached_property 488 @cached_property
471 def call(self): 489 def call(self):
472 ''' The route callback with all plugins applied. This property is 490 ''' The route callback with all plugins applied. This property is
482 ''' Do all on-demand work immediately (useful for debugging).''' 500 ''' Do all on-demand work immediately (useful for debugging).'''
483 self.call 501 self.call
484 502
485 @property 503 @property
486 def _context(self): 504 def _context(self):
487 depr('Switch to Plugin API v2 and access the Route object directly.') 505 depr('Switch to Plugin API v2 and access the Route object directly.') #0.12
488 return dict(rule=self.rule, method=self.method, callback=self.callback, 506 return dict(rule=self.rule, method=self.method, callback=self.callback,
489 name=self.name, app=self.app, config=self.config, 507 name=self.name, app=self.app, config=self.config,
490 apply=self.plugins, skip=self.skiplist) 508 apply=self.plugins, skip=self.skiplist)
491 509
492 def all_plugins(self): 510 def all_plugins(self):
514 return self._make_callback() 532 return self._make_callback()
515 if not callback is self.callback: 533 if not callback is self.callback:
516 update_wrapper(callback, self.callback) 534 update_wrapper(callback, self.callback)
517 return callback 535 return callback
518 536
537 def get_undecorated_callback(self):
538 ''' Return the callback. If the callback is a decorated function, try to
539 recover the original function. '''
540 func = self.callback
541 func = getattr(func, '__func__' if py3k else 'im_func', func)
542 closure_attr = '__closure__' if py3k else 'func_closure'
543 while hasattr(func, closure_attr) and getattr(func, closure_attr):
544 func = getattr(func, closure_attr)[0].cell_contents
545 return func
546
547 def get_callback_args(self):
548 ''' Return a list of argument names the callback (most likely) accepts
549 as keyword arguments. If the callback is a decorated function, try
550 to recover the original function before inspection. '''
551 return getargspec(self.get_undecorated_callback())[0]
552
553 def get_config(self, key, default=None):
554 ''' Lookup a config field and return its value, first checking the
555 route.config, then route.app.config.'''
556 for conf in (self.config, self.app.conifg):
557 if key in conf: return conf[key]
558 return default
559
519 def __repr__(self): 560 def __repr__(self):
520 return '<%s %r %r>' % (self.method, self.rule, self.callback) 561 cb = self.get_undecorated_callback()
562 return '<%s %r %r>' % (self.method, self.rule, cb)
521 563
522 564
523 565
524 566
525 567
537 :param catchall: If true (default), handle all exceptions. Turn off to 579 :param catchall: If true (default), handle all exceptions. Turn off to
538 let debugging middleware handle exceptions. 580 let debugging middleware handle exceptions.
539 """ 581 """
540 582
541 def __init__(self, catchall=True, autojson=True): 583 def __init__(self, catchall=True, autojson=True):
542 #: If true, most exceptions are caught and returned as :exc:`HTTPError` 584
543 self.catchall = catchall 585 #: A :class:`ConfigDict` for app specific configuration.
544 586 self.config = ConfigDict()
545 #: A :cls:`ResourceManager` for application files 587 self.config._on_change = functools.partial(self.trigger_hook, 'config')
588 self.config.meta_set('autojson', 'validate', bool)
589 self.config.meta_set('catchall', 'validate', bool)
590 self.config['catchall'] = catchall
591 self.config['autojson'] = autojson
592
593 #: A :class:`ResourceManager` for application files
546 self.resources = ResourceManager() 594 self.resources = ResourceManager()
547
548 #: A :cls:`ConfigDict` for app specific configuration.
549 self.config = ConfigDict()
550 self.config.autojson = autojson
551 595
552 self.routes = [] # List of installed :class:`Route` instances. 596 self.routes = [] # List of installed :class:`Route` instances.
553 self.router = Router() # Maps requests to :class:`Route` instances. 597 self.router = Router() # Maps requests to :class:`Route` instances.
554 self.error_handler = {} 598 self.error_handler = {}
555 599
556 # Core plugins 600 # Core plugins
557 self.plugins = [] # List of installed plugins. 601 self.plugins = [] # List of installed plugins.
558 self.hooks = HooksPlugin() 602 if self.config['autojson']:
559 self.install(self.hooks)
560 if self.config.autojson:
561 self.install(JSONPlugin()) 603 self.install(JSONPlugin())
562 self.install(TemplatePlugin()) 604 self.install(TemplatePlugin())
563 605
606 #: If true, most exceptions are caught and returned as :exc:`HTTPError`
607 catchall = DictProperty('config', 'catchall')
608
609 __hook_names = 'before_request', 'after_request', 'app_reset', 'config'
610 __hook_reversed = 'after_request'
611
612 @cached_property
613 def _hooks(self):
614 return dict((name, []) for name in self.__hook_names)
615
616 def add_hook(self, name, func):
617 ''' Attach a callback to a hook. Three hooks are currently implemented:
618
619 before_request
620 Executed once before each request. The request context is
621 available, but no routing has happened yet.
622 after_request
623 Executed once after each request regardless of its outcome.
624 app_reset
625 Called whenever :meth:`Bottle.reset` is called.
626 '''
627 if name in self.__hook_reversed:
628 self._hooks[name].insert(0, func)
629 else:
630 self._hooks[name].append(func)
631
632 def remove_hook(self, name, func):
633 ''' Remove a callback from a hook. '''
634 if name in self._hooks and func in self._hooks[name]:
635 self._hooks[name].remove(func)
636 return True
637
638 def trigger_hook(self, __name, *args, **kwargs):
639 ''' Trigger a hook and return a list of results. '''
640 return [hook(*args, **kwargs) for hook in self._hooks[__name][:]]
641
642 def hook(self, name):
643 """ Return a decorator that attaches a callback to a hook. See
644 :meth:`add_hook` for details."""
645 def decorator(func):
646 self.add_hook(name, func)
647 return func
648 return decorator
564 649
565 def mount(self, prefix, app, **options): 650 def mount(self, prefix, app, **options):
566 ''' Mount an application (:class:`Bottle` or plain WSGI) to a specific 651 ''' Mount an application (:class:`Bottle` or plain WSGI) to a specific
567 URL prefix. Example:: 652 URL prefix. Example::
568 653
573 :param app: an instance of :class:`Bottle` or a WSGI application. 658 :param app: an instance of :class:`Bottle` or a WSGI application.
574 659
575 All other parameters are passed to the underlying :meth:`route` call. 660 All other parameters are passed to the underlying :meth:`route` call.
576 ''' 661 '''
577 if isinstance(app, basestring): 662 if isinstance(app, basestring):
578 prefix, app = app, prefix 663 depr('Parameter order of Bottle.mount() changed.', True) # 0.10
579 depr('Parameter order of Bottle.mount() changed.') # 0.10
580 664
581 segments = [p for p in prefix.split('/') if p] 665 segments = [p for p in prefix.split('/') if p]
582 if not segments: raise ValueError('Empty path prefix.') 666 if not segments: raise ValueError('Empty path prefix.')
583 path_depth = len(segments) 667 path_depth = len(segments)
584 668
585 def mountpoint_wrapper(): 669 def mountpoint_wrapper():
586 try: 670 try:
587 request.path_shift(path_depth) 671 request.path_shift(path_depth)
588 rs = BaseResponse([], 200) 672 rs = HTTPResponse([])
589 def start_response(status, header): 673 def start_response(status, headerlist, exc_info=None):
674 if exc_info:
675 try:
676 _raise(*exc_info)
677 finally:
678 exc_info = None
590 rs.status = status 679 rs.status = status
591 for name, value in header: rs.add_header(name, value) 680 for name, value in headerlist: rs.add_header(name, value)
592 return rs.body.append 681 return rs.body.append
593 body = app(request.environ, start_response) 682 body = app(request.environ, start_response)
594 body = itertools.chain(rs.body, body) 683 if body and rs.body: body = itertools.chain(rs.body, body)
595 return HTTPResponse(body, rs.status_code, rs.headers) 684 rs.body = body or rs.body
685 return rs
596 finally: 686 finally:
597 request.path_shift(-path_depth) 687 request.path_shift(-path_depth)
598 688
599 options.setdefault('skip', True) 689 options.setdefault('skip', True)
600 options.setdefault('method', 'ANY') 690 options.setdefault('method', 'PROXY')
601 options.setdefault('mountpoint', {'prefix': prefix, 'target': app}) 691 options.setdefault('mountpoint', {'prefix': prefix, 'target': app})
602 options['callback'] = mountpoint_wrapper 692 options['callback'] = mountpoint_wrapper
603 693
604 self.route('/%s/<:re:.*>' % '/'.join(segments), **options) 694 self.route('/%s/<:re:.*>' % '/'.join(segments), **options)
605 if not prefix.endswith('/'): 695 if not prefix.endswith('/'):
640 del self.plugins[i] 730 del self.plugins[i]
641 if hasattr(plugin, 'close'): plugin.close() 731 if hasattr(plugin, 'close'): plugin.close()
642 if removed: self.reset() 732 if removed: self.reset()
643 return removed 733 return removed
644 734
645 def run(self, **kwargs):
646 ''' Calls :func:`run` with the same parameters. '''
647 run(self, **kwargs)
648
649 def reset(self, route=None): 735 def reset(self, route=None):
650 ''' Reset all routes (force plugins to be re-applied) and clear all 736 ''' Reset all routes (force plugins to be re-applied) and clear all
651 caches. If an ID or route object is given, only that specific route 737 caches. If an ID or route object is given, only that specific route
652 is affected. ''' 738 is affected. '''
653 if route is None: routes = self.routes 739 if route is None: routes = self.routes
654 elif isinstance(route, Route): routes = [route] 740 elif isinstance(route, Route): routes = [route]
655 else: routes = [self.routes[route]] 741 else: routes = [self.routes[route]]
656 for route in routes: route.reset() 742 for route in routes: route.reset()
657 if DEBUG: 743 if DEBUG:
658 for route in routes: route.prepare() 744 for route in routes: route.prepare()
659 self.hooks.trigger('app_reset') 745 self.trigger_hook('app_reset')
660 746
661 def close(self): 747 def close(self):
662 ''' Close the application and all installed plugins. ''' 748 ''' Close the application and all installed plugins. '''
663 for plugin in self.plugins: 749 for plugin in self.plugins:
664 if hasattr(plugin, 'close'): plugin.close() 750 if hasattr(plugin, 'close'): plugin.close()
665 self.stopped = True 751 self.stopped = True
752
753 def run(self, **kwargs):
754 ''' Calls :func:`run` with the same parameters. '''
755 run(self, **kwargs)
666 756
667 def match(self, environ): 757 def match(self, environ):
668 """ Search for a matching route and return a (:class:`Route` , urlargs) 758 """ Search for a matching route and return a (:class:`Route` , urlargs)
669 tuple. The second value is a dictionary with parameters extracted 759 tuple. The second value is a dictionary with parameters extracted
670 from the URL. Raise :exc:`HTTPError` (404/405) on a non-match.""" 760 from the URL. Raise :exc:`HTTPError` (404/405) on a non-match."""
746 def wrapper(handler): 836 def wrapper(handler):
747 self.error_handler[int(code)] = handler 837 self.error_handler[int(code)] = handler
748 return handler 838 return handler
749 return wrapper 839 return wrapper
750 840
751 def hook(self, name): 841 def default_error_handler(self, res):
752 """ Return a decorator that attaches a callback to a hook. Three hooks 842 return tob(template(ERROR_PAGE_TEMPLATE, e=res))
753 are currently implemented:
754
755 - before_request: Executed once before each request
756 - after_request: Executed once after each request
757 - app_reset: Called whenever :meth:`reset` is called.
758 """
759 def wrapper(func):
760 self.hooks.add(name, func)
761 return func
762 return wrapper
763
764 def handle(self, path, method='GET'):
765 """ (deprecated) Execute the first matching route callback and return
766 the result. :exc:`HTTPResponse` exceptions are caught and returned.
767 If :attr:`Bottle.catchall` is true, other exceptions are caught as
768 well and returned as :exc:`HTTPError` instances (500).
769 """
770 depr("This method will change semantics in 0.10. Try to avoid it.")
771 if isinstance(path, dict):
772 return self._handle(path)
773 return self._handle({'PATH_INFO': path, 'REQUEST_METHOD': method.upper()})
774 843
775 def _handle(self, environ): 844 def _handle(self, environ):
845 path = environ['bottle.raw_path'] = environ['PATH_INFO']
846 if py3k:
847 try:
848 environ['PATH_INFO'] = path.encode('latin1').decode('utf8')
849 except UnicodeError:
850 return HTTPError(400, 'Invalid path string. Expected UTF-8')
851
776 try: 852 try:
777 environ['bottle.app'] = self 853 environ['bottle.app'] = self
778 request.bind(environ) 854 request.bind(environ)
779 response.bind() 855 response.bind()
780 route, args = self.router.match(environ) 856 try:
781 environ['route.handle'] = environ['bottle.route'] = route 857 self.trigger_hook('before_request')
782 environ['route.url_args'] = args 858 route, args = self.router.match(environ)
783 return route.call(**args) 859 environ['route.handle'] = route
860 environ['bottle.route'] = route
861 environ['route.url_args'] = args
862 return route.call(**args)
863 finally:
864 self.trigger_hook('after_request')
865
784 except HTTPResponse: 866 except HTTPResponse:
785 return _e() 867 return _e()
786 except RouteReset: 868 except RouteReset:
787 route.reset() 869 route.reset()
788 return self._handle(environ) 870 return self._handle(environ)
801 iterable of strings and iterable of unicodes 883 iterable of strings and iterable of unicodes
802 """ 884 """
803 885
804 # Empty output is done here 886 # Empty output is done here
805 if not out: 887 if not out:
806 response['Content-Length'] = 0 888 if 'Content-Length' not in response:
889 response['Content-Length'] = 0
807 return [] 890 return []
808 # Join lists of byte or unicode strings. Mixed lists are NOT supported 891 # Join lists of byte or unicode strings. Mixed lists are NOT supported
809 if isinstance(out, (tuple, list))\ 892 if isinstance(out, (tuple, list))\
810 and isinstance(out[0], (bytes, unicode)): 893 and isinstance(out[0], (bytes, unicode)):
811 out = out[0][0:0].join(out) # b'abc'[0:0] -> b'' 894 out = out[0][0:0].join(out) # b'abc'[0:0] -> b''
812 # Encode unicode strings 895 # Encode unicode strings
813 if isinstance(out, unicode): 896 if isinstance(out, unicode):
814 out = out.encode(response.charset) 897 out = out.encode(response.charset)
815 # Byte Strings are just returned 898 # Byte Strings are just returned
816 if isinstance(out, bytes): 899 if isinstance(out, bytes):
817 response['Content-Length'] = len(out) 900 if 'Content-Length' not in response:
901 response['Content-Length'] = len(out)
818 return [out] 902 return [out]
819 # HTTPError or HTTPException (recursive, because they may wrap anything) 903 # HTTPError or HTTPException (recursive, because they may wrap anything)
820 # TODO: Handle these explicitly in handle() or make them iterable. 904 # TODO: Handle these explicitly in handle() or make them iterable.
821 if isinstance(out, HTTPError): 905 if isinstance(out, HTTPError):
822 out.apply(response) 906 out.apply(response)
823 out = self.error_handler.get(out.status, repr)(out) 907 out = self.error_handler.get(out.status_code, self.default_error_handler)(out)
824 if isinstance(out, HTTPResponse):
825 depr('Error handlers must not return :exc:`HTTPResponse`.') #0.9
826 return self._cast(out) 908 return self._cast(out)
827 if isinstance(out, HTTPResponse): 909 if isinstance(out, HTTPResponse):
828 out.apply(response) 910 out.apply(response)
829 return self._cast(out.output) 911 return self._cast(out.body)
830 912
831 # File-like objects. 913 # File-like objects.
832 if hasattr(out, 'read'): 914 if hasattr(out, 'read'):
833 if 'wsgi.file_wrapper' in request.environ: 915 if 'wsgi.file_wrapper' in request.environ:
834 return request.environ['wsgi.file_wrapper'](out) 916 return request.environ['wsgi.file_wrapper'](out)
835 elif hasattr(out, 'close') or not hasattr(out, '__iter__'): 917 elif hasattr(out, 'close') or not hasattr(out, '__iter__'):
836 return WSGIFileWrapper(out) 918 return WSGIFileWrapper(out)
837 919
838 # Handle Iterables. We peek into them to detect their inner type. 920 # Handle Iterables. We peek into them to detect their inner type.
839 try: 921 try:
840 out = iter(out) 922 iout = iter(out)
841 first = next(out) 923 first = next(iout)
842 while not first: 924 while not first:
843 first = next(out) 925 first = next(iout)
844 except StopIteration: 926 except StopIteration:
845 return self._cast('') 927 return self._cast('')
846 except HTTPResponse: 928 except HTTPResponse:
847 first = _e() 929 first = _e()
848 except (KeyboardInterrupt, SystemExit, MemoryError): 930 except (KeyboardInterrupt, SystemExit, MemoryError):
852 first = HTTPError(500, 'Unhandled exception', _e(), format_exc()) 934 first = HTTPError(500, 'Unhandled exception', _e(), format_exc())
853 935
854 # These are the inner types allowed in iterator or generator objects. 936 # These are the inner types allowed in iterator or generator objects.
855 if isinstance(first, HTTPResponse): 937 if isinstance(first, HTTPResponse):
856 return self._cast(first) 938 return self._cast(first)
857 if isinstance(first, bytes): 939 elif isinstance(first, bytes):
858 return itertools.chain([first], out) 940 new_iter = itertools.chain([first], iout)
859 if isinstance(first, unicode): 941 elif isinstance(first, unicode):
860 return imap(lambda x: x.encode(response.charset), 942 encoder = lambda x: x.encode(response.charset)
861 itertools.chain([first], out)) 943 new_iter = imap(encoder, itertools.chain([first], iout))
862 return self._cast(HTTPError(500, 'Unsupported response type: %s'\ 944 else:
863 % type(first))) 945 msg = 'Unsupported response type: %s' % type(first)
946 return self._cast(HTTPError(500, msg))
947 if hasattr(out, 'close'):
948 new_iter = _closeiter(new_iter, out.close)
949 return new_iter
864 950
865 def wsgi(self, environ, start_response): 951 def wsgi(self, environ, start_response):
866 """ The bottle WSGI-interface. """ 952 """ The bottle WSGI-interface. """
867 try: 953 try:
868 out = self._cast(self._handle(environ)) 954 out = self._cast(self._handle(environ))
869 # rfc2616 section 4.3 955 # rfc2616 section 4.3
870 if response._status_code in (100, 101, 204, 304)\ 956 if response._status_code in (100, 101, 204, 304)\
871 or request.method == 'HEAD': 957 or environ['REQUEST_METHOD'] == 'HEAD':
872 if hasattr(out, 'close'): out.close() 958 if hasattr(out, 'close'): out.close()
873 out = [] 959 out = []
874 if isinstance(response._status_line, unicode): 960 start_response(response._status_line, response.headerlist)
875 response._status_line = str(response._status_line)
876 start_response(response._status_line, list(response.iter_headers()))
877 return out 961 return out
878 except (KeyboardInterrupt, SystemExit, MemoryError): 962 except (KeyboardInterrupt, SystemExit, MemoryError):
879 raise 963 raise
880 except Exception: 964 except Exception:
881 if not self.catchall: raise 965 if not self.catchall: raise
885 err += '<h2>Error:</h2>\n<pre>\n%s\n</pre>\n' \ 969 err += '<h2>Error:</h2>\n<pre>\n%s\n</pre>\n' \
886 '<h2>Traceback:</h2>\n<pre>\n%s\n</pre>\n' \ 970 '<h2>Traceback:</h2>\n<pre>\n%s\n</pre>\n' \
887 % (html_escape(repr(_e())), html_escape(format_exc())) 971 % (html_escape(repr(_e())), html_escape(format_exc()))
888 environ['wsgi.errors'].write(err) 972 environ['wsgi.errors'].write(err)
889 headers = [('Content-Type', 'text/html; charset=UTF-8')] 973 headers = [('Content-Type', 'text/html; charset=UTF-8')]
890 start_response('500 INTERNAL SERVER ERROR', headers) 974 start_response('500 INTERNAL SERVER ERROR', headers, sys.exc_info())
891 return [tob(err)] 975 return [tob(err)]
892 976
893 def __call__(self, environ, start_response): 977 def __call__(self, environ, start_response):
894 ''' Each instance of :class:'Bottle' is a WSGI application. ''' 978 ''' Each instance of :class:'Bottle' is a WSGI application. '''
895 return self.wsgi(environ, start_response) 979 return self.wsgi(environ, start_response)
900 984
901 985
902 ############################################################################### 986 ###############################################################################
903 # HTTP and WSGI Tools ########################################################## 987 # HTTP and WSGI Tools ##########################################################
904 ############################################################################### 988 ###############################################################################
905
906 989
907 class BaseRequest(object): 990 class BaseRequest(object):
908 """ A wrapper for WSGI environment dictionaries that adds a lot of 991 """ A wrapper for WSGI environment dictionaries that adds a lot of
909 convenient access methods and properties. Most of them are read-only. 992 convenient access methods and properties. Most of them are read-only.
910 993
915 998
916 __slots__ = ('environ') 999 __slots__ = ('environ')
917 1000
918 #: Maximum size of memory buffer for :attr:`body` in bytes. 1001 #: Maximum size of memory buffer for :attr:`body` in bytes.
919 MEMFILE_MAX = 102400 1002 MEMFILE_MAX = 102400
920 #: Maximum number pr GET or POST parameters per request
921 MAX_PARAMS = 100
922 1003
923 def __init__(self, environ=None): 1004 def __init__(self, environ=None):
924 """ Wrap a WSGI environ dictionary. """ 1005 """ Wrap a WSGI environ dictionary. """
925 #: The wrapped WSGI environ dictionary. This is the only real attribute. 1006 #: The wrapped WSGI environ dictionary. This is the only real attribute.
926 #: All other attributes actually are read-only properties. 1007 #: All other attributes actually are read-only properties.
930 @DictProperty('environ', 'bottle.app', read_only=True) 1011 @DictProperty('environ', 'bottle.app', read_only=True)
931 def app(self): 1012 def app(self):
932 ''' Bottle application handling this request. ''' 1013 ''' Bottle application handling this request. '''
933 raise RuntimeError('This request is not connected to an application.') 1014 raise RuntimeError('This request is not connected to an application.')
934 1015
1016 @DictProperty('environ', 'bottle.route', read_only=True)
1017 def route(self):
1018 """ The bottle :class:`Route` object that matches this request. """
1019 raise RuntimeError('This request is not connected to a route.')
1020
1021 @DictProperty('environ', 'route.url_args', read_only=True)
1022 def url_args(self):
1023 """ The arguments extracted from the URL. """
1024 raise RuntimeError('This request is not connected to a route.')
1025
935 @property 1026 @property
936 def path(self): 1027 def path(self):
937 ''' The value of ``PATH_INFO`` with exactly one prefixed slash (to fix 1028 ''' The value of ``PATH_INFO`` with exactly one prefixed slash (to fix
938 broken clients and avoid the "empty path" edge case). ''' 1029 broken clients and avoid the "empty path" edge case). '''
939 return '/' + self.environ.get('PATH_INFO','').lstrip('/') 1030 return '/' + self.environ.get('PATH_INFO','').lstrip('/')
955 1046
956 @DictProperty('environ', 'bottle.request.cookies', read_only=True) 1047 @DictProperty('environ', 'bottle.request.cookies', read_only=True)
957 def cookies(self): 1048 def cookies(self):
958 """ Cookies parsed into a :class:`FormsDict`. Signed cookies are NOT 1049 """ Cookies parsed into a :class:`FormsDict`. Signed cookies are NOT
959 decoded. Use :meth:`get_cookie` if you expect signed cookies. """ 1050 decoded. Use :meth:`get_cookie` if you expect signed cookies. """
960 cookies = SimpleCookie(self.environ.get('HTTP_COOKIE','')) 1051 cookies = SimpleCookie(self.environ.get('HTTP_COOKIE','')).values()
961 cookies = list(cookies.values())[:self.MAX_PARAMS]
962 return FormsDict((c.key, c.value) for c in cookies) 1052 return FormsDict((c.key, c.value) for c in cookies)
963 1053
964 def get_cookie(self, key, default=None, secret=None): 1054 def get_cookie(self, key, default=None, secret=None):
965 """ Return the content of a cookie. To read a `Signed Cookie`, the 1055 """ Return the content of a cookie. To read a `Signed Cookie`, the
966 `secret` must match the one used to create the cookie (see 1056 `secret` must match the one used to create the cookie (see
976 def query(self): 1066 def query(self):
977 ''' The :attr:`query_string` parsed into a :class:`FormsDict`. These 1067 ''' The :attr:`query_string` parsed into a :class:`FormsDict`. These
978 values are sometimes called "URL arguments" or "GET parameters", but 1068 values are sometimes called "URL arguments" or "GET parameters", but
979 not to be confused with "URL wildcards" as they are provided by the 1069 not to be confused with "URL wildcards" as they are provided by the
980 :class:`Router`. ''' 1070 :class:`Router`. '''
981 pairs = parse_qsl(self.query_string, keep_blank_values=True)
982 get = self.environ['bottle.get'] = FormsDict() 1071 get = self.environ['bottle.get'] = FormsDict()
983 for key, value in pairs[:self.MAX_PARAMS]: 1072 pairs = _parse_qsl(self.environ.get('QUERY_STRING', ''))
1073 for key, value in pairs:
984 get[key] = value 1074 get[key] = value
985 return get 1075 return get
986 1076
987 @DictProperty('environ', 'bottle.request.forms', read_only=True) 1077 @DictProperty('environ', 'bottle.request.forms', read_only=True)
988 def forms(self): 1078 def forms(self):
989 """ Form values parsed from an `url-encoded` or `multipart/form-data` 1079 """ Form values parsed from an `url-encoded` or `multipart/form-data`
990 encoded POST or PUT request body. The result is retuned as a 1080 encoded POST or PUT request body. The result is returned as a
991 :class:`FormsDict`. All keys and values are strings. File uploads 1081 :class:`FormsDict`. All keys and values are strings. File uploads
992 are stored separately in :attr:`files`. """ 1082 are stored separately in :attr:`files`. """
993 forms = FormsDict() 1083 forms = FormsDict()
994 for name, item in self.POST.allitems(): 1084 for name, item in self.POST.allitems():
995 if not hasattr(item, 'filename'): 1085 if not isinstance(item, FileUpload):
996 forms[name] = item 1086 forms[name] = item
997 return forms 1087 return forms
998 1088
999 @DictProperty('environ', 'bottle.request.params', read_only=True) 1089 @DictProperty('environ', 'bottle.request.params', read_only=True)
1000 def params(self): 1090 def params(self):
1007 params[key] = value 1097 params[key] = value
1008 return params 1098 return params
1009 1099
1010 @DictProperty('environ', 'bottle.request.files', read_only=True) 1100 @DictProperty('environ', 'bottle.request.files', read_only=True)
1011 def files(self): 1101 def files(self):
1012 """ File uploads parsed from an `url-encoded` or `multipart/form-data` 1102 """ File uploads parsed from `multipart/form-data` encoded POST or PUT
1013 encoded POST or PUT request body. The values are instances of 1103 request body. The values are instances of :class:`FileUpload`.
1014 :class:`cgi.FieldStorage`. The most important attributes are: 1104
1015
1016 filename
1017 The filename, if specified; otherwise None; this is the client
1018 side filename, *not* the file name on which it is stored (that's
1019 a temporary file you don't deal with)
1020 file
1021 The file(-like) object from which you can read the data.
1022 value
1023 The value as a *string*; for file uploads, this transparently
1024 reads the file every time you request the value. Do not do this
1025 on big files.
1026 """ 1105 """
1027 files = FormsDict() 1106 files = FormsDict()
1028 for name, item in self.POST.allitems(): 1107 for name, item in self.POST.allitems():
1029 if hasattr(item, 'filename'): 1108 if isinstance(item, FileUpload):
1030 files[name] = item 1109 files[name] = item
1031 return files 1110 return files
1032 1111
1033 @DictProperty('environ', 'bottle.request.json', read_only=True) 1112 @DictProperty('environ', 'bottle.request.json', read_only=True)
1034 def json(self): 1113 def json(self):
1035 ''' If the ``Content-Type`` header is ``application/json``, this 1114 ''' If the ``Content-Type`` header is ``application/json``, this
1036 property holds the parsed content of the request body. Only requests 1115 property holds the parsed content of the request body. Only requests
1037 smaller than :attr:`MEMFILE_MAX` are processed to avoid memory 1116 smaller than :attr:`MEMFILE_MAX` are processed to avoid memory
1038 exhaustion. ''' 1117 exhaustion. '''
1039 if 'application/json' in self.environ.get('CONTENT_TYPE', '') \ 1118 ctype = self.environ.get('CONTENT_TYPE', '').lower().split(';')[0]
1040 and 0 < self.content_length < self.MEMFILE_MAX: 1119 if ctype == 'application/json':
1041 return json_loads(self.body.read(self.MEMFILE_MAX)) 1120 b = self._get_body_string()
1121 if not b:
1122 return None
1123 return json_loads(b)
1042 return None 1124 return None
1125
1126 def _iter_body(self, read, bufsize):
1127 maxread = max(0, self.content_length)
1128 while maxread:
1129 part = read(min(maxread, bufsize))
1130 if not part: break
1131 yield part
1132 maxread -= len(part)
1133
1134 def _iter_chunked(self, read, bufsize):
1135 err = HTTPError(400, 'Error while parsing chunked transfer body.')
1136 rn, sem, bs = tob('\r\n'), tob(';'), tob('')
1137 while True:
1138 header = read(1)
1139 while header[-2:] != rn:
1140 c = read(1)
1141 header += c
1142 if not c: raise err
1143 if len(header) > bufsize: raise err
1144 size, _, _ = header.partition(sem)
1145 try:
1146 maxread = int(tonat(size.strip()), 16)
1147 except ValueError:
1148 raise err
1149 if maxread == 0: break
1150 buff = bs
1151 while maxread > 0:
1152 if not buff:
1153 buff = read(min(maxread, bufsize))
1154 part, buff = buff[:maxread], buff[maxread:]
1155 if not part: raise err
1156 yield part
1157 maxread -= len(part)
1158 if read(2) != rn:
1159 raise err
1043 1160
1044 @DictProperty('environ', 'bottle.request.body', read_only=True) 1161 @DictProperty('environ', 'bottle.request.body', read_only=True)
1045 def _body(self): 1162 def _body(self):
1046 maxread = max(0, self.content_length) 1163 body_iter = self._iter_chunked if self.chunked else self._iter_body
1047 stream = self.environ['wsgi.input'] 1164 read_func = self.environ['wsgi.input'].read
1048 body = BytesIO() if maxread < self.MEMFILE_MAX else TemporaryFile(mode='w+b') 1165 body, body_size, is_temp_file = BytesIO(), 0, False
1049 while maxread > 0: 1166 for part in body_iter(read_func, self.MEMFILE_MAX):
1050 part = stream.read(min(maxread, self.MEMFILE_MAX))
1051 if not part: break
1052 body.write(part) 1167 body.write(part)
1053 maxread -= len(part) 1168 body_size += len(part)
1169 if not is_temp_file and body_size > self.MEMFILE_MAX:
1170 body, tmp = TemporaryFile(mode='w+b'), body
1171 body.write(tmp.getvalue())
1172 del tmp
1173 is_temp_file = True
1054 self.environ['wsgi.input'] = body 1174 self.environ['wsgi.input'] = body
1055 body.seek(0) 1175 body.seek(0)
1056 return body 1176 return body
1177
1178 def _get_body_string(self):
1179 ''' read body until content-length or MEMFILE_MAX into a string. Raise
1180 HTTPError(413) on requests that are to large. '''
1181 clen = self.content_length
1182 if clen > self.MEMFILE_MAX:
1183 raise HTTPError(413, 'Request to large')
1184 if clen < 0: clen = self.MEMFILE_MAX + 1
1185 data = self.body.read(clen)
1186 if len(data) > self.MEMFILE_MAX: # Fail fast
1187 raise HTTPError(413, 'Request to large')
1188 return data
1057 1189
1058 @property 1190 @property
1059 def body(self): 1191 def body(self):
1060 """ The HTTP request body as a seek-able file-like object. Depending on 1192 """ The HTTP request body as a seek-able file-like object. Depending on
1061 :attr:`MEMFILE_MAX`, this is either a temporary file or a 1193 :attr:`MEMFILE_MAX`, this is either a temporary file or a
1063 time reads and replaces the ``wsgi.input`` environ variable. 1195 time reads and replaces the ``wsgi.input`` environ variable.
1064 Subsequent accesses just do a `seek(0)` on the file object. """ 1196 Subsequent accesses just do a `seek(0)` on the file object. """
1065 self._body.seek(0) 1197 self._body.seek(0)
1066 return self._body 1198 return self._body
1067 1199
1200 @property
1201 def chunked(self):
1202 ''' True if Chunked transfer encoding was. '''
1203 return 'chunked' in self.environ.get('HTTP_TRANSFER_ENCODING', '').lower()
1204
1068 #: An alias for :attr:`query`. 1205 #: An alias for :attr:`query`.
1069 GET = query 1206 GET = query
1070 1207
1071 @DictProperty('environ', 'bottle.request.post', read_only=True) 1208 @DictProperty('environ', 'bottle.request.post', read_only=True)
1072 def POST(self): 1209 def POST(self):
1073 """ The values of :attr:`forms` and :attr:`files` combined into a single 1210 """ The values of :attr:`forms` and :attr:`files` combined into a single
1074 :class:`FormsDict`. Values are either strings (form values) or 1211 :class:`FormsDict`. Values are either strings (form values) or
1075 instances of :class:`cgi.FieldStorage` (file uploads). 1212 instances of :class:`cgi.FieldStorage` (file uploads).
1076 """ 1213 """
1077 post = FormsDict() 1214 post = FormsDict()
1215 # We default to application/x-www-form-urlencoded for everything that
1216 # is not multipart and take the fast path (also: 3.1 workaround)
1217 if not self.content_type.startswith('multipart/'):
1218 pairs = _parse_qsl(tonat(self._get_body_string(), 'latin1'))
1219 for key, value in pairs:
1220 post[key] = value
1221 return post
1222
1078 safe_env = {'QUERY_STRING':''} # Build a safe environment for cgi 1223 safe_env = {'QUERY_STRING':''} # Build a safe environment for cgi
1079 for key in ('REQUEST_METHOD', 'CONTENT_TYPE', 'CONTENT_LENGTH'): 1224 for key in ('REQUEST_METHOD', 'CONTENT_TYPE', 'CONTENT_LENGTH'):
1080 if key in self.environ: safe_env[key] = self.environ[key] 1225 if key in self.environ: safe_env[key] = self.environ[key]
1081 if NCTextIOWrapper: 1226 args = dict(fp=self.body, environ=safe_env, keep_blank_values=True)
1082 fb = NCTextIOWrapper(self.body, encoding='ISO-8859-1', newline='\n') 1227 if py31:
1083 else: 1228 args['fp'] = NCTextIOWrapper(args['fp'], encoding='utf8',
1084 fb = self.body 1229 newline='\n')
1085 data = cgi.FieldStorage(fp=fb, environ=safe_env, keep_blank_values=True) 1230 elif py3k:
1086 for item in (data.list or [])[:self.MAX_PARAMS]: 1231 args['encoding'] = 'utf8'
1087 post[item.name] = item if item.filename else item.value 1232 data = cgi.FieldStorage(**args)
1233 self['_cgi.FieldStorage'] = data #http://bugs.python.org/issue18394#msg207958
1234 data = data.list or []
1235 for item in data:
1236 if item.filename:
1237 post[item.name] = FileUpload(item.file, item.name,
1238 item.filename, item.headers)
1239 else:
1240 post[item.name] = item.value
1088 return post 1241 return post
1089
1090 @property
1091 def COOKIES(self):
1092 ''' Alias for :attr:`cookies` (deprecated). '''
1093 depr('BaseRequest.COOKIES was renamed to BaseRequest.cookies (lowercase).')
1094 return self.cookies
1095 1242
1096 @property 1243 @property
1097 def url(self): 1244 def url(self):
1098 """ The full request URI including hostname and scheme. If your app 1245 """ The full request URI including hostname and scheme. If your app
1099 lives behind a reverse proxy or load balancer and you get confusing 1246 lives behind a reverse proxy or load balancer and you get confusing
1106 ''' The :attr:`url` string as an :class:`urlparse.SplitResult` tuple. 1253 ''' The :attr:`url` string as an :class:`urlparse.SplitResult` tuple.
1107 The tuple contains (scheme, host, path, query_string and fragment), 1254 The tuple contains (scheme, host, path, query_string and fragment),
1108 but the fragment is always empty because it is not visible to the 1255 but the fragment is always empty because it is not visible to the
1109 server. ''' 1256 server. '''
1110 env = self.environ 1257 env = self.environ
1111 http = env.get('wsgi.url_scheme', 'http') 1258 http = env.get('HTTP_X_FORWARDED_PROTO') or env.get('wsgi.url_scheme', 'http')
1112 host = env.get('HTTP_X_FORWARDED_HOST') or env.get('HTTP_HOST') 1259 host = env.get('HTTP_X_FORWARDED_HOST') or env.get('HTTP_HOST')
1113 if not host: 1260 if not host:
1114 # HTTP 1.1 requires a Host-header. This is for HTTP/1.0 clients. 1261 # HTTP 1.1 requires a Host-header. This is for HTTP/1.0 clients.
1115 host = env.get('SERVER_NAME', '127.0.0.1') 1262 host = env.get('SERVER_NAME', '127.0.0.1')
1116 port = env.get('SERVER_PORT') 1263 port = env.get('SERVER_PORT')
1153 def content_length(self): 1300 def content_length(self):
1154 ''' The request body length as an integer. The client is responsible to 1301 ''' The request body length as an integer. The client is responsible to
1155 set this header. Otherwise, the real length of the body is unknown 1302 set this header. Otherwise, the real length of the body is unknown
1156 and -1 is returned. In this case, :attr:`body` will be empty. ''' 1303 and -1 is returned. In this case, :attr:`body` will be empty. '''
1157 return int(self.environ.get('CONTENT_LENGTH') or -1) 1304 return int(self.environ.get('CONTENT_LENGTH') or -1)
1305
1306 @property
1307 def content_type(self):
1308 ''' The Content-Type header as a lowercase-string (default: empty). '''
1309 return self.environ.get('CONTENT_TYPE', '').lower()
1158 1310
1159 @property 1311 @property
1160 def is_xhr(self): 1312 def is_xhr(self):
1161 ''' True if the request was triggered by a XMLHttpRequest. This only 1313 ''' True if the request was triggered by a XMLHttpRequest. This only
1162 works with JavaScript libraries that support the `X-Requested-With` 1314 works with JavaScript libraries that support the `X-Requested-With`
1237 ''' Search in self.environ for additional user defined attributes. ''' 1389 ''' Search in self.environ for additional user defined attributes. '''
1238 try: 1390 try:
1239 var = self.environ['bottle.request.ext.%s'%name] 1391 var = self.environ['bottle.request.ext.%s'%name]
1240 return var.__get__(self) if hasattr(var, '__get__') else var 1392 return var.__get__(self) if hasattr(var, '__get__') else var
1241 except KeyError: 1393 except KeyError:
1242 raise AttributeError('Attribute %r not defined.' % name) 1394 raise AttributeError('Attribute %r not defined.' % name)
1243 1395
1244 def __setattr__(self, name, value): 1396 def __setattr__(self, name, value):
1245 if name == 'environ': return object.__setattr__(self, name, value) 1397 if name == 'environ': return object.__setattr__(self, name, value)
1246 self.environ['bottle.request.ext.%s'%name] = value 1398 self.environ['bottle.request.ext.%s'%name] = value
1247 1399
1274 """ Storage class for a response body as well as headers and cookies. 1426 """ Storage class for a response body as well as headers and cookies.
1275 1427
1276 This class does support dict-like case-insensitive item-access to 1428 This class does support dict-like case-insensitive item-access to
1277 headers, but is NOT a dict. Most notably, iterating over a response 1429 headers, but is NOT a dict. Most notably, iterating over a response
1278 yields parts of the body and not the headers. 1430 yields parts of the body and not the headers.
1431
1432 :param body: The response body as one of the supported types.
1433 :param status: Either an HTTP status code (e.g. 200) or a status line
1434 including the reason phrase (e.g. '200 OK').
1435 :param headers: A dictionary or a list of name-value pairs.
1436
1437 Additional keyword arguments are added to the list of headers.
1438 Underscores in the header name are replaced with dashes.
1279 """ 1439 """
1280 1440
1281 default_status = 200 1441 default_status = 200
1282 default_content_type = 'text/html; charset=UTF-8' 1442 default_content_type = 'text/html; charset=UTF-8'
1283 1443
1287 204: set(('Content-Type',)), 1447 204: set(('Content-Type',)),
1288 304: set(('Allow', 'Content-Encoding', 'Content-Language', 1448 304: set(('Allow', 'Content-Encoding', 'Content-Language',
1289 'Content-Length', 'Content-Range', 'Content-Type', 1449 'Content-Length', 'Content-Range', 'Content-Type',
1290 'Content-Md5', 'Last-Modified'))} 1450 'Content-Md5', 'Last-Modified'))}
1291 1451
1292 def __init__(self, body='', status=None, **headers): 1452 def __init__(self, body='', status=None, headers=None, **more_headers):
1293 self._status_line = None
1294 self._status_code = None
1295 self._cookies = None 1453 self._cookies = None
1296 self._headers = {'Content-Type': [self.default_content_type]} 1454 self._headers = {}
1297 self.body = body 1455 self.body = body
1298 self.status = status or self.default_status 1456 self.status = status or self.default_status
1299 if headers: 1457 if headers:
1300 for name, value in headers.items(): 1458 if isinstance(headers, dict):
1301 self[name] = value 1459 headers = headers.items()
1302 1460 for name, value in headers:
1303 def copy(self): 1461 self.add_header(name, value)
1462 if more_headers:
1463 for name, value in more_headers.items():
1464 self.add_header(name, value)
1465
1466 def copy(self, cls=None):
1304 ''' Returns a copy of self. ''' 1467 ''' Returns a copy of self. '''
1305 copy = Response() 1468 cls = cls or BaseResponse
1469 assert issubclass(cls, BaseResponse)
1470 copy = cls()
1306 copy.status = self.status 1471 copy.status = self.status
1307 copy._headers = dict((k, v[:]) for (k, v) in self._headers.items()) 1472 copy._headers = dict((k, v[:]) for (k, v) in self._headers.items())
1473 if self._cookies:
1474 copy._cookies = SimpleCookie()
1475 copy._cookies.load(self._cookies.output(header=''))
1308 return copy 1476 return copy
1309 1477
1310 def __iter__(self): 1478 def __iter__(self):
1311 return iter(self.body) 1479 return iter(self.body)
1312 1480
1332 code = int(status.split()[0]) 1500 code = int(status.split()[0])
1333 else: 1501 else:
1334 raise ValueError('String status line without a reason phrase.') 1502 raise ValueError('String status line without a reason phrase.')
1335 if not 100 <= code <= 999: raise ValueError('Status code out of range.') 1503 if not 100 <= code <= 999: raise ValueError('Status code out of range.')
1336 self._status_code = code 1504 self._status_code = code
1337 self._status_line = status or ('%d Unknown' % code) 1505 self._status_line = str(status or ('%d Unknown' % code))
1338 1506
1339 def _get_status(self): 1507 def _get_status(self):
1340 return self._status_line 1508 return self._status_line
1341 1509
1342 status = property(_get_status, _set_status, None, 1510 status = property(_get_status, _set_status, None,
1349 1517
1350 @property 1518 @property
1351 def headers(self): 1519 def headers(self):
1352 ''' An instance of :class:`HeaderDict`, a case-insensitive dict-like 1520 ''' An instance of :class:`HeaderDict`, a case-insensitive dict-like
1353 view on the response headers. ''' 1521 view on the response headers. '''
1354 self.__dict__['headers'] = hdict = HeaderDict() 1522 hdict = HeaderDict()
1355 hdict.dict = self._headers 1523 hdict.dict = self._headers
1356 return hdict 1524 return hdict
1357 1525
1358 def __contains__(self, name): return _hkey(name) in self._headers 1526 def __contains__(self, name): return _hkey(name) in self._headers
1359 def __delitem__(self, name): del self._headers[_hkey(name)] 1527 def __delitem__(self, name): del self._headers[_hkey(name)]
1363 def get_header(self, name, default=None): 1531 def get_header(self, name, default=None):
1364 ''' Return the value of a previously defined header. If there is no 1532 ''' Return the value of a previously defined header. If there is no
1365 header with that name, return a default value. ''' 1533 header with that name, return a default value. '''
1366 return self._headers.get(_hkey(name), [default])[-1] 1534 return self._headers.get(_hkey(name), [default])[-1]
1367 1535
1368 def set_header(self, name, value, append=False): 1536 def set_header(self, name, value):
1369 ''' Create a new response header, replacing any previously defined 1537 ''' Create a new response header, replacing any previously defined
1370 headers with the same name. ''' 1538 headers with the same name. '''
1371 if append: 1539 self._headers[_hkey(name)] = [str(value)]
1372 self.add_header(name, value)
1373 else:
1374 self._headers[_hkey(name)] = [str(value)]
1375 1540
1376 def add_header(self, name, value): 1541 def add_header(self, name, value):
1377 ''' Add an additional response header, not removing duplicates. ''' 1542 ''' Add an additional response header, not removing duplicates. '''
1378 self._headers.setdefault(_hkey(name), []).append(str(value)) 1543 self._headers.setdefault(_hkey(name), []).append(str(value))
1379 1544
1380 def iter_headers(self): 1545 def iter_headers(self):
1381 ''' Yield (header, value) tuples, skipping headers that are not 1546 ''' Yield (header, value) tuples, skipping headers that are not
1382 allowed with the current response status code. ''' 1547 allowed with the current response status code. '''
1383 headers = self._headers.items()
1384 bad_headers = self.bad_headers.get(self._status_code)
1385 if bad_headers:
1386 headers = [h for h in headers if h[0] not in bad_headers]
1387 for name, values in headers:
1388 for value in values:
1389 yield name, value
1390 if self._cookies:
1391 for c in self._cookies.values():
1392 yield 'Set-Cookie', c.OutputString()
1393
1394 def wsgiheader(self):
1395 depr('The wsgiheader method is deprecated. See headerlist.') #0.10
1396 return self.headerlist 1548 return self.headerlist
1397 1549
1398 @property 1550 @property
1399 def headerlist(self): 1551 def headerlist(self):
1400 ''' WSGI conform list of (header, value) tuples. ''' 1552 ''' WSGI conform list of (header, value) tuples. '''
1401 return list(self.iter_headers()) 1553 out = []
1554 headers = list(self._headers.items())
1555 if 'Content-Type' not in self._headers:
1556 headers.append(('Content-Type', [self.default_content_type]))
1557 if self._status_code in self.bad_headers:
1558 bad_headers = self.bad_headers[self._status_code]
1559 headers = [h for h in headers if h[0] not in bad_headers]
1560 out += [(name, val) for name, vals in headers for val in vals]
1561 if self._cookies:
1562 for c in self._cookies.values():
1563 out.append(('Set-Cookie', c.OutputString()))
1564 return out
1402 1565
1403 content_type = HeaderProperty('Content-Type') 1566 content_type = HeaderProperty('Content-Type')
1404 content_length = HeaderProperty('Content-Length', reader=int) 1567 content_length = HeaderProperty('Content-Length', reader=int)
1568 expires = HeaderProperty('Expires',
1569 reader=lambda x: datetime.utcfromtimestamp(parse_date(x)),
1570 writer=lambda x: http_date(x))
1405 1571
1406 @property 1572 @property
1407 def charset(self): 1573 def charset(self, default='UTF-8'):
1408 """ Return the charset specified in the content-type header (default: utf8). """ 1574 """ Return the charset specified in the content-type header (default: utf8). """
1409 if 'charset=' in self.content_type: 1575 if 'charset=' in self.content_type:
1410 return self.content_type.split('charset=')[-1].split(';')[0].strip() 1576 return self.content_type.split('charset=')[-1].split(';')[0].strip()
1411 return 'UTF-8' 1577 return default
1412
1413 @property
1414 def COOKIES(self):
1415 """ A dict-like SimpleCookie instance. This should not be used directly.
1416 See :meth:`set_cookie`. """
1417 depr('The COOKIES dict is deprecated. Use `set_cookie()` instead.') # 0.10
1418 if not self._cookies:
1419 self._cookies = SimpleCookie()
1420 return self._cookies
1421 1578
1422 def set_cookie(self, name, value, secret=None, **options): 1579 def set_cookie(self, name, value, secret=None, **options):
1423 ''' Create a new cookie or replace an old one. If the `secret` parameter is 1580 ''' Create a new cookie or replace an old one. If the `secret` parameter is
1424 set, create a `Signed Cookie` (described below). 1581 set, create a `Signed Cookie` (described below).
1425 1582
1486 out = '' 1643 out = ''
1487 for name, value in self.headerlist: 1644 for name, value in self.headerlist:
1488 out += '%s: %s\n' % (name.title(), value.strip()) 1645 out += '%s: %s\n' % (name.title(), value.strip())
1489 return out 1646 return out
1490 1647
1491 #: Thread-local storage for :class:`LocalRequest` and :class:`LocalResponse` 1648
1492 #: attributes. 1649 def local_property(name=None):
1493 _lctx = threading.local() 1650 if name: depr('local_property() is deprecated and will be removed.') #0.12
1494 1651 ls = threading.local()
1495 def local_property(name):
1496 def fget(self): 1652 def fget(self):
1497 try: 1653 try: return ls.var
1498 return getattr(_lctx, name)
1499 except AttributeError: 1654 except AttributeError:
1500 raise RuntimeError("Request context not initialized.") 1655 raise RuntimeError("Request context not initialized.")
1501 def fset(self, value): setattr(_lctx, name, value) 1656 def fset(self, value): ls.var = value
1502 def fdel(self): delattr(_lctx, name) 1657 def fdel(self): del ls.var
1503 return property(fget, fset, fdel, 1658 return property(fget, fset, fdel, 'Thread-local property')
1504 'Thread-local property stored in :data:`_lctx.%s`' % name)
1505 1659
1506 1660
1507 class LocalRequest(BaseRequest): 1661 class LocalRequest(BaseRequest):
1508 ''' A thread-local subclass of :class:`BaseRequest` with a different 1662 ''' A thread-local subclass of :class:`BaseRequest` with a different
1509 set of attribues for each thread. There is usually only one global 1663 set of attributes for each thread. There is usually only one global
1510 instance of this class (:data:`request`). If accessed during a 1664 instance of this class (:data:`request`). If accessed during a
1511 request/response cycle, this instance always refers to the *current* 1665 request/response cycle, this instance always refers to the *current*
1512 request (even on a multithreaded server). ''' 1666 request (even on a multithreaded server). '''
1513 bind = BaseRequest.__init__ 1667 bind = BaseRequest.__init__
1514 environ = local_property('request_environ') 1668 environ = local_property()
1515 1669
1516 1670
1517 class LocalResponse(BaseResponse): 1671 class LocalResponse(BaseResponse):
1518 ''' A thread-local subclass of :class:`BaseResponse` with a different 1672 ''' A thread-local subclass of :class:`BaseResponse` with a different
1519 set of attribues for each thread. There is usually only one global 1673 set of attributes for each thread. There is usually only one global
1520 instance of this class (:data:`response`). Its attributes are used 1674 instance of this class (:data:`response`). Its attributes are used
1521 to build the HTTP response at the end of the request/response cycle. 1675 to build the HTTP response at the end of the request/response cycle.
1522 ''' 1676 '''
1523 bind = BaseResponse.__init__ 1677 bind = BaseResponse.__init__
1524 _status_line = local_property('response_status_line') 1678 _status_line = local_property()
1525 _status_code = local_property('response_status_code') 1679 _status_code = local_property()
1526 _cookies = local_property('response_cookies') 1680 _cookies = local_property()
1527 _headers = local_property('response_headers') 1681 _headers = local_property()
1528 body = local_property('response_body') 1682 body = local_property()
1529 1683
1530 Response = LocalResponse # BC 0.9 1684
1531 Request = LocalRequest # BC 0.9 1685 Request = BaseRequest
1532 1686 Response = BaseResponse
1687
1688
1689 class HTTPResponse(Response, BottleException):
1690 def __init__(self, body='', status=None, headers=None, **more_headers):
1691 super(HTTPResponse, self).__init__(body, status, headers, **more_headers)
1692
1693 def apply(self, response):
1694 response._status_code = self._status_code
1695 response._status_line = self._status_line
1696 response._headers = self._headers
1697 response._cookies = self._cookies
1698 response.body = self.body
1699
1700
1701 class HTTPError(HTTPResponse):
1702 default_status = 500
1703 def __init__(self, status=None, body=None, exception=None, traceback=None,
1704 **options):
1705 self.exception = exception
1706 self.traceback = traceback
1707 super(HTTPError, self).__init__(body, status, **options)
1533 1708
1534 1709
1535 1710
1536 1711
1537 1712
1539 # Plugins ###################################################################### 1714 # Plugins ######################################################################
1540 ############################################################################### 1715 ###############################################################################
1541 1716
1542 class PluginError(BottleException): pass 1717 class PluginError(BottleException): pass
1543 1718
1719
1544 class JSONPlugin(object): 1720 class JSONPlugin(object):
1545 name = 'json' 1721 name = 'json'
1546 api = 2 1722 api = 2
1547 1723
1548 def __init__(self, json_dumps=json_dumps): 1724 def __init__(self, json_dumps=json_dumps):
1549 self.json_dumps = json_dumps 1725 self.json_dumps = json_dumps
1550 1726
1551 def apply(self, callback, context): 1727 def apply(self, callback, route):
1552 dumps = self.json_dumps 1728 dumps = self.json_dumps
1553 if not dumps: return callback 1729 if not dumps: return callback
1554 def wrapper(*a, **ka): 1730 def wrapper(*a, **ka):
1555 rv = callback(*a, **ka) 1731 try:
1732 rv = callback(*a, **ka)
1733 except HTTPError:
1734 rv = _e()
1735
1556 if isinstance(rv, dict): 1736 if isinstance(rv, dict):
1557 #Attempt to serialize, raises exception on failure 1737 #Attempt to serialize, raises exception on failure
1558 json_response = dumps(rv) 1738 json_response = dumps(rv)
1559 #Set content type only if serialization succesful 1739 #Set content type only if serialization succesful
1560 response.content_type = 'application/json' 1740 response.content_type = 'application/json'
1561 return json_response 1741 return json_response
1742 elif isinstance(rv, HTTPResponse) and isinstance(rv.body, dict):
1743 rv.body = dumps(rv.body)
1744 rv.content_type = 'application/json'
1562 return rv 1745 return rv
1563 return wrapper 1746
1564
1565
1566 class HooksPlugin(object):
1567 name = 'hooks'
1568 api = 2
1569
1570 _names = 'before_request', 'after_request', 'app_reset'
1571
1572 def __init__(self):
1573 self.hooks = dict((name, []) for name in self._names)
1574 self.app = None
1575
1576 def _empty(self):
1577 return not (self.hooks['before_request'] or self.hooks['after_request'])
1578
1579 def setup(self, app):
1580 self.app = app
1581
1582 def add(self, name, func):
1583 ''' Attach a callback to a hook. '''
1584 was_empty = self._empty()
1585 self.hooks.setdefault(name, []).append(func)
1586 if self.app and was_empty and not self._empty(): self.app.reset()
1587
1588 def remove(self, name, func):
1589 ''' Remove a callback from a hook. '''
1590 was_empty = self._empty()
1591 if name in self.hooks and func in self.hooks[name]:
1592 self.hooks[name].remove(func)
1593 if self.app and not was_empty and self._empty(): self.app.reset()
1594
1595 def trigger(self, name, *a, **ka):
1596 ''' Trigger a hook and return a list of results. '''
1597 hooks = self.hooks[name]
1598 if ka.pop('reversed', False): hooks = hooks[::-1]
1599 return [hook(*a, **ka) for hook in hooks]
1600
1601 def apply(self, callback, context):
1602 if self._empty(): return callback
1603 def wrapper(*a, **ka):
1604 self.trigger('before_request')
1605 rv = callback(*a, **ka)
1606 self.trigger('after_request', reversed=True)
1607 return rv
1608 return wrapper 1747 return wrapper
1609 1748
1610 1749
1611 class TemplatePlugin(object): 1750 class TemplatePlugin(object):
1612 ''' This plugin applies the :func:`view` decorator to all routes with a 1751 ''' This plugin applies the :func:`view` decorator to all routes with a
1618 1757
1619 def apply(self, callback, route): 1758 def apply(self, callback, route):
1620 conf = route.config.get('template') 1759 conf = route.config.get('template')
1621 if isinstance(conf, (tuple, list)) and len(conf) == 2: 1760 if isinstance(conf, (tuple, list)) and len(conf) == 2:
1622 return view(conf[0], **conf[1])(callback) 1761 return view(conf[0], **conf[1])(callback)
1623 elif isinstance(conf, str) and 'template_opts' in route.config:
1624 depr('The `template_opts` parameter is deprecated.') #0.9
1625 return view(conf, **route.config['template_opts'])(callback)
1626 elif isinstance(conf, str): 1762 elif isinstance(conf, str):
1627 return view(conf)(callback) 1763 return view(conf)(callback)
1628 else: 1764 else:
1629 return callback 1765 return callback
1630 1766
1640 '__all__': [], '__loader__': self}) 1776 '__all__': [], '__loader__': self})
1641 sys.meta_path.append(self) 1777 sys.meta_path.append(self)
1642 1778
1643 def find_module(self, fullname, path=None): 1779 def find_module(self, fullname, path=None):
1644 if '.' not in fullname: return 1780 if '.' not in fullname: return
1645 packname, modname = fullname.rsplit('.', 1) 1781 packname = fullname.rsplit('.', 1)[0]
1646 if packname != self.name: return 1782 if packname != self.name: return
1647 return self 1783 return self
1648 1784
1649 def load_module(self, fullname): 1785 def load_module(self, fullname):
1650 if fullname in sys.modules: return sys.modules[fullname] 1786 if fullname in sys.modules: return sys.modules[fullname]
1651 packname, modname = fullname.rsplit('.', 1) 1787 modname = fullname.rsplit('.', 1)[1]
1652 realname = self.impmask % modname 1788 realname = self.impmask % modname
1653 __import__(realname) 1789 __import__(realname)
1654 module = sys.modules[fullname] = sys.modules[realname] 1790 module = sys.modules[fullname] = sys.modules[realname]
1655 setattr(self.module, modname, module) 1791 setattr(self.module, modname, module)
1656 module.__loader__ = self 1792 module.__loader__ = self
1737 #: Aliases for WTForms to mimic other multi-dict APIs (Django) 1873 #: Aliases for WTForms to mimic other multi-dict APIs (Django)
1738 getone = get 1874 getone = get
1739 getlist = getall 1875 getlist = getall
1740 1876
1741 1877
1742
1743 class FormsDict(MultiDict): 1878 class FormsDict(MultiDict):
1744 ''' This :class:`MultiDict` subclass is used to store request form data. 1879 ''' This :class:`MultiDict` subclass is used to store request form data.
1745 Additionally to the normal dict-like item access methods (which return 1880 Additionally to the normal dict-like item access methods (which return
1746 unmodified data as native strings), this container also supports 1881 unmodified data as native strings), this container also supports
1747 attribute-like access to its values. Attributes are automatically de- 1882 attribute-like access to its values. Attributes are automatically de-
1754 #: and then decoded to match :attr:`input_encoding`. 1889 #: and then decoded to match :attr:`input_encoding`.
1755 recode_unicode = True 1890 recode_unicode = True
1756 1891
1757 def _fix(self, s, encoding=None): 1892 def _fix(self, s, encoding=None):
1758 if isinstance(s, unicode) and self.recode_unicode: # Python 3 WSGI 1893 if isinstance(s, unicode) and self.recode_unicode: # Python 3 WSGI
1759 s = s.encode('latin1') 1894 return s.encode('latin1').decode(encoding or self.input_encoding)
1760 if isinstance(s, bytes): # Python 2 WSGI 1895 elif isinstance(s, bytes): # Python 2 WSGI
1761 return s.decode(encoding or self.input_encoding) 1896 return s.decode(encoding or self.input_encoding)
1762 return s 1897 else:
1898 return s
1763 1899
1764 def decode(self, encoding=None): 1900 def decode(self, encoding=None):
1765 ''' Returns a copy with all keys and values de- or recoded to match 1901 ''' Returns a copy with all keys and values de- or recoded to match
1766 :attr:`input_encoding`. Some libraries (e.g. WTForms) want a 1902 :attr:`input_encoding`. Some libraries (e.g. WTForms) want a
1767 unicode dictionary. ''' 1903 unicode dictionary. '''
1771 for key, value in self.allitems(): 1907 for key, value in self.allitems():
1772 copy.append(self._fix(key, enc), self._fix(value, enc)) 1908 copy.append(self._fix(key, enc), self._fix(value, enc))
1773 return copy 1909 return copy
1774 1910
1775 def getunicode(self, name, default=None, encoding=None): 1911 def getunicode(self, name, default=None, encoding=None):
1912 ''' Return the value as a unicode string, or the default. '''
1776 try: 1913 try:
1777 return self._fix(self[name], encoding) 1914 return self._fix(self[name], encoding)
1778 except (UnicodeError, KeyError): 1915 except (UnicodeError, KeyError):
1779 return default 1916 return default
1780 1917
1818 1955
1819 The API will remain stable even on changes to the relevant PEPs. 1956 The API will remain stable even on changes to the relevant PEPs.
1820 Currently PEP 333, 444 and 3333 are supported. (PEP 444 is the only one 1957 Currently PEP 333, 444 and 3333 are supported. (PEP 444 is the only one
1821 that uses non-native strings.) 1958 that uses non-native strings.)
1822 ''' 1959 '''
1823 #: List of keys that do not have a 'HTTP_' prefix. 1960 #: List of keys that do not have a ``HTTP_`` prefix.
1824 cgikeys = ('CONTENT_TYPE', 'CONTENT_LENGTH') 1961 cgikeys = ('CONTENT_TYPE', 'CONTENT_LENGTH')
1825 1962
1826 def __init__(self, environ): 1963 def __init__(self, environ):
1827 self.environ = environ 1964 self.environ = environ
1828 1965
1856 def keys(self): return [x for x in self] 1993 def keys(self): return [x for x in self]
1857 def __len__(self): return len(self.keys()) 1994 def __len__(self): return len(self.keys())
1858 def __contains__(self, key): return self._ekey(key) in self.environ 1995 def __contains__(self, key): return self._ekey(key) in self.environ
1859 1996
1860 1997
1998
1861 class ConfigDict(dict): 1999 class ConfigDict(dict):
1862 ''' A dict-subclass with some extras: You can access keys like attributes. 2000 ''' A dict-like configuration storage with additional support for
1863 Uppercase attributes create new ConfigDicts and act as name-spaces. 2001 namespaces, validators, meta-data, on_change listeners and more.
1864 Other missing attributes return None. Calling a ConfigDict updates its 2002
1865 values and returns itself. 2003 This storage is optimized for fast read access. Retrieving a key
1866 2004 or using non-altering dict methods (e.g. `dict.get()`) has no overhead
1867 >>> cfg = ConfigDict() 2005 compared to a native dict.
1868 >>> cfg.Namespace.value = 5
1869 >>> cfg.OtherNamespace(a=1, b=2)
1870 >>> cfg
1871 {'Namespace': {'value': 5}, 'OtherNamespace': {'a': 1, 'b': 2}}
1872 ''' 2006 '''
1873 2007 __slots__ = ('_meta', '_on_change')
2008
2009 class Namespace(DictMixin):
2010
2011 def __init__(self, config, namespace):
2012 self._config = config
2013 self._prefix = namespace
2014
2015 def __getitem__(self, key):
2016 depr('Accessing namespaces as dicts is discouraged. '
2017 'Only use flat item access: '
2018 'cfg["names"]["pace"]["key"] -> cfg["name.space.key"]') #0.12
2019 return self._config[self._prefix + '.' + key]
2020
2021 def __setitem__(self, key, value):
2022 self._config[self._prefix + '.' + key] = value
2023
2024 def __delitem__(self, key):
2025 del self._config[self._prefix + '.' + key]
2026
2027 def __iter__(self):
2028 ns_prefix = self._prefix + '.'
2029 for key in self._config:
2030 ns, dot, name = key.rpartition('.')
2031 if ns == self._prefix and name:
2032 yield name
2033
2034 def keys(self): return [x for x in self]
2035 def __len__(self): return len(self.keys())
2036 def __contains__(self, key): return self._prefix + '.' + key in self._config
2037 def __repr__(self): return '<Config.Namespace %s.*>' % self._prefix
2038 def __str__(self): return '<Config.Namespace %s.*>' % self._prefix
2039
2040 # Deprecated ConfigDict features
2041 def __getattr__(self, key):
2042 depr('Attribute access is deprecated.') #0.12
2043 if key not in self and key[0].isupper():
2044 self[key] = ConfigDict.Namespace(self._config, self._prefix + '.' + key)
2045 if key not in self and key.startswith('__'):
2046 raise AttributeError(key)
2047 return self.get(key)
2048
2049 def __setattr__(self, key, value):
2050 if key in ('_config', '_prefix'):
2051 self.__dict__[key] = value
2052 return
2053 depr('Attribute assignment is deprecated.') #0.12
2054 if hasattr(DictMixin, key):
2055 raise AttributeError('Read-only attribute.')
2056 if key in self and self[key] and isinstance(self[key], self.__class__):
2057 raise AttributeError('Non-empty namespace attribute.')
2058 self[key] = value
2059
2060 def __delattr__(self, key):
2061 if key in self:
2062 val = self.pop(key)
2063 if isinstance(val, self.__class__):
2064 prefix = key + '.'
2065 for key in self:
2066 if key.startswith(prefix):
2067 del self[prefix+key]
2068
2069 def __call__(self, *a, **ka):
2070 depr('Calling ConfDict is deprecated. Use the update() method.') #0.12
2071 self.update(*a, **ka)
2072 return self
2073
2074 def __init__(self, *a, **ka):
2075 self._meta = {}
2076 self._on_change = lambda name, value: None
2077 if a or ka:
2078 depr('Constructor does no longer accept parameters.') #0.12
2079 self.update(*a, **ka)
2080
2081 def load_config(self, filename):
2082 ''' Load values from an *.ini style config file.
2083
2084 If the config file contains sections, their names are used as
2085 namespaces for the values within. The two special sections
2086 ``DEFAULT`` and ``bottle`` refer to the root namespace (no prefix).
2087 '''
2088 conf = ConfigParser()
2089 conf.read(filename)
2090 for section in conf.sections():
2091 for key, value in conf.items(section):
2092 if section not in ('DEFAULT', 'bottle'):
2093 key = section + '.' + key
2094 self[key] = value
2095 return self
2096
2097 def load_dict(self, source, namespace='', make_namespaces=False):
2098 ''' Import values from a dictionary structure. Nesting can be used to
2099 represent namespaces.
2100
2101 >>> ConfigDict().load_dict({'name': {'space': {'key': 'value'}}})
2102 {'name.space.key': 'value'}
2103 '''
2104 stack = [(namespace, source)]
2105 while stack:
2106 prefix, source = stack.pop()
2107 if not isinstance(source, dict):
2108 raise TypeError('Source is not a dict (r)' % type(key))
2109 for key, value in source.items():
2110 if not isinstance(key, str):
2111 raise TypeError('Key is not a string (%r)' % type(key))
2112 full_key = prefix + '.' + key if prefix else key
2113 if isinstance(value, dict):
2114 stack.append((full_key, value))
2115 if make_namespaces:
2116 self[full_key] = self.Namespace(self, full_key)
2117 else:
2118 self[full_key] = value
2119 return self
2120
2121 def update(self, *a, **ka):
2122 ''' If the first parameter is a string, all keys are prefixed with this
2123 namespace. Apart from that it works just as the usual dict.update().
2124 Example: ``update('some.namespace', key='value')`` '''
2125 prefix = ''
2126 if a and isinstance(a[0], str):
2127 prefix = a[0].strip('.') + '.'
2128 a = a[1:]
2129 for key, value in dict(*a, **ka).items():
2130 self[prefix+key] = value
2131
2132 def setdefault(self, key, value):
2133 if key not in self:
2134 self[key] = value
2135 return self[key]
2136
2137 def __setitem__(self, key, value):
2138 if not isinstance(key, str):
2139 raise TypeError('Key has type %r (not a string)' % type(key))
2140
2141 value = self.meta_get(key, 'filter', lambda x: x)(value)
2142 if key in self and self[key] is value:
2143 return
2144 self._on_change(key, value)
2145 dict.__setitem__(self, key, value)
2146
2147 def __delitem__(self, key):
2148 dict.__delitem__(self, key)
2149
2150 def clear(self):
2151 for key in self:
2152 del self[key]
2153
2154 def meta_get(self, key, metafield, default=None):
2155 ''' Return the value of a meta field for a key. '''
2156 return self._meta.get(key, {}).get(metafield, default)
2157
2158 def meta_set(self, key, metafield, value):
2159 ''' Set the meta field for a key to a new value. This triggers the
2160 on-change handler for existing keys. '''
2161 self._meta.setdefault(key, {})[metafield] = value
2162 if key in self:
2163 self[key] = self[key]
2164
2165 def meta_list(self, key):
2166 ''' Return an iterable of meta field names defined for a key. '''
2167 return self._meta.get(key, {}).keys()
2168
2169 # Deprecated ConfigDict features
1874 def __getattr__(self, key): 2170 def __getattr__(self, key):
2171 depr('Attribute access is deprecated.') #0.12
1875 if key not in self and key[0].isupper(): 2172 if key not in self and key[0].isupper():
1876 self[key] = ConfigDict() 2173 self[key] = self.Namespace(self, key)
2174 if key not in self and key.startswith('__'):
2175 raise AttributeError(key)
1877 return self.get(key) 2176 return self.get(key)
1878 2177
1879 def __setattr__(self, key, value): 2178 def __setattr__(self, key, value):
2179 if key in self.__slots__:
2180 return dict.__setattr__(self, key, value)
2181 depr('Attribute assignment is deprecated.') #0.12
1880 if hasattr(dict, key): 2182 if hasattr(dict, key):
1881 raise AttributeError('Read-only attribute.') 2183 raise AttributeError('Read-only attribute.')
1882 if key in self and self[key] and isinstance(self[key], ConfigDict): 2184 if key in self and self[key] and isinstance(self[key], self.Namespace):
1883 raise AttributeError('Non-empty namespace attribute.') 2185 raise AttributeError('Non-empty namespace attribute.')
1884 self[key] = value 2186 self[key] = value
1885 2187
1886 def __delattr__(self, key): 2188 def __delattr__(self, key):
1887 if key in self: del self[key] 2189 if key in self:
2190 val = self.pop(key)
2191 if isinstance(val, self.Namespace):
2192 prefix = key + '.'
2193 for key in self:
2194 if key.startswith(prefix):
2195 del self[prefix+key]
1888 2196
1889 def __call__(self, *a, **ka): 2197 def __call__(self, *a, **ka):
1890 for key, value in dict(*a, **ka).items(): setattr(self, key, value) 2198 depr('Calling ConfDict is deprecated. Use the update() method.') #0.12
2199 self.update(*a, **ka)
1891 return self 2200 return self
2201
1892 2202
1893 2203
1894 class AppStack(list): 2204 class AppStack(list):
1895 """ A stack-like list. Calling it returns the head of the stack. """ 2205 """ A stack-like list. Calling it returns the head of the stack. """
1896 2206
1919 part = read(buff) 2229 part = read(buff)
1920 if not part: return 2230 if not part: return
1921 yield part 2231 yield part
1922 2232
1923 2233
2234 class _closeiter(object):
2235 ''' This only exists to be able to attach a .close method to iterators that
2236 do not support attribute assignment (most of itertools). '''
2237
2238 def __init__(self, iterator, close=None):
2239 self.iterator = iterator
2240 self.close_callbacks = makelist(close)
2241
2242 def __iter__(self):
2243 return iter(self.iterator)
2244
2245 def close(self):
2246 for func in self.close_callbacks:
2247 func()
2248
2249
1924 class ResourceManager(object): 2250 class ResourceManager(object):
1925 ''' This class manages a list of search paths and helps to find and open 2251 ''' This class manages a list of search paths and helps to find and open
1926 aplication-bound resources (files). 2252 application-bound resources (files).
1927 2253
1928 :param base: default value for same-named :meth:`add_path` parameter. 2254 :param base: default value for :meth:`add_path` calls.
1929 :param opener: callable used to open resources. 2255 :param opener: callable used to open resources.
1930 :param cachemode: controls which lookups are cached. One of 'all', 2256 :param cachemode: controls which lookups are cached. One of 'all',
1931 'found' or 'none'. 2257 'found' or 'none'.
1932 ''' 2258 '''
1933 2259
1936 self.base = base 2262 self.base = base
1937 self.cachemode = cachemode 2263 self.cachemode = cachemode
1938 2264
1939 #: A list of search paths. See :meth:`add_path` for details. 2265 #: A list of search paths. See :meth:`add_path` for details.
1940 self.path = [] 2266 self.path = []
1941 #: A cache for resolved paths. `res.cache.clear()`` clears the cache. 2267 #: A cache for resolved paths. ``res.cache.clear()`` clears the cache.
1942 self.cache = {} 2268 self.cache = {}
1943 2269
1944 def add_path(self, path, base=None, index=None, create=False): 2270 def add_path(self, path, base=None, index=None, create=False):
1945 ''' Add a new path to the list of search paths. Return False if it does 2271 ''' Add a new path to the list of search paths. Return False if the
1946 not exist. 2272 path does not exist.
1947 2273
1948 :param path: The new search path. Relative paths are turned into an 2274 :param path: The new search path. Relative paths are turned into
1949 absolute and normalized form. If the path looks like a file (not 2275 an absolute and normalized form. If the path looks like a file
1950 ending in `/`), the filename is stripped off. 2276 (not ending in `/`), the filename is stripped off.
1951 :param base: Path used to absolutize relative search paths. 2277 :param base: Path used to absolutize relative search paths.
1952 Defaults to `:attr:base` which defaults to ``./``. 2278 Defaults to :attr:`base` which defaults to ``os.getcwd()``.
1953 :param index: Position within the list of search paths. Defaults to 2279 :param index: Position within the list of search paths. Defaults
1954 last index (appends to the list). 2280 to last index (appends to the list).
1955 :param create: Create non-existent search paths. Off by default.
1956 2281
1957 The `base` parameter makes it easy to reference files installed 2282 The `base` parameter makes it easy to reference files installed
1958 along with a python module or package:: 2283 along with a python module or package::
1959 2284
1960 res.add_path('./resources/', __file__) 2285 res.add_path('./resources/', __file__)
1961 ''' 2286 '''
1962 base = os.path.abspath(os.path.dirname(base or self.base)) 2287 base = os.path.abspath(os.path.dirname(base or self.base))
1963 path = os.path.abspath(os.path.join(base, os.path.dirname(path))) 2288 path = os.path.abspath(os.path.join(base, os.path.dirname(path)))
1964 path += os.sep 2289 path += os.sep
1965 if path in self.path: 2290 if path in self.path:
1966 self.path.remove(path) 2291 self.path.remove(path)
1967 if create and not os.path.isdir(path): 2292 if create and not os.path.isdir(path):
1968 os.mkdirs(path) 2293 os.makedirs(path)
1969 if index is None: 2294 if index is None:
1970 self.path.append(path) 2295 self.path.append(path)
1971 else: 2296 else:
1972 self.path.insert(index, path) 2297 self.path.insert(index, path)
1973 self.cache.clear() 2298 self.cache.clear()
2299 return os.path.exists(path)
1974 2300
1975 def __iter__(self): 2301 def __iter__(self):
1976 ''' Iterate over all existing files in all registered paths. ''' 2302 ''' Iterate over all existing files in all registered paths. '''
1977 search = self.path[:] 2303 search = self.path[:]
1978 while search: 2304 while search:
2002 2328
2003 def open(self, name, mode='r', *args, **kwargs): 2329 def open(self, name, mode='r', *args, **kwargs):
2004 ''' Find a resource and return a file object, or raise IOError. ''' 2330 ''' Find a resource and return a file object, or raise IOError. '''
2005 fname = self.lookup(name) 2331 fname = self.lookup(name)
2006 if not fname: raise IOError("Resource %r not found." % name) 2332 if not fname: raise IOError("Resource %r not found." % name)
2007 return self.opener(name, mode=mode, *args, **kwargs) 2333 return self.opener(fname, mode=mode, *args, **kwargs)
2334
2335
2336 class FileUpload(object):
2337
2338 def __init__(self, fileobj, name, filename, headers=None):
2339 ''' Wrapper for file uploads. '''
2340 #: Open file(-like) object (BytesIO buffer or temporary file)
2341 self.file = fileobj
2342 #: Name of the upload form field
2343 self.name = name
2344 #: Raw filename as sent by the client (may contain unsafe characters)
2345 self.raw_filename = filename
2346 #: A :class:`HeaderDict` with additional headers (e.g. content-type)
2347 self.headers = HeaderDict(headers) if headers else HeaderDict()
2348
2349 content_type = HeaderProperty('Content-Type')
2350 content_length = HeaderProperty('Content-Length', reader=int, default=-1)
2351
2352 @cached_property
2353 def filename(self):
2354 ''' Name of the file on the client file system, but normalized to ensure
2355 file system compatibility. An empty filename is returned as 'empty'.
2356
2357 Only ASCII letters, digits, dashes, underscores and dots are
2358 allowed in the final filename. Accents are removed, if possible.
2359 Whitespace is replaced by a single dash. Leading or tailing dots
2360 or dashes are removed. The filename is limited to 255 characters.
2361 '''
2362 fname = self.raw_filename
2363 if not isinstance(fname, unicode):
2364 fname = fname.decode('utf8', 'ignore')
2365 fname = normalize('NFKD', fname).encode('ASCII', 'ignore').decode('ASCII')
2366 fname = os.path.basename(fname.replace('\\', os.path.sep))
2367 fname = re.sub(r'[^a-zA-Z0-9-_.\s]', '', fname).strip()
2368 fname = re.sub(r'[-\s]+', '-', fname).strip('.-')
2369 return fname[:255] or 'empty'
2370
2371 def _copy_file(self, fp, chunk_size=2**16):
2372 read, write, offset = self.file.read, fp.write, self.file.tell()
2373 while 1:
2374 buf = read(chunk_size)
2375 if not buf: break
2376 write(buf)
2377 self.file.seek(offset)
2378
2379 def save(self, destination, overwrite=False, chunk_size=2**16):
2380 ''' Save file to disk or copy its content to an open file(-like) object.
2381 If *destination* is a directory, :attr:`filename` is added to the
2382 path. Existing files are not overwritten by default (IOError).
2383
2384 :param destination: File path, directory or file(-like) object.
2385 :param overwrite: If True, replace existing files. (default: False)
2386 :param chunk_size: Bytes to read at a time. (default: 64kb)
2387 '''
2388 if isinstance(destination, basestring): # Except file-likes here
2389 if os.path.isdir(destination):
2390 destination = os.path.join(destination, self.filename)
2391 if not overwrite and os.path.exists(destination):
2392 raise IOError('File exists.')
2393 with open(destination, 'wb') as fp:
2394 self._copy_file(fp, chunk_size)
2395 else:
2396 self._copy_file(destination, chunk_size)
2008 2397
2009 2398
2010 2399
2011 2400
2012 2401
2014 ############################################################################### 2403 ###############################################################################
2015 # Application Helper ########################################################### 2404 # Application Helper ###########################################################
2016 ############################################################################### 2405 ###############################################################################
2017 2406
2018 2407
2019 def abort(code=500, text='Unknown Error: Application stopped.'): 2408 def abort(code=500, text='Unknown Error.'):
2020 """ Aborts execution and causes a HTTP error. """ 2409 """ Aborts execution and causes a HTTP error. """
2021 raise HTTPError(code, text) 2410 raise HTTPError(code, text)
2022 2411
2023 2412
2024 def redirect(url, code=None): 2413 def redirect(url, code=None):
2025 """ Aborts execution and causes a 303 or 302 redirect, depending on 2414 """ Aborts execution and causes a 303 or 302 redirect, depending on
2026 the HTTP protocol version. """ 2415 the HTTP protocol version. """
2027 if code is None: 2416 if not code:
2028 code = 303 if request.get('SERVER_PROTOCOL') == "HTTP/1.1" else 302 2417 code = 303 if request.get('SERVER_PROTOCOL') == "HTTP/1.1" else 302
2029 location = urljoin(request.url, url) 2418 res = response.copy(cls=HTTPResponse)
2030 raise HTTPResponse("", status=code, header=dict(Location=location)) 2419 res.status = code
2420 res.body = ""
2421 res.set_header('Location', urljoin(request.url, url))
2422 raise res
2031 2423
2032 2424
2033 def _file_iter_range(fp, offset, bytes, maxread=1024*1024): 2425 def _file_iter_range(fp, offset, bytes, maxread=1024*1024):
2034 ''' Yield chunks from a range in a file. No chunk is bigger than maxread.''' 2426 ''' Yield chunks from a range in a file. No chunk is bigger than maxread.'''
2035 fp.seek(offset) 2427 fp.seek(offset)
2038 if not part: break 2430 if not part: break
2039 bytes -= len(part) 2431 bytes -= len(part)
2040 yield part 2432 yield part
2041 2433
2042 2434
2043 def static_file(filename, root, mimetype='auto', download=False): 2435 def static_file(filename, root, mimetype='auto', download=False, charset='UTF-8'):
2044 """ Open a file in a safe way and return :exc:`HTTPResponse` with status 2436 """ Open a file in a safe way and return :exc:`HTTPResponse` with status
2045 code 200, 305, 401 or 404. Set Content-Type, Content-Encoding, 2437 code 200, 305, 403 or 404. The ``Content-Type``, ``Content-Encoding``,
2046 Content-Length and Last-Modified header. Obey If-Modified-Since header 2438 ``Content-Length`` and ``Last-Modified`` headers are set if possible.
2047 and HEAD requests. 2439 Special support for ``If-Modified-Since``, ``Range`` and ``HEAD``
2440 requests.
2441
2442 :param filename: Name or path of the file to send.
2443 :param root: Root path for file lookups. Should be an absolute directory
2444 path.
2445 :param mimetype: Defines the content-type header (default: guess from
2446 file extension)
2447 :param download: If True, ask the browser to open a `Save as...` dialog
2448 instead of opening the file with the associated program. You can
2449 specify a custom filename as a string. If not specified, the
2450 original filename is used (default: False).
2451 :param charset: The charset to use for files with a ``text/*``
2452 mime-type. (default: UTF-8)
2048 """ 2453 """
2454
2049 root = os.path.abspath(root) + os.sep 2455 root = os.path.abspath(root) + os.sep
2050 filename = os.path.abspath(os.path.join(root, filename.strip('/\\'))) 2456 filename = os.path.abspath(os.path.join(root, filename.strip('/\\')))
2051 header = dict() 2457 headers = dict()
2052 2458
2053 if not filename.startswith(root): 2459 if not filename.startswith(root):
2054 return HTTPError(403, "Access denied.") 2460 return HTTPError(403, "Access denied.")
2055 if not os.path.exists(filename) or not os.path.isfile(filename): 2461 if not os.path.exists(filename) or not os.path.isfile(filename):
2056 return HTTPError(404, "File does not exist.") 2462 return HTTPError(404, "File does not exist.")
2057 if not os.access(filename, os.R_OK): 2463 if not os.access(filename, os.R_OK):
2058 return HTTPError(403, "You do not have permission to access this file.") 2464 return HTTPError(403, "You do not have permission to access this file.")
2059 2465
2060 if mimetype == 'auto': 2466 if mimetype == 'auto':
2061 mimetype, encoding = mimetypes.guess_type(filename) 2467 mimetype, encoding = mimetypes.guess_type(filename)
2062 if mimetype: header['Content-Type'] = mimetype 2468 if encoding: headers['Content-Encoding'] = encoding
2063 if encoding: header['Content-Encoding'] = encoding 2469
2064 elif mimetype: 2470 if mimetype:
2065 header['Content-Type'] = mimetype 2471 if mimetype[:5] == 'text/' and charset and 'charset' not in mimetype:
2472 mimetype += '; charset=%s' % charset
2473 headers['Content-Type'] = mimetype
2066 2474
2067 if download: 2475 if download:
2068 download = os.path.basename(filename if download == True else download) 2476 download = os.path.basename(filename if download == True else download)
2069 header['Content-Disposition'] = 'attachment; filename="%s"' % download 2477 headers['Content-Disposition'] = 'attachment; filename="%s"' % download
2070 2478
2071 stats = os.stat(filename) 2479 stats = os.stat(filename)
2072 header['Content-Length'] = clen = stats.st_size 2480 headers['Content-Length'] = clen = stats.st_size
2073 lm = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(stats.st_mtime)) 2481 lm = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(stats.st_mtime))
2074 header['Last-Modified'] = lm 2482 headers['Last-Modified'] = lm
2075 2483
2076 ims = request.environ.get('HTTP_IF_MODIFIED_SINCE') 2484 ims = request.environ.get('HTTP_IF_MODIFIED_SINCE')
2077 if ims: 2485 if ims:
2078 ims = parse_date(ims.split(";")[0].strip()) 2486 ims = parse_date(ims.split(";")[0].strip())
2079 if ims is not None and ims >= int(stats.st_mtime): 2487 if ims is not None and ims >= int(stats.st_mtime):
2080 header['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()) 2488 headers['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime())
2081 return HTTPResponse(status=304, header=header) 2489 return HTTPResponse(status=304, **headers)
2082 2490
2083 body = '' if request.method == 'HEAD' else open(filename, 'rb') 2491 body = '' if request.method == 'HEAD' else open(filename, 'rb')
2084 2492
2085 header["Accept-Ranges"] = "bytes" 2493 headers["Accept-Ranges"] = "bytes"
2086 ranges = request.environ.get('HTTP_RANGE') 2494 ranges = request.environ.get('HTTP_RANGE')
2087 if 'HTTP_RANGE' in request.environ: 2495 if 'HTTP_RANGE' in request.environ:
2088 ranges = list(parse_range_header(request.environ['HTTP_RANGE'], clen)) 2496 ranges = list(parse_range_header(request.environ['HTTP_RANGE'], clen))
2089 if not ranges: 2497 if not ranges:
2090 return HTTPError(416, "Requested Range Not Satisfiable") 2498 return HTTPError(416, "Requested Range Not Satisfiable")
2091 offset, end = ranges[0] 2499 offset, end = ranges[0]
2092 header["Content-Range"] = "bytes %d-%d/%d" % (offset, end-1, clen) 2500 headers["Content-Range"] = "bytes %d-%d/%d" % (offset, end-1, clen)
2093 header["Content-Length"] = str(end-offset) 2501 headers["Content-Length"] = str(end-offset)
2094 if body: body = _file_iter_range(body, offset, end-offset) 2502 if body: body = _file_iter_range(body, offset, end-offset)
2095 return HTTPResponse(body, header=header, status=206) 2503 return HTTPResponse(body, status=206, **headers)
2096 return HTTPResponse(body, header=header) 2504 return HTTPResponse(body, **headers)
2097 2505
2098 2506
2099 2507
2100 2508
2101 2509
2107 2515
2108 def debug(mode=True): 2516 def debug(mode=True):
2109 """ Change the debug level. 2517 """ Change the debug level.
2110 There is only one debug level supported at the moment.""" 2518 There is only one debug level supported at the moment."""
2111 global DEBUG 2519 global DEBUG
2520 if mode: warnings.simplefilter('default')
2112 DEBUG = bool(mode) 2521 DEBUG = bool(mode)
2113 2522
2523 def http_date(value):
2524 if isinstance(value, (datedate, datetime)):
2525 value = value.utctimetuple()
2526 elif isinstance(value, (int, float)):
2527 value = time.gmtime(value)
2528 if not isinstance(value, basestring):
2529 value = time.strftime("%a, %d %b %Y %H:%M:%S GMT", value)
2530 return value
2114 2531
2115 def parse_date(ims): 2532 def parse_date(ims):
2116 """ Parse rfc1123, rfc850 and asctime timestamps and return UTC epoch. """ 2533 """ Parse rfc1123, rfc850 and asctime timestamps and return UTC epoch. """
2117 try: 2534 try:
2118 ts = email.utils.parsedate_tz(ims) 2535 ts = email.utils.parsedate_tz(ims)
2119 return time.mktime(ts[:8] + (0,)) - (ts[9] or 0) - time.timezone 2536 return time.mktime(ts[:8] + (0,)) - (ts[9] or 0) - time.timezone
2120 except (TypeError, ValueError, IndexError, OverflowError): 2537 except (TypeError, ValueError, IndexError, OverflowError):
2121 return None 2538 return None
2122
2123 2539
2124 def parse_auth(header): 2540 def parse_auth(header):
2125 """ Parse rfc2617 HTTP authentication header string (basic) and return (user,pass) tuple or None""" 2541 """ Parse rfc2617 HTTP authentication header string (basic) and return (user,pass) tuple or None"""
2126 try: 2542 try:
2127 method, data = header.split(None, 1) 2543 method, data = header.split(None, 1)
2147 if 0 <= start < end <= maxlen: 2563 if 0 <= start < end <= maxlen:
2148 yield start, end 2564 yield start, end
2149 except ValueError: 2565 except ValueError:
2150 pass 2566 pass
2151 2567
2568 def _parse_qsl(qs):
2569 r = []
2570 for pair in qs.replace(';','&').split('&'):
2571 if not pair: continue
2572 nv = pair.split('=', 1)
2573 if len(nv) != 2: nv.append('')
2574 key = urlunquote(nv[0].replace('+', ' '))
2575 value = urlunquote(nv[1].replace('+', ' '))
2576 r.append((key, value))
2577 return r
2578
2152 def _lscmp(a, b): 2579 def _lscmp(a, b):
2153 ''' Compares two strings in a cryptographically safe way: 2580 ''' Compares two strings in a cryptographically safe way:
2154 Runtime is not affected by length of common prefix. ''' 2581 Runtime is not affected by length of common prefix. '''
2155 return not sum(0 if x==y else 1 for x, y in zip(a, b)) and len(a) == len(b) 2582 return not sum(0 if x==y else 1 for x, y in zip(a, b)) and len(a) == len(b)
2156 2583
2183 .replace('"','&quot;').replace("'",'&#039;') 2610 .replace('"','&quot;').replace("'",'&#039;')
2184 2611
2185 2612
2186 def html_quote(string): 2613 def html_quote(string):
2187 ''' Escape and quote a string to be used as an HTTP attribute.''' 2614 ''' Escape and quote a string to be used as an HTTP attribute.'''
2188 return '"%s"' % html_escape(string).replace('\n','%#10;')\ 2615 return '"%s"' % html_escape(string).replace('\n','&#10;')\
2189 .replace('\r','&#13;').replace('\t','&#9;') 2616 .replace('\r','&#13;').replace('\t','&#9;')
2190 2617
2191 2618
2192 def yieldroutes(func): 2619 def yieldroutes(func):
2193 """ Return a generator for routes that match the signature (name, args) 2620 """ Return a generator for routes that match the signature (name, args)
2194 of the func parameter. This may yield more than one route if the function 2621 of the func parameter. This may yield more than one route if the function
2195 takes optional keyword arguments. The output is best described by example:: 2622 takes optional keyword arguments. The output is best described by example::
2196 2623
2197 a() -> '/a' 2624 a() -> '/a'
2198 b(x, y) -> '/b/:x/:y' 2625 b(x, y) -> '/b/<x>/<y>'
2199 c(x, y=5) -> '/c/:x' and '/c/:x/:y' 2626 c(x, y=5) -> '/c/<x>' and '/c/<x>/<y>'
2200 d(x=5, y=6) -> '/d' and '/d/:x' and '/d/:x/:y' 2627 d(x=5, y=6) -> '/d' and '/d/<x>' and '/d/<x>/<y>'
2201 """ 2628 """
2202 import inspect # Expensive module. Only import if necessary.
2203 path = '/' + func.__name__.replace('__','/').lstrip('/') 2629 path = '/' + func.__name__.replace('__','/').lstrip('/')
2204 spec = inspect.getargspec(func) 2630 spec = getargspec(func)
2205 argc = len(spec[0]) - len(spec[3] or []) 2631 argc = len(spec[0]) - len(spec[3] or [])
2206 path += ('/:%s' * argc) % tuple(spec[0][:argc]) 2632 path += ('/<%s>' * argc) % tuple(spec[0][:argc])
2207 yield path 2633 yield path
2208 for arg in spec[0][argc:]: 2634 for arg in spec[0][argc:]:
2209 path += '/:%s' % arg 2635 path += '/<%s>' % arg
2210 yield path 2636 yield path
2211 2637
2212 2638
2213 def path_shift(script_name, path_info, shift=1): 2639 def path_shift(script_name, path_info, shift=1):
2214 ''' Shift path fragments from PATH_INFO to SCRIPT_NAME and vice versa. 2640 ''' Shift path fragments from PATH_INFO to SCRIPT_NAME and vice versa.
2239 new_path_info = '/' + '/'.join(pathlist) 2665 new_path_info = '/' + '/'.join(pathlist)
2240 if path_info.endswith('/') and pathlist: new_path_info += '/' 2666 if path_info.endswith('/') and pathlist: new_path_info += '/'
2241 return new_script_name, new_path_info 2667 return new_script_name, new_path_info
2242 2668
2243 2669
2244 def validate(**vkargs):
2245 """
2246 Validates and manipulates keyword arguments by user defined callables.
2247 Handles ValueError and missing arguments by raising HTTPError(403).
2248 """
2249 depr('Use route wildcard filters instead.')
2250 def decorator(func):
2251 @functools.wraps(func)
2252 def wrapper(*args, **kargs):
2253 for key, value in vkargs.items():
2254 if key not in kargs:
2255 abort(403, 'Missing parameter: %s' % key)
2256 try:
2257 kargs[key] = value(kargs[key])
2258 except ValueError:
2259 abort(403, 'Wrong parameter format for: %s' % key)
2260 return func(*args, **kargs)
2261 return wrapper
2262 return decorator
2263
2264
2265 def auth_basic(check, realm="private", text="Access denied"): 2670 def auth_basic(check, realm="private", text="Access denied"):
2266 ''' Callback decorator to require HTTP auth (basic). 2671 ''' Callback decorator to require HTTP auth (basic).
2267 TODO: Add route(check_auth=...) parameter. ''' 2672 TODO: Add route(check_auth=...) parameter. '''
2268 def decorator(func): 2673 def decorator(func):
2269 def wrapper(*a, **ka): 2674 def wrapper(*a, **ka):
2270 user, password = request.auth or (None, None) 2675 user, password = request.auth or (None, None)
2271 if user is None or not check(user, password): 2676 if user is None or not check(user, password):
2272 response.headers['WWW-Authenticate'] = 'Basic realm="%s"' % realm 2677 err = HTTPError(401, text)
2273 return HTTPError(401, text) 2678 err.add_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
2274 return func(*a, **ka) 2679 return err
2275 return wrapper 2680 return func(*a, **ka)
2681 return wrapper
2276 return decorator 2682 return decorator
2277 2683
2278 2684
2279 # Shortcuts for common Bottle methods. 2685 # Shortcuts for common Bottle methods.
2280 # They all refer to the current default application. 2686 # They all refer to the current default application.
2309 ############################################################################### 2715 ###############################################################################
2310 2716
2311 2717
2312 class ServerAdapter(object): 2718 class ServerAdapter(object):
2313 quiet = False 2719 quiet = False
2314 def __init__(self, host='127.0.0.1', port=8080, **config): 2720 def __init__(self, host='127.0.0.1', port=8080, **options):
2315 self.options = config 2721 self.options = options
2316 self.host = host 2722 self.host = host
2317 self.port = int(port) 2723 self.port = int(port)
2318 2724
2319 def run(self, handler): # pragma: no cover 2725 def run(self, handler): # pragma: no cover
2320 pass 2726 pass
2340 self.options.setdefault('bindAddress', (self.host, self.port)) 2746 self.options.setdefault('bindAddress', (self.host, self.port))
2341 flup.server.fcgi.WSGIServer(handler, **self.options).run() 2747 flup.server.fcgi.WSGIServer(handler, **self.options).run()
2342 2748
2343 2749
2344 class WSGIRefServer(ServerAdapter): 2750 class WSGIRefServer(ServerAdapter):
2345 def run(self, handler): # pragma: no cover 2751 def run(self, app): # pragma: no cover
2346 from wsgiref.simple_server import make_server, WSGIRequestHandler 2752 from wsgiref.simple_server import WSGIRequestHandler, WSGIServer
2347 if self.quiet: 2753 from wsgiref.simple_server import make_server
2348 class QuietHandler(WSGIRequestHandler): 2754 import socket
2349 def log_request(*args, **kw): pass 2755
2350 self.options['handler_class'] = QuietHandler 2756 class FixedHandler(WSGIRequestHandler):
2351 srv = make_server(self.host, self.port, handler, **self.options) 2757 def address_string(self): # Prevent reverse DNS lookups please.
2758 return self.client_address[0]
2759 def log_request(*args, **kw):
2760 if not self.quiet:
2761 return WSGIRequestHandler.log_request(*args, **kw)
2762
2763 handler_cls = self.options.get('handler_class', FixedHandler)
2764 server_cls = self.options.get('server_class', WSGIServer)
2765
2766 if ':' in self.host: # Fix wsgiref for IPv6 addresses.
2767 if getattr(server_cls, 'address_family') == socket.AF_INET:
2768 class server_cls(server_cls):
2769 address_family = socket.AF_INET6
2770
2771 srv = make_server(self.host, self.port, app, server_cls, handler_cls)
2352 srv.serve_forever() 2772 srv.serve_forever()
2353 2773
2354 2774
2355 class CherryPyServer(ServerAdapter): 2775 class CherryPyServer(ServerAdapter):
2356 def run(self, handler): # pragma: no cover 2776 def run(self, handler): # pragma: no cover
2357 from cherrypy import wsgiserver 2777 from cherrypy import wsgiserver
2358 server = wsgiserver.CherryPyWSGIServer((self.host, self.port), handler) 2778 self.options['bind_addr'] = (self.host, self.port)
2779 self.options['wsgi_app'] = handler
2780
2781 certfile = self.options.get('certfile')
2782 if certfile:
2783 del self.options['certfile']
2784 keyfile = self.options.get('keyfile')
2785 if keyfile:
2786 del self.options['keyfile']
2787
2788 server = wsgiserver.CherryPyWSGIServer(**self.options)
2789 if certfile:
2790 server.ssl_certificate = certfile
2791 if keyfile:
2792 server.ssl_private_key = keyfile
2793
2359 try: 2794 try:
2360 server.start() 2795 server.start()
2361 finally: 2796 finally:
2362 server.stop() 2797 server.stop()
2363 2798
2369 2804
2370 2805
2371 class PasteServer(ServerAdapter): 2806 class PasteServer(ServerAdapter):
2372 def run(self, handler): # pragma: no cover 2807 def run(self, handler): # pragma: no cover
2373 from paste import httpserver 2808 from paste import httpserver
2374 if not self.quiet: 2809 from paste.translogger import TransLogger
2375 from paste.translogger import TransLogger 2810 handler = TransLogger(handler, setup_console_handler=(not self.quiet))
2376 handler = TransLogger(handler)
2377 httpserver.serve(handler, host=self.host, port=str(self.port), 2811 httpserver.serve(handler, host=self.host, port=str(self.port),
2378 **self.options) 2812 **self.options)
2379 2813
2380 2814
2381 class MeinheldServer(ServerAdapter): 2815 class MeinheldServer(ServerAdapter):
2411 """ The super hyped asynchronous server by facebook. Untested. """ 2845 """ The super hyped asynchronous server by facebook. Untested. """
2412 def run(self, handler): # pragma: no cover 2846 def run(self, handler): # pragma: no cover
2413 import tornado.wsgi, tornado.httpserver, tornado.ioloop 2847 import tornado.wsgi, tornado.httpserver, tornado.ioloop
2414 container = tornado.wsgi.WSGIContainer(handler) 2848 container = tornado.wsgi.WSGIContainer(handler)
2415 server = tornado.httpserver.HTTPServer(container) 2849 server = tornado.httpserver.HTTPServer(container)
2416 server.listen(port=self.port) 2850 server.listen(port=self.port,address=self.host)
2417 tornado.ioloop.IOLoop.instance().start() 2851 tornado.ioloop.IOLoop.instance().start()
2418 2852
2419 2853
2420 class AppEngineServer(ServerAdapter): 2854 class AppEngineServer(ServerAdapter):
2421 """ Adapter for Google App Engine. """ 2855 """ Adapter for Google App Engine. """
2453 2887
2454 2888
2455 class GeventServer(ServerAdapter): 2889 class GeventServer(ServerAdapter):
2456 """ Untested. Options: 2890 """ Untested. Options:
2457 2891
2458 * `monkey` (default: True) fixes the stdlib to use greenthreads.
2459 * `fast` (default: False) uses libevent's http server, but has some 2892 * `fast` (default: False) uses libevent's http server, but has some
2460 issues: No streaming, no pipelining, no SSL. 2893 issues: No streaming, no pipelining, no SSL.
2894 * See gevent.wsgi.WSGIServer() documentation for more options.
2461 """ 2895 """
2462 def run(self, handler): 2896 def run(self, handler):
2463 from gevent import wsgi as wsgi_fast, pywsgi, monkey, local 2897 from gevent import wsgi, pywsgi, local
2464 if self.options.get('monkey', True): 2898 if not isinstance(threading.local(), local.local):
2465 if not threading.local is local.local: monkey.patch_all() 2899 msg = "Bottle requires gevent.monkey.patch_all() (before import)"
2466 wsgi = wsgi_fast if self.options.get('fast') else pywsgi 2900 raise RuntimeError(msg)
2467 log = None if self.quiet else 'default' 2901 if not self.options.pop('fast', None): wsgi = pywsgi
2468 wsgi.WSGIServer((self.host, self.port), handler, log=log).serve_forever() 2902 self.options['log'] = None if self.quiet else 'default'
2903 address = (self.host, self.port)
2904 server = wsgi.WSGIServer(address, handler, **self.options)
2905 if 'BOTTLE_CHILD' in os.environ:
2906 import signal
2907 signal.signal(signal.SIGINT, lambda s, f: server.stop())
2908 server.serve_forever()
2909
2910
2911 class GeventSocketIOServer(ServerAdapter):
2912 def run(self,handler):
2913 from socketio import server
2914 address = (self.host, self.port)
2915 server.SocketIOServer(address, handler, **self.options).serve_forever()
2469 2916
2470 2917
2471 class GunicornServer(ServerAdapter): 2918 class GunicornServer(ServerAdapter):
2472 """ Untested. See http://gunicorn.org/configure.html for options. """ 2919 """ Untested. See http://gunicorn.org/configure.html for options. """
2473 def run(self, handler): 2920 def run(self, handler):
2537 'diesel': DieselServer, 2984 'diesel': DieselServer,
2538 'meinheld': MeinheldServer, 2985 'meinheld': MeinheldServer,
2539 'gunicorn': GunicornServer, 2986 'gunicorn': GunicornServer,
2540 'eventlet': EventletServer, 2987 'eventlet': EventletServer,
2541 'gevent': GeventServer, 2988 'gevent': GeventServer,
2989 'geventSocketIO':GeventSocketIOServer,
2542 'rocket': RocketServer, 2990 'rocket': RocketServer,
2543 'bjoern' : BjoernServer, 2991 'bjoern' : BjoernServer,
2544 'auto': AutoServer, 2992 'auto': AutoServer,
2545 } 2993 }
2546 2994
2588 NORUN = nr_old 3036 NORUN = nr_old
2589 3037
2590 _debug = debug 3038 _debug = debug
2591 def run(app=None, server='wsgiref', host='127.0.0.1', port=8080, 3039 def run(app=None, server='wsgiref', host='127.0.0.1', port=8080,
2592 interval=1, reloader=False, quiet=False, plugins=None, 3040 interval=1, reloader=False, quiet=False, plugins=None,
2593 debug=False, **kargs): 3041 debug=None, **kargs):
2594 """ Start a server instance. This method blocks until the server terminates. 3042 """ Start a server instance. This method blocks until the server terminates.
2595 3043
2596 :param app: WSGI application or target string supported by 3044 :param app: WSGI application or target string supported by
2597 :func:`load_app`. (default: :func:`default_app`) 3045 :func:`load_app`. (default: :func:`default_app`)
2598 :param server: Server adapter to use. See :data:`server_names` keys 3046 :param server: Server adapter to use. See :data:`server_names` keys
2631 if os.path.exists(lockfile): 3079 if os.path.exists(lockfile):
2632 os.unlink(lockfile) 3080 os.unlink(lockfile)
2633 return 3081 return
2634 3082
2635 try: 3083 try:
2636 _debug(debug) 3084 if debug is not None: _debug(debug)
2637 app = app or default_app() 3085 app = app or default_app()
2638 if isinstance(app, basestring): 3086 if isinstance(app, basestring):
2639 app = load_app(app) 3087 app = load_app(app)
2640 if not callable(app): 3088 if not callable(app):
2641 raise ValueError("Application is not callable: %r" % app) 3089 raise ValueError("Application is not callable: %r" % app)
2768 3216
2769 @classmethod 3217 @classmethod
2770 def search(cls, name, lookup=[]): 3218 def search(cls, name, lookup=[]):
2771 """ Search name in all directories specified in lookup. 3219 """ Search name in all directories specified in lookup.
2772 First without, then with common extensions. Return first hit. """ 3220 First without, then with common extensions. Return first hit. """
2773 if os.path.isfile(name): return name 3221 if not lookup:
3222 depr('The template lookup path list should not be empty.') #0.12
3223 lookup = ['.']
3224
3225 if os.path.isabs(name) and os.path.isfile(name):
3226 depr('Absolute template path names are deprecated.') #0.12
3227 return os.path.abspath(name)
3228
2774 for spath in lookup: 3229 for spath in lookup:
2775 fname = os.path.join(spath, name) 3230 spath = os.path.abspath(spath) + os.sep
2776 if os.path.isfile(fname): 3231 fname = os.path.abspath(os.path.join(spath, name))
2777 return fname 3232 if not fname.startswith(spath): continue
3233 if os.path.isfile(fname): return fname
2778 for ext in cls.extensions: 3234 for ext in cls.extensions:
2779 if os.path.isfile('%s.%s' % (fname, ext)): 3235 if os.path.isfile('%s.%s' % (fname, ext)):
2780 return '%s.%s' % (fname, ext) 3236 return '%s.%s' % (fname, ext)
2781 3237
2782 @classmethod 3238 @classmethod
2797 3253
2798 def render(self, *args, **kwargs): 3254 def render(self, *args, **kwargs):
2799 """ Render the template with the specified local variables and return 3255 """ Render the template with the specified local variables and return
2800 a single byte or unicode string. If it is a byte string, the encoding 3256 a single byte or unicode string. If it is a byte string, the encoding
2801 must match self.encoding. This method must be thread-safe! 3257 must match self.encoding. This method must be thread-safe!
2802 Local variables may be provided in dictionaries (*args) 3258 Local variables may be provided in dictionaries (args)
2803 or directly, as keywords (**kwargs). 3259 or directly, as keywords (kwargs).
2804 """ 3260 """
2805 raise NotImplementedError 3261 raise NotImplementedError
2806 3262
2807 3263
2808 class MakoTemplate(BaseTemplate): 3264 class MakoTemplate(BaseTemplate):
2843 self.context.vars.clear() 3299 self.context.vars.clear()
2844 return out 3300 return out
2845 3301
2846 3302
2847 class Jinja2Template(BaseTemplate): 3303 class Jinja2Template(BaseTemplate):
2848 def prepare(self, filters=None, tests=None, **kwargs): 3304 def prepare(self, filters=None, tests=None, globals={}, **kwargs):
2849 from jinja2 import Environment, FunctionLoader 3305 from jinja2 import Environment, FunctionLoader
2850 if 'prefix' in kwargs: # TODO: to be removed after a while 3306 if 'prefix' in kwargs: # TODO: to be removed after a while
2851 raise RuntimeError('The keyword argument `prefix` has been removed. ' 3307 raise RuntimeError('The keyword argument `prefix` has been removed. '
2852 'Use the full jinja2 environment name line_statement_prefix instead.') 3308 'Use the full jinja2 environment name line_statement_prefix instead.')
2853 self.env = Environment(loader=FunctionLoader(self.loader), **kwargs) 3309 self.env = Environment(loader=FunctionLoader(self.loader), **kwargs)
2854 if filters: self.env.filters.update(filters) 3310 if filters: self.env.filters.update(filters)
2855 if tests: self.env.tests.update(tests) 3311 if tests: self.env.tests.update(tests)
3312 if globals: self.env.globals.update(globals)
2856 if self.source: 3313 if self.source:
2857 self.tpl = self.env.from_string(self.source) 3314 self.tpl = self.env.from_string(self.source)
2858 else: 3315 else:
2859 self.tpl = self.env.get_template(self.filename) 3316 self.tpl = self.env.get_template(self.filename)
2860 3317
2869 if not fname: return 3326 if not fname: return
2870 with open(fname, "rb") as f: 3327 with open(fname, "rb") as f:
2871 return f.read().decode(self.encoding) 3328 return f.read().decode(self.encoding)
2872 3329
2873 3330
2874 class SimpleTALTemplate(BaseTemplate):
2875 ''' Deprecated, do not use. '''
2876 def prepare(self, **options):
2877 depr('The SimpleTAL template handler is deprecated'\
2878 ' and will be removed in 0.12')
2879 from simpletal import simpleTAL
2880 if self.source:
2881 self.tpl = simpleTAL.compileHTMLTemplate(self.source)
2882 else:
2883 with open(self.filename, 'rb') as fp:
2884 self.tpl = simpleTAL.compileHTMLTemplate(tonat(fp.read()))
2885
2886 def render(self, *args, **kwargs):
2887 from simpletal import simpleTALES
2888 for dictarg in args: kwargs.update(dictarg)
2889 context = simpleTALES.Context()
2890 for k,v in self.defaults.items():
2891 context.addGlobal(k, v)
2892 for k,v in kwargs.items():
2893 context.addGlobal(k, v)
2894 output = StringIO()
2895 self.tpl.expand(context, output)
2896 return output.getvalue()
2897
2898
2899 class SimpleTemplate(BaseTemplate): 3331 class SimpleTemplate(BaseTemplate):
2900 blocks = ('if', 'elif', 'else', 'try', 'except', 'finally', 'for', 'while', 3332
2901 'with', 'def', 'class') 3333 def prepare(self, escape_func=html_escape, noescape=False, syntax=None, **ka):
2902 dedent_blocks = ('elif', 'else', 'except', 'finally')
2903
2904 @lazy_attribute
2905 def re_pytokens(cls):
2906 ''' This matches comments and all kinds of quoted strings but does
2907 NOT match comments (#...) within quoted strings. (trust me) '''
2908 return re.compile(r'''
2909 (''(?!')|""(?!")|'{6}|"{6} # Empty strings (all 4 types)
2910 |'(?:[^\\']|\\.)+?' # Single quotes (')
2911 |"(?:[^\\"]|\\.)+?" # Double quotes (")
2912 |'{3}(?:[^\\]|\\.|\n)+?'{3} # Triple-quoted strings (')
2913 |"{3}(?:[^\\]|\\.|\n)+?"{3} # Triple-quoted strings (")
2914 |\#.* # Comments
2915 )''', re.VERBOSE)
2916
2917 def prepare(self, escape_func=html_escape, noescape=False, **kwargs):
2918 self.cache = {} 3334 self.cache = {}
2919 enc = self.encoding 3335 enc = self.encoding
2920 self._str = lambda x: touni(x, enc) 3336 self._str = lambda x: touni(x, enc)
2921 self._escape = lambda x: escape_func(touni(x, enc)) 3337 self._escape = lambda x: escape_func(touni(x, enc))
3338 self.syntax = syntax
2922 if noescape: 3339 if noescape:
2923 self._str, self._escape = self._escape, self._str 3340 self._str, self._escape = self._escape, self._str
2924
2925 @classmethod
2926 def split_comment(cls, code):
2927 """ Removes comments (#...) from python code. """
2928 if '#' not in code: return code
2929 #: Remove comments only (leave quoted strings as they are)
2930 subf = lambda m: '' if m.group(0)[0]=='#' else m.group(0)
2931 return re.sub(cls.re_pytokens, subf, code)
2932 3341
2933 @cached_property 3342 @cached_property
2934 def co(self): 3343 def co(self):
2935 return compile(self.code, self.filename or '<string>', 'exec') 3344 return compile(self.code, self.filename or '<string>', 'exec')
2936 3345
2937 @cached_property 3346 @cached_property
2938 def code(self): 3347 def code(self):
2939 stack = [] # Current Code indentation 3348 source = self.source
2940 lineno = 0 # Current line of code 3349 if not source:
2941 ptrbuffer = [] # Buffer for printable strings and token tuple instances 3350 with open(self.filename, 'rb') as f:
2942 codebuffer = [] # Buffer for generated python code 3351 source = f.read()
2943 multiline = dedent = oneline = False 3352 try:
2944 template = self.source or open(self.filename, 'rb').read() 3353 source, encoding = touni(source), 'utf8'
2945 3354 except UnicodeError:
2946 def yield_tokens(line): 3355 depr('Template encodings other than utf8 are no longer supported.') #0.11
2947 for i, part in enumerate(re.split(r'\{\{(.*?)\}\}', line)): 3356 source, encoding = touni(source, 'latin1'), 'latin1'
2948 if i % 2: 3357 parser = StplParser(source, encoding=encoding, syntax=self.syntax)
2949 if part.startswith('!'): yield 'RAW', part[1:] 3358 code = parser.translate()
2950 else: yield 'CMD', part 3359 self.encoding = parser.encoding
2951 else: yield 'TXT', part 3360 return code
2952 3361
2953 def flush(): # Flush the ptrbuffer 3362 def _rebase(self, _env, _name=None, **kwargs):
2954 if not ptrbuffer: return 3363 if _name is None:
2955 cline = '' 3364 depr('Rebase function called without arguments.'
2956 for line in ptrbuffer: 3365 ' You were probably looking for {{base}}?', True) #0.12
2957 for token, value in line: 3366 _env['_rebase'] = (_name, kwargs)
2958 if token == 'TXT': cline += repr(value) 3367
2959 elif token == 'RAW': cline += '_str(%s)' % value 3368 def _include(self, _env, _name=None, **kwargs):
2960 elif token == 'CMD': cline += '_escape(%s)' % value 3369 if _name is None:
2961 cline += ', ' 3370 depr('Rebase function called without arguments.'
2962 cline = cline[:-2] + '\\\n' 3371 ' You were probably looking for {{base}}?', True) #0.12
2963 cline = cline[:-2] 3372 env = _env.copy()
2964 if cline[:-1].endswith('\\\\\\\\\\n'): 3373 env.update(kwargs)
2965 cline = cline[:-7] + cline[-1] # 'nobr\\\\\n' --> 'nobr'
2966 cline = '_printlist([' + cline + '])'
2967 del ptrbuffer[:] # Do this before calling code() again
2968 code(cline)
2969
2970 def code(stmt):
2971 for line in stmt.splitlines():
2972 codebuffer.append(' ' * len(stack) + line.strip())
2973
2974 for line in template.splitlines(True):
2975 lineno += 1
2976 line = touni(line, self.encoding)
2977 sline = line.lstrip()
2978 if lineno <= 2:
2979 m = re.match(r"%\s*#.*coding[:=]\s*([-\w.]+)", sline)
2980 if m: self.encoding = m.group(1)
2981 if m: line = line.replace('coding','coding (removed)')
2982 if sline and sline[0] == '%' and sline[:2] != '%%':
2983 line = line.split('%',1)[1].lstrip() # Full line following the %
2984 cline = self.split_comment(line).strip()
2985 cmd = re.split(r'[^a-zA-Z0-9_]', cline)[0]
2986 flush() # You are actually reading this? Good luck, it's a mess :)
2987 if cmd in self.blocks or multiline:
2988 cmd = multiline or cmd
2989 dedent = cmd in self.dedent_blocks # "else:"
2990 if dedent and not oneline and not multiline:
2991 cmd = stack.pop()
2992 code(line)
2993 oneline = not cline.endswith(':') # "if 1: pass"
2994 multiline = cmd if cline.endswith('\\') else False
2995 if not oneline and not multiline:
2996 stack.append(cmd)
2997 elif cmd == 'end' and stack:
2998 code('#end(%s) %s' % (stack.pop(), line.strip()[3:]))
2999 elif cmd == 'include':
3000 p = cline.split(None, 2)[1:]
3001 if len(p) == 2:
3002 code("_=_include(%s, _stdout, %s)" % (repr(p[0]), p[1]))
3003 elif p:
3004 code("_=_include(%s, _stdout)" % repr(p[0]))
3005 else: # Empty %include -> reverse of %rebase
3006 code("_printlist(_base)")
3007 elif cmd == 'rebase':
3008 p = cline.split(None, 2)[1:]
3009 if len(p) == 2:
3010 code("globals()['_rebase']=(%s, dict(%s))" % (repr(p[0]), p[1]))
3011 elif p:
3012 code("globals()['_rebase']=(%s, {})" % repr(p[0]))
3013 else:
3014 code(line)
3015 else: # Line starting with text (not '%') or '%%' (escaped)
3016 if line.strip().startswith('%%'):
3017 line = line.replace('%%', '%', 1)
3018 ptrbuffer.append(yield_tokens(line))
3019 flush()
3020 return '\n'.join(codebuffer) + '\n'
3021
3022 def subtemplate(self, _name, _stdout, *args, **kwargs):
3023 for dictarg in args: kwargs.update(dictarg)
3024 if _name not in self.cache: 3374 if _name not in self.cache:
3025 self.cache[_name] = self.__class__(name=_name, lookup=self.lookup) 3375 self.cache[_name] = self.__class__(name=_name, lookup=self.lookup)
3026 return self.cache[_name].execute(_stdout, kwargs) 3376 return self.cache[_name].execute(env['_stdout'], env)
3027 3377
3028 def execute(self, _stdout, *args, **kwargs): 3378 def execute(self, _stdout, kwargs):
3029 for dictarg in args: kwargs.update(dictarg)
3030 env = self.defaults.copy() 3379 env = self.defaults.copy()
3380 env.update(kwargs)
3031 env.update({'_stdout': _stdout, '_printlist': _stdout.extend, 3381 env.update({'_stdout': _stdout, '_printlist': _stdout.extend,
3032 '_include': self.subtemplate, '_str': self._str, 3382 'include': functools.partial(self._include, env),
3033 '_escape': self._escape, 'get': env.get, 3383 'rebase': functools.partial(self._rebase, env), '_rebase': None,
3034 'setdefault': env.setdefault, 'defined': env.__contains__}) 3384 '_str': self._str, '_escape': self._escape, 'get': env.get,
3035 env.update(kwargs) 3385 'setdefault': env.setdefault, 'defined': env.__contains__ })
3036 eval(self.co, env) 3386 eval(self.co, env)
3037 if '_rebase' in env: 3387 if env.get('_rebase'):
3038 subtpl, rargs = env['_rebase'] 3388 subtpl, rargs = env.pop('_rebase')
3039 rargs['_base'] = _stdout[:] #copy stdout 3389 rargs['base'] = ''.join(_stdout) #copy stdout
3040 del _stdout[:] # clear stdout 3390 del _stdout[:] # clear stdout
3041 return self.subtemplate(subtpl,_stdout,rargs) 3391 return self._include(env, subtpl, **rargs)
3042 return env 3392 return env
3043 3393
3044 def render(self, *args, **kwargs): 3394 def render(self, *args, **kwargs):
3045 """ Render the template using keyword arguments as local variables. """ 3395 """ Render the template using keyword arguments as local variables. """
3046 for dictarg in args: kwargs.update(dictarg) 3396 env = {}; stdout = []
3047 stdout = [] 3397 for dictarg in args: env.update(dictarg)
3048 self.execute(stdout, kwargs) 3398 env.update(kwargs)
3399 self.execute(stdout, env)
3049 return ''.join(stdout) 3400 return ''.join(stdout)
3401
3402
3403 class StplSyntaxError(TemplateError): pass
3404
3405
3406 class StplParser(object):
3407 ''' Parser for stpl templates. '''
3408 _re_cache = {} #: Cache for compiled re patterns
3409 # This huge pile of voodoo magic splits python code into 8 different tokens.
3410 # 1: All kinds of python strings (trust me, it works)
3411 _re_tok = '((?m)[urbURB]?(?:\'\'(?!\')|""(?!")|\'{6}|"{6}' \
3412 '|\'(?:[^\\\\\']|\\\\.)+?\'|"(?:[^\\\\"]|\\\\.)+?"' \
3413 '|\'{3}(?:[^\\\\]|\\\\.|\\n)+?\'{3}' \
3414 '|"{3}(?:[^\\\\]|\\\\.|\\n)+?"{3}))'
3415 _re_inl = _re_tok.replace('|\\n','') # We re-use this string pattern later
3416 # 2: Comments (until end of line, but not the newline itself)
3417 _re_tok += '|(#.*)'
3418 # 3,4: Keywords that start or continue a python block (only start of line)
3419 _re_tok += '|^([ \\t]*(?:if|for|while|with|try|def|class)\\b)' \
3420 '|^([ \\t]*(?:elif|else|except|finally)\\b)'
3421 # 5: Our special 'end' keyword (but only if it stands alone)
3422 _re_tok += '|((?:^|;)[ \\t]*end[ \\t]*(?=(?:%(block_close)s[ \\t]*)?\\r?$|;|#))'
3423 # 6: A customizable end-of-code-block template token (only end of line)
3424 _re_tok += '|(%(block_close)s[ \\t]*(?=$))'
3425 # 7: And finally, a single newline. The 8th token is 'everything else'
3426 _re_tok += '|(\\r?\\n)'
3427 # Match the start tokens of code areas in a template
3428 _re_split = '(?m)^[ \t]*(\\\\?)((%(line_start)s)|(%(block_start)s))(%%?)'
3429 # Match inline statements (may contain python strings)
3430 _re_inl = '%%(inline_start)s((?:%s|[^\'"\n]*?)+)%%(inline_end)s' % _re_inl
3431
3432 default_syntax = '<% %> % {{ }}'
3433
3434 def __init__(self, source, syntax=None, encoding='utf8'):
3435 self.source, self.encoding = touni(source, encoding), encoding
3436 self.set_syntax(syntax or self.default_syntax)
3437 self.code_buffer, self.text_buffer = [], []
3438 self.lineno, self.offset = 1, 0
3439 self.indent, self.indent_mod = 0, 0
3440
3441 def get_syntax(self):
3442 ''' Tokens as a space separated string (default: <% %> % {{ }}) '''
3443 return self._syntax
3444
3445 def set_syntax(self, syntax):
3446 self._syntax = syntax
3447 self._tokens = syntax.split()
3448 if not syntax in self._re_cache:
3449 names = 'block_start block_close line_start inline_start inline_end'
3450 etokens = map(re.escape, self._tokens)
3451 pattern_vars = dict(zip(names.split(), etokens))
3452 patterns = (self._re_split, self._re_tok, self._re_inl)
3453 patterns = [re.compile(p%pattern_vars) for p in patterns]
3454 self._re_cache[syntax] = patterns
3455 self.re_split, self.re_tok, self.re_inl = self._re_cache[syntax]
3456
3457 syntax = property(get_syntax, set_syntax)
3458
3459 def translate(self):
3460 if self.offset: raise RuntimeError('Parser is a one time instance.')
3461 while True:
3462 m = self.re_split.search(self.source[self.offset:])
3463 if m:
3464 text = self.source[self.offset:self.offset+m.start()]
3465 self.text_buffer.append(text)
3466 self.offset += m.end()
3467 if m.group(1): # New escape syntax
3468 line, sep, _ = self.source[self.offset:].partition('\n')
3469 self.text_buffer.append(m.group(2)+m.group(5)+line+sep)
3470 self.offset += len(line+sep)+1
3471 continue
3472 elif m.group(5): # Old escape syntax
3473 depr('Escape code lines with a backslash.') #0.12
3474 line, sep, _ = self.source[self.offset:].partition('\n')
3475 self.text_buffer.append(m.group(2)+line+sep)
3476 self.offset += len(line+sep)+1
3477 continue
3478 self.flush_text()
3479 self.read_code(multiline=bool(m.group(4)))
3480 else: break
3481 self.text_buffer.append(self.source[self.offset:])
3482 self.flush_text()
3483 return ''.join(self.code_buffer)
3484
3485 def read_code(self, multiline):
3486 code_line, comment = '', ''
3487 while True:
3488 m = self.re_tok.search(self.source[self.offset:])
3489 if not m:
3490 code_line += self.source[self.offset:]
3491 self.offset = len(self.source)
3492 self.write_code(code_line.strip(), comment)
3493 return
3494 code_line += self.source[self.offset:self.offset+m.start()]
3495 self.offset += m.end()
3496 _str, _com, _blk1, _blk2, _end, _cend, _nl = m.groups()
3497 if code_line and (_blk1 or _blk2): # a if b else c
3498 code_line += _blk1 or _blk2
3499 continue
3500 if _str: # Python string
3501 code_line += _str
3502 elif _com: # Python comment (up to EOL)
3503 comment = _com
3504 if multiline and _com.strip().endswith(self._tokens[1]):
3505 multiline = False # Allow end-of-block in comments
3506 elif _blk1: # Start-block keyword (if/for/while/def/try/...)
3507 code_line, self.indent_mod = _blk1, -1
3508 self.indent += 1
3509 elif _blk2: # Continue-block keyword (else/elif/except/...)
3510 code_line, self.indent_mod = _blk2, -1
3511 elif _end: # The non-standard 'end'-keyword (ends a block)
3512 self.indent -= 1
3513 elif _cend: # The end-code-block template token (usually '%>')
3514 if multiline: multiline = False
3515 else: code_line += _cend
3516 else: # \n
3517 self.write_code(code_line.strip(), comment)
3518 self.lineno += 1
3519 code_line, comment, self.indent_mod = '', '', 0
3520 if not multiline:
3521 break
3522
3523 def flush_text(self):
3524 text = ''.join(self.text_buffer)
3525 del self.text_buffer[:]
3526 if not text: return
3527 parts, pos, nl = [], 0, '\\\n'+' '*self.indent
3528 for m in self.re_inl.finditer(text):
3529 prefix, pos = text[pos:m.start()], m.end()
3530 if prefix:
3531 parts.append(nl.join(map(repr, prefix.splitlines(True))))
3532 if prefix.endswith('\n'): parts[-1] += nl
3533 parts.append(self.process_inline(m.group(1).strip()))
3534 if pos < len(text):
3535 prefix = text[pos:]
3536 lines = prefix.splitlines(True)
3537 if lines[-1].endswith('\\\\\n'): lines[-1] = lines[-1][:-3]
3538 elif lines[-1].endswith('\\\\\r\n'): lines[-1] = lines[-1][:-4]
3539 parts.append(nl.join(map(repr, lines)))
3540 code = '_printlist((%s,))' % ', '.join(parts)
3541 self.lineno += code.count('\n')+1
3542 self.write_code(code)
3543
3544 def process_inline(self, chunk):
3545 if chunk[0] == '!': return '_str(%s)' % chunk[1:]
3546 return '_escape(%s)' % chunk
3547
3548 def write_code(self, line, comment=''):
3549 line, comment = self.fix_backward_compatibility(line, comment)
3550 code = ' ' * (self.indent+self.indent_mod)
3551 code += line.lstrip() + comment + '\n'
3552 self.code_buffer.append(code)
3553
3554 def fix_backward_compatibility(self, line, comment):
3555 parts = line.strip().split(None, 2)
3556 if parts and parts[0] in ('include', 'rebase'):
3557 depr('The include and rebase keywords are functions now.') #0.12
3558 if len(parts) == 1: return "_printlist([base])", comment
3559 elif len(parts) == 2: return "_=%s(%r)" % tuple(parts), comment
3560 else: return "_=%s(%r, %s)" % tuple(parts), comment
3561 if self.lineno <= 2 and not line.strip() and 'coding' in comment:
3562 m = re.match(r"#.*coding[:=]\s*([-\w.]+)", comment)
3563 if m:
3564 depr('PEP263 encoding strings in templates are deprecated.') #0.12
3565 enc = m.group(1)
3566 self.source = self.source.encode(self.encoding).decode(enc)
3567 self.encoding = enc
3568 return line, comment.replace('coding','coding*')
3569 return line, comment
3050 3570
3051 3571
3052 def template(*args, **kwargs): 3572 def template(*args, **kwargs):
3053 ''' 3573 '''
3054 Get a rendered template as a string iterator. 3574 Get a rendered template as a string iterator.
3055 You can use a name, a filename or a template string as first parameter. 3575 You can use a name, a filename or a template string as first parameter.
3056 Template rendering arguments can be passed as dictionaries 3576 Template rendering arguments can be passed as dictionaries
3057 or directly (as keyword arguments). 3577 or directly (as keyword arguments).
3058 ''' 3578 '''
3059 tpl = args[0] if args else None 3579 tpl = args[0] if args else None
3060 template_adapter = kwargs.pop('template_adapter', SimpleTemplate) 3580 adapter = kwargs.pop('template_adapter', SimpleTemplate)
3061 if tpl not in TEMPLATES or DEBUG: 3581 lookup = kwargs.pop('template_lookup', TEMPLATE_PATH)
3582 tplid = (id(lookup), tpl)
3583 if tplid not in TEMPLATES or DEBUG:
3062 settings = kwargs.pop('template_settings', {}) 3584 settings = kwargs.pop('template_settings', {})
3063 lookup = kwargs.pop('template_lookup', TEMPLATE_PATH) 3585 if isinstance(tpl, adapter):
3064 if isinstance(tpl, template_adapter): 3586 TEMPLATES[tplid] = tpl
3065 TEMPLATES[tpl] = tpl 3587 if settings: TEMPLATES[tplid].prepare(**settings)
3066 if settings: TEMPLATES[tpl].prepare(**settings)
3067 elif "\n" in tpl or "{" in tpl or "%" in tpl or '$' in tpl: 3588 elif "\n" in tpl or "{" in tpl or "%" in tpl or '$' in tpl:
3068 TEMPLATES[tpl] = template_adapter(source=tpl, lookup=lookup, **settings) 3589 TEMPLATES[tplid] = adapter(source=tpl, lookup=lookup, **settings)
3069 else: 3590 else:
3070 TEMPLATES[tpl] = template_adapter(name=tpl, lookup=lookup, **settings) 3591 TEMPLATES[tplid] = adapter(name=tpl, lookup=lookup, **settings)
3071 if not TEMPLATES[tpl]: 3592 if not TEMPLATES[tplid]:
3072 abort(500, 'Template (%s) not found' % tpl) 3593 abort(500, 'Template (%s) not found' % tpl)
3073 for dictarg in args[1:]: kwargs.update(dictarg) 3594 for dictarg in args[1:]: kwargs.update(dictarg)
3074 return TEMPLATES[tpl].render(kwargs) 3595 return TEMPLATES[tplid].render(kwargs)
3075 3596
3076 mako_template = functools.partial(template, template_adapter=MakoTemplate) 3597 mako_template = functools.partial(template, template_adapter=MakoTemplate)
3077 cheetah_template = functools.partial(template, template_adapter=CheetahTemplate) 3598 cheetah_template = functools.partial(template, template_adapter=CheetahTemplate)
3078 jinja2_template = functools.partial(template, template_adapter=Jinja2Template) 3599 jinja2_template = functools.partial(template, template_adapter=Jinja2Template)
3079 simpletal_template = functools.partial(template, template_adapter=SimpleTALTemplate)
3080 3600
3081 3601
3082 def view(tpl_name, **defaults): 3602 def view(tpl_name, **defaults):
3083 ''' Decorator: renders a template for a handler. 3603 ''' Decorator: renders a template for a handler.
3084 The handler can control its behavior like that: 3604 The handler can control its behavior like that:
3095 result = func(*args, **kwargs) 3615 result = func(*args, **kwargs)
3096 if isinstance(result, (dict, DictMixin)): 3616 if isinstance(result, (dict, DictMixin)):
3097 tplvars = defaults.copy() 3617 tplvars = defaults.copy()
3098 tplvars.update(result) 3618 tplvars.update(result)
3099 return template(tpl_name, **tplvars) 3619 return template(tpl_name, **tplvars)
3620 elif result is None:
3621 return template(tpl_name, defaults)
3100 return result 3622 return result
3101 return wrapper 3623 return wrapper
3102 return decorator 3624 return decorator
3103 3625
3104 mako_view = functools.partial(view, template_adapter=MakoTemplate) 3626 mako_view = functools.partial(view, template_adapter=MakoTemplate)
3105 cheetah_view = functools.partial(view, template_adapter=CheetahTemplate) 3627 cheetah_view = functools.partial(view, template_adapter=CheetahTemplate)
3106 jinja2_view = functools.partial(view, template_adapter=Jinja2Template) 3628 jinja2_view = functools.partial(view, template_adapter=Jinja2Template)
3107 simpletal_view = functools.partial(view, template_adapter=SimpleTALTemplate)
3108 3629
3109 3630
3110 3631
3111 3632
3112 3633
3122 NORUN = False # If set, run() does nothing. Used by load_app() 3643 NORUN = False # If set, run() does nothing. Used by load_app()
3123 3644
3124 #: A dict to map HTTP status codes (e.g. 404) to phrases (e.g. 'Not Found') 3645 #: A dict to map HTTP status codes (e.g. 404) to phrases (e.g. 'Not Found')
3125 HTTP_CODES = httplib.responses 3646 HTTP_CODES = httplib.responses
3126 HTTP_CODES[418] = "I'm a teapot" # RFC 2324 3647 HTTP_CODES[418] = "I'm a teapot" # RFC 2324
3648 HTTP_CODES[422] = "Unprocessable Entity" # RFC 4918
3127 HTTP_CODES[428] = "Precondition Required" 3649 HTTP_CODES[428] = "Precondition Required"
3128 HTTP_CODES[429] = "Too Many Requests" 3650 HTTP_CODES[429] = "Too Many Requests"
3129 HTTP_CODES[431] = "Request Header Fields Too Large" 3651 HTTP_CODES[431] = "Request Header Fields Too Large"
3130 HTTP_CODES[511] = "Network Authentication Required" 3652 HTTP_CODES[511] = "Network Authentication Required"
3131 _HTTP_STATUS_LINES = dict((k, '%d %s'%(k,v)) for (k,v) in HTTP_CODES.items()) 3653 _HTTP_STATUS_LINES = dict((k, '%d %s'%(k,v)) for (k,v) in HTTP_CODES.items())
3132 3654
3133 #: The default template used for error pages. Override with @error() 3655 #: The default template used for error pages. Override with @error()
3134 ERROR_PAGE_TEMPLATE = """ 3656 ERROR_PAGE_TEMPLATE = """
3135 %%try: 3657 %%try:
3136 %%from %s import DEBUG, HTTP_CODES, request, touni 3658 %%from %s import DEBUG, HTTP_CODES, request, touni
3137 %%status_name = HTTP_CODES.get(e.status, 'Unknown').title()
3138 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> 3659 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
3139 <html> 3660 <html>
3140 <head> 3661 <head>
3141 <title>Error {{e.status}}: {{status_name}}</title> 3662 <title>Error: {{e.status}}</title>
3142 <style type="text/css"> 3663 <style type="text/css">
3143 html {background-color: #eee; font-family: sans;} 3664 html {background-color: #eee; font-family: sans;}
3144 body {background-color: #fff; border: 1px solid #ddd; 3665 body {background-color: #fff; border: 1px solid #ddd;
3145 padding: 15px; margin: 15px;} 3666 padding: 15px; margin: 15px;}
3146 pre {background-color: #eee; border: 1px solid #ddd; padding: 5px;} 3667 pre {background-color: #eee; border: 1px solid #ddd; padding: 5px;}
3147 </style> 3668 </style>
3148 </head> 3669 </head>
3149 <body> 3670 <body>
3150 <h1>Error {{e.status}}: {{status_name}}</h1> 3671 <h1>Error: {{e.status}}</h1>
3151 <p>Sorry, the requested URL <tt>{{repr(request.url)}}</tt> 3672 <p>Sorry, the requested URL <tt>{{repr(request.url)}}</tt>
3152 caused an error:</p> 3673 caused an error:</p>
3153 <pre>{{e.output}}</pre> 3674 <pre>{{e.body}}</pre>
3154 %%if DEBUG and e.exception: 3675 %%if DEBUG and e.exception:
3155 <h2>Exception:</h2> 3676 <h2>Exception:</h2>
3156 <pre>{{repr(e.exception)}}</pre> 3677 <pre>{{repr(e.exception)}}</pre>
3157 %%end 3678 %%end
3158 %%if DEBUG and e.traceback: 3679 %%if DEBUG and e.traceback:
3165 <b>ImportError:</b> Could not generate the error page. Please add bottle to 3686 <b>ImportError:</b> Could not generate the error page. Please add bottle to
3166 the import path. 3687 the import path.
3167 %%end 3688 %%end
3168 """ % __name__ 3689 """ % __name__
3169 3690
3170 #: A thread-safe instance of :class:`LocalRequest`. If accessed from within a 3691 #: A thread-safe instance of :class:`LocalRequest`. If accessed from within a
3171 #: request callback, this instance always refers to the *current* request 3692 #: request callback, this instance always refers to the *current* request
3172 #: (even on a multithreaded server). 3693 #: (even on a multithreaded server).
3173 request = LocalRequest() 3694 request = LocalRequest()
3174 3695
3175 #: A thread-safe instance of :class:`LocalResponse`. It is used to change the 3696 #: A thread-safe instance of :class:`LocalResponse`. It is used to change the
3184 app = default_app = AppStack() 3705 app = default_app = AppStack()
3185 app.push() 3706 app.push()
3186 3707
3187 #: A virtual package that redirects import statements. 3708 #: A virtual package that redirects import statements.
3188 #: Example: ``import bottle.ext.sqlite`` actually imports `bottle_sqlite`. 3709 #: Example: ``import bottle.ext.sqlite`` actually imports `bottle_sqlite`.
3189 ext = _ImportRedirect(__name__+'.ext', 'bottle_%s').module 3710 ext = _ImportRedirect('bottle.ext' if __name__ == '__main__' else __name__+".ext", 'bottle_%s').module
3190 3711
3191 if __name__ == '__main__': 3712 if __name__ == '__main__':
3192 opt, args, parser = _cmd_options, _cmd_args, _cmd_parser 3713 opt, args, parser = _cmd_options, _cmd_args, _cmd_parser
3193 if opt.version: 3714 if opt.version:
3194 _stdout('Bottle %s\n'%__version__) 3715 _stdout('Bottle %s\n'%__version__)
3200 3721
3201 sys.path.insert(0, '.') 3722 sys.path.insert(0, '.')
3202 sys.modules.setdefault('bottle', sys.modules['__main__']) 3723 sys.modules.setdefault('bottle', sys.modules['__main__'])
3203 3724
3204 host, port = (opt.bind or 'localhost'), 8080 3725 host, port = (opt.bind or 'localhost'), 8080
3205 if ':' in host: 3726 if ':' in host and host.rfind(']') < host.rfind(':'):
3206 host, port = host.rsplit(':', 1) 3727 host, port = host.rsplit(':', 1)
3207 3728 host = host.strip('[]')
3208 run(args[0], host=host, port=port, server=opt.server, 3729
3730 run(args[0], host=host, port=int(port), server=opt.server,
3209 reloader=opt.reload, plugins=opt.plugin, debug=opt.debug) 3731 reloader=opt.reload, plugins=opt.plugin, debug=opt.debug)
3210 3732
3211 3733
3212 3734
3213 3735