Switch to unified view

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