comparison bottle.py @ 0:e1264e58154c

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