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