2 # -*- coding: utf-8 -*-
4 Bottle is a fast and simple micro-framework for small web applications. It
5 offers request dispatching (Routes) with URL parameter support, templates,
6 a built-in HTTP Server and adapters for many third party WSGI/HTTP-server and
7 template engines - all in a single file and with no dependencies other than the
8 Python Standard Library.
10 Homepage and documentation: http://bottlepy.org/
12 Copyright (c) 2017, Marcel Hellkamp.
13 License: MIT (see LICENSE for details)
18 __author__ = 'Marcel Hellkamp'
19 __version__ = '0.13-dev'
22 ###############################################################################
23 # Command-line interface ######################################################
24 ###############################################################################
25 # INFO: Some server adapters need to monkey-patch std-lib modules before they
26 # are imported. This is why some of the command-line handling is done here, but
27 # the actual call to _main() is at the end of the file.
30 def _cli_parse(args): # pragma: no coverage
31 from argparse import ArgumentParser
33 parser = ArgumentParser(prog=args[0], usage="%(prog)s [options] package.module:app")
34 opt = parser.add_argument
35 opt("--version", action="store_true", help="show version number.")
36 opt("-b", "--bind", metavar="ADDRESS", help="bind socket to ADDRESS.")
37 opt("-s", "--server", default='wsgiref', help="use SERVER as backend.")
38 opt("-p", "--plugin", action="append", help="install additional plugin/s.")
39 opt("-c", "--conf", action="append", metavar="FILE",
40 help="load config values from FILE.")
41 opt("-C", "--param", action="append", metavar="NAME=VALUE",
42 help="override config values.")
43 opt("--debug", action="store_true", help="start server in debug mode.")
44 opt("--reload", action="store_true", help="auto-reload on file changes.")
45 opt('app', help='WSGI app entry point.', nargs='?')
47 cli_args = parser.parse_args(args[1:])
49 return cli_args, parser
52 def _cli_patch(cli_args): # pragma: no coverage
53 parsed_args, _ = _cli_parse(cli_args)
56 if opts.server.startswith('gevent'):
58 gevent.monkey.patch_all()
59 elif opts.server.startswith('eventlet'):
61 eventlet.monkey_patch()
64 if __name__ == '__main__':
67 ###############################################################################
68 # Imports and Python 2/3 unification ##########################################
69 ###############################################################################
72 import base64, cgi, email.utils, functools, hmac, imp, itertools, mimetypes,\
73 os, re, tempfile, threading, time, warnings, weakref, hashlib
75 from types import FunctionType
76 from datetime import date as datedate, datetime, timedelta
77 from tempfile import TemporaryFile
78 from traceback import format_exc, print_exc
79 from unicodedata import normalize
82 from ujson import dumps as json_dumps, loads as json_lds
84 from json import dumps as json_dumps, loads as json_lds
86 # inspect.getargspec was removed in Python 3.6, use
87 # Signature-based version where we can (Python 3.3+)
89 from inspect import signature
91 params = signature(func).parameters
92 args, varargs, keywords, defaults = [], None, None, []
93 for name, param in params.items():
94 if param.kind == param.VAR_POSITIONAL:
96 elif param.kind == param.VAR_KEYWORD:
100 if param.default is not param.empty:
101 defaults.append(param.default)
102 return (args, varargs, keywords, tuple(defaults) or None)
105 from inspect import getfullargspec
106 def getargspec(func):
107 spec = getfullargspec(func)
108 kwargs = makelist(spec[0]) + makelist(spec.kwonlyargs)
109 return kwargs, spec[1], spec[2], spec[3]
111 from inspect import getargspec
113 py3k = sys.version_info.major > 2
116 # Workaround for the "print is a keyword/function" Python 2/3 dilemma
117 # and a fallback for mod_wsgi (resticts stdout/err attribute access)
119 _stdout, _stderr = sys.stdout.write, sys.stderr.write
121 _stdout = lambda x: sys.stdout.write(x)
122 _stderr = lambda x: sys.stderr.write(x)
124 # Lots of stdlib and builtin differences.
126 import http.client as httplib
127 import _thread as thread
128 from urllib.parse import urljoin, SplitResult as UrlSplitResult
129 from urllib.parse import urlencode, quote as urlquote, unquote as urlunquote
130 urlunquote = functools.partial(urlunquote, encoding='latin1')
131 from http.cookies import SimpleCookie, Morsel, CookieError
132 from collections import MutableMapping as DictMixin
134 from io import BytesIO
139 json_loads = lambda s: json_lds(touni(s))
140 callable = lambda x: hasattr(x, '__call__')
144 raise a[0](a[1]).with_traceback(a[2])
148 from urlparse import urljoin, SplitResult as UrlSplitResult
149 from urllib import urlencode, quote as urlquote, unquote as urlunquote
150 from Cookie import SimpleCookie, Morsel, CookieError
151 from itertools import imap
152 import cPickle as pickle
153 from StringIO import StringIO as BytesIO
154 import ConfigParser as configparser
155 from collections import MutableMapping as DictMixin
157 json_loads = json_lds
158 exec(compile('def _raise(*a): raise a[0], a[1], a[2]', '<py3fix>', 'exec'))
160 # Some helpers for string/byte handling
161 def tob(s, enc='utf8'):
162 if isinstance(s, unicode):
164 return b'' if s is None else bytes(s)
167 def touni(s, enc='utf8', err='strict'):
168 if isinstance(s, bytes):
169 return s.decode(enc, err)
170 return unicode("" if s is None else s)
173 tonat = touni if py3k else tob
175 # 3.2 fixes cgi.FieldStorage to accept bytes (which makes a lot of sense).
178 # A bug in functools causes it to break if the wrapper is an instance method
179 def update_wrapper(wrapper, wrapped, *a, **ka):
181 functools.update_wrapper(wrapper, wrapped, *a, **ka)
182 except AttributeError:
185 # These helpers are used at module level and need to be defined first.
186 # And yes, I know PEP-8, but sometimes a lower-case classname makes more sense.
189 def depr(major, minor, cause, fix):
190 text = "Warning: Use of deprecated feature or API. (Deprecated in Bottle-%d.%d)\n"\
192 "Fix: %s\n" % (major, minor, cause, fix)
193 if DEBUG == 'strict':
194 raise DeprecationWarning(text)
195 warnings.warn(text, DeprecationWarning, stacklevel=3)
196 return DeprecationWarning(text)
199 def makelist(data): # This is just too handy
200 if isinstance(data, (tuple, list, set, dict)):
208 class DictProperty(object):
209 """ Property that maps to a key in a local dict-like attribute. """
211 def __init__(self, attr, key=None, read_only=False):
212 self.attr, self.key, self.read_only = attr, key, read_only
214 def __call__(self, func):
215 functools.update_wrapper(self, func, updated=[])
216 self.getter, self.key = func, self.key or func.__name__
219 def __get__(self, obj, cls):
220 if obj is None: return self
221 key, storage = self.key, getattr(obj, self.attr)
222 if key not in storage: storage[key] = self.getter(obj)
225 def __set__(self, obj, value):
226 if self.read_only: raise AttributeError("Read-Only property.")
227 getattr(obj, self.attr)[self.key] = value
229 def __delete__(self, obj):
230 if self.read_only: raise AttributeError("Read-Only property.")
231 del getattr(obj, self.attr)[self.key]
234 class cached_property(object):
235 """ A property that is only computed once per instance and then replaces
236 itself with an ordinary attribute. Deleting the attribute resets the
239 def __init__(self, func):
240 update_wrapper(self, func)
243 def __get__(self, obj, cls):
244 if obj is None: return self
245 value = obj.__dict__[self.func.__name__] = self.func(obj)
249 class lazy_attribute(object):
250 """ A property that caches itself to the class object. """
252 def __init__(self, func):
253 functools.update_wrapper(self, func, updated=[])
256 def __get__(self, obj, cls):
257 value = self.getter(cls)
258 setattr(cls, self.__name__, value)
261 ###############################################################################
262 # Exceptions and Events #######################################################
263 ###############################################################################
266 class BottleException(Exception):
267 """ A base class for exceptions used by bottle. """
270 ###############################################################################
271 # Routing ######################################################################
272 ###############################################################################
275 class RouteError(BottleException):
276 """ This is a base class for all routing related exceptions """
279 class RouteReset(BottleException):
280 """ If raised by a plugin or request handler, the route is reset and all
281 plugins are re-applied. """
284 class RouterUnknownModeError(RouteError):
289 class RouteSyntaxError(RouteError):
290 """ The route parser found something not supported by this router. """
293 class RouteBuildError(RouteError):
294 """ The route could not be built. """
298 """ Turn all capturing groups in a regular expression pattern into
299 non-capturing groups. """
302 return re.sub(r'(\\*)(\(\?P<[^>]+>|\((?!\?))', lambda m: m.group(0) if
303 len(m.group(1)) % 2 else m.group(1) + '(?:', p)
306 class Router(object):
307 """ A Router is an ordered collection of route->target pairs. It is used to
308 efficiently match WSGI requests against a number of routes and return
309 the first target that satisfies the request. The target may be anything,
310 usually a string, ID or callable object. A route consists of a path-rule
313 The path-rule is either a static path (e.g. `/contact`) or a dynamic
314 path that contains wildcards (e.g. `/wiki/<page>`). The wildcard syntax
315 and details on the matching order are described in docs:`routing`.
318 default_pattern = '[^/]+'
319 default_filter = 're'
321 #: The current CPython regexp implementation does not allow more
322 #: than 99 matching groups per regular expression.
323 _MAX_GROUPS_PER_PATTERN = 99
325 def __init__(self, strict=False):
326 self.rules = [] # All rules in order
327 self._groups = {} # index of regexes to find them in dyna_routes
328 self.builder = {} # Data structure for the url builder
329 self.static = {} # Search structure for static routes
330 self.dyna_routes = {}
331 self.dyna_regexes = {} # Search structure for dynamic routes
332 #: If true, static routes are no longer checked first.
333 self.strict_order = strict
335 're': lambda conf: (_re_flatten(conf or self.default_pattern),
337 'int': lambda conf: (r'-?\d+', int, lambda x: str(int(x))),
338 'float': lambda conf: (r'-?[\d.]+', float, lambda x: str(float(x))),
339 'path': lambda conf: (r'.+?', None, None)
342 def add_filter(self, name, func):
343 """ Add a filter. The provided function is called with the configuration
344 string as parameter and must return a (regexp, to_python, to_url) tuple.
345 The first element is a string, the last two are callables or None. """
346 self.filters[name] = func
348 rule_syntax = re.compile('(\\\\*)'
349 '(?:(?::([a-zA-Z_][a-zA-Z_0-9]*)?()(?:#(.*?)#)?)'
350 '|(?:<([a-zA-Z_][a-zA-Z_0-9]*)?(?::([a-zA-Z_]*)'
351 '(?::((?:\\\\.|[^\\\\>]+)+)?)?)?>))')
353 def _itertokens(self, rule):
354 offset, prefix = 0, ''
355 for match in self.rule_syntax.finditer(rule):
356 prefix += rule[offset:match.start()]
359 depr(0, 13, "Use of old route syntax.",
360 "Use <name> instead of :name in routes.")
361 if len(g[0]) % 2: # Escaped wildcard
362 prefix += match.group(0)[len(g[0]):]
366 yield prefix, None, None
367 name, filtr, conf = g[4:7] if g[2] is None else g[1:4]
368 yield name, filtr or 'default', conf or None
369 offset, prefix = match.end(), ''
370 if offset <= len(rule) or prefix:
371 yield prefix + rule[offset:], None, None
373 def add(self, rule, method, target, name=None):
374 """ Add a new rule or replace the target for an existing rule. """
375 anons = 0 # Number of anonymous wildcards found
376 keys = [] # Names of keys
377 pattern = '' # Regular expression pattern with named groups
378 filters = [] # Lists of wildcard input filters
379 builder = [] # Data structure for the URL builder
382 for key, mode, conf in self._itertokens(rule):
385 if mode == 'default': mode = self.default_filter
386 mask, in_filter, out_filter = self.filters[mode](conf)
388 pattern += '(?:%s)' % mask
389 key = 'anon%d' % anons
392 pattern += '(?P<%s>%s)' % (key, mask)
394 if in_filter: filters.append((key, in_filter))
395 builder.append((key, out_filter or str))
397 pattern += re.escape(key)
398 builder.append((None, key))
400 self.builder[rule] = builder
401 if name: self.builder[name] = builder
403 if is_static and not self.strict_order:
404 self.static.setdefault(method, {})
405 self.static[method][self.build(rule)] = (target, None)
409 re_pattern = re.compile('^(%s)$' % pattern)
410 re_match = re_pattern.match
411 except re.error as e:
412 raise RouteSyntaxError("Could not add Route: %s (%s)" % (rule, e))
417 url_args = re_match(path).groupdict()
418 for name, wildcard_filter in filters:
420 url_args[name] = wildcard_filter(url_args[name])
422 raise HTTPError(400, 'Path has wrong format.')
424 elif re_pattern.groupindex:
427 return re_match(path).groupdict()
431 flatpat = _re_flatten(pattern)
432 whole_rule = (rule, flatpat, target, getargs)
434 if (flatpat, method) in self._groups:
436 msg = 'Route <%s %s> overwrites a previously defined route'
437 warnings.warn(msg % (method, rule), RuntimeWarning)
438 self.dyna_routes[method][
439 self._groups[flatpat, method]] = whole_rule
441 self.dyna_routes.setdefault(method, []).append(whole_rule)
442 self._groups[flatpat, method] = len(self.dyna_routes[method]) - 1
444 self._compile(method)
446 def _compile(self, method):
447 all_rules = self.dyna_routes[method]
448 comborules = self.dyna_regexes[method] = []
449 maxgroups = self._MAX_GROUPS_PER_PATTERN
450 for x in range(0, len(all_rules), maxgroups):
451 some = all_rules[x:x + maxgroups]
452 combined = (flatpat for (_, flatpat, _, _) in some)
453 combined = '|'.join('(^%s$)' % flatpat for flatpat in combined)
454 combined = re.compile(combined).match
455 rules = [(target, getargs) for (_, _, target, getargs) in some]
456 comborules.append((combined, rules))
458 def build(self, _name, *anons, **query):
459 """ Build an URL by filling the wildcards in a rule. """
460 builder = self.builder.get(_name)
462 raise RouteBuildError("No route with that name.", _name)
464 for i, value in enumerate(anons):
465 query['anon%d' % i] = value
466 url = ''.join([f(query.pop(n)) if n else f for (n, f) in builder])
467 return url if not query else url + '?' + urlencode(query)
468 except KeyError as E:
469 raise RouteBuildError('Missing URL argument: %r' % E.args[0])
471 def match(self, environ):
472 """ Return a (target, url_args) tuple or raise HTTPError(400/404/405). """
473 verb = environ['REQUEST_METHOD'].upper()
474 path = environ['PATH_INFO'] or '/'
477 methods = ['PROXY', verb, 'GET', 'ANY']
479 methods = ['PROXY', verb, 'ANY']
481 for method in methods:
482 if method in self.static and path in self.static[method]:
483 target, getargs = self.static[method][path]
484 return target, getargs(path) if getargs else {}
485 elif method in self.dyna_regexes:
486 for combined, rules in self.dyna_regexes[method]:
487 match = combined(path)
489 target, getargs = rules[match.lastindex - 1]
490 return target, getargs(path) if getargs else {}
492 # No matching route found. Collect alternative methods for 405 response
494 nocheck = set(methods)
495 for method in set(self.static) - nocheck:
496 if path in self.static[method]:
498 for method in set(self.dyna_regexes) - allowed - nocheck:
499 for combined, rules in self.dyna_regexes[method]:
500 match = combined(path)
504 allow_header = ",".join(sorted(allowed))
505 raise HTTPError(405, "Method not allowed.", Allow=allow_header)
507 # No matching route and no alternative method found. We give up
508 raise HTTPError(404, "Not found: " + repr(path))
512 """ This class wraps a route callback along with route specific metadata and
513 configuration and applies Plugins on demand. It is also responsible for
514 turing an URL path rule into a regular expression usable by the Router.
517 def __init__(self, app, rule, method, callback,
520 skiplist=None, **config):
521 #: The application this route is installed to.
523 #: The path-rule string (e.g. ``/wiki/<page>``).
525 #: The HTTP method as a string (e.g. ``GET``).
527 #: The original callback with no plugins applied. Useful for introspection.
528 self.callback = callback
529 #: The name of the route (if specified) or ``None``.
530 self.name = name or None
531 #: A list of route-specific plugins (see :meth:`Bottle.route`).
532 self.plugins = plugins or []
533 #: A list of plugins to not apply to this route (see :meth:`Bottle.route`).
534 self.skiplist = skiplist or []
535 #: Additional keyword arguments passed to the :meth:`Bottle.route`
536 #: decorator are stored in this dictionary. Used for route-specific
537 #: plugin configuration and meta-data.
538 self.config = app.config._make_overlay()
539 self.config.load_dict(config)
543 """ The route callback with all plugins applied. This property is
544 created on demand and then cached to speed up subsequent requests."""
545 return self._make_callback()
548 """ Forget any cached values. The next time :attr:`call` is accessed,
549 all plugins are re-applied. """
550 self.__dict__.pop('call', None)
553 """ Do all on-demand work immediately (useful for debugging)."""
556 def all_plugins(self):
557 """ Yield all Plugins affecting this route. """
559 for p in reversed(self.app.plugins + self.plugins):
560 if True in self.skiplist: break
561 name = getattr(p, 'name', False)
562 if name and (name in self.skiplist or name in unique): continue
563 if p in self.skiplist or type(p) in self.skiplist: continue
564 if name: unique.add(name)
567 def _make_callback(self):
568 callback = self.callback
569 for plugin in self.all_plugins():
571 if hasattr(plugin, 'apply'):
572 callback = plugin.apply(callback, self)
574 callback = plugin(callback)
575 except RouteReset: # Try again with changed configuration.
576 return self._make_callback()
577 if not callback is self.callback:
578 update_wrapper(callback, self.callback)
581 def get_undecorated_callback(self):
582 """ Return the callback. If the callback is a decorated function, try to
583 recover the original function. """
585 func = getattr(func, '__func__' if py3k else 'im_func', func)
586 closure_attr = '__closure__' if py3k else 'func_closure'
587 while hasattr(func, closure_attr) and getattr(func, closure_attr):
588 attributes = getattr(func, closure_attr)
589 func = attributes[0].cell_contents
591 # in case of decorators with multiple arguments
592 if not isinstance(func, FunctionType):
593 # pick first FunctionType instance from multiple arguments
594 func = filter(lambda x: isinstance(x, FunctionType),
595 map(lambda x: x.cell_contents, attributes))
596 func = list(func)[0] # py3 support
599 def get_callback_args(self):
600 """ Return a list of argument names the callback (most likely) accepts
601 as keyword arguments. If the callback is a decorated function, try
602 to recover the original function before inspection. """
603 return getargspec(self.get_undecorated_callback())[0]
605 def get_config(self, key, default=None):
606 """ Lookup a config field and return its value, first checking the
607 route.config, then route.app.config."""
608 depr(0, 13, "Route.get_config() is deprectated.",
609 "The Route.config property already includes values from the"
610 " application config for missing keys. Access it directly.")
611 return self.config.get(key, default)
614 cb = self.get_undecorated_callback()
615 return '<%s %r %r>' % (self.method, self.rule, cb)
617 ###############################################################################
618 # Application Object ###########################################################
619 ###############################################################################
622 class Bottle(object):
623 """ Each Bottle object represents a single, distinct web application and
624 consists of routes, callbacks, plugins, resources and configuration.
625 Instances are callable WSGI applications.
627 :param catchall: If true (default), handle all exceptions. Turn off to
628 let debugging middleware handle exceptions.
632 def _global_config(cls):
634 cfg.meta_set('catchall', 'validate', bool)
637 def __init__(self, **kwargs):
638 #: A :class:`ConfigDict` for app specific configuration.
639 self.config = self._global_config._make_overlay()
640 self.config._add_change_listener(
641 functools.partial(self.trigger_hook, 'config'))
647 if kwargs.get('catchall') is False:
648 depr(0, 13, "Bottle(catchall) keyword argument.",
649 "The 'catchall' setting is now part of the app "
650 "configuration. Fix: `app.config['catchall'] = False`")
651 self.config['catchall'] = False
652 if kwargs.get('autojson') is False:
653 depr(0, 13, "Bottle(autojson) keyword argument.",
654 "The 'autojson' setting is now part of the app "
655 "configuration. Fix: `app.config['json.enable'] = False`")
656 self.config['json.disable'] = True
660 #: A :class:`ResourceManager` for application files
661 self.resources = ResourceManager()
663 self.routes = [] # List of installed :class:`Route` instances.
664 self.router = Router() # Maps requests to :class:`Route` instances.
665 self.error_handler = {}
668 self.plugins = [] # List of installed plugins.
669 self.install(JSONPlugin())
670 self.install(TemplatePlugin())
672 #: If true, most exceptions are caught and returned as :exc:`HTTPError`
673 catchall = DictProperty('config', 'catchall')
675 __hook_names = 'before_request', 'after_request', 'app_reset', 'config'
676 __hook_reversed = {'after_request'}
680 return dict((name, []) for name in self.__hook_names)
682 def add_hook(self, name, func):
683 """ Attach a callback to a hook. Three hooks are currently implemented:
686 Executed once before each request. The request context is
687 available, but no routing has happened yet.
689 Executed once after each request regardless of its outcome.
691 Called whenever :meth:`Bottle.reset` is called.
693 if name in self.__hook_reversed:
694 self._hooks[name].insert(0, func)
696 self._hooks[name].append(func)
698 def remove_hook(self, name, func):
699 """ Remove a callback from a hook. """
700 if name in self._hooks and func in self._hooks[name]:
701 self._hooks[name].remove(func)
704 def trigger_hook(self, __name, *args, **kwargs):
705 """ Trigger a hook and return a list of results. """
706 return [hook(*args, **kwargs) for hook in self._hooks[__name][:]]
708 def hook(self, name):
709 """ Return a decorator that attaches a callback to a hook. See
710 :meth:`add_hook` for details."""
713 self.add_hook(name, func)
718 def _mount_wsgi(self, prefix, app, **options):
719 segments = [p for p in prefix.split('/') if p]
721 raise ValueError('WSGI applications cannot be mounted to "/".')
722 path_depth = len(segments)
724 def mountpoint_wrapper():
726 request.path_shift(path_depth)
727 rs = HTTPResponse([])
729 def start_response(status, headerlist, exc_info=None):
733 for name, value in headerlist:
734 rs.add_header(name, value)
735 return rs.body.append
737 body = app(request.environ, start_response)
738 rs.body = itertools.chain(rs.body, body) if rs.body else body
741 request.path_shift(-path_depth)
743 options.setdefault('skip', True)
744 options.setdefault('method', 'PROXY')
745 options.setdefault('mountpoint', {'prefix': prefix, 'target': app})
746 options['callback'] = mountpoint_wrapper
748 self.route('/%s/<:re:.*>' % '/'.join(segments), **options)
749 if not prefix.endswith('/'):
750 self.route('/' + '/'.join(segments), **options)
752 def _mount_app(self, prefix, app, **options):
753 if app in self._mounts or '_mount.app' in app.config:
754 depr(0, 13, "Application mounted multiple times. Falling back to WSGI mount.",
755 "Clone application before mounting to a different location.")
756 return self._mount_wsgi(prefix, app, **options)
759 depr(0, 13, "Unsupported mount options. Falling back to WSGI mount.",
760 "Do not specify any route options when mounting bottle application.")
761 return self._mount_wsgi(prefix, app, **options)
763 if not prefix.endswith("/"):
764 depr(0, 13, "Prefix must end in '/'. Falling back to WSGI mount.",
765 "Consider adding an explicit redirect from '/prefix' to '/prefix/' in the parent application.")
766 return self._mount_wsgi(prefix, app, **options)
768 self._mounts.append(app)
769 app.config['_mount.prefix'] = prefix
770 app.config['_mount.app'] = self
771 for route in app.routes:
772 route.rule = prefix + route.rule.lstrip('/')
773 self.add_route(route)
775 def mount(self, prefix, app, **options):
776 """ Mount an application (:class:`Bottle` or plain WSGI) to a specific
777 URL prefix. Example::
779 parent_app.mount('/prefix/', child_app)
781 :param prefix: path prefix or `mount-point`.
782 :param app: an instance of :class:`Bottle` or a WSGI application.
784 Plugins from the parent application are not applied to the routes
785 of the mounted child application. If you need plugins in the child
786 application, install them separately.
788 While it is possible to use path wildcards within the prefix path
789 (:class:`Bottle` childs only), it is highly discouraged.
791 The prefix path must end with a slash. If you want to access the
792 root of the child application via `/prefix` in addition to
793 `/prefix/`, consider adding a route with a 307 redirect to the
797 if not prefix.startswith('/'):
798 raise ValueError("Prefix must start with '/'")
800 if isinstance(app, Bottle):
801 return self._mount_app(prefix, app, **options)
803 return self._mount_wsgi(prefix, app, **options)
805 def merge(self, routes):
806 """ Merge the routes of another :class:`Bottle` application or a list of
807 :class:`Route` objects into this application. The routes keep their
808 'owner', meaning that the :data:`Route.app` attribute is not
810 if isinstance(routes, Bottle):
811 routes = routes.routes
813 self.add_route(route)
815 def install(self, plugin):
816 """ Add a plugin to the list of plugins and prepare it for being
817 applied to all routes of this application. A plugin may be a simple
818 decorator or an object that implements the :class:`Plugin` API.
820 if hasattr(plugin, 'setup'): plugin.setup(self)
821 if not callable(plugin) and not hasattr(plugin, 'apply'):
822 raise TypeError("Plugins must be callable or implement .apply()")
823 self.plugins.append(plugin)
827 def uninstall(self, plugin):
828 """ Uninstall plugins. Pass an instance to remove a specific plugin, a type
829 object to remove all plugins that match that type, a string to remove
830 all plugins with a matching ``name`` attribute or ``True`` to remove all
831 plugins. Return the list of removed plugins. """
832 removed, remove = [], plugin
833 for i, plugin in list(enumerate(self.plugins))[::-1]:
834 if remove is True or remove is plugin or remove is type(plugin) \
835 or getattr(plugin, 'name', True) == remove:
836 removed.append(plugin)
838 if hasattr(plugin, 'close'): plugin.close()
839 if removed: self.reset()
842 def reset(self, route=None):
843 """ Reset all routes (force plugins to be re-applied) and clear all
844 caches. If an ID or route object is given, only that specific route
846 if route is None: routes = self.routes
847 elif isinstance(route, Route): routes = [route]
848 else: routes = [self.routes[route]]
854 self.trigger_hook('app_reset')
857 """ Close the application and all installed plugins. """
858 for plugin in self.plugins:
859 if hasattr(plugin, 'close'): plugin.close()
861 def run(self, **kwargs):
862 """ Calls :func:`run` with the same parameters. """
865 def match(self, environ):
866 """ Search for a matching route and return a (:class:`Route` , urlargs)
867 tuple. The second value is a dictionary with parameters extracted
868 from the URL. Raise :exc:`HTTPError` (404/405) on a non-match."""
869 return self.router.match(environ)
871 def get_url(self, routename, **kargs):
872 """ Return a string that matches a named route """
873 scriptname = request.environ.get('SCRIPT_NAME', '').strip('/') + '/'
874 location = self.router.build(routename, **kargs).lstrip('/')
875 return urljoin(urljoin('/', scriptname), location)
877 def add_route(self, route):
878 """ Add a route object, but do not change the :data:`Route.app`
880 self.routes.append(route)
881 self.router.add(route.rule, route.method, route, name=route.name)
882 if DEBUG: route.prepare()
890 skip=None, **config):
891 """ A decorator to bind a function to a request URL. Example::
893 @app.route('/hello/<name>')
895 return 'Hello %s' % name
897 The ``<name>`` part is a wildcard. See :class:`Router` for syntax
900 :param path: Request path or a list of paths to listen to. If no
901 path is specified, it is automatically generated from the
902 signature of the function.
903 :param method: HTTP method (`GET`, `POST`, `PUT`, ...) or a list of
904 methods to listen to. (default: `GET`)
905 :param callback: An optional shortcut to avoid the decorator
906 syntax. ``route(..., callback=func)`` equals ``route(...)(func)``
907 :param name: The name for this route. (default: None)
908 :param apply: A decorator or plugin or a list of plugins. These are
909 applied to the route callback in addition to installed plugins.
910 :param skip: A list of plugins, plugin classes or names. Matching
911 plugins are not installed to this route. ``True`` skips all.
913 Any additional keyword arguments are stored as route-specific
914 configuration and passed to plugins (see :meth:`Plugin.apply`).
916 if callable(path): path, callback = None, path
917 plugins = makelist(apply)
918 skiplist = makelist(skip)
920 def decorator(callback):
921 if isinstance(callback, basestring): callback = load(callback)
922 for rule in makelist(path) or yieldroutes(callback):
923 for verb in makelist(method):
925 route = Route(self, rule, verb, callback,
928 skiplist=skiplist, **config)
929 self.add_route(route)
932 return decorator(callback) if callback else decorator
934 def get(self, path=None, method='GET', **options):
935 """ Equals :meth:`route`. """
936 return self.route(path, method, **options)
938 def post(self, path=None, method='POST', **options):
939 """ Equals :meth:`route` with a ``POST`` method parameter. """
940 return self.route(path, method, **options)
942 def put(self, path=None, method='PUT', **options):
943 """ Equals :meth:`route` with a ``PUT`` method parameter. """
944 return self.route(path, method, **options)
946 def delete(self, path=None, method='DELETE', **options):
947 """ Equals :meth:`route` with a ``DELETE`` method parameter. """
948 return self.route(path, method, **options)
950 def patch(self, path=None, method='PATCH', **options):
951 """ Equals :meth:`route` with a ``PATCH`` method parameter. """
952 return self.route(path, method, **options)
954 def error(self, code=500, callback=None):
955 """ Register an output handler for a HTTP error code. Can
956 be used as a decorator or called directly ::
958 def error_handler_500(error):
959 return 'error_handler_500'
961 app.error(code=500, callback=error_handler_500)
964 def error_handler_404(error):
965 return 'error_handler_404'
969 def decorator(callback):
970 if isinstance(callback, basestring): callback = load(callback)
971 self.error_handler[int(code)] = callback
974 return decorator(callback) if callback else decorator
976 def default_error_handler(self, res):
977 return tob(template(ERROR_PAGE_TEMPLATE, e=res, template_settings=dict(name='__ERROR_PAGE_TEMPLATE')))
979 def _handle(self, environ):
980 path = environ['bottle.raw_path'] = environ['PATH_INFO']
982 environ['PATH_INFO'] = path.encode('latin1').decode('utf8', 'ignore')
984 environ['bottle.app'] = self
985 request.bind(environ)
989 while True: # Remove in 0.14 together with RouteReset
992 self.trigger_hook('before_request')
993 route, args = self.router.match(environ)
994 environ['route.handle'] = route
995 environ['bottle.route'] = route
996 environ['route.url_args'] = args
997 out = route.call(**args)
999 except HTTPResponse as E:
1003 depr(0, 13, "RouteReset exception deprecated",
1004 "Call route.call() after route.reset() and "
1005 "return the result.")
1009 if isinstance(out, HTTPResponse):
1012 self.trigger_hook('after_request')
1013 except HTTPResponse as E:
1016 except (KeyboardInterrupt, SystemExit, MemoryError):
1018 except Exception as E:
1019 if not self.catchall: raise
1020 stacktrace = format_exc()
1021 environ['wsgi.errors'].write(stacktrace)
1022 environ['wsgi.errors'].flush()
1023 out = HTTPError(500, "Internal Server Error", E, stacktrace)
1028 def _cast(self, out, peek=None):
1029 """ Try to convert the parameter into something WSGI compatible and set
1030 correct HTTP headers when possible.
1031 Support: False, str, unicode, dict, HTTPResponse, HTTPError, file-like,
1032 iterable of strings and iterable of unicodes
1035 # Empty output is done here
1037 if 'Content-Length' not in response:
1038 response['Content-Length'] = 0
1040 # Join lists of byte or unicode strings. Mixed lists are NOT supported
1041 if isinstance(out, (tuple, list))\
1042 and isinstance(out[0], (bytes, unicode)):
1043 out = out[0][0:0].join(out) # b'abc'[0:0] -> b''
1044 # Encode unicode strings
1045 if isinstance(out, unicode):
1046 out = out.encode(response.charset)
1047 # Byte Strings are just returned
1048 if isinstance(out, bytes):
1049 if 'Content-Length' not in response:
1050 response['Content-Length'] = len(out)
1052 # HTTPError or HTTPException (recursive, because they may wrap anything)
1053 # TODO: Handle these explicitly in handle() or make them iterable.
1054 if isinstance(out, HTTPError):
1056 out = self.error_handler.get(out.status_code,
1057 self.default_error_handler)(out)
1058 return self._cast(out)
1059 if isinstance(out, HTTPResponse):
1061 return self._cast(out.body)
1063 # File-like objects.
1064 if hasattr(out, 'read'):
1065 if 'wsgi.file_wrapper' in request.environ:
1066 return request.environ['wsgi.file_wrapper'](out)
1067 elif hasattr(out, 'close') or not hasattr(out, '__iter__'):
1068 return WSGIFileWrapper(out)
1070 # Handle Iterables. We peek into them to detect their inner type.
1076 except StopIteration:
1077 return self._cast('')
1078 except HTTPResponse as E:
1080 except (KeyboardInterrupt, SystemExit, MemoryError):
1082 except Exception as error:
1083 if not self.catchall: raise
1084 first = HTTPError(500, 'Unhandled exception', error, format_exc())
1086 # These are the inner types allowed in iterator or generator objects.
1087 if isinstance(first, HTTPResponse):
1088 return self._cast(first)
1089 elif isinstance(first, bytes):
1090 new_iter = itertools.chain([first], iout)
1091 elif isinstance(first, unicode):
1092 encoder = lambda x: x.encode(response.charset)
1093 new_iter = imap(encoder, itertools.chain([first], iout))
1095 msg = 'Unsupported response type: %s' % type(first)
1096 return self._cast(HTTPError(500, msg))
1097 if hasattr(out, 'close'):
1098 new_iter = _closeiter(new_iter, out.close)
1101 def wsgi(self, environ, start_response):
1102 """ The bottle WSGI-interface. """
1104 out = self._cast(self._handle(environ))
1105 # rfc2616 section 4.3
1106 if response._status_code in (100, 101, 204, 304)\
1107 or environ['REQUEST_METHOD'] == 'HEAD':
1108 if hasattr(out, 'close'): out.close()
1110 start_response(response._status_line, response.headerlist)
1112 except (KeyboardInterrupt, SystemExit, MemoryError):
1114 except Exception as E:
1115 if not self.catchall: raise
1116 err = '<h1>Critical error while processing request: %s</h1>' \
1117 % html_escape(environ.get('PATH_INFO', '/'))
1119 err += '<h2>Error:</h2>\n<pre>\n%s\n</pre>\n' \
1120 '<h2>Traceback:</h2>\n<pre>\n%s\n</pre>\n' \
1121 % (html_escape(repr(E)), html_escape(format_exc()))
1122 environ['wsgi.errors'].write(err)
1123 environ['wsgi.errors'].flush()
1124 headers = [('Content-Type', 'text/html; charset=UTF-8')]
1125 start_response('500 INTERNAL SERVER ERROR', headers, sys.exc_info())
1128 def __call__(self, environ, start_response):
1129 """ Each instance of :class:'Bottle' is a WSGI application. """
1130 return self.wsgi(environ, start_response)
1132 def __enter__(self):
1133 """ Use this application as default for all module-level shortcuts. """
1134 default_app.push(self)
1137 def __exit__(self, exc_type, exc_value, traceback):
1140 def __setattr__(self, name, value):
1141 if name in self.__dict__:
1142 raise AttributeError("Attribute %s already defined. Plugin conflict?" % name)
1143 self.__dict__[name] = value
1146 ###############################################################################
1147 # HTTP and WSGI Tools ##########################################################
1148 ###############################################################################
1151 class BaseRequest(object):
1152 """ A wrapper for WSGI environment dictionaries that adds a lot of
1153 convenient access methods and properties. Most of them are read-only.
1155 Adding new attributes to a request actually adds them to the environ
1156 dictionary (as 'bottle.request.ext.<name>'). This is the recommended
1157 way to store and access request-specific data.
1160 __slots__ = ('environ', )
1162 #: Maximum size of memory buffer for :attr:`body` in bytes.
1163 MEMFILE_MAX = 102400
1165 def __init__(self, environ=None):
1166 """ Wrap a WSGI environ dictionary. """
1167 #: The wrapped WSGI environ dictionary. This is the only real attribute.
1168 #: All other attributes actually are read-only properties.
1169 self.environ = {} if environ is None else environ
1170 self.environ['bottle.request'] = self
1172 @DictProperty('environ', 'bottle.app', read_only=True)
1174 """ Bottle application handling this request. """
1175 raise RuntimeError('This request is not connected to an application.')
1177 @DictProperty('environ', 'bottle.route', read_only=True)
1179 """ The bottle :class:`Route` object that matches this request. """
1180 raise RuntimeError('This request is not connected to a route.')
1182 @DictProperty('environ', 'route.url_args', read_only=True)
1184 """ The arguments extracted from the URL. """
1185 raise RuntimeError('This request is not connected to a route.')
1189 """ The value of ``PATH_INFO`` with exactly one prefixed slash (to fix
1190 broken clients and avoid the "empty path" edge case). """
1191 return '/' + self.environ.get('PATH_INFO', '').lstrip('/')
1195 """ The ``REQUEST_METHOD`` value as an uppercase string. """
1196 return self.environ.get('REQUEST_METHOD', 'GET').upper()
1198 @DictProperty('environ', 'bottle.request.headers', read_only=True)
1200 """ A :class:`WSGIHeaderDict` that provides case-insensitive access to
1201 HTTP request headers. """
1202 return WSGIHeaderDict(self.environ)
1204 def get_header(self, name, default=None):
1205 """ Return the value of a request header, or a given default value. """
1206 return self.headers.get(name, default)
1208 @DictProperty('environ', 'bottle.request.cookies', read_only=True)
1210 """ Cookies parsed into a :class:`FormsDict`. Signed cookies are NOT
1211 decoded. Use :meth:`get_cookie` if you expect signed cookies. """
1212 cookies = SimpleCookie(self.environ.get('HTTP_COOKIE', '')).values()
1213 return FormsDict((c.key, c.value) for c in cookies)
1215 def get_cookie(self, key, default=None, secret=None, digestmod=hashlib.sha256):
1216 """ Return the content of a cookie. To read a `Signed Cookie`, the
1217 `secret` must match the one used to create the cookie (see
1218 :meth:`BaseResponse.set_cookie`). If anything goes wrong (missing
1219 cookie or wrong signature), return a default value. """
1220 value = self.cookies.get(key)
1222 # See BaseResponse.set_cookie for details on signed cookies.
1223 if value and value.startswith('!') and '?' in value:
1224 sig, msg = map(tob, value[1:].split('?', 1))
1225 hash = hmac.new(tob(secret), msg, digestmod=digestmod).digest()
1226 if _lscmp(sig, base64.b64encode(hash)):
1227 dst = pickle.loads(base64.b64decode(msg))
1228 if dst and dst[0] == key:
1231 return value or default
1233 @DictProperty('environ', 'bottle.request.query', read_only=True)
1235 """ The :attr:`query_string` parsed into a :class:`FormsDict`. These
1236 values are sometimes called "URL arguments" or "GET parameters", but
1237 not to be confused with "URL wildcards" as they are provided by the
1238 :class:`Router`. """
1239 get = self.environ['bottle.get'] = FormsDict()
1240 pairs = _parse_qsl(self.environ.get('QUERY_STRING', ''))
1241 for key, value in pairs:
1245 @DictProperty('environ', 'bottle.request.forms', read_only=True)
1247 """ Form values parsed from an `url-encoded` or `multipart/form-data`
1248 encoded POST or PUT request body. The result is returned as a
1249 :class:`FormsDict`. All keys and values are strings. File uploads
1250 are stored separately in :attr:`files`. """
1252 for name, item in self.POST.allitems():
1253 if not isinstance(item, FileUpload):
1257 @DictProperty('environ', 'bottle.request.params', read_only=True)
1259 """ A :class:`FormsDict` with the combined values of :attr:`query` and
1260 :attr:`forms`. File uploads are stored in :attr:`files`. """
1261 params = FormsDict()
1262 for key, value in self.query.allitems():
1264 for key, value in self.forms.allitems():
1268 @DictProperty('environ', 'bottle.request.files', read_only=True)
1270 """ File uploads parsed from `multipart/form-data` encoded POST or PUT
1271 request body. The values are instances of :class:`FileUpload`.
1275 for name, item in self.POST.allitems():
1276 if isinstance(item, FileUpload):
1280 @DictProperty('environ', 'bottle.request.json', read_only=True)
1282 """ If the ``Content-Type`` header is ``application/json`` or
1283 ``application/json-rpc``, this property holds the parsed content
1284 of the request body. Only requests smaller than :attr:`MEMFILE_MAX`
1285 are processed to avoid memory exhaustion.
1286 Invalid JSON raises a 400 error response.
1288 ctype = self.environ.get('CONTENT_TYPE', '').lower().split(';')[0]
1289 if ctype in ('application/json', 'application/json-rpc'):
1290 b = self._get_body_string()
1294 return json_loads(b)
1295 except (ValueError, TypeError):
1296 raise HTTPError(400, 'Invalid JSON')
1299 def _iter_body(self, read, bufsize):
1300 maxread = max(0, self.content_length)
1302 part = read(min(maxread, bufsize))
1305 maxread -= len(part)
1308 def _iter_chunked(read, bufsize):
1309 err = HTTPError(400, 'Error while parsing chunked transfer body.')
1310 rn, sem, bs = tob('\r\n'), tob(';'), tob('')
1313 while header[-2:] != rn:
1317 if len(header) > bufsize: raise err
1318 size, _, _ = header.partition(sem)
1320 maxread = int(tonat(size.strip()), 16)
1323 if maxread == 0: break
1327 buff = read(min(maxread, bufsize))
1328 part, buff = buff[:maxread], buff[maxread:]
1329 if not part: raise err
1331 maxread -= len(part)
1335 @DictProperty('environ', 'bottle.request.body', read_only=True)
1338 read_func = self.environ['wsgi.input'].read
1340 self.environ['wsgi.input'] = BytesIO()
1341 return self.environ['wsgi.input']
1342 body_iter = self._iter_chunked if self.chunked else self._iter_body
1343 body, body_size, is_temp_file = BytesIO(), 0, False
1344 for part in body_iter(read_func, self.MEMFILE_MAX):
1346 body_size += len(part)
1347 if not is_temp_file and body_size > self.MEMFILE_MAX:
1348 body, tmp = TemporaryFile(mode='w+b'), body
1349 body.write(tmp.getvalue())
1352 self.environ['wsgi.input'] = body
1356 def _get_body_string(self):
1357 """ read body until content-length or MEMFILE_MAX into a string. Raise
1358 HTTPError(413) on requests that are to large. """
1359 clen = self.content_length
1360 if clen > self.MEMFILE_MAX:
1361 raise HTTPError(413, 'Request entity too large')
1362 if clen < 0: clen = self.MEMFILE_MAX + 1
1363 data = self.body.read(clen)
1364 if len(data) > self.MEMFILE_MAX: # Fail fast
1365 raise HTTPError(413, 'Request entity too large')
1370 """ The HTTP request body as a seek-able file-like object. Depending on
1371 :attr:`MEMFILE_MAX`, this is either a temporary file or a
1372 :class:`io.BytesIO` instance. Accessing this property for the first
1373 time reads and replaces the ``wsgi.input`` environ variable.
1374 Subsequent accesses just do a `seek(0)` on the file object. """
1380 """ True if Chunked transfer encoding was. """
1381 return 'chunked' in self.environ.get(
1382 'HTTP_TRANSFER_ENCODING', '').lower()
1384 #: An alias for :attr:`query`.
1387 @DictProperty('environ', 'bottle.request.post', read_only=True)
1389 """ The values of :attr:`forms` and :attr:`files` combined into a single
1390 :class:`FormsDict`. Values are either strings (form values) or
1391 instances of :class:`cgi.FieldStorage` (file uploads).
1394 # We default to application/x-www-form-urlencoded for everything that
1395 # is not multipart and take the fast path (also: 3.1 workaround)
1396 if not self.content_type.startswith('multipart/'):
1397 pairs = _parse_qsl(tonat(self._get_body_string(), 'latin1'))
1398 for key, value in pairs:
1402 safe_env = {'QUERY_STRING': ''} # Build a safe environment for cgi
1403 for key in ('REQUEST_METHOD', 'CONTENT_TYPE', 'CONTENT_LENGTH'):
1404 if key in self.environ: safe_env[key] = self.environ[key]
1405 args = dict(fp=self.body, environ=safe_env, keep_blank_values=True)
1408 args['encoding'] = 'utf8'
1409 data = cgi.FieldStorage(**args)
1410 self['_cgi.FieldStorage'] = data #http://bugs.python.org/issue18394
1411 data = data.list or []
1414 post[item.name] = FileUpload(item.file, item.name,
1415 item.filename, item.headers)
1417 post[item.name] = item.value
1422 """ The full request URI including hostname and scheme. If your app
1423 lives behind a reverse proxy or load balancer and you get confusing
1424 results, make sure that the ``X-Forwarded-Host`` header is set
1426 return self.urlparts.geturl()
1428 @DictProperty('environ', 'bottle.request.urlparts', read_only=True)
1430 """ The :attr:`url` string as an :class:`urlparse.SplitResult` tuple.
1431 The tuple contains (scheme, host, path, query_string and fragment),
1432 but the fragment is always empty because it is not visible to the
1435 http = env.get('HTTP_X_FORWARDED_PROTO') \
1436 or env.get('wsgi.url_scheme', 'http')
1437 host = env.get('HTTP_X_FORWARDED_HOST') or env.get('HTTP_HOST')
1439 # HTTP 1.1 requires a Host-header. This is for HTTP/1.0 clients.
1440 host = env.get('SERVER_NAME', '127.0.0.1')
1441 port = env.get('SERVER_PORT')
1442 if port and port != ('80' if http == 'http' else '443'):
1444 path = urlquote(self.fullpath)
1445 return UrlSplitResult(http, host, path, env.get('QUERY_STRING'), '')
1449 """ Request path including :attr:`script_name` (if present). """
1450 return urljoin(self.script_name, self.path.lstrip('/'))
1453 def query_string(self):
1454 """ The raw :attr:`query` part of the URL (everything in between ``?``
1455 and ``#``) as a string. """
1456 return self.environ.get('QUERY_STRING', '')
1459 def script_name(self):
1460 """ The initial portion of the URL's `path` that was removed by a higher
1461 level (server or routing middleware) before the application was
1462 called. This script path is returned with leading and tailing
1464 script_name = self.environ.get('SCRIPT_NAME', '').strip('/')
1465 return '/' + script_name + '/' if script_name else '/'
1467 def path_shift(self, shift=1):
1468 """ Shift path segments from :attr:`path` to :attr:`script_name` and
1471 :param shift: The number of path segments to shift. May be negative
1472 to change the shift direction. (default: 1)
1474 script, path = path_shift(self.environ.get('SCRIPT_NAME', '/'), self.path, shift)
1475 self['SCRIPT_NAME'], self['PATH_INFO'] = script, path
1478 def content_length(self):
1479 """ The request body length as an integer. The client is responsible to
1480 set this header. Otherwise, the real length of the body is unknown
1481 and -1 is returned. In this case, :attr:`body` will be empty. """
1482 return int(self.environ.get('CONTENT_LENGTH') or -1)
1485 def content_type(self):
1486 """ The Content-Type header as a lowercase-string (default: empty). """
1487 return self.environ.get('CONTENT_TYPE', '').lower()
1491 """ True if the request was triggered by a XMLHttpRequest. This only
1492 works with JavaScript libraries that support the `X-Requested-With`
1493 header (most of the popular libraries do). """
1494 requested_with = self.environ.get('HTTP_X_REQUESTED_WITH', '')
1495 return requested_with.lower() == 'xmlhttprequest'
1499 """ Alias for :attr:`is_xhr`. "Ajax" is not the right term. """
1504 """ HTTP authentication data as a (user, password) tuple. This
1505 implementation currently supports basic (not digest) authentication
1506 only. If the authentication happened at a higher level (e.g. in the
1507 front web-server or a middleware), the password field is None, but
1508 the user field is looked up from the ``REMOTE_USER`` environ
1509 variable. On any errors, None is returned. """
1510 basic = parse_auth(self.environ.get('HTTP_AUTHORIZATION', ''))
1511 if basic: return basic
1512 ruser = self.environ.get('REMOTE_USER')
1513 if ruser: return (ruser, None)
1517 def remote_route(self):
1518 """ A list of all IPs that were involved in this request, starting with
1519 the client IP and followed by zero or more proxies. This does only
1520 work if all proxies support the ```X-Forwarded-For`` header. Note
1521 that this information can be forged by malicious clients. """
1522 proxy = self.environ.get('HTTP_X_FORWARDED_FOR')
1523 if proxy: return [ip.strip() for ip in proxy.split(',')]
1524 remote = self.environ.get('REMOTE_ADDR')
1525 return [remote] if remote else []
1528 def remote_addr(self):
1529 """ The client IP as a string. Note that this information can be forged
1530 by malicious clients. """
1531 route = self.remote_route
1532 return route[0] if route else None
1535 """ Return a new :class:`Request` with a shallow :attr:`environ` copy. """
1536 return Request(self.environ.copy())
1538 def get(self, value, default=None):
1539 return self.environ.get(value, default)
1541 def __getitem__(self, key):
1542 return self.environ[key]
1544 def __delitem__(self, key):
1546 del (self.environ[key])
1549 return iter(self.environ)
1552 return len(self.environ)
1555 return self.environ.keys()
1557 def __setitem__(self, key, value):
1558 """ Change an environ value and clear all caches that depend on it. """
1560 if self.environ.get('bottle.request.readonly'):
1561 raise KeyError('The environ dictionary is read-only.')
1563 self.environ[key] = value
1566 if key == 'wsgi.input':
1567 todelete = ('body', 'forms', 'files', 'params', 'post', 'json')
1568 elif key == 'QUERY_STRING':
1569 todelete = ('query', 'params')
1570 elif key.startswith('HTTP_'):
1571 todelete = ('headers', 'cookies')
1573 for key in todelete:
1574 self.environ.pop('bottle.request.' + key, None)
1577 return '<%s: %s %s>' % (self.__class__.__name__, self.method, self.url)
1579 def __getattr__(self, name):
1580 """ Search in self.environ for additional user defined attributes. """
1582 var = self.environ['bottle.request.ext.%s' % name]
1583 return var.__get__(self) if hasattr(var, '__get__') else var
1585 raise AttributeError('Attribute %r not defined.' % name)
1587 def __setattr__(self, name, value):
1588 if name == 'environ': return object.__setattr__(self, name, value)
1589 key = 'bottle.request.ext.%s' % name
1590 if key in self.environ:
1591 raise AttributeError("Attribute already defined: %s" % name)
1592 self.environ[key] = value
1594 def __delattr__(self, name):
1596 del self.environ['bottle.request.ext.%s' % name]
1598 raise AttributeError("Attribute not defined: %s" % name)
1602 if '\n' in key or '\r' in key or '\0' in key:
1603 raise ValueError("Header names must not contain control characters: %r" % key)
1604 return key.title().replace('_', '-')
1608 value = tonat(value)
1609 if '\n' in value or '\r' in value or '\0' in value:
1610 raise ValueError("Header value must not contain control characters: %r" % value)
1614 class HeaderProperty(object):
1615 def __init__(self, name, reader=None, writer=None, default=''):
1616 self.name, self.default = name, default
1617 self.reader, self.writer = reader, writer
1618 self.__doc__ = 'Current value of the %r header.' % name.title()
1620 def __get__(self, obj, _):
1621 if obj is None: return self
1622 value = obj.get_header(self.name, self.default)
1623 return self.reader(value) if self.reader else value
1625 def __set__(self, obj, value):
1626 obj[self.name] = self.writer(value) if self.writer else value
1628 def __delete__(self, obj):
1632 class BaseResponse(object):
1633 """ Storage class for a response body as well as headers and cookies.
1635 This class does support dict-like case-insensitive item-access to
1636 headers, but is NOT a dict. Most notably, iterating over a response
1637 yields parts of the body and not the headers.
1639 :param body: The response body as one of the supported types.
1640 :param status: Either an HTTP status code (e.g. 200) or a status line
1641 including the reason phrase (e.g. '200 OK').
1642 :param headers: A dictionary or a list of name-value pairs.
1644 Additional keyword arguments are added to the list of headers.
1645 Underscores in the header name are replaced with dashes.
1648 default_status = 200
1649 default_content_type = 'text/html; charset=UTF-8'
1651 # Header blacklist for specific response codes
1652 # (rfc2616 section 10.2.3 and 10.3.5)
1654 204: frozenset(('Content-Type', 'Content-Length')),
1655 304: frozenset(('Allow', 'Content-Encoding', 'Content-Language',
1656 'Content-Length', 'Content-Range', 'Content-Type',
1657 'Content-Md5', 'Last-Modified'))
1660 def __init__(self, body='', status=None, headers=None, **more_headers):
1661 self._cookies = None
1664 self.status = status or self.default_status
1666 if isinstance(headers, dict):
1667 headers = headers.items()
1668 for name, value in headers:
1669 self.add_header(name, value)
1671 for name, value in more_headers.items():
1672 self.add_header(name, value)
1674 def copy(self, cls=None):
1675 """ Returns a copy of self. """
1676 cls = cls or BaseResponse
1677 assert issubclass(cls, BaseResponse)
1679 copy.status = self.status
1680 copy._headers = dict((k, v[:]) for (k, v) in self._headers.items())
1682 copy._cookies = SimpleCookie()
1683 copy._cookies.load(self._cookies.output(header=''))
1687 return iter(self.body)
1690 if hasattr(self.body, 'close'):
1694 def status_line(self):
1695 """ The HTTP status line as a string (e.g. ``404 Not Found``)."""
1696 return self._status_line
1699 def status_code(self):
1700 """ The HTTP status code as an integer (e.g. 404)."""
1701 return self._status_code
1703 def _set_status(self, status):
1704 if isinstance(status, int):
1705 code, status = status, _HTTP_STATUS_LINES.get(status)
1707 status = status.strip()
1708 code = int(status.split()[0])
1710 raise ValueError('String status line without a reason phrase.')
1711 if not 100 <= code <= 999:
1712 raise ValueError('Status code out of range.')
1713 self._status_code = code
1714 self._status_line = str(status or ('%d Unknown' % code))
1716 def _get_status(self):
1717 return self._status_line
1720 _get_status, _set_status, None,
1721 ''' A writeable property to change the HTTP response status. It accepts
1722 either a numeric code (100-999) or a string with a custom reason
1723 phrase (e.g. "404 Brain not found"). Both :data:`status_line` and
1724 :data:`status_code` are updated accordingly. The return value is
1725 always a status string. ''')
1726 del _get_status, _set_status
1730 """ An instance of :class:`HeaderDict`, a case-insensitive dict-like
1731 view on the response headers. """
1732 hdict = HeaderDict()
1733 hdict.dict = self._headers
1736 def __contains__(self, name):
1737 return _hkey(name) in self._headers
1739 def __delitem__(self, name):
1740 del self._headers[_hkey(name)]
1742 def __getitem__(self, name):
1743 return self._headers[_hkey(name)][-1]
1745 def __setitem__(self, name, value):
1746 self._headers[_hkey(name)] = [_hval(value)]
1748 def get_header(self, name, default=None):
1749 """ Return the value of a previously defined header. If there is no
1750 header with that name, return a default value. """
1751 return self._headers.get(_hkey(name), [default])[-1]
1753 def set_header(self, name, value):
1754 """ Create a new response header, replacing any previously defined
1755 headers with the same name. """
1756 self._headers[_hkey(name)] = [_hval(value)]
1758 def add_header(self, name, value):
1759 """ Add an additional response header, not removing duplicates. """
1760 self._headers.setdefault(_hkey(name), []).append(_hval(value))
1762 def iter_headers(self):
1763 """ Yield (header, value) tuples, skipping headers that are not
1764 allowed with the current response status code. """
1765 return self.headerlist
1768 def headerlist(self):
1769 """ WSGI conform list of (header, value) tuples. """
1771 headers = list(self._headers.items())
1772 if 'Content-Type' not in self._headers:
1773 headers.append(('Content-Type', [self.default_content_type]))
1774 if self._status_code in self.bad_headers:
1775 bad_headers = self.bad_headers[self._status_code]
1776 headers = [h for h in headers if h[0] not in bad_headers]
1777 out += [(name, val) for (name, vals) in headers for val in vals]
1779 for c in self._cookies.values():
1780 out.append(('Set-Cookie', _hval(c.OutputString())))
1782 out = [(k, v.encode('utf8').decode('latin1')) for (k, v) in out]
1785 content_type = HeaderProperty('Content-Type')
1786 content_length = HeaderProperty('Content-Length', reader=int)
1787 expires = HeaderProperty(
1789 reader=lambda x: datetime.utcfromtimestamp(parse_date(x)),
1790 writer=lambda x: http_date(x))
1793 def charset(self, default='UTF-8'):
1794 """ Return the charset specified in the content-type header (default: utf8). """
1795 if 'charset=' in self.content_type:
1796 return self.content_type.split('charset=')[-1].split(';')[0].strip()
1799 def set_cookie(self, name, value, secret=None, digestmod=hashlib.sha256, **options):
1800 """ Create a new cookie or replace an old one. If the `secret` parameter is
1801 set, create a `Signed Cookie` (described below).
1803 :param name: the name of the cookie.
1804 :param value: the value of the cookie.
1805 :param secret: a signature key required for signed cookies.
1807 Additionally, this method accepts all RFC 2109 attributes that are
1808 supported by :class:`cookie.Morsel`, including:
1810 :param max_age: maximum age in seconds. (default: None)
1811 :param expires: a datetime object or UNIX timestamp. (default: None)
1812 :param domain: the domain that is allowed to read the cookie.
1813 (default: current domain)
1814 :param path: limits the cookie to a given path (default: current path)
1815 :param secure: limit the cookie to HTTPS connections (default: off).
1816 :param httponly: prevents client-side javascript to read this cookie
1817 (default: off, requires Python 2.6 or newer).
1818 :param same_site: disables third-party use for a cookie.
1819 Allowed attributes: `lax` and `strict`.
1820 In strict mode the cookie will never be sent.
1821 In lax mode the cookie is only sent with a top-level GET request.
1823 If neither `expires` nor `max_age` is set (default), the cookie will
1824 expire at the end of the browser session (as soon as the browser
1827 Signed cookies may store any pickle-able object and are
1828 cryptographically signed to prevent manipulation. Keep in mind that
1829 cookies are limited to 4kb in most browsers.
1831 Warning: Pickle is a potentially dangerous format. If an attacker
1832 gains access to the secret key, he could forge cookies that execute
1833 code on server side if unpickeld. Using pickle is discouraged and
1834 support for it will be removed in later versions of bottle.
1836 Warning: Signed cookies are not encrypted (the client can still see
1837 the content) and not copy-protected (the client can restore an old
1838 cookie). The main intention is to make pickling and unpickling
1839 save, not to store secret information at client side.
1841 if not self._cookies:
1842 self._cookies = SimpleCookie()
1844 # To add "SameSite" cookie support.
1845 Morsel._reserved['same-site'] = 'SameSite'
1848 if not isinstance(value, basestring):
1849 depr(0, 13, "Pickling of arbitrary objects into cookies is "
1850 "deprecated.", "Only store strings in cookies. "
1851 "JSON strings are fine, too.")
1852 encoded = base64.b64encode(pickle.dumps([name, value], -1))
1853 sig = base64.b64encode(hmac.new(tob(secret), encoded,
1854 digestmod=digestmod).digest())
1855 value = touni(tob('!') + sig + tob('?') + encoded)
1856 elif not isinstance(value, basestring):
1857 raise TypeError('Secret key required for non-string cookies.')
1859 # Cookie size plus options must not exceed 4kb.
1860 if len(name) + len(value) > 3800:
1861 raise ValueError('Content does not fit into a cookie.')
1863 self._cookies[name] = value
1865 for key, value in options.items():
1866 if key == 'max_age':
1867 if isinstance(value, timedelta):
1868 value = value.seconds + value.days * 24 * 3600
1869 if key == 'expires':
1870 if isinstance(value, (datedate, datetime)):
1871 value = value.timetuple()
1872 elif isinstance(value, (int, float)):
1873 value = time.gmtime(value)
1874 value = time.strftime("%a, %d %b %Y %H:%M:%S GMT", value)
1875 # check values for SameSite cookie, because it's not natively supported by http.cookies.
1876 if key == 'same_site' and value.lower() not in ('lax', 'strict'):
1877 raise CookieError("Invalid attribute %r" % (key,))
1878 if key in ('secure', 'httponly') and not value:
1880 self._cookies[name][key.replace('_', '-')] = value
1882 def delete_cookie(self, key, **kwargs):
1883 """ Delete a cookie. Be sure to use the same `domain` and `path`
1884 settings as used to create the cookie. """
1885 kwargs['max_age'] = -1
1886 kwargs['expires'] = 0
1887 self.set_cookie(key, '', **kwargs)
1891 for name, value in self.headerlist:
1892 out += '%s: %s\n' % (name.title(), value.strip())
1896 def _local_property():
1897 ls = threading.local()
1902 except AttributeError:
1903 raise RuntimeError("Request context not initialized.")
1911 return property(fget, fset, fdel, 'Thread-local property')
1914 class LocalRequest(BaseRequest):
1915 """ A thread-local subclass of :class:`BaseRequest` with a different
1916 set of attributes for each thread. There is usually only one global
1917 instance of this class (:data:`request`). If accessed during a
1918 request/response cycle, this instance always refers to the *current*
1919 request (even on a multithreaded server). """
1920 bind = BaseRequest.__init__
1921 environ = _local_property()
1924 class LocalResponse(BaseResponse):
1925 """ A thread-local subclass of :class:`BaseResponse` with a different
1926 set of attributes for each thread. There is usually only one global
1927 instance of this class (:data:`response`). Its attributes are used
1928 to build the HTTP response at the end of the request/response cycle.
1930 bind = BaseResponse.__init__
1931 _status_line = _local_property()
1932 _status_code = _local_property()
1933 _cookies = _local_property()
1934 _headers = _local_property()
1935 body = _local_property()
1938 Request = BaseRequest
1939 Response = BaseResponse
1942 class HTTPResponse(Response, BottleException):
1943 def __init__(self, body='', status=None, headers=None, **more_headers):
1944 super(HTTPResponse, self).__init__(body, status, headers, **more_headers)
1946 def apply(self, other):
1947 other._status_code = self._status_code
1948 other._status_line = self._status_line
1949 other._headers = self._headers
1950 other._cookies = self._cookies
1951 other.body = self.body
1954 class HTTPError(HTTPResponse):
1955 default_status = 500
1961 traceback=None, **more_headers):
1962 self.exception = exception
1963 self.traceback = traceback
1964 super(HTTPError, self).__init__(body, status, **more_headers)
1966 ###############################################################################
1967 # Plugins ######################################################################
1968 ###############################################################################
1971 class PluginError(BottleException):
1975 class JSONPlugin(object):
1979 def __init__(self, json_dumps=json_dumps):
1980 self.json_dumps = json_dumps
1982 def setup(self, app):
1983 app.config._define('json.enable', default=True, validate=bool,
1984 help="Enable or disable automatic dict->json filter.")
1985 app.config._define('json.ascii', default=False, validate=bool,
1986 help="Use only 7-bit ASCII characters in output.")
1987 app.config._define('json.indent', default=True, validate=bool,
1988 help="Add whitespace to make json more readable.")
1989 app.config._define('json.dump_func', default=None,
1990 help="If defined, use this function to transform"
1991 " dict into json. The other options no longer"
1994 def apply(self, callback, route):
1995 dumps = self.json_dumps
1996 if not self.json_dumps: return callback
1998 def wrapper(*a, **ka):
2000 rv = callback(*a, **ka)
2001 except HTTPResponse as resp:
2004 if isinstance(rv, dict):
2005 #Attempt to serialize, raises exception on failure
2006 json_response = dumps(rv)
2007 #Set content type only if serialization successful
2008 response.content_type = 'application/json'
2009 return json_response
2010 elif isinstance(rv, HTTPResponse) and isinstance(rv.body, dict):
2011 rv.body = dumps(rv.body)
2012 rv.content_type = 'application/json'
2018 class TemplatePlugin(object):
2019 """ This plugin applies the :func:`view` decorator to all routes with a
2020 `template` config parameter. If the parameter is a tuple, the second
2021 element must be a dict with additional options (e.g. `template_engine`)
2022 or default variables for the template. """
2026 def setup(self, app):
2029 def apply(self, callback, route):
2030 conf = route.config.get('template')
2031 if isinstance(conf, (tuple, list)) and len(conf) == 2:
2032 return view(conf[0], **conf[1])(callback)
2033 elif isinstance(conf, str):
2034 return view(conf)(callback)
2039 #: Not a plugin, but part of the plugin API. TODO: Find a better place.
2040 class _ImportRedirect(object):
2041 def __init__(self, name, impmask):
2042 """ Create a virtual package that redirects imports (see PEP 302). """
2044 self.impmask = impmask
2045 self.module = sys.modules.setdefault(name, imp.new_module(name))
2046 self.module.__dict__.update({
2047 '__file__': __file__,
2052 sys.meta_path.append(self)
2054 def find_module(self, fullname, path=None):
2055 if '.' not in fullname: return
2056 packname = fullname.rsplit('.', 1)[0]
2057 if packname != self.name: return
2060 def load_module(self, fullname):
2061 if fullname in sys.modules: return sys.modules[fullname]
2062 modname = fullname.rsplit('.', 1)[1]
2063 realname = self.impmask % modname
2064 __import__(realname)
2065 module = sys.modules[fullname] = sys.modules[realname]
2066 setattr(self.module, modname, module)
2067 module.__loader__ = self
2070 ###############################################################################
2071 # Common Utilities #############################################################
2072 ###############################################################################
2075 class MultiDict(DictMixin):
2076 """ This dict stores multiple values per key, but behaves exactly like a
2077 normal dict in that it returns only the newest value for any given key.
2078 There are special methods available to access the full list of values.
2081 def __init__(self, *a, **k):
2082 self.dict = dict((k, [v]) for (k, v) in dict(*a, **k).items())
2085 return len(self.dict)
2088 return iter(self.dict)
2090 def __contains__(self, key):
2091 return key in self.dict
2093 def __delitem__(self, key):
2096 def __getitem__(self, key):
2097 return self.dict[key][-1]
2099 def __setitem__(self, key, value):
2100 self.append(key, value)
2103 return self.dict.keys()
2108 return (v[-1] for v in self.dict.values())
2111 return ((k, v[-1]) for k, v in self.dict.items())
2114 return ((k, v) for k, vl in self.dict.items() for v in vl)
2119 iterallitems = allitems
2124 return [v[-1] for v in self.dict.values()]
2127 return [(k, v[-1]) for k, v in self.dict.items()]
2130 return self.dict.iterkeys()
2132 def itervalues(self):
2133 return (v[-1] for v in self.dict.itervalues())
2135 def iteritems(self):
2136 return ((k, v[-1]) for k, v in self.dict.iteritems())
2138 def iterallitems(self):
2139 return ((k, v) for k, vl in self.dict.iteritems() for v in vl)
2142 return [(k, v) for k, vl in self.dict.iteritems() for v in vl]
2144 def get(self, key, default=None, index=-1, type=None):
2145 """ Return the most recent value for a key.
2147 :param default: The default value to be returned if the key is not
2148 present or the type conversion fails.
2149 :param index: An index for the list of available values.
2150 :param type: If defined, this callable is used to cast the value
2151 into a specific type. Exception are suppressed and result in
2152 the default value to be returned.
2155 val = self.dict[key][index]
2156 return type(val) if type else val
2161 def append(self, key, value):
2162 """ Add a new value to the list of values for this key. """
2163 self.dict.setdefault(key, []).append(value)
2165 def replace(self, key, value):
2166 """ Replace the list of values with a single value. """
2167 self.dict[key] = [value]
2169 def getall(self, key):
2170 """ Return a (possibly empty) list of values for a key. """
2171 return self.dict.get(key) or []
2173 #: Aliases for WTForms to mimic other multi-dict APIs (Django)
2178 class FormsDict(MultiDict):
2179 """ This :class:`MultiDict` subclass is used to store request form data.
2180 Additionally to the normal dict-like item access methods (which return
2181 unmodified data as native strings), this container also supports
2182 attribute-like access to its values. Attributes are automatically de-
2183 or recoded to match :attr:`input_encoding` (default: 'utf8'). Missing
2184 attributes default to an empty string. """
2186 #: Encoding used for attribute values.
2187 input_encoding = 'utf8'
2188 #: If true (default), unicode strings are first encoded with `latin1`
2189 #: and then decoded to match :attr:`input_encoding`.
2190 recode_unicode = True
2192 def _fix(self, s, encoding=None):
2193 if isinstance(s, unicode) and self.recode_unicode: # Python 3 WSGI
2194 return s.encode('latin1').decode(encoding or self.input_encoding)
2195 elif isinstance(s, bytes): # Python 2 WSGI
2196 return s.decode(encoding or self.input_encoding)
2200 def decode(self, encoding=None):
2201 """ Returns a copy with all keys and values de- or recoded to match
2202 :attr:`input_encoding`. Some libraries (e.g. WTForms) want a
2203 unicode dictionary. """
2205 enc = copy.input_encoding = encoding or self.input_encoding
2206 copy.recode_unicode = False
2207 for key, value in self.allitems():
2208 copy.append(self._fix(key, enc), self._fix(value, enc))
2211 def getunicode(self, name, default=None, encoding=None):
2212 """ Return the value as a unicode string, or the default. """
2214 return self._fix(self[name], encoding)
2215 except (UnicodeError, KeyError):
2218 def __getattr__(self, name, default=unicode()):
2219 # Without this guard, pickle generates a cryptic TypeError:
2220 if name.startswith('__') and name.endswith('__'):
2221 return super(FormsDict, self).__getattr__(name)
2222 return self.getunicode(name, default=default)
2224 class HeaderDict(MultiDict):
2225 """ A case-insensitive version of :class:`MultiDict` that defaults to
2226 replace the old value instead of appending it. """
2228 def __init__(self, *a, **ka):
2230 if a or ka: self.update(*a, **ka)
2232 def __contains__(self, key):
2233 return _hkey(key) in self.dict
2235 def __delitem__(self, key):
2236 del self.dict[_hkey(key)]
2238 def __getitem__(self, key):
2239 return self.dict[_hkey(key)][-1]
2241 def __setitem__(self, key, value):
2242 self.dict[_hkey(key)] = [_hval(value)]
2244 def append(self, key, value):
2245 self.dict.setdefault(_hkey(key), []).append(_hval(value))
2247 def replace(self, key, value):
2248 self.dict[_hkey(key)] = [_hval(value)]
2250 def getall(self, key):
2251 return self.dict.get(_hkey(key)) or []
2253 def get(self, key, default=None, index=-1):
2254 return MultiDict.get(self, _hkey(key), default, index)
2256 def filter(self, names):
2257 for name in (_hkey(n) for n in names):
2258 if name in self.dict:
2262 class WSGIHeaderDict(DictMixin):
2263 """ This dict-like class wraps a WSGI environ dict and provides convenient
2264 access to HTTP_* fields. Keys and values are native strings
2265 (2.x bytes or 3.x unicode) and keys are case-insensitive. If the WSGI
2266 environment contains non-native string values, these are de- or encoded
2267 using a lossless 'latin1' character set.
2269 The API will remain stable even on changes to the relevant PEPs.
2270 Currently PEP 333, 444 and 3333 are supported. (PEP 444 is the only one
2271 that uses non-native strings.)
2273 #: List of keys that do not have a ``HTTP_`` prefix.
2274 cgikeys = ('CONTENT_TYPE', 'CONTENT_LENGTH')
2276 def __init__(self, environ):
2277 self.environ = environ
2279 def _ekey(self, key):
2280 """ Translate header field name to CGI/WSGI environ key. """
2281 key = key.replace('-', '_').upper()
2282 if key in self.cgikeys:
2284 return 'HTTP_' + key
2286 def raw(self, key, default=None):
2287 """ Return the header value as is (may be bytes or unicode). """
2288 return self.environ.get(self._ekey(key), default)
2290 def __getitem__(self, key):
2291 val = self.environ[self._ekey(key)]
2293 if isinstance(val, unicode):
2294 val = val.encode('latin1').decode('utf8')
2296 val = val.decode('utf8')
2299 def __setitem__(self, key, value):
2300 raise TypeError("%s is read-only." % self.__class__)
2302 def __delitem__(self, key):
2303 raise TypeError("%s is read-only." % self.__class__)
2306 for key in self.environ:
2307 if key[:5] == 'HTTP_':
2308 yield _hkey(key[5:])
2309 elif key in self.cgikeys:
2313 return [x for x in self]
2316 return len(self.keys())
2318 def __contains__(self, key):
2319 return self._ekey(key) in self.environ
2323 class ConfigDict(dict):
2324 """ A dict-like configuration storage with additional support for
2325 namespaces, validators, meta-data, overlays and more.
2327 This dict-like class is heavily optimized for read access. All read-only
2328 methods as well as item access should be as fast as the built-in dict.
2331 __slots__ = ('_meta', '_change_listener', '_overlays', '_virtual_keys', '_source', '__weakref__')
2335 self._change_listener = []
2336 #: Weak references of overlays that need to be kept in sync.
2338 #: Config that is the source for this overlay.
2340 #: Keys of values copied from the source (values we do not own)
2341 self._virtual_keys = set()
2343 def load_module(self, path, squash=True):
2344 """Load values from a Python module.
2346 Example modue ``config.py``::
2354 >>> c = ConfigDict()
2355 >>> c.load_module('config')
2356 {DEBUG: True, 'SQLITE.DB': 'memory'}
2357 >>> c.load_module("config", False)
2358 {'DEBUG': True, 'SQLITE': {'DB': 'memory'}}
2360 :param squash: If true (default), dictionary values are assumed to
2361 represent namespaces (see :meth:`load_dict`).
2363 config_obj = load(path)
2364 obj = {key: getattr(config_obj, key) for key in dir(config_obj)
2373 def load_config(self, filename, **options):
2374 """ Load values from an ``*.ini`` style config file.
2376 A configuration file consists of sections, each led by a
2377 ``[section]`` header, followed by key/value entries separated by
2378 either ``=`` or ``:``. Section names and keys are case-insensitive.
2379 Leading and trailing whitespace is removed from keys and values.
2380 Values can be omitted, in which case the key/value delimiter may
2381 also be left out. Values can also span multiple lines, as long as
2382 they are indented deeper than the first line of the value. Commands
2383 are prefixed by ``#`` or ``;`` and may only appear on their own on
2384 an otherwise empty line.
2386 Both section and key names may contain dots (``.``) as namespace
2387 separators. The actual configuration parameter name is constructed
2388 by joining section name and key name together and converting to
2391 The special sections ``bottle`` and ``ROOT`` refer to the root
2392 namespace and the ``DEFAULT`` section defines default values for all
2395 With Python 3, extended string interpolation is enabled.
2397 :param filename: The path of a config file, or a list of paths.
2398 :param options: All keyword parameters are passed to the underlying
2399 :class:`python:configparser.ConfigParser` constructor call.
2402 options.setdefault('allow_no_value', True)
2404 options.setdefault('interpolation',
2405 configparser.ExtendedInterpolation())
2406 conf = configparser.ConfigParser(**options)
2408 for section in conf.sections():
2409 for key in conf.options(section):
2410 value = conf.get(section, key)
2411 if section not in ['bottle', 'ROOT']:
2412 key = section + '.' + key
2413 self[key.lower()] = value
2416 def load_dict(self, source, namespace=''):
2417 """ Load values from a dictionary structure. Nesting can be used to
2418 represent namespaces.
2420 >>> c = ConfigDict()
2421 >>> c.load_dict({'some': {'namespace': {'key': 'value'} } })
2422 {'some.namespace.key': 'value'}
2424 for key, value in source.items():
2425 if isinstance(key, basestring):
2426 nskey = (namespace + '.' + key).strip('.')
2427 if isinstance(value, dict):
2428 self.load_dict(value, namespace=nskey)
2432 raise TypeError('Key has type %r (not a string)' % type(key))
2435 def update(self, *a, **ka):
2436 """ If the first parameter is a string, all keys are prefixed with this
2437 namespace. Apart from that it works just as the usual dict.update().
2439 >>> c = ConfigDict()
2440 >>> c.update('some.namespace', key='value')
2443 if a and isinstance(a[0], basestring):
2444 prefix = a[0].strip('.') + '.'
2446 for key, value in dict(*a, **ka).items():
2447 self[prefix + key] = value
2449 def setdefault(self, key, value):
2454 def __setitem__(self, key, value):
2455 if not isinstance(key, basestring):
2456 raise TypeError('Key has type %r (not a string)' % type(key))
2458 self._virtual_keys.discard(key)
2460 value = self.meta_get(key, 'filter', lambda x: x)(value)
2461 if key in self and self[key] is value:
2464 self._on_change(key, value)
2465 dict.__setitem__(self, key, value)
2467 for overlay in self._iter_overlays():
2468 overlay._set_virtual(key, value)
2470 def __delitem__(self, key):
2473 if key in self._virtual_keys:
2474 raise KeyError("Virtual keys cannot be deleted: %s" % key)
2476 if self._source and key in self._source:
2477 # Not virtual, but present in source -> Restore virtual value
2478 dict.__delitem__(self, key)
2479 self._set_virtual(key, self._source[key])
2480 else: # not virtual, not present in source. This is OUR value
2481 self._on_change(key, None)
2482 dict.__delitem__(self, key)
2483 for overlay in self._iter_overlays():
2484 overlay._delete_virtual(key)
2486 def _set_virtual(self, key, value):
2487 """ Recursively set or update virtual keys. Do nothing if non-virtual
2488 value is present. """
2489 if key in self and key not in self._virtual_keys:
2490 return # Do nothing for non-virtual keys.
2492 self._virtual_keys.add(key)
2493 if key in self and self[key] is not value:
2494 self._on_change(key, value)
2495 dict.__setitem__(self, key, value)
2496 for overlay in self._iter_overlays():
2497 overlay._set_virtual(key, value)
2499 def _delete_virtual(self, key):
2500 """ Recursively delete virtual entry. Do nothing if key is not virtual.
2502 if key not in self._virtual_keys:
2503 return # Do nothing for non-virtual keys.
2506 self._on_change(key, None)
2507 dict.__delitem__(self, key)
2508 self._virtual_keys.discard(key)
2509 for overlay in self._iter_overlays():
2510 overlay._delete_virtual(key)
2512 def _on_change(self, key, value):
2513 for cb in self._change_listener:
2514 if cb(self, key, value):
2517 def _add_change_listener(self, func):
2518 self._change_listener.append(func)
2521 def meta_get(self, key, metafield, default=None):
2522 """ Return the value of a meta field for a key. """
2523 return self._meta.get(key, {}).get(metafield, default)
2525 def meta_set(self, key, metafield, value):
2526 """ Set the meta field for a key to a new value. """
2527 self._meta.setdefault(key, {})[metafield] = value
2529 def meta_list(self, key):
2530 """ Return an iterable of meta field names defined for a key. """
2531 return self._meta.get(key, {}).keys()
2533 def _define(self, key, default=_UNSET, help=_UNSET, validate=_UNSET):
2534 """ (Unstable) Shortcut for plugins to define own config parameters. """
2535 if default is not _UNSET:
2536 self.setdefault(key, default)
2537 if help is not _UNSET:
2538 self.meta_set(key, 'help', help)
2539 if validate is not _UNSET:
2540 self.meta_set(key, 'validate', validate)
2542 def _iter_overlays(self):
2543 for ref in self._overlays:
2545 if overlay is not None:
2548 def _make_overlay(self):
2549 """ (Unstable) Create a new overlay that acts like a chained map: Values
2550 missing in the overlay are copied from the source map. Both maps
2551 share the same meta entries.
2553 Entries that were copied from the source are called 'virtual'. You
2554 can not delete virtual keys, but overwrite them, which turns them
2555 into non-virtual entries. Setting keys on an overlay never affects
2556 its source, but may affect any number of child overlays.
2558 Other than collections.ChainMap or most other implementations, this
2559 approach does not resolve missing keys on demand, but instead
2560 actively copies all values from the source to the overlay and keeps
2561 track of virtual and non-virtual keys internally. This removes any
2562 lookup-overhead. Read-access is as fast as a build-in dict for both
2563 virtual and non-virtual keys.
2565 Changes are propagated recursively and depth-first. A failing
2566 on-change handler in an overlay stops the propagation of virtual
2567 values and may result in an partly updated tree. Take extra care
2568 here and make sure that on-change handlers never fail.
2570 Used by Route.config
2572 # Cleanup dead references
2573 self._overlays[:] = [ref for ref in self._overlays if ref() is not None]
2575 overlay = ConfigDict()
2576 overlay._meta = self._meta
2577 overlay._source = self
2578 self._overlays.append(weakref.ref(overlay))
2580 overlay._set_virtual(key, self[key])
2586 class AppStack(list):
2587 """ A stack-like list. Calling it returns the head of the stack. """
2590 """ Return the current default application. """
2593 def push(self, value=None):
2594 """ Add a new :class:`Bottle` instance to the stack """
2595 if not isinstance(value, Bottle):
2609 class WSGIFileWrapper(object):
2610 def __init__(self, fp, buffer_size=1024 * 64):
2611 self.fp, self.buffer_size = fp, buffer_size
2612 for attr in ('fileno', 'close', 'read', 'readlines', 'tell', 'seek'):
2613 if hasattr(fp, attr): setattr(self, attr, getattr(fp, attr))
2616 buff, read = self.buffer_size, self.read
2623 class _closeiter(object):
2624 """ This only exists to be able to attach a .close method to iterators that
2625 do not support attribute assignment (most of itertools). """
2627 def __init__(self, iterator, close=None):
2628 self.iterator = iterator
2629 self.close_callbacks = makelist(close)
2632 return iter(self.iterator)
2635 for func in self.close_callbacks:
2639 class ResourceManager(object):
2640 """ This class manages a list of search paths and helps to find and open
2641 application-bound resources (files).
2643 :param base: default value for :meth:`add_path` calls.
2644 :param opener: callable used to open resources.
2645 :param cachemode: controls which lookups are cached. One of 'all',
2649 def __init__(self, base='./', opener=open, cachemode='all'):
2650 self.opener = opener
2652 self.cachemode = cachemode
2654 #: A list of search paths. See :meth:`add_path` for details.
2656 #: A cache for resolved paths. ``res.cache.clear()`` clears the cache.
2659 def add_path(self, path, base=None, index=None, create=False):
2660 """ Add a new path to the list of search paths. Return False if the
2661 path does not exist.
2663 :param path: The new search path. Relative paths are turned into
2664 an absolute and normalized form. If the path looks like a file
2665 (not ending in `/`), the filename is stripped off.
2666 :param base: Path used to absolutize relative search paths.
2667 Defaults to :attr:`base` which defaults to ``os.getcwd()``.
2668 :param index: Position within the list of search paths. Defaults
2669 to last index (appends to the list).
2671 The `base` parameter makes it easy to reference files installed
2672 along with a python module or package::
2674 res.add_path('./resources/', __file__)
2676 base = os.path.abspath(os.path.dirname(base or self.base))
2677 path = os.path.abspath(os.path.join(base, os.path.dirname(path)))
2679 if path in self.path:
2680 self.path.remove(path)
2681 if create and not os.path.isdir(path):
2684 self.path.append(path)
2686 self.path.insert(index, path)
2688 return os.path.exists(path)
2691 """ Iterate over all existing files in all registered paths. """
2692 search = self.path[:]
2695 if not os.path.isdir(path): continue
2696 for name in os.listdir(path):
2697 full = os.path.join(path, name)
2698 if os.path.isdir(full): search.append(full)
2701 def lookup(self, name):
2702 """ Search for a resource and return an absolute file path, or `None`.
2704 The :attr:`path` list is searched in order. The first match is
2705 returend. Symlinks are followed. The result is cached to speed up
2707 if name not in self.cache or DEBUG:
2708 for path in self.path:
2709 fpath = os.path.join(path, name)
2710 if os.path.isfile(fpath):
2711 if self.cachemode in ('all', 'found'):
2712 self.cache[name] = fpath
2714 if self.cachemode == 'all':
2715 self.cache[name] = None
2716 return self.cache[name]
2718 def open(self, name, mode='r', *args, **kwargs):
2719 """ Find a resource and return a file object, or raise IOError. """
2720 fname = self.lookup(name)
2721 if not fname: raise IOError("Resource %r not found." % name)
2722 return self.opener(fname, mode=mode, *args, **kwargs)
2725 class FileUpload(object):
2726 def __init__(self, fileobj, name, filename, headers=None):
2727 """ Wrapper for file uploads. """
2728 #: Open file(-like) object (BytesIO buffer or temporary file)
2730 #: Name of the upload form field
2732 #: Raw filename as sent by the client (may contain unsafe characters)
2733 self.raw_filename = filename
2734 #: A :class:`HeaderDict` with additional headers (e.g. content-type)
2735 self.headers = HeaderDict(headers) if headers else HeaderDict()
2737 content_type = HeaderProperty('Content-Type')
2738 content_length = HeaderProperty('Content-Length', reader=int, default=-1)
2740 def get_header(self, name, default=None):
2741 """ Return the value of a header within the mulripart part. """
2742 return self.headers.get(name, default)
2746 """ Name of the file on the client file system, but normalized to ensure
2747 file system compatibility. An empty filename is returned as 'empty'.
2749 Only ASCII letters, digits, dashes, underscores and dots are
2750 allowed in the final filename. Accents are removed, if possible.
2751 Whitespace is replaced by a single dash. Leading or tailing dots
2752 or dashes are removed. The filename is limited to 255 characters.
2754 fname = self.raw_filename
2755 if not isinstance(fname, unicode):
2756 fname = fname.decode('utf8', 'ignore')
2757 fname = normalize('NFKD', fname)
2758 fname = fname.encode('ASCII', 'ignore').decode('ASCII')
2759 fname = os.path.basename(fname.replace('\\', os.path.sep))
2760 fname = re.sub(r'[^a-zA-Z0-9-_.\s]', '', fname).strip()
2761 fname = re.sub(r'[-\s]+', '-', fname).strip('.-')
2762 return fname[:255] or 'empty'
2764 def _copy_file(self, fp, chunk_size=2 ** 16):
2765 read, write, offset = self.file.read, fp.write, self.file.tell()
2767 buf = read(chunk_size)
2770 self.file.seek(offset)
2772 def save(self, destination, overwrite=False, chunk_size=2 ** 16):
2773 """ Save file to disk or copy its content to an open file(-like) object.
2774 If *destination* is a directory, :attr:`filename` is added to the
2775 path. Existing files are not overwritten by default (IOError).
2777 :param destination: File path, directory or file(-like) object.
2778 :param overwrite: If True, replace existing files. (default: False)
2779 :param chunk_size: Bytes to read at a time. (default: 64kb)
2781 if isinstance(destination, basestring): # Except file-likes here
2782 if os.path.isdir(destination):
2783 destination = os.path.join(destination, self.filename)
2784 if not overwrite and os.path.exists(destination):
2785 raise IOError('File exists.')
2786 with open(destination, 'wb') as fp:
2787 self._copy_file(fp, chunk_size)
2789 self._copy_file(destination, chunk_size)
2791 ###############################################################################
2792 # Application Helper ###########################################################
2793 ###############################################################################
2796 def abort(code=500, text='Unknown Error.'):
2797 """ Aborts execution and causes a HTTP error. """
2798 raise HTTPError(code, text)
2801 def redirect(url, code=None):
2802 """ Aborts execution and causes a 303 or 302 redirect, depending on
2803 the HTTP protocol version. """
2805 code = 303 if request.get('SERVER_PROTOCOL') == "HTTP/1.1" else 302
2806 res = response.copy(cls=HTTPResponse)
2809 res.set_header('Location', urljoin(request.url, url))
2813 def _file_iter_range(fp, offset, bytes, maxread=1024 * 1024, close=False):
2814 """ Yield chunks from a range in a file, optionally closing it at the end.
2815 No chunk is bigger than maxread. """
2818 part = fp.read(min(bytes, maxread))
2827 def static_file(filename, root,
2832 """ Open a file in a safe way and return an instance of :exc:`HTTPResponse`
2833 that can be sent back to the client.
2835 :param filename: Name or path of the file to send, relative to ``root``.
2836 :param root: Root path for file lookups. Should be an absolute directory
2838 :param mimetype: Provide the content-type header (default: guess from
2840 :param download: If True, ask the browser to open a `Save as...` dialog
2841 instead of opening the file with the associated program. You can
2842 specify a custom filename as a string. If not specified, the
2843 original filename is used (default: False).
2844 :param charset: The charset for files with a ``text/*`` mime-type.
2846 :param etag: Provide a pre-computed ETag header. If set to ``False``,
2847 ETag handling is disabled. (default: auto-generate ETag header)
2849 While checking user input is always a good idea, this function provides
2850 additional protection against malicious ``filename`` parameters from
2851 breaking out of the ``root`` directory and leaking sensitive information
2854 Read-protected files or files outside of the ``root`` directory are
2855 answered with ``403 Access Denied``. Missing files result in a
2856 ``404 Not Found`` response. Conditional requests (``If-Modified-Since``,
2857 ``If-None-Match``) are answered with ``304 Not Modified`` whenever
2858 possible. ``HEAD`` and ``Range`` requests (used by download managers to
2859 check or continue partial downloads) are also handled automatically.
2863 root = os.path.join(os.path.abspath(root), '')
2864 filename = os.path.abspath(os.path.join(root, filename.strip('/\\')))
2867 if not filename.startswith(root):
2868 return HTTPError(403, "Access denied.")
2869 if not os.path.exists(filename) or not os.path.isfile(filename):
2870 return HTTPError(404, "File does not exist.")
2871 if not os.access(filename, os.R_OK):
2872 return HTTPError(403, "You do not have permission to access this file.")
2874 if mimetype is True:
2875 if download and download is not True:
2876 mimetype, encoding = mimetypes.guess_type(download)
2878 mimetype, encoding = mimetypes.guess_type(filename)
2879 if encoding: headers['Content-Encoding'] = encoding
2882 if (mimetype[:5] == 'text/' or mimetype == 'application/javascript')\
2883 and charset and 'charset' not in mimetype:
2884 mimetype += '; charset=%s' % charset
2885 headers['Content-Type'] = mimetype
2888 download = os.path.basename(filename if download is True else download)
2889 headers['Content-Disposition'] = 'attachment; filename="%s"' % download
2891 stats = os.stat(filename)
2892 headers['Content-Length'] = clen = stats.st_size
2893 headers['Last-Modified'] = email.utils.formatdate(stats.st_mtime,
2895 headers['Date'] = email.utils.formatdate(time.time(), usegmt=True)
2897 getenv = request.environ.get
2900 etag = '%d:%d:%d:%d:%s' % (stats.st_dev, stats.st_ino, stats.st_mtime,
2902 etag = hashlib.sha1(tob(etag)).hexdigest()
2905 headers['ETag'] = etag
2906 check = getenv('HTTP_IF_NONE_MATCH')
2907 if check and check == etag:
2908 return HTTPResponse(status=304, **headers)
2910 ims = getenv('HTTP_IF_MODIFIED_SINCE')
2912 ims = parse_date(ims.split(";")[0].strip())
2913 if ims is not None and ims >= int(stats.st_mtime):
2914 return HTTPResponse(status=304, **headers)
2916 body = '' if request.method == 'HEAD' else open(filename, 'rb')
2918 headers["Accept-Ranges"] = "bytes"
2919 range_header = getenv('HTTP_RANGE')
2921 ranges = list(parse_range_header(range_header, clen))
2923 return HTTPError(416, "Requested Range Not Satisfiable")
2924 offset, end = ranges[0]
2925 headers["Content-Range"] = "bytes %d-%d/%d" % (offset, end - 1, clen)
2926 headers["Content-Length"] = str(end - offset)
2927 if body: body = _file_iter_range(body, offset, end - offset, close=True)
2928 return HTTPResponse(body, status=206, **headers)
2929 return HTTPResponse(body, **headers)
2931 ###############################################################################
2932 # HTTP Utilities and MISC (TODO) ###############################################
2933 ###############################################################################
2936 def debug(mode=True):
2937 """ Change the debug level.
2938 There is only one debug level supported at the moment."""
2940 if mode: warnings.simplefilter('default')
2944 def http_date(value):
2945 if isinstance(value, (datedate, datetime)):
2946 value = value.utctimetuple()
2947 elif isinstance(value, (int, float)):
2948 value = time.gmtime(value)
2949 if not isinstance(value, basestring):
2950 value = time.strftime("%a, %d %b %Y %H:%M:%S GMT", value)
2954 def parse_date(ims):
2955 """ Parse rfc1123, rfc850 and asctime timestamps and return UTC epoch. """
2957 ts = email.utils.parsedate_tz(ims)
2958 return time.mktime(ts[:8] + (0, )) - (ts[9] or 0) - time.timezone
2959 except (TypeError, ValueError, IndexError, OverflowError):
2963 def parse_auth(header):
2964 """ Parse rfc2617 HTTP authentication header string (basic) and return (user,pass) tuple or None"""
2966 method, data = header.split(None, 1)
2967 if method.lower() == 'basic':
2968 user, pwd = touni(base64.b64decode(tob(data))).split(':', 1)
2970 except (KeyError, ValueError):
2974 def parse_range_header(header, maxlen=0):
2975 """ Yield (start, end) ranges parsed from a HTTP Range header. Skip
2976 unsatisfiable ranges. The end index is non-inclusive."""
2977 if not header or header[:6] != 'bytes=': return
2978 ranges = [r.split('-', 1) for r in header[6:].split(',') if '-' in r]
2979 for start, end in ranges:
2981 if not start: # bytes=-100 -> last 100 bytes
2982 start, end = max(0, maxlen - int(end)), maxlen
2983 elif not end: # bytes=100- -> all but the first 99 bytes
2984 start, end = int(start), maxlen
2985 else: # bytes=100-200 -> bytes 100-200 (inclusive)
2986 start, end = int(start), min(int(end) + 1, maxlen)
2987 if 0 <= start < end <= maxlen:
2993 #: Header tokenizer used by _parse_http_header()
2994 _hsplit = re.compile('(?:(?:"((?:[^"\\\\]+|\\\\.)*)")|([^;,=]+))([;,=]?)').findall
2996 def _parse_http_header(h):
2997 """ Parses a typical multi-valued and parametrised HTTP header (e.g. Accept headers) and returns a list of values
2998 and parameters. For non-standard or broken input, this implementation may return partial results.
2999 :param h: A header string (e.g. ``text/html,text/plain;q=0.9,*/*;q=0.8``)
3000 :return: List of (value, params) tuples. The second element is a (possibly empty) dict.
3003 if '"' not in h: # INFO: Fast path without regexp (~2x faster)
3004 for value in h.split(','):
3005 parts = value.split(';')
3006 values.append((parts[0].strip(), {}))
3007 for attr in parts[1:]:
3008 name, value = attr.split('=', 1)
3009 values[-1][1][name.strip()] = value.strip()
3011 lop, key, attrs = ',', None, {}
3012 for quoted, plain, tok in _hsplit(h):
3013 value = plain.strip() if plain else quoted.replace('\\"', '"')
3016 values.append((value, attrs))
3022 elif lop == '=' and key:
3031 for pair in qs.replace(';', '&').split('&'):
3032 if not pair: continue
3033 nv = pair.split('=', 1)
3034 if len(nv) != 2: nv.append('')
3035 key = urlunquote(nv[0].replace('+', ' '))
3036 value = urlunquote(nv[1].replace('+', ' '))
3037 r.append((key, value))
3042 """ Compares two strings in a cryptographically safe way:
3043 Runtime is not affected by length of common prefix. """
3044 return not sum(0 if x == y else 1
3045 for x, y in zip(a, b)) and len(a) == len(b)
3048 def cookie_encode(data, key, digestmod=None):
3049 """ Encode and sign a pickle-able object. Return a (byte) string """
3050 depr(0, 13, "cookie_encode() will be removed soon.",
3051 "Do not use this API directly.")
3052 digestmod = digestmod or hashlib.sha256
3053 msg = base64.b64encode(pickle.dumps(data, -1))
3054 sig = base64.b64encode(hmac.new(tob(key), msg, digestmod=digestmod).digest())
3055 return tob('!') + sig + tob('?') + msg
3058 def cookie_decode(data, key, digestmod=None):
3059 """ Verify and decode an encoded string. Return an object or None."""
3060 depr(0, 13, "cookie_decode() will be removed soon.",
3061 "Do not use this API directly.")
3063 if cookie_is_encoded(data):
3064 sig, msg = data.split(tob('?'), 1)
3065 digestmod = digestmod or hashlib.sha256
3066 hashed = hmac.new(tob(key), msg, digestmod=digestmod).digest()
3067 if _lscmp(sig[1:], base64.b64encode(hashed)):
3068 return pickle.loads(base64.b64decode(msg))
3072 def cookie_is_encoded(data):
3073 """ Return True if the argument looks like a encoded cookie."""
3074 depr(0, 13, "cookie_is_encoded() will be removed soon.",
3075 "Do not use this API directly.")
3076 return bool(data.startswith(tob('!')) and tob('?') in data)
3079 def html_escape(string):
3080 """ Escape HTML special characters ``&<>`` and quotes ``'"``. """
3081 return string.replace('&', '&').replace('<', '<').replace('>', '>')\
3082 .replace('"', '"').replace("'", ''')
3085 def html_quote(string):
3086 """ Escape and quote a string to be used as an HTTP attribute."""
3087 return '"%s"' % html_escape(string).replace('\n', ' ')\
3088 .replace('\r', ' ').replace('\t', '	')
3091 def yieldroutes(func):
3092 """ Return a generator for routes that match the signature (name, args)
3093 of the func parameter. This may yield more than one route if the function
3094 takes optional keyword arguments. The output is best described by example::
3097 b(x, y) -> '/b/<x>/<y>'
3098 c(x, y=5) -> '/c/<x>' and '/c/<x>/<y>'
3099 d(x=5, y=6) -> '/d' and '/d/<x>' and '/d/<x>/<y>'
3101 path = '/' + func.__name__.replace('__', '/').lstrip('/')
3102 spec = getargspec(func)
3103 argc = len(spec[0]) - len(spec[3] or [])
3104 path += ('/<%s>' * argc) % tuple(spec[0][:argc])
3106 for arg in spec[0][argc:]:
3107 path += '/<%s>' % arg
3111 def path_shift(script_name, path_info, shift=1):
3112 """ Shift path fragments from PATH_INFO to SCRIPT_NAME and vice versa.
3114 :return: The modified paths.
3115 :param script_name: The SCRIPT_NAME path.
3116 :param script_name: The PATH_INFO path.
3117 :param shift: The number of path fragments to shift. May be negative to
3118 change the shift direction. (default: 1)
3120 if shift == 0: return script_name, path_info
3121 pathlist = path_info.strip('/').split('/')
3122 scriptlist = script_name.strip('/').split('/')
3123 if pathlist and pathlist[0] == '': pathlist = []
3124 if scriptlist and scriptlist[0] == '': scriptlist = []
3125 if 0 < shift <= len(pathlist):
3126 moved = pathlist[:shift]
3127 scriptlist = scriptlist + moved
3128 pathlist = pathlist[shift:]
3129 elif 0 > shift >= -len(scriptlist):
3130 moved = scriptlist[shift:]
3131 pathlist = moved + pathlist
3132 scriptlist = scriptlist[:shift]
3134 empty = 'SCRIPT_NAME' if shift < 0 else 'PATH_INFO'
3135 raise AssertionError("Cannot shift. Nothing left from %s" % empty)
3136 new_script_name = '/' + '/'.join(scriptlist)
3137 new_path_info = '/' + '/'.join(pathlist)
3138 if path_info.endswith('/') and pathlist: new_path_info += '/'
3139 return new_script_name, new_path_info
3142 def auth_basic(check, realm="private", text="Access denied"):
3143 """ Callback decorator to require HTTP auth (basic).
3144 TODO: Add route(check_auth=...) parameter. """
3146 def decorator(func):
3148 @functools.wraps(func)
3149 def wrapper(*a, **ka):
3150 user, password = request.auth or (None, None)
3151 if user is None or not check(user, password):
3152 err = HTTPError(401, text)
3153 err.add_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
3155 return func(*a, **ka)
3161 # Shortcuts for common Bottle methods.
3162 # They all refer to the current default application.
3165 def make_default_app_wrapper(name):
3166 """ Return a callable that relays calls to the current default app. """
3168 @functools.wraps(getattr(Bottle, name))
3169 def wrapper(*a, **ka):
3170 return getattr(app(), name)(*a, **ka)
3175 route = make_default_app_wrapper('route')
3176 get = make_default_app_wrapper('get')
3177 post = make_default_app_wrapper('post')
3178 put = make_default_app_wrapper('put')
3179 delete = make_default_app_wrapper('delete')
3180 patch = make_default_app_wrapper('patch')
3181 error = make_default_app_wrapper('error')
3182 mount = make_default_app_wrapper('mount')
3183 hook = make_default_app_wrapper('hook')
3184 install = make_default_app_wrapper('install')
3185 uninstall = make_default_app_wrapper('uninstall')
3186 url = make_default_app_wrapper('get_url')
3188 ###############################################################################
3189 # Server Adapter ###############################################################
3190 ###############################################################################
3192 # Before you edit or add a server adapter, please read:
3193 # - https://github.com/bottlepy/bottle/pull/647#issuecomment-60152870
3194 # - https://github.com/bottlepy/bottle/pull/865#issuecomment-242795341
3196 class ServerAdapter(object):
3199 def __init__(self, host='127.0.0.1', port=8080, **options):
3200 self.options = options
3202 self.port = int(port)
3204 def run(self, handler): # pragma: no cover
3208 args = ', '.join(['%s=%s' % (k, repr(v))
3209 for k, v in self.options.items()])
3210 return "%s(%s)" % (self.__class__.__name__, args)
3213 class CGIServer(ServerAdapter):
3216 def run(self, handler): # pragma: no cover
3217 from wsgiref.handlers import CGIHandler
3219 def fixed_environ(environ, start_response):
3220 environ.setdefault('PATH_INFO', '')
3221 return handler(environ, start_response)
3223 CGIHandler().run(fixed_environ)
3226 class FlupFCGIServer(ServerAdapter):
3227 def run(self, handler): # pragma: no cover
3228 import flup.server.fcgi
3229 self.options.setdefault('bindAddress', (self.host, self.port))
3230 flup.server.fcgi.WSGIServer(handler, **self.options).run()
3233 class WSGIRefServer(ServerAdapter):
3234 def run(self, app): # pragma: no cover
3235 from wsgiref.simple_server import make_server
3236 from wsgiref.simple_server import WSGIRequestHandler, WSGIServer
3239 class FixedHandler(WSGIRequestHandler):
3240 def address_string(self): # Prevent reverse DNS lookups please.
3241 return self.client_address[0]
3243 def log_request(*args, **kw):
3245 return WSGIRequestHandler.log_request(*args, **kw)
3247 handler_cls = self.options.get('handler_class', FixedHandler)
3248 server_cls = self.options.get('server_class', WSGIServer)
3250 if ':' in self.host: # Fix wsgiref for IPv6 addresses.
3251 if getattr(server_cls, 'address_family') == socket.AF_INET:
3253 class server_cls(server_cls):
3254 address_family = socket.AF_INET6
3256 self.srv = make_server(self.host, self.port, app, server_cls,
3258 self.port = self.srv.server_port # update port actual port (0 means random)
3260 self.srv.serve_forever()
3261 except KeyboardInterrupt:
3262 self.srv.server_close() # Prevent ResourceWarning: unclosed socket
3266 class CherryPyServer(ServerAdapter):
3267 def run(self, handler): # pragma: no cover
3268 depr(0, 13, "The wsgi server part of cherrypy was split into a new "
3269 "project called 'cheroot'.", "Use the 'cheroot' server "
3270 "adapter instead of cherrypy.")
3271 from cherrypy import wsgiserver # This will fail for CherryPy >= 9
3273 self.options['bind_addr'] = (self.host, self.port)
3274 self.options['wsgi_app'] = handler
3276 certfile = self.options.get('certfile')
3278 del self.options['certfile']
3279 keyfile = self.options.get('keyfile')
3281 del self.options['keyfile']
3283 server = wsgiserver.CherryPyWSGIServer(**self.options)
3285 server.ssl_certificate = certfile
3287 server.ssl_private_key = keyfile
3295 class CherootServer(ServerAdapter):
3296 def run(self, handler): # pragma: no cover
3297 from cheroot import wsgi
3298 from cheroot.ssl import builtin
3299 self.options['bind_addr'] = (self.host, self.port)
3300 self.options['wsgi_app'] = handler
3301 certfile = self.options.pop('certfile', None)
3302 keyfile = self.options.pop('keyfile', None)
3303 chainfile = self.options.pop('chainfile', None)
3304 server = wsgi.Server(**self.options)
3305 if certfile and keyfile:
3306 server.ssl_adapter = builtin.BuiltinSSLAdapter(
3307 certfile, keyfile, chainfile)
3314 class WaitressServer(ServerAdapter):
3315 def run(self, handler):
3316 from waitress import serve
3317 serve(handler, host=self.host, port=self.port, _quiet=self.quiet, **self.options)
3320 class PasteServer(ServerAdapter):
3321 def run(self, handler): # pragma: no cover
3322 from paste import httpserver
3323 from paste.translogger import TransLogger
3324 handler = TransLogger(handler, setup_console_handler=(not self.quiet))
3325 httpserver.serve(handler,
3327 port=str(self.port), **self.options)
3330 class MeinheldServer(ServerAdapter):
3331 def run(self, handler):
3332 from meinheld import server
3333 server.listen((self.host, self.port))
3337 class FapwsServer(ServerAdapter):
3338 """ Extremely fast webserver using libev. See http://www.fapws.org/ """
3340 def run(self, handler): # pragma: no cover
3341 import fapws._evwsgi as evwsgi
3342 from fapws import base, config
3344 if float(config.SERVER_IDENT[-2:]) > 0.4:
3345 # fapws3 silently changed its API in 0.5
3347 evwsgi.start(self.host, port)
3348 # fapws3 never releases the GIL. Complain upstream. I tried. No luck.
3349 if 'BOTTLE_CHILD' in os.environ and not self.quiet:
3350 _stderr("WARNING: Auto-reloading does not work with Fapws3.\n")
3351 _stderr(" (Fapws3 breaks python thread support)\n")
3352 evwsgi.set_base_module(base)
3354 def app(environ, start_response):
3355 environ['wsgi.multiprocess'] = False
3356 return handler(environ, start_response)
3358 evwsgi.wsgi_cb(('', app))
3362 class TornadoServer(ServerAdapter):
3363 """ The super hyped asynchronous server by facebook. Untested. """
3365 def run(self, handler): # pragma: no cover
3366 import tornado.wsgi, tornado.httpserver, tornado.ioloop
3367 container = tornado.wsgi.WSGIContainer(handler)
3368 server = tornado.httpserver.HTTPServer(container)
3369 server.listen(port=self.port, address=self.host)
3370 tornado.ioloop.IOLoop.instance().start()
3373 class AppEngineServer(ServerAdapter):
3374 """ Adapter for Google App Engine. """
3377 def run(self, handler):
3378 depr(0, 13, "AppEngineServer no longer required",
3379 "Configure your application directly in your app.yaml")
3380 from google.appengine.ext.webapp import util
3381 # A main() function in the handler script enables 'App Caching'.
3382 # Lets makes sure it is there. This _really_ improves performance.
3383 module = sys.modules.get('__main__')
3384 if module and not hasattr(module, 'main'):
3385 module.main = lambda: util.run_wsgi_app(handler)
3386 util.run_wsgi_app(handler)
3389 class TwistedServer(ServerAdapter):
3392 def run(self, handler):
3393 from twisted.web import server, wsgi
3394 from twisted.python.threadpool import ThreadPool
3395 from twisted.internet import reactor
3396 thread_pool = ThreadPool()
3398 reactor.addSystemEventTrigger('after', 'shutdown', thread_pool.stop)
3399 factory = server.Site(wsgi.WSGIResource(reactor, thread_pool, handler))
3400 reactor.listenTCP(self.port, factory, interface=self.host)
3401 if not reactor.running:
3405 class DieselServer(ServerAdapter):
3408 def run(self, handler):
3409 from diesel.protocols.wsgi import WSGIApplication
3410 app = WSGIApplication(handler, port=self.port)
3414 class GeventServer(ServerAdapter):
3415 """ Untested. Options:
3417 * See gevent.wsgi.WSGIServer() documentation for more options.
3420 def run(self, handler):
3421 from gevent import pywsgi, local
3422 if not isinstance(threading.local(), local.local):
3423 msg = "Bottle requires gevent.monkey.patch_all() (before import)"
3424 raise RuntimeError(msg)
3426 self.options['log'] = None
3427 address = (self.host, self.port)
3428 server = pywsgi.WSGIServer(address, handler, **self.options)
3429 if 'BOTTLE_CHILD' in os.environ:
3431 signal.signal(signal.SIGINT, lambda s, f: server.stop())
3432 server.serve_forever()
3435 class GunicornServer(ServerAdapter):
3436 """ Untested. See http://gunicorn.org/configure.html for options. """
3438 def run(self, handler):
3439 from gunicorn.app.base import Application
3441 config = {'bind': "%s:%d" % (self.host, int(self.port))}
3442 config.update(self.options)
3444 class GunicornApplication(Application):
3445 def init(self, parser, opts, args):
3451 GunicornApplication().run()
3454 class EventletServer(ServerAdapter):
3455 """ Untested. Options:
3457 * `backlog` adjust the eventlet backlog parameter which is the maximum
3458 number of queued connections. Should be at least 1; the maximum
3459 value is system-dependent.
3460 * `family`: (default is 2) socket family, optional. See socket
3461 documentation for available families.
3464 def run(self, handler):
3465 from eventlet import wsgi, listen, patcher
3466 if not patcher.is_monkey_patched(os):
3467 msg = "Bottle requires eventlet.monkey_patch() (before import)"
3468 raise RuntimeError(msg)
3470 for arg in ('backlog', 'family'):
3472 socket_args[arg] = self.options.pop(arg)
3475 address = (self.host, self.port)
3477 wsgi.server(listen(address, **socket_args), handler,
3478 log_output=(not self.quiet))
3480 # Fallback, if we have old version of eventlet
3481 wsgi.server(listen(address), handler)
3484 class RocketServer(ServerAdapter):
3487 def run(self, handler):
3488 from rocket import Rocket
3489 server = Rocket((self.host, self.port), 'wsgi', {'wsgi_app': handler})
3493 class BjoernServer(ServerAdapter):
3494 """ Fast server written in C: https://github.com/jonashaag/bjoern """
3496 def run(self, handler):
3497 from bjoern import run
3498 run(handler, self.host, self.port)
3500 class AsyncioServerAdapter(ServerAdapter):
3501 """ Extend ServerAdapter for adding custom event loop """
3502 def get_event_loop(self):
3505 class AiohttpServer(AsyncioServerAdapter):
3508 https://pypi.python.org/pypi/aiohttp/
3511 def get_event_loop(self):
3513 return asyncio.new_event_loop()
3515 def run(self, handler):
3517 from aiohttp.wsgi import WSGIServerHttpProtocol
3518 self.loop = self.get_event_loop()
3519 asyncio.set_event_loop(self.loop)
3521 protocol_factory = lambda: WSGIServerHttpProtocol(
3524 debug=(not self.quiet))
3525 self.loop.run_until_complete(self.loop.create_server(protocol_factory,
3529 if 'BOTTLE_CHILD' in os.environ:
3531 signal.signal(signal.SIGINT, lambda s, f: self.loop.stop())
3534 self.loop.run_forever()
3535 except KeyboardInterrupt:
3538 class AiohttpUVLoopServer(AiohttpServer):
3540 https://github.com/MagicStack/uvloop
3542 def get_event_loop(self):
3544 return uvloop.new_event_loop()
3546 class AutoServer(ServerAdapter):
3548 adapters = [WaitressServer, PasteServer, TwistedServer, CherryPyServer,
3549 CherootServer, WSGIRefServer]
3551 def run(self, handler):
3552 for sa in self.adapters:
3554 return sa(self.host, self.port, **self.options).run(handler)
3561 'flup': FlupFCGIServer,
3562 'wsgiref': WSGIRefServer,
3563 'waitress': WaitressServer,
3564 'cherrypy': CherryPyServer,
3565 'cheroot': CherootServer,
3566 'paste': PasteServer,
3567 'fapws3': FapwsServer,
3568 'tornado': TornadoServer,
3569 'gae': AppEngineServer,
3570 'twisted': TwistedServer,
3571 'diesel': DieselServer,
3572 'meinheld': MeinheldServer,
3573 'gunicorn': GunicornServer,
3574 'eventlet': EventletServer,
3575 'gevent': GeventServer,
3576 'rocket': RocketServer,
3577 'bjoern': BjoernServer,
3578 'aiohttp': AiohttpServer,
3579 'uvloop': AiohttpUVLoopServer,
3583 ###############################################################################
3584 # Application Control ##########################################################
3585 ###############################################################################
3588 def load(target, **namespace):
3589 """ Import a module or fetch an object from a module.
3591 * ``package.module`` returns `module` as a module object.
3592 * ``pack.mod:name`` returns the module variable `name` from `pack.mod`.
3593 * ``pack.mod:func()`` calls `pack.mod.func()` and returns the result.
3595 The last form accepts not only function calls, but any type of
3596 expression. Keyword arguments passed to this function are available as
3597 local variables. Example: ``import_string('re:compile(x)', x='[a-z]')``
3599 module, target = target.split(":", 1) if ':' in target else (target, None)
3600 if module not in sys.modules: __import__(module)
3601 if not target: return sys.modules[module]
3602 if target.isalnum(): return getattr(sys.modules[module], target)
3603 package_name = module.split('.')[0]
3604 namespace[package_name] = sys.modules[package_name]
3605 return eval('%s.%s' % (module, target), namespace)
3608 def load_app(target):
3609 """ Load a bottle application from a module and make sure that the import
3610 does not affect the current default application, but returns a separate
3611 application object. See :func:`load` for the target parameter. """
3613 NORUN, nr_old = True, NORUN
3614 tmp = default_app.push() # Create a new "default application"
3616 rv = load(target) # Import the target module
3617 return rv if callable(rv) else tmp
3619 default_app.remove(tmp) # Remove the temporary added default application
3635 config=None, **kargs):
3636 """ Start a server instance. This method blocks until the server terminates.
3638 :param app: WSGI application or target string supported by
3639 :func:`load_app`. (default: :func:`default_app`)
3640 :param server: Server adapter to use. See :data:`server_names` keys
3641 for valid names or pass a :class:`ServerAdapter` subclass.
3642 (default: `wsgiref`)
3643 :param host: Server address to bind to. Pass ``0.0.0.0`` to listens on
3644 all interfaces including the external one. (default: 127.0.0.1)
3645 :param port: Server port to bind to. Values below 1024 require root
3646 privileges. (default: 8080)
3647 :param reloader: Start auto-reloading server? (default: False)
3648 :param interval: Auto-reloader interval in seconds (default: 1)
3649 :param quiet: Suppress output to stdout and stderr? (default: False)
3650 :param options: Options passed to the server adapter.
3653 if reloader and not os.environ.get('BOTTLE_CHILD'):
3657 fd, lockfile = tempfile.mkstemp(prefix='bottle.', suffix='.lock')
3658 os.close(fd) # We only need this file to exist. We never write to it
3659 while os.path.exists(lockfile):
3660 args = [sys.executable] + sys.argv
3661 environ = os.environ.copy()
3662 environ['BOTTLE_CHILD'] = 'true'
3663 environ['BOTTLE_LOCKFILE'] = lockfile
3664 p = subprocess.Popen(args, env=environ)
3665 while p.poll() is None: # Busy wait...
3666 os.utime(lockfile, None) # I am alive!
3667 time.sleep(interval)
3669 if os.path.exists(lockfile): os.unlink(lockfile)
3671 except KeyboardInterrupt:
3674 if os.path.exists(lockfile):
3679 if debug is not None: _debug(debug)
3680 app = app or default_app()
3681 if isinstance(app, basestring):
3683 if not callable(app):
3684 raise ValueError("Application is not callable: %r" % app)
3686 for plugin in plugins or []:
3687 if isinstance(plugin, basestring):
3688 plugin = load(plugin)
3692 app.config.update(config)
3694 if server in server_names:
3695 server = server_names.get(server)
3696 if isinstance(server, basestring):
3697 server = load(server)
3698 if isinstance(server, type):
3699 server = server(host=host, port=port, **kargs)
3700 if not isinstance(server, ServerAdapter):
3701 raise ValueError("Unknown or unsupported server: %r" % server)
3703 server.quiet = server.quiet or quiet
3704 if not server.quiet:
3705 _stderr("Bottle v%s server starting up (using %s)...\n" %
3706 (__version__, repr(server)))
3707 _stderr("Listening on http://%s:%d/\n" %
3708 (server.host, server.port))
3709 _stderr("Hit Ctrl-C to quit.\n\n")
3712 lockfile = os.environ.get('BOTTLE_LOCKFILE')
3713 bgcheck = FileCheckerThread(lockfile, interval)
3716 if bgcheck.status == 'reload':
3720 except KeyboardInterrupt:
3722 except (SystemExit, MemoryError):
3725 if not reloader: raise
3726 if not getattr(server, 'quiet', quiet):
3728 time.sleep(interval)
3732 class FileCheckerThread(threading.Thread):
3733 """ Interrupt main-thread as soon as a changed module file is detected,
3734 the lockfile gets deleted or gets too old. """
3736 def __init__(self, lockfile, interval):
3737 threading.Thread.__init__(self)
3739 self.lockfile, self.interval = lockfile, interval
3740 #: Is one of 'reload', 'error' or 'exit'
3744 exists = os.path.exists
3745 mtime = lambda p: os.stat(p).st_mtime
3748 for module in list(sys.modules.values()):
3749 path = getattr(module, '__file__', '')
3750 if path[-4:] in ('.pyo', '.pyc'): path = path[:-1]
3751 if path and exists(path): files[path] = mtime(path)
3753 while not self.status:
3754 if not exists(self.lockfile)\
3755 or mtime(self.lockfile) < time.time() - self.interval - 5:
3756 self.status = 'error'
3757 thread.interrupt_main()
3758 for path, lmtime in list(files.items()):
3759 if not exists(path) or mtime(path) > lmtime:
3760 self.status = 'reload'
3761 thread.interrupt_main()
3763 time.sleep(self.interval)
3765 def __enter__(self):
3768 def __exit__(self, exc_type, *_):
3769 if not self.status: self.status = 'exit' # silent exit
3771 return exc_type is not None and issubclass(exc_type, KeyboardInterrupt)
3773 ###############################################################################
3774 # Template Adapters ############################################################
3775 ###############################################################################
3778 class TemplateError(BottleException):
3782 class BaseTemplate(object):
3783 """ Base class and minimal API for template adapters """
3784 extensions = ['tpl', 'html', 'thtml', 'stpl']
3785 settings = {} #used in prepare()
3786 defaults = {} #used in render()
3792 encoding='utf8', **settings):
3793 """ Create a new template.
3794 If the source parameter (str or buffer) is missing, the name argument
3795 is used to guess a template filename. Subclasses can assume that
3796 self.source and/or self.filename are set. Both are strings.
3797 The lookup, encoding and settings parameters are stored as instance
3799 The lookup parameter stores a list containing directory paths.
3800 The encoding parameter should be used to decode byte strings or files.
3801 The settings parameter contains a dict for engine-specific settings.
3804 self.source = source.read() if hasattr(source, 'read') else source
3805 self.filename = source.filename if hasattr(source, 'filename') else None
3806 self.lookup = [os.path.abspath(x) for x in lookup] if lookup else []
3807 self.encoding = encoding
3808 self.settings = self.settings.copy() # Copy from class variable
3809 self.settings.update(settings) # Apply
3810 if not self.source and self.name:
3811 self.filename = self.search(self.name, self.lookup)
3812 if not self.filename:
3813 raise TemplateError('Template %s not found.' % repr(name))
3814 if not self.source and not self.filename:
3815 raise TemplateError('No template specified.')
3816 self.prepare(**self.settings)
3819 def search(cls, name, lookup=None):
3820 """ Search name in all directories specified in lookup.
3821 First without, then with common extensions. Return first hit. """
3823 raise depr(0, 12, "Empty template lookup path.", "Configure a template lookup path.")
3825 if os.path.isabs(name):
3826 raise depr(0, 12, "Use of absolute path for template name.",
3827 "Refer to templates with names or paths relative to the lookup path.")
3829 for spath in lookup:
3830 spath = os.path.abspath(spath) + os.sep
3831 fname = os.path.abspath(os.path.join(spath, name))
3832 if not fname.startswith(spath): continue
3833 if os.path.isfile(fname): return fname
3834 for ext in cls.extensions:
3835 if os.path.isfile('%s.%s' % (fname, ext)):
3836 return '%s.%s' % (fname, ext)
3839 def global_config(cls, key, *args):
3840 """ This reads or sets the global settings stored in class.settings. """
3842 cls.settings = cls.settings.copy() # Make settings local to class
3843 cls.settings[key] = args[0]
3845 return cls.settings[key]
3847 def prepare(self, **options):
3848 """ Run preparations (parsing, caching, ...).
3849 It should be possible to call this again to refresh a template or to
3852 raise NotImplementedError
3854 def render(self, *args, **kwargs):
3855 """ Render the template with the specified local variables and return
3856 a single byte or unicode string. If it is a byte string, the encoding
3857 must match self.encoding. This method must be thread-safe!
3858 Local variables may be provided in dictionaries (args)
3859 or directly, as keywords (kwargs).
3861 raise NotImplementedError
3864 class MakoTemplate(BaseTemplate):
3865 def prepare(self, **options):
3866 from mako.template import Template
3867 from mako.lookup import TemplateLookup
3868 options.update({'input_encoding': self.encoding})
3869 options.setdefault('format_exceptions', bool(DEBUG))
3870 lookup = TemplateLookup(directories=self.lookup, **options)
3872 self.tpl = Template(self.source, lookup=lookup, **options)
3874 self.tpl = Template(uri=self.name,
3875 filename=self.filename,
3876 lookup=lookup, **options)
3878 def render(self, *args, **kwargs):
3879 for dictarg in args:
3880 kwargs.update(dictarg)
3881 _defaults = self.defaults.copy()
3882 _defaults.update(kwargs)
3883 return self.tpl.render(**_defaults)
3886 class CheetahTemplate(BaseTemplate):
3887 def prepare(self, **options):
3888 from Cheetah.Template import Template
3889 self.context = threading.local()
3890 self.context.vars = {}
3891 options['searchList'] = [self.context.vars]
3893 self.tpl = Template(source=self.source, **options)
3895 self.tpl = Template(file=self.filename, **options)
3897 def render(self, *args, **kwargs):
3898 for dictarg in args:
3899 kwargs.update(dictarg)
3900 self.context.vars.update(self.defaults)
3901 self.context.vars.update(kwargs)
3903 self.context.vars.clear()
3907 class Jinja2Template(BaseTemplate):
3908 def prepare(self, filters=None, tests=None, globals={}, **kwargs):
3909 from jinja2 import Environment, FunctionLoader
3910 self.env = Environment(loader=FunctionLoader(self.loader), **kwargs)
3911 if filters: self.env.filters.update(filters)
3912 if tests: self.env.tests.update(tests)
3913 if globals: self.env.globals.update(globals)
3915 self.tpl = self.env.from_string(self.source)
3917 self.tpl = self.env.get_template(self.name)
3919 def render(self, *args, **kwargs):
3920 for dictarg in args:
3921 kwargs.update(dictarg)
3922 _defaults = self.defaults.copy()
3923 _defaults.update(kwargs)
3924 return self.tpl.render(**_defaults)
3926 def loader(self, name):
3927 if name == self.filename:
3930 fname = self.search(name, self.lookup)
3931 if not fname: return
3932 with open(fname, "rb") as f:
3933 return (f.read().decode(self.encoding), fname, lambda: False)
3936 class SimpleTemplate(BaseTemplate):
3938 escape_func=html_escape,
3943 self._str = lambda x: touni(x, enc)
3944 self._escape = lambda x: escape_func(touni(x, enc))
3945 self.syntax = syntax
3947 self._str, self._escape = self._escape, self._str
3951 return compile(self.code, self.filename or '<string>', 'exec')
3955 source = self.source
3957 with open(self.filename, 'rb') as f:
3960 source, encoding = touni(source), 'utf8'
3961 except UnicodeError:
3962 raise depr(0, 11, 'Unsupported template encodings.', 'Use utf-8 for templates.')
3963 parser = StplParser(source, encoding=encoding, syntax=self.syntax)
3964 code = parser.translate()
3965 self.encoding = parser.encoding
3968 def _rebase(self, _env, _name=None, **kwargs):
3969 _env['_rebase'] = (_name, kwargs)
3971 def _include(self, _env, _name=None, **kwargs):
3974 if _name not in self.cache:
3975 self.cache[_name] = self.__class__(name=_name, lookup=self.lookup, syntax=self.syntax)
3976 return self.cache[_name].execute(env['_stdout'], env)
3978 def execute(self, _stdout, kwargs):
3979 env = self.defaults.copy()
3983 '_printlist': _stdout.extend,
3984 'include': functools.partial(self._include, env),
3985 'rebase': functools.partial(self._rebase, env),
3988 '_escape': self._escape,
3990 'setdefault': env.setdefault,
3991 'defined': env.__contains__
3994 if env.get('_rebase'):
3995 subtpl, rargs = env.pop('_rebase')
3996 rargs['base'] = ''.join(_stdout) #copy stdout
3997 del _stdout[:] # clear stdout
3998 return self._include(env, subtpl, **rargs)
4001 def render(self, *args, **kwargs):
4002 """ Render the template using keyword arguments as local variables. """
4005 for dictarg in args:
4008 self.execute(stdout, env)
4009 return ''.join(stdout)
4012 class StplSyntaxError(TemplateError):
4017 class StplParser(object):
4018 """ Parser for stpl templates. """
4019 _re_cache = {} #: Cache for compiled re patterns
4021 # This huge pile of voodoo magic splits python code into 8 different tokens.
4022 # We use the verbose (?x) regex mode to make this more manageable
4024 _re_tok = _re_inl = r'''(
4032 |'{3}(?:[^\\]|\\.|\n)+?'{3}
4033 |"{3}(?:[^\\]|\\.|\n)+?"{3}
4037 _re_inl = _re_tok.replace(r'|\n', '') # We re-use this string pattern later
4040 # 2: Comments (until end of line, but not the newline itself)
4043 # 3: Open and close (4) grouping tokens
4047 # 5,6: Keywords that start or continue a python block (only start of line)
4048 |^([\ \t]*(?:if|for|while|with|try|def|class)\b)
4049 |^([\ \t]*(?:elif|else|except|finally)\b)
4051 # 7: Our special 'end' keyword (but only if it stands alone)
4052 |((?:^|;)[\ \t]*end[\ \t]*(?=(?:%(block_close)s[\ \t]*)?\r?$|;|\#))
4054 # 8: A customizable end-of-code-block template token (only end of line)
4055 |(%(block_close)s[\ \t]*(?=\r?$))
4057 # 9: And finally, a single newline. The 10th token is 'everything else'
4061 # Match the start tokens of code areas in a template
4062 _re_split = r'''(?m)^[ \t]*(\\?)((%(line_start)s)|(%(block_start)s))'''
4063 # Match inline statements (may contain python strings)
4064 _re_inl = r'''%%(inline_start)s((?:%s|[^'"\n]+?)*?)%%(inline_end)s''' % _re_inl
4066 # add the flag in front of the regexp to avoid Deprecation warning (see Issue #949)
4067 # verbose and dot-matches-newline mode
4068 _re_tok = '(?mx)' + _re_tok
4069 _re_inl = '(?mx)' + _re_inl
4072 default_syntax = '<% %> % {{ }}'
4074 def __init__(self, source, syntax=None, encoding='utf8'):
4075 self.source, self.encoding = touni(source, encoding), encoding
4076 self.set_syntax(syntax or self.default_syntax)
4077 self.code_buffer, self.text_buffer = [], []
4078 self.lineno, self.offset = 1, 0
4079 self.indent, self.indent_mod = 0, 0
4080 self.paren_depth = 0
4082 def get_syntax(self):
4083 """ Tokens as a space separated string (default: <% %> % {{ }}) """
4086 def set_syntax(self, syntax):
4087 self._syntax = syntax
4088 self._tokens = syntax.split()
4089 if syntax not in self._re_cache:
4090 names = 'block_start block_close line_start inline_start inline_end'
4091 etokens = map(re.escape, self._tokens)
4092 pattern_vars = dict(zip(names.split(), etokens))
4093 patterns = (self._re_split, self._re_tok, self._re_inl)
4094 patterns = [re.compile(p % pattern_vars) for p in patterns]
4095 self._re_cache[syntax] = patterns
4096 self.re_split, self.re_tok, self.re_inl = self._re_cache[syntax]
4098 syntax = property(get_syntax, set_syntax)
4100 def translate(self):
4101 if self.offset: raise RuntimeError('Parser is a one time instance.')
4103 m = self.re_split.search(self.source, pos=self.offset)
4105 text = self.source[self.offset:m.start()]
4106 self.text_buffer.append(text)
4107 self.offset = m.end()
4108 if m.group(1): # Escape syntax
4109 line, sep, _ = self.source[self.offset:].partition('\n')
4110 self.text_buffer.append(self.source[m.start():m.start(1)] +
4111 m.group(2) + line + sep)
4112 self.offset += len(line + sep)
4115 self.offset += self.read_code(self.source[self.offset:],
4116 multiline=bool(m.group(4)))
4119 self.text_buffer.append(self.source[self.offset:])
4121 return ''.join(self.code_buffer)
4123 def read_code(self, pysource, multiline):
4124 code_line, comment = '', ''
4127 m = self.re_tok.search(pysource, pos=offset)
4129 code_line += pysource[offset:]
4130 offset = len(pysource)
4131 self.write_code(code_line.strip(), comment)
4133 code_line += pysource[offset:m.start()]
4135 _str, _com, _po, _pc, _blk1, _blk2, _end, _cend, _nl = m.groups()
4136 if self.paren_depth > 0 and (_blk1 or _blk2): # a if b else c
4137 code_line += _blk1 or _blk2
4139 if _str: # Python string
4141 elif _com: # Python comment (up to EOL)
4143 if multiline and _com.strip().endswith(self._tokens[1]):
4144 multiline = False # Allow end-of-block in comments
4145 elif _po: # open parenthesis
4146 self.paren_depth += 1
4148 elif _pc: # close parenthesis
4149 if self.paren_depth > 0:
4150 # we could check for matching parentheses here, but it's
4151 # easier to leave that to python - just check counts
4152 self.paren_depth -= 1
4154 elif _blk1: # Start-block keyword (if/for/while/def/try/...)
4155 code_line, self.indent_mod = _blk1, -1
4157 elif _blk2: # Continue-block keyword (else/elif/except/...)
4158 code_line, self.indent_mod = _blk2, -1
4159 elif _end: # The non-standard 'end'-keyword (ends a block)
4161 elif _cend: # The end-code-block template token (usually '%>')
4162 if multiline: multiline = False
4163 else: code_line += _cend
4165 self.write_code(code_line.strip(), comment)
4167 code_line, comment, self.indent_mod = '', '', 0
4173 def flush_text(self):
4174 text = ''.join(self.text_buffer)
4175 del self.text_buffer[:]
4177 parts, pos, nl = [], 0, '\\\n' + ' ' * self.indent
4178 for m in self.re_inl.finditer(text):
4179 prefix, pos = text[pos:m.start()], m.end()
4181 parts.append(nl.join(map(repr, prefix.splitlines(True))))
4182 if prefix.endswith('\n'): parts[-1] += nl
4183 parts.append(self.process_inline(m.group(1).strip()))
4186 lines = prefix.splitlines(True)
4187 if lines[-1].endswith('\\\\\n'): lines[-1] = lines[-1][:-3]
4188 elif lines[-1].endswith('\\\\\r\n'): lines[-1] = lines[-1][:-4]
4189 parts.append(nl.join(map(repr, lines)))
4190 code = '_printlist((%s,))' % ', '.join(parts)
4191 self.lineno += code.count('\n') + 1
4192 self.write_code(code)
4195 def process_inline(chunk):
4196 if chunk[0] == '!': return '_str(%s)' % chunk[1:]
4197 return '_escape(%s)' % chunk
4199 def write_code(self, line, comment=''):
4200 code = ' ' * (self.indent + self.indent_mod)
4201 code += line.lstrip() + comment + '\n'
4202 self.code_buffer.append(code)
4205 def template(*args, **kwargs):
4207 Get a rendered template as a string iterator.
4208 You can use a name, a filename or a template string as first parameter.
4209 Template rendering arguments can be passed as dictionaries
4210 or directly (as keyword arguments).
4212 tpl = args[0] if args else None
4213 for dictarg in args[1:]:
4214 kwargs.update(dictarg)
4215 adapter = kwargs.pop('template_adapter', SimpleTemplate)
4216 lookup = kwargs.pop('template_lookup', TEMPLATE_PATH)
4217 tplid = (id(lookup), tpl)
4218 if tplid not in TEMPLATES or DEBUG:
4219 settings = kwargs.pop('template_settings', {})
4220 if isinstance(tpl, adapter):
4221 TEMPLATES[tplid] = tpl
4222 if settings: TEMPLATES[tplid].prepare(**settings)
4223 elif "\n" in tpl or "{" in tpl or "%" in tpl or '$' in tpl:
4224 TEMPLATES[tplid] = adapter(source=tpl, lookup=lookup, **settings)
4226 TEMPLATES[tplid] = adapter(name=tpl, lookup=lookup, **settings)
4227 if not TEMPLATES[tplid]:
4228 abort(500, 'Template (%s) not found' % tpl)
4229 return TEMPLATES[tplid].render(kwargs)
4232 mako_template = functools.partial(template, template_adapter=MakoTemplate)
4233 cheetah_template = functools.partial(template,
4234 template_adapter=CheetahTemplate)
4235 jinja2_template = functools.partial(template, template_adapter=Jinja2Template)
4238 def view(tpl_name, **defaults):
4239 """ Decorator: renders a template for a handler.
4240 The handler can control its behavior like that:
4242 - return a dict of template vars to fill out the template
4243 - return something other than a dict and the view decorator will not
4244 process the template, but return the handler result as is.
4245 This includes returning a HTTPResponse(dict) to get,
4246 for instance, JSON with autojson or other castfilters.
4249 def decorator(func):
4251 @functools.wraps(func)
4252 def wrapper(*args, **kwargs):
4253 result = func(*args, **kwargs)
4254 if isinstance(result, (dict, DictMixin)):
4255 tplvars = defaults.copy()
4256 tplvars.update(result)
4257 return template(tpl_name, **tplvars)
4258 elif result is None:
4259 return template(tpl_name, defaults)
4267 mako_view = functools.partial(view, template_adapter=MakoTemplate)
4268 cheetah_view = functools.partial(view, template_adapter=CheetahTemplate)
4269 jinja2_view = functools.partial(view, template_adapter=Jinja2Template)
4271 ###############################################################################
4272 # Constants and Globals ########################################################
4273 ###############################################################################
4275 TEMPLATE_PATH = ['./', './views/']
4278 NORUN = False # If set, run() does nothing. Used by load_app()
4280 #: A dict to map HTTP status codes (e.g. 404) to phrases (e.g. 'Not Found')
4281 HTTP_CODES = httplib.responses.copy()
4282 HTTP_CODES[418] = "I'm a teapot" # RFC 2324
4283 HTTP_CODES[428] = "Precondition Required"
4284 HTTP_CODES[429] = "Too Many Requests"
4285 HTTP_CODES[431] = "Request Header Fields Too Large"
4286 HTTP_CODES[451] = "Unavailable For Legal Reasons" # RFC 7725
4287 HTTP_CODES[511] = "Network Authentication Required"
4288 _HTTP_STATUS_LINES = dict((k, '%d %s' % (k, v))
4289 for (k, v) in HTTP_CODES.items())
4291 #: The default template used for error pages. Override with @error()
4292 ERROR_PAGE_TEMPLATE = """
4294 %%from %s import DEBUG, request
4295 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
4298 <title>Error: {{e.status}}</title>
4299 <style type="text/css">
4300 html {background-color: #eee; font-family: sans-serif;}
4301 body {background-color: #fff; border: 1px solid #ddd;
4302 padding: 15px; margin: 15px;}
4303 pre {background-color: #eee; border: 1px solid #ddd; padding: 5px;}
4307 <h1>Error: {{e.status}}</h1>
4308 <p>Sorry, the requested URL <tt>{{repr(request.url)}}</tt>
4309 caused an error:</p>
4310 <pre>{{e.body}}</pre>
4311 %%if DEBUG and e.exception:
4314 %%exc = repr(e.exception)
4316 %%exc = '<unprintable %%s object>' %% type(e.exception).__name__
4320 %%if DEBUG and e.traceback:
4322 <pre>{{e.traceback}}</pre>
4326 %%except ImportError:
4327 <b>ImportError:</b> Could not generate the error page. Please add bottle to
4332 #: A thread-safe instance of :class:`LocalRequest`. If accessed from within a
4333 #: request callback, this instance always refers to the *current* request
4334 #: (even on a multi-threaded server).
4335 request = LocalRequest()
4337 #: A thread-safe instance of :class:`LocalResponse`. It is used to change the
4338 #: HTTP response for the *current* request.
4339 response = LocalResponse()
4341 #: A thread-safe namespace. Not used by Bottle.
4342 local = threading.local()
4344 # Initialize app stack (create first empty Bottle app now deferred until needed)
4345 # BC: 0.6.4 and needed for run()
4346 apps = app = default_app = AppStack()
4348 #: A virtual package that redirects import statements.
4349 #: Example: ``import bottle.ext.sqlite`` actually imports `bottle_sqlite`.
4350 ext = _ImportRedirect('bottle.ext' if __name__ == '__main__' else
4351 __name__ + ".ext", 'bottle_%s').module
4354 def _main(argv): # pragma: no coverage
4355 args, parser = _cli_parse(argv)
4357 def _cli_error(cli_msg):
4359 _stderr('\nError: %s\n' % cli_msg)
4363 _stdout('Bottle %s\n' % __version__)
4366 _cli_error("No application entry point specified.")
4368 sys.path.insert(0, '.')
4369 sys.modules.setdefault('bottle', sys.modules['__main__'])
4371 host, port = (args.bind or 'localhost'), 8080
4372 if ':' in host and host.rfind(']') < host.rfind(':'):
4373 host, port = host.rsplit(':', 1)
4374 host = host.strip('[]')
4376 config = ConfigDict()
4378 for cfile in args.conf or []:
4380 if cfile.endswith('.json'):
4381 with open(cfile, 'rb') as fp:
4382 config.load_dict(json_loads(fp.read()))
4384 config.load_config(cfile)
4385 except configparser.Error as parse_error:
4386 _cli_error(parse_error)
4388 _cli_error("Unable to read config file %r" % cfile)
4389 except (UnicodeError, TypeError, ValueError) as error:
4390 _cli_error("Unable to parse config file %r: %s" % (cfile, error))
4392 for cval in args.param or []:
4394 config.update((cval.split('=', 1),))
4402 reloader=args.reload,
4403 plugins=args.plugin,
4408 if __name__ == '__main__': # pragma: no coverage