diff options
Diffstat (limited to 'module/web/bottle.py')
-rw-r--r-- | module/web/bottle.py | 883 |
1 files changed, 883 insertions, 0 deletions
diff --git a/module/web/bottle.py b/module/web/bottle.py new file mode 100644 index 000000000..8335f112b --- /dev/null +++ b/module/web/bottle.py @@ -0,0 +1,883 @@ +# -*- coding: utf-8 -*- +""" +Bottle is a fast and simple mirco-framework for small web-applications. It +offers request dispatching (Routes) with url parameter support, Templates, +key/value Databases, a build-in HTTP Server and adapters for many third party +WSGI/HTTP-server and template engines. All in a single file and with no +dependencies other than the Python Standard Library. + +Homepage and documentation: http://wiki.github.com/defnull/bottle + +Special thanks to Stefan Matthias Aust [http://github.com/sma] + for his contribution to SimpelTemplate + +Licence (MIT) +------------- + + Copyright (c) 2009, Marcel Hellkamp. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + +Example +------- + + from bottle import route, run, request, response, send_file, abort + + @route('/') + def hello_world(): + return 'Hello World!' + + @route('/hello/:name') + def hello_name(name): + return 'Hello %s!' % name + + @route('/hello', method='POST') + def hello_post(): + name = request.POST['name'] + return 'Hello %s!' % name + + @route('/static/:filename#.*#') + def static_file(filename): + send_file(filename, root='/path/to/static/files/') + + run(host='localhost', port=8080) + +""" + +__author__ = 'Marcel Hellkamp' +__version__ = '0.5.7' +__license__ = 'MIT' + +import sys +import cgi +import mimetypes +import os +import os.path +import traceback +import re +import random +import threading +import time +from wsgiref.headers import Headers as HeaderWrapper +from Cookie import SimpleCookie +import anydbm as dbm + +try: + from urlparse import parse_qs +except ImportError: + from cgi import parse_qs + +try: + import cPickle as pickle +except ImportError: + import pickle as pickle + +try: + try: + from json import dumps as json_dumps + except ImportError: + from simplejson import dumps as json_dumps +except ImportError: + json_dumps = None + + + + + + +# Exceptions and Events + +class BottleException(Exception): + """ A base class for exceptions used by bottle.""" + pass + + +class HTTPError(BottleException): + """ A way to break the execution and instantly jump to an error handler. """ + def __init__(self, status, text): + self.output = text + self.http_status = int(status) + + def __repr__(self): + return "HTTPError(%d,%s)" % (self.http_status, repr(self.output)) + + def __str__(self): + out = [] + status = self.http_status + name = HTTP_CODES.get(status,'Unknown').title() + url = request.path + out.append('<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">') + out.append('<html><head><title>Error %d: %s</title>' % (status, name)) + out.append('</head><body><h1>Error %d: %s</h1>' % (status, name)) + out.append('<p>Sorry, the requested URL "%s" caused an error.</p>' % url) + out.append(''.join(list(self.output))) + out.append('</body></html>') + return "\n".join(out) + + +class BreakTheBottle(BottleException): + """ Not an exception, but a straight jump out of the controller code. + + Causes the Bottle to instantly call start_response() and return the + content of output """ + def __init__(self, output): + self.output = output + + + + + + +# WSGI abstraction: Request and response management + +_default_app = None +def default_app(newapp = None): + ''' Returns the current default app or sets a new one. + Defaults to an instance of Bottle ''' + global _default_app + if newapp: + _default_app = newapp + if not _default_app: + _default_app = Bottle() + return _default_app + + +class Bottle(object): + + def __init__(self, catchall=True, debug=False, optimize=False, autojson=True): + self.simple_routes = {} + self.regexp_routes = {} + self.error_handler = {} + self.optimize = optimize + self.debug = debug + self.autojson = autojson + self.catchall = catchall + + def match_url(self, url, method='GET'): + """Returns the first matching handler and a parameter dict or (None, None) """ + url = url.strip().lstrip("/ ") + # Search for static routes first + route = self.simple_routes.get(method,{}).get(url,None) + if route: + return (route, {}) + + routes = self.regexp_routes.get(method,[]) + for i in range(len(routes)): + match = routes[i][0].match(url) + if match: + handler = routes[i][1] + if i > 0 and self.optimize and random.random() <= 0.001: + routes[i-1], routes[i] = routes[i], routes[i-1] + return (handler, match.groupdict()) + return (None, None) + + def add_route(self, route, handler, method='GET', simple=False): + """ Adds a new route to the route mappings. """ + method = method.strip().upper() + route = route.strip().lstrip('$^/ ').rstrip('$^ ') + if re.match(r'^(\w+/)*\w*$', route) or simple: + self.simple_routes.setdefault(method, {})[route] = handler + else: + route = re.sub(r':([a-zA-Z_]+)(?P<uniq>[^\w/])(?P<re>.+?)(?P=uniq)',r'(?P<\1>\g<re>)',route) + route = re.sub(r':([a-zA-Z_]+)',r'(?P<\1>[^/]+)', route) + route = re.compile('^%s$' % route) + self.regexp_routes.setdefault(method, []).append([route, handler]) + + def route(self, url, **kargs): + """ Decorator for request handler. Same as add_route(url, handler, **kargs).""" + def wrapper(handler): + self.add_route(url, handler, **kargs) + return handler + return wrapper + + def set_error_handler(self, code, handler): + """ Adds a new error handler. """ + code = int(code) + self.error_handler[code] = handler + + def error(self, code=500): + """ Decorator for error handler. Same as set_error_handler(code, handler).""" + def wrapper(handler): + self.set_error_handler(code, handler) + return handler + return wrapper + + def __call__(self, environ, start_response): + """ The bottle WSGI-interface .""" + request.bind(environ) + response.bind() + try: # Unhandled Exceptions + try: # Bottle Error Handling + handler, args = self.match_url(request.path, request.method) + if not handler: raise HTTPError(404, "Not found") + output = handler(**args) + db.close() + except BreakTheBottle, e: + output = e.output + except HTTPError, e: + response.status = e.http_status + output = self.error_handler.get(response.status, str)(e) + # output casting + if hasattr(output, 'read'): + output = environ.get('wsgi.file_wrapper', lambda x: iter(lambda: x.read(8192), ''))(output) + elif self.autojson and json_dumps and isinstance(output, dict): + output = json_dumps(output) + response.content_type = 'application/json' + if isinstance(output, str): + response.header['Content-Length'] = str(len(output)) + output = [output] + except (KeyboardInterrupt, SystemExit, MemoryError): + raise + except Exception, e: + response.status = 500 + if self.catchall: + err = "Unhandled Exception: %s\n" % (repr(e)) + if self.debug: + err += "<h2>Traceback:</h2>\n<pre>\n" + err += traceback.format_exc(10) + err += "\n</pre>" + output = str(HTTPError(500, err)) + request._environ['wsgi.errors'].write(err) + else: + raise + status = '%d %s' % (response.status, HTTP_CODES[response.status]) + start_response(status, response.wsgiheaders()) + return output + + + +class Request(threading.local): + """ Represents a single request using thread-local namespace. """ + + def bind(self, environ): + """ Binds the enviroment of the current request to this request handler """ + self._environ = environ + self._GET = None + self._POST = None + self._GETPOST = None + self._COOKIES = None + self.path = self._environ.get('PATH_INFO', '/').strip() + if not self.path.startswith('/'): + self.path = '/' + self.path + + @property + def method(self): + ''' Returns the request method (GET,POST,PUT,DELETE,...) ''' + return self._environ.get('REQUEST_METHOD', 'GET').upper() + + @property + def query_string(self): + ''' Content of QUERY_STRING ''' + return self._environ.get('QUERY_STRING', '') + + @property + def input_length(self): + ''' Content of CONTENT_LENGTH ''' + try: + return int(self._environ.get('CONTENT_LENGTH', '0')) + except ValueError: + return 0 + + @property + def GET(self): + """Returns a dict with GET parameters.""" + if self._GET is None: + data = parse_qs(self.query_string, keep_blank_values=True) + self._GET = {} + for key, value in data.iteritems(): + if len(value) == 1: + self._GET[key] = value[0] + else: + self._GET[key] = value + return self._GET + + @property + def POST(self): + """Returns a dict with parsed POST or PUT data.""" + if self._POST is None: + data = cgi.FieldStorage(fp=self._environ['wsgi.input'], environ=self._environ, keep_blank_values=True) + self._POST = {} + for item in data.list: + name = item.name + if not item.filename: + item = item.value + self._POST.setdefault(name, []).append(item) + for key in self._POST: + if len(self._POST[key]) == 1: + self._POST[key] = self._POST[key][0] + return self._POST + + @property + def params(self): + ''' Returns a mix of GET and POST data. POST overwrites GET ''' + if self._GETPOST is None: + self._GETPOST = dict(self.GET) + self._GETPOST.update(dict(self.POST)) + return self._GETPOST + + @property + def COOKIES(self): + """Returns a dict with COOKIES.""" + if self._COOKIES is None: + raw_dict = SimpleCookie(self._environ.get('HTTP_COOKIE','')) + self._COOKIES = {} + for cookie in raw_dict.itervalues(): + self._COOKIES[cookie.key] = cookie.value + return self._COOKIES + + +class Response(threading.local): + """ Represents a single response using thread-local namespace. """ + + def bind(self): + """ Clears old data and creates a brand new Response object """ + self._COOKIES = None + self.status = 200 + self.header_list = [] + self.header = HeaderWrapper(self.header_list) + self.content_type = 'text/html' + self.error = None + + def wsgiheaders(self): + ''' Returns a wsgi conform list of header/value pairs ''' + for c in self.COOKIES.itervalues(): + self.header.add_header('Set-Cookie', c.OutputString()) + return [(h.title(), str(v)) for h, v in self.header.items()] + + @property + def COOKIES(self): + if not self._COOKIES: + self._COOKIES = SimpleCookie() + return self._COOKIES + + def set_cookie(self, key, value, **kargs): + """ Sets a Cookie. Optional settings: expires, path, comment, domain, max-age, secure, version, httponly """ + self.COOKIES[key] = value + for k in kargs: + self.COOKIES[key][k] = kargs[k] + + def get_content_type(self): + '''Gives access to the 'Content-Type' header and defaults to 'text/html'.''' + return self.header['Content-Type'] + + def set_content_type(self, value): + self.header['Content-Type'] = value + + content_type = property(get_content_type, set_content_type, None, get_content_type.__doc__) + + +def abort(code=500, text='Unknown Error: Appliction stopped.'): + """ Aborts execution and causes a HTTP error. """ + raise HTTPError(code, text) + + +def redirect(url, code=307): + """ Aborts execution and causes a 307 redirect """ + response.status = code + response.header['Location'] = url + raise BreakTheBottle("") + + +def send_file(filename, root, guessmime = True, mimetype = 'text/plain'): + """ Aborts execution and sends a static files as response. """ + root = os.path.abspath(root) + '/' + filename = os.path.normpath(filename).strip('/') + filename = os.path.join(root, filename) + + if not filename.startswith(root): + abort(401, "Access denied.") + if not os.path.exists(filename) or not os.path.isfile(filename): + abort(404, "File does not exist.") + if not os.access(filename, os.R_OK): + abort(401, "You do not have permission to access this file.") + + if guessmime: + guess = mimetypes.guess_type(filename)[0] + if guess: + response.content_type = guess + elif mimetype: + response.content_type = mimetype + elif mimetype: + response.content_type = mimetype + + stats = os.stat(filename) + # TODO: HTTP_IF_MODIFIED_SINCE -> 304 (Thu, 02 Jul 2009 23:16:31 CEST) + if 'Content-Length' not in response.header: + response.header['Content-Length'] = stats.st_size + if 'Last-Modified' not in response.header: + ts = time.gmtime(stats.st_mtime) + ts = time.strftime("%a, %d %b %Y %H:%M:%S +0000", ts) + response.header['Last-Modified'] = ts + + raise BreakTheBottle(open(filename, 'r')) + + + + + + +# Decorators + +def validate(**vkargs): + ''' Validates and manipulates keyword arguments by user defined callables + and handles ValueError and missing arguments by raising HTTPError(403) ''' + def decorator(func): + def wrapper(**kargs): + for key in vkargs: + if key not in kargs: + abort(403, 'Missing parameter: %s' % key) + try: + kargs[key] = vkargs[key](kargs[key]) + except ValueError, e: + abort(403, 'Wrong parameter format for: %s' % key) + return func(**kargs) + return wrapper + return decorator + + +def route(url, **kargs): + """ Decorator for request handler. Same as add_route(url, handler, **kargs).""" + return default_app().route(url, **kargs) + + +def error(code=500): + """ Decorator for error handler. Same as set_error_handler(code, handler).""" + return default_app().error(code) + + + + + + +# Server adapter + +class ServerAdapter(object): + def __init__(self, host='127.0.0.1', port=8080, **kargs): + self.host = host + self.port = int(port) + self.options = kargs + + def __repr__(self): + return "%s (%s:%d)" % (self.__class__.__name__, self.host, self.port) + + def run(self, handler): + pass + + +class WSGIRefServer(ServerAdapter): + def run(self, handler): + from wsgiref.simple_server import make_server + srv = make_server(self.host, self.port, handler) + srv.serve_forever() + + +class CherryPyServer(ServerAdapter): + def run(self, handler): + from cherrypy import wsgiserver + server = wsgiserver.CherryPyWSGIServer((self.host, self.port), handler) + server.start() + + +class FlupServer(ServerAdapter): + def run(self, handler): + from flup.server.fcgi import WSGIServer + WSGIServer(handler, bindAddress=(self.host, self.port)).run() + + +class PasteServer(ServerAdapter): + def run(self, handler): + from paste import httpserver + from paste.translogger import TransLogger + app = TransLogger(handler) + httpserver.serve(app, host=self.host, port=str(self.port)) + + +class FapwsServer(ServerAdapter): + """ Extreamly fast Webserver using libev (see http://william-os4y.livejournal.com/) + Experimental ... """ + def run(self, handler): + import fapws._evwsgi as evwsgi + from fapws import base + import sys + evwsgi.start(self.host, self.port) + evwsgi.set_base_module(base) + def app(environ, start_response): + environ['wsgi.multiprocess'] = False + return handler(environ, start_response) + evwsgi.wsgi_cb(('',app)) + evwsgi.run() + + +def run(app=None, server=WSGIRefServer, host='127.0.0.1', port=8080, **kargs): + """ Runs bottle as a web server, using Python's built-in wsgiref implementation by default. + + You may choose between WSGIRefServer, CherryPyServer, FlupServer and + PasteServer or write your own server adapter. + """ + if not app: + app = default_app() + + quiet = bool('quiet' in kargs and kargs['quiet']) + + # Instanciate server, if it is a class instead of an instance + if isinstance(server, type) and issubclass(server, ServerAdapter): + server = server(host=host, port=port, **kargs) + + if not isinstance(server, ServerAdapter): + raise RuntimeError("Server must be a subclass of ServerAdapter") + + if not quiet: + print 'Bottle server starting up (using %s)...' % repr(server) + print 'Listening on http://%s:%d/' % (server.host, server.port) + print 'Use Ctrl-C to quit.' + print + + try: + server.run(app) + except KeyboardInterrupt: + print "Shuting down..." + + + + + + +# Templates + +class BaseTemplate(object): + def __init__(self, template='', filename=None): + self.source = filename + if self.source: + fp = open(filename) + template = fp.read() + fp.close() + self.parse(template) + + def parse(self, template): raise NotImplementedError + def render(self, **args): raise NotImplementedError + + @classmethod + def find(cls, name): + for path in TEMPLATE_PATH: + if os.path.isfile(path % name): + return cls(filename = path % name) + return None + + +class MakoTemplate(BaseTemplate): + def parse(self, template): + from mako.template import Template + self.tpl = Template(template) + + def render(self, **args): + return self.tpl.render(**args) + + +class CheetahTemplate(BaseTemplate): + def parse(self, template): + from Cheetah.Template import Template + self.context = threading.local() + self.context.vars = {} + self.tpl = Template(source = template, searchList=[self.context.vars]) + + def render(self, **args): + self.context.vars.update(args) + out = str(self.tpl) + self.context.vars.clear() + return out + + +class SimpleTemplate(BaseTemplate): + re_python = re.compile(r'^\s*%\s*(?:(if|elif|else|try|except|finally|for|while|with|def|class)|(include)|(end)|(.*))') + re_inline = re.compile(r'\{\{(.*?)\}\}') + dedent_keywords = ('elif', 'else', 'except', 'finally') + + def translate(self, template): + indent = 0 + strbuffer = [] + code = [] + self.subtemplates = {} + class PyStmt(str): + def __repr__(self): return 'str(' + self + ')' + def flush(allow_nobreak=False): + if len(strbuffer): + if allow_nobreak and strbuffer[-1].endswith("\\\\\n"): + strbuffer[-1]=strbuffer[-1][:-3] + code.append(" " * indent + "stdout.append(%s)" % repr(''.join(strbuffer))) + code.append((" " * indent + "\n") * len(strbuffer)) # to preserve line numbers + del strbuffer[:] + for line in template.splitlines(True): + m = self.re_python.match(line) + if m: + flush(allow_nobreak=True) + keyword, include, end, statement = m.groups() + if keyword: + if keyword in self.dedent_keywords: + indent -= 1 + code.append(" " * indent + line[m.start(1):]) + indent += 1 + elif include: + tmp = line[m.end(2):].strip().split(None, 1) + name = tmp[0] + args = tmp[1:] and tmp[1] or '' + self.subtemplates[name] = SimpleTemplate.find(name) + code.append(" " * indent + "stdout.append(_subtemplates[%s].render(%s))\n" % (repr(name), args)) + elif end: + indent -= 1 + code.append(" " * indent + '#' + line[m.start(3):]) + elif statement: + code.append(" " * indent + line[m.start(4):]) + else: + splits = self.re_inline.split(line) # text, (expr, text)* + if len(splits) == 1: + strbuffer.append(line) + else: + flush() + for i in range(1, len(splits), 2): + splits[i] = PyStmt(splits[i]) + splits = [x for x in splits if bool(x)] + code.append(" " * indent + "stdout.extend(%s)\n" % repr(splits)) + flush() + return ''.join(code) + + def parse(self, template): + code = self.translate(template) + self.co = compile(code, self.source or '<template>', 'exec') + + def render(self, **args): + ''' Returns the rendered template using keyword arguments as local variables. ''' + args['stdout'] = [] + args['_subtemplates'] = self.subtemplates + eval(self.co, args) + return ''.join(args['stdout']) + + +def template(template, template_adapter=SimpleTemplate, **args): + ''' Returns a string from a template ''' + if template not in TEMPLATES: + if template.find("\n") == template.find("{") == template.find("%") == -1: + TEMPLATES[template] = template_adapter.find(template) + else: + TEMPLATES[template] = template_adapter(template) + if not TEMPLATES[template]: + abort(500, 'Template not found') + args['abort'] = abort + args['request'] = request + args['response'] = response + return TEMPLATES[template].render(**args) + + +def mako_template(template_name, **args): return template(template_name, template_adapter=MakoTemplate, **args) + +def cheetah_template(template_name, **args): return template(template_name, template_adapter=CheetahTemplate, **args) + + + + + + +# Database + +class BottleBucket(object): + '''Memory-caching wrapper around anydbm''' + def __init__(self, name): + self.__dict__['name'] = name + self.__dict__['db'] = dbm.open(DB_PATH + '/%s.db' % name, 'c') + self.__dict__['mmap'] = {} + + def __getitem__(self, key): + if key not in self.mmap: + self.mmap[key] = pickle.loads(self.db[key]) + return self.mmap[key] + + def __setitem__(self, key, value): + self.mmap[key] = value + + def __delitem__(self, key): + if key in self.mmap: + del self.mmap[key] + del self.db[key] + + def __getattr__(self, key): + try: return self[key] + except KeyError: raise AttributeError(key) + + def __setattr__(self, key, value): + self[key] = value + + def __delattr__(self, key): + try: del self[key] + except KeyError: raise AttributeError(key) + + def __iter__(self): + return iter(self.ukeys()) + + def __contains__(self, key): + return key in self.ukeys() + + def __len__(self): + return len(self.ukeys()) + + def keys(self): + return list(self.ukeys()) + + def ukeys(self): + return set(self.db.keys()) | set(self.mmap.keys()) + + def save(self): + self.close() + self.__init__(self.name) + + def close(self): + for key in self.mmap: + pvalue = pickle.dumps(self.mmap[key], pickle.HIGHEST_PROTOCOL) + if key not in self.db or pvalue != self.db[key]: + self.db[key] = pvalue + self.mmap.clear() + self.db.close() + + def clear(self): + for key in self.db: + del self.db[key] + self.mmap.clear() + + def update(self, other): + self.mmap.update(other) + + def get(self, key, default=None): + try: + return self[key] + except KeyError: + if default: + return default + raise + + +class BottleDB(threading.local): + '''Holds multible BottleBucket instances in a thread-local way.''' + def __init__(self): + self.__dict__['open'] = {} + + def __getitem__(self, key): + if key not in self.open and not key.startswith('_'): + self.open[key] = BottleBucket(key) + return self.open[key] + + def __setitem__(self, key, value): + if isinstance(value, BottleBucket): + self.open[key] = value + elif hasattr(value, 'items'): + if key not in self.open: + self.open[key] = BottleBucket(key) + self.open[key].clear() + for k, v in value.iteritems(): + self.open[key][k] = v + else: + raise ValueError("Only dicts and BottleBuckets are allowed.") + + def __delitem__(self, key): + if key not in self.open: + self.open[key].clear() + self.open[key].save() + del self.open[key] + + def __getattr__(self, key): + try: return self[key] + except KeyError: raise AttributeError(key) + + def __setattr__(self, key, value): + self[key] = value + + def __delattr__(self, key): + try: del self[key] + except KeyError: raise AttributeError(key) + + def save(self): + self.close() + self.__init__() + + def close(self): + for db in self.open: + self.open[db].close() + self.open.clear() + + + + + + +# Modul initialization and configuration + +DB_PATH = './' +TEMPLATE_PATH = ['./%s.tpl', './views/%s.tpl'] +TEMPLATES = {} +HTTP_CODES = { + 100: 'CONTINUE', + 101: 'SWITCHING PROTOCOLS', + 200: 'OK', + 201: 'CREATED', + 202: 'ACCEPTED', + 203: 'NON-AUTHORITATIVE INFORMATION', + 204: 'NO CONTENT', + 205: 'RESET CONTENT', + 206: 'PARTIAL CONTENT', + 300: 'MULTIPLE CHOICES', + 301: 'MOVED PERMANENTLY', + 302: 'FOUND', + 303: 'SEE OTHER', + 304: 'NOT MODIFIED', + 305: 'USE PROXY', + 306: 'RESERVED', + 307: 'TEMPORARY REDIRECT', + 400: 'BAD REQUEST', + 401: 'UNAUTHORIZED', + 402: 'PAYMENT REQUIRED', + 403: 'FORBIDDEN', + 404: 'NOT FOUND', + 405: 'METHOD NOT ALLOWED', + 406: 'NOT ACCEPTABLE', + 407: 'PROXY AUTHENTICATION REQUIRED', + 408: 'REQUEST TIMEOUT', + 409: 'CONFLICT', + 410: 'GONE', + 411: 'LENGTH REQUIRED', + 412: 'PRECONDITION FAILED', + 413: 'REQUEST ENTITY TOO LARGE', + 414: 'REQUEST-URI TOO LONG', + 415: 'UNSUPPORTED MEDIA TYPE', + 416: 'REQUESTED RANGE NOT SATISFIABLE', + 417: 'EXPECTATION FAILED', + 500: 'INTERNAL SERVER ERROR', + 501: 'NOT IMPLEMENTED', + 502: 'BAD GATEWAY', + 503: 'SERVICE UNAVAILABLE', + 504: 'GATEWAY TIMEOUT', + 505: 'HTTP VERSION NOT SUPPORTED', +} + +request = Request() +response = Response() +db = BottleDB() +local = threading.local() + +def debug(mode=True): default_app().debug = bool(mode) +def optimize(mode=True): default_app().optimize = bool(mode) + + |