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