diff options
Diffstat (limited to 'module/web/bottle.py')
-rw-r--r-- | module/web/bottle.py | 1231 |
1 files changed, 0 insertions, 1231 deletions
diff --git a/module/web/bottle.py b/module/web/bottle.py deleted file mode 100644 index 41a8c8fc0..000000000 --- a/module/web/bottle.py +++ /dev/null @@ -1,1231 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Bottle is a fast and simple micro-framework for small web applications. It -offers request dispatching (Routes) with url parameter support, templates, -key/value databases, a built-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 SimpleTemplate - -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.6.4' -__license__ = 'MIT' - -import types -import sys -import cgi -import mimetypes -import os -import os.path -import traceback -import re -import random -import threading -import time -import warnings -import email.utils -from wsgiref.headers import Headers as HeaderWrapper -from Cookie import SimpleCookie -import anydbm as dbm -import subprocess -import thread - - -try: - from urlparse import parse_qs -except ImportError: # pragma: no cover - from cgi import parse_qs - -try: - import cPickle as pickle -except ImportError: # pragma: no cover - import pickle as pickle - -try: - try: - from json import dumps as json_dumps - except ImportError: # pragma: no cover - from simplejson import dumps as json_dumps -except ImportError: # pragma: no cover - 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) - BottleException.__init__(self, status, text) - - def __repr__(self): - return 'HTTPError(%d,%s)' % (self.http_status, repr(self.output)) - - def __str__(self): - return HTTP_ERROR_TEMPLATE % { - 'status' : self.http_status, - 'url' : request.path, - 'error_name' : HTTP_CODES.get(self.http_status, 'Unknown').title(), - 'error_message' : ''.join(self.output) - } - - -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, optimize=False, autojson=True): - self.simple_routes = {} - self.regexp_routes = {} - self.default_route = None - self.error_handler = {} - self.optimize = optimize - self.autojson = autojson - self.catchall = catchall - self.serve = True - - 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()) - if self.default_route: - return (self.default_route, {}) - if method == 'HEAD': # Fall back to GET - return self.match_url(url) - else: - return (None, None) - - def add_controller(self, route, controller, **kargs): - """ Adds a controller class or object """ - if '{action}' not in route and 'action' not in kargs: - raise BottleException("Routes to controller classes or object MUST" - " contain an {action} placeholder or use the action-parameter") - for action in (m for m in dir(controller) if not m.startswith('_')): - handler = getattr(controller, action) - if callable(handler) and action == kargs.get('action', action): - self.add_route(route.replace('{action}', action), handler, **kargs) - - def add_route(self, route, handler, method='GET', simple=False, **kargs): - """ Adds a new route to the route mappings. """ - if isinstance(handler, type) and issubclass(handler, BaseController): - handler = handler() - if isinstance(handler, BaseController): - self.add_controller(route, handler, method=method, simple=simple, **kargs) - return - 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_default(self, handler): - self.default_route = handler - - def default(self): - """ Decorator for request handler. Same as add_defroute( handler ).""" - def wrapper(handler): - self.set_default(handler) - return handler - return wrapper - - def set_error_handler(self, code, handler): - """ Adds a new error handler. """ - self.error_handler[int(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 cast(self, out): - """ - Cast the output to an iterable of strings or something WSGI can handle. - Set Content-Type and Content-Length when possible. Then clear output - on HEAD requests. - Supports: False, str, unicode, list(unicode), dict(), open() - """ - if not out: - out = [] - response.header['Content-Length'] = '0' - elif isinstance(out, types.StringType): - out = [out] - elif isinstance(out, unicode): - out = [out.encode(response.charset)] - elif isinstance(out, list) and isinstance(out[0], unicode): - out = map(lambda x: x.encode(response.charset), out) - elif self.autojson and json_dumps and isinstance(out, dict): - out = [json_dumps(out)] - response.content_type = 'application/json' - elif hasattr(out, 'read'): - out = request.environ.get('wsgi.file_wrapper', - lambda x: iter(lambda: x.read(8192), ''))(out) - if isinstance(out, list) and len(out) == 1: - response.header['Content-Length'] = str(len(out[0])) - if not hasattr(out, '__iter__'): - raise TypeError('Request handler for route "%s" returned [%s] ' - 'which is not iterable.' % (request.path, type(out).__name__)) - return out - - - def __call__(self, environ, start_response): - """ The bottle WSGI-interface. """ - request.bind(environ) - response.bind() - try: # Unhandled Exceptions - try: # Bottle Error Handling - if not self.serve: - abort(503, "Server stopped") - 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 = self.cast(output) - if response.status in (100, 101, 204, 304) or request.method == 'HEAD': - output = [] # rfc2616 section 4.3 - except (KeyboardInterrupt, SystemExit, MemoryError): - raise - except Exception, e: - response.status = 500 - if self.catchall: - err = "Unhandled Exception: %s\n" % (repr(e)) - if DEBUG: - err += TRACEBACK_TEMPLATE % traceback.format_exc(10) - 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.environ = self._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): - """ Get the request method (GET,POST,PUT,DELETE,...) """ - return self._environ.get('REQUEST_METHOD', 'GET').upper() - - @property - def query_string(self): - """ Get content of QUERY_STRING """ - return self._environ.get('QUERY_STRING', '') - - @property - def input_length(self): - """ Get content of CONTENT_LENGTH """ - try: - return max(0,int(self._environ.get('CONTENT_LENGTH', '0'))) - except ValueError: - return 0 - - @property - def GET(self): - """ Get 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): - """ Get 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 - - def HEADER(self, header): - """Returns HTTP header""" - return self._environ.get(header, '') - -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 - self.charset = 'utf8' - - 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, v in kargs.iteritems(): - self.COOKIES[key][k] = v - - def get_content_type(self): - """ Get the current 'Content-Type' header. """ - return self.header['Content-Type'] - - def set_content_type(self, value): - if 'charset=' in value: - self.charset = value.split('charset=')[-1].split(';')[0].strip() - self.header['Content-Type'] = value - - content_type = property(get_content_type, set_content_type, None, - get_content_type.__doc__) - - -class BaseController(object): - _singleton = None - def __new__(cls, *a, **k): - if not cls._singleton: - cls._singleton = object.__new__(cls, *a, **k) - return cls._singleton - - -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 = None): - """ Aborts execution and sends a static files as response. """ - root = os.path.abspath(root) + os.sep - filename = os.path.abspath(os.path.join(root, filename.strip('/\\'))) - - 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 and not mimetype: - mimetype = mimetypes.guess_type(filename)[0] - if not mimetype: mimetype = 'text/plain' - response.content_type = mimetype - - stats = os.stat(filename) - if 'Last-Modified' not in response.header: - lm = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(stats.st_mtime)) - response.header['Last-Modified'] = lm - if 'HTTP_IF_MODIFIED_SINCE' in request.environ: - ims = request.environ['HTTP_IF_MODIFIED_SINCE'] - # IE sends "<date>; length=146" - ims = ims.split(";")[0].strip() - ims = parse_date(ims) - if ims is not None and ims >= stats.st_mtime: - abort(304, "Not modified") - if 'Content-Length' not in response.header: - response.header['Content-Length'] = str(stats.st_size) - raise BreakTheBottle(open(filename, 'rb')) - - -def parse_date(ims): - """ - Parses date strings usually found in HTTP header and returns UTC epoch. - Understands rfc1123, rfc850 and asctime. - """ - try: - ts = email.utils.parsedate_tz(ims) - if ts is not None: - if ts[9] is None: - return time.mktime(ts[:8] + (0,)) - time.timezone - else: - return time.mktime(ts[:8] + (0,)) - ts[9] - time.timezone - except (ValueError, IndexError): - return None - - - - - - -# Decorators - -def validate(**vkargs): - """ - Validates and manipulates keyword arguments by user defined callables. - Handles ValueError and missing arguments by raising HTTPError(403). - """ - def decorator(func): - def wrapper(**kargs): - for key, value in vkargs.iteritems(): - if key not in kargs: - abort(403, 'Missing parameter: %s' % key) - try: - kargs[key] = value(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 default(): - """ - Decorator for request handler. Same as set_default(handler). - """ - return default_app().default() - -def error(code=500): - """ - Decorator for error handler. Same as set_error_handler(code, handler). - """ - return default_app().error(code) - - - - - - -# Server adapter - -class WSGIAdapter(object): - def run(self, handler): # pragma: no cover - pass - - def __repr__(self): - return "%s()" % (self.__class__.__name__) - - -class CGIServer(WSGIAdapter): - def run(self, handler): - from wsgiref.handlers import CGIHandler - CGIHandler().run(handler) - - -class ServerAdapter(WSGIAdapter): - def __init__(self, host='127.0.0.1', port=8080, **kargs): - WSGIAdapter.__init__(self) - self.host = host - self.port = int(port) - self.options = kargs - - def __repr__(self): - return "%s (%s:%d)" % (self.__class__.__name__, self.host, self.port) - - -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): - """ - Extremly fast webserver using libev. - See http://william-os4y.livejournal.com/ - Experimental ... - """ - def run(self, handler): - import fapws._evwsgi as evwsgi - from fapws import base - 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, - interval=1, reloader=False, **kargs): - """ Runs bottle as a web server. """ - if not app: - app = default_app() - - quiet = bool(kargs.get('quiet', False)) - - # Instantiate server, if it is a class instead of an instance - if isinstance(server, type): - if issubclass(server, CGIServer): - server = server() - elif issubclass(server, ServerAdapter): - server = server(host=host, port=port, **kargs) - - if not isinstance(server, WSGIAdapter): - raise RuntimeError("Server must be a subclass of WSGIAdapter") - - if not quiet and isinstance(server, ServerAdapter): # pragma: no cover - if not reloader or os.environ.get('BOTTLE_CHILD') == 'true': - 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 - else: - print "Bottle auto reloader starting up..." - - try: - if reloader and interval: - reloader_run(server, app, interval) - else: - server.run(app) - except KeyboardInterrupt: - if not quiet: # pragma: no cover - print "Shutting Down..." - - -#TODO: If the parent process is killed (with SIGTERM) the childs survive... -def reloader_run(server, app, interval): - if os.environ.get('BOTTLE_CHILD') == 'true': - # We are a child process - files = dict() - for module in sys.modules.values(): - file_path = getattr(module, '__file__', None) - if file_path and os.path.isfile(file_path): - file_split = os.path.splitext(file_path) - if file_split[1] in ('.py', '.pyc', '.pyo'): - file_path = file_split[0] + '.py' - files[file_path] = os.stat(file_path).st_mtime - thread.start_new_thread(server.run, (app,)) - while True: - time.sleep(interval) - for file_path, file_mtime in files.iteritems(): - if not os.path.exists(file_path): - print "File changed: %s (deleted)" % file_path - elif os.stat(file_path).st_mtime > file_mtime: - print "File changed: %s (modified)" % file_path - else: continue - print "Restarting..." - app.serve = False - time.sleep(interval) # be nice and wait for running requests - sys.exit(3) - while True: - args = [sys.executable] + sys.argv - environ = os.environ.copy() - environ['BOTTLE_CHILD'] = 'true' - exit_status = subprocess.call(args, env=environ) - if exit_status != 3: - sys.exit(exit_status) - - - - - -# Templates - -class TemplateError(HTTPError): - def __init__(self, message): - HTTPError.__init__(self, 500, message) - -class BaseTemplate(object): - def __init__(self, template='', name=None, filename=None, lookup=[]): - """ - Create a new template. - If a name is provided, but no filename and no template string, the - filename is guessed using the lookup path list. - Subclasses can assume that either self.template or self.filename is set. - If both are present, self.template should be used. - """ - self.name = name - self.filename = filename - self.template = template - self.lookup = lookup - if self.name and not self.filename: - for path in self.lookup: - fpath = os.path.join(path, self.name+'.tpl') - if os.path.isfile(fpath): - self.filename = fpath - if not self.template and not self.filename: - raise TemplateError('Template (%s) not found.' % self.name) - self.prepare() - - def prepare(self): - """ - Run preparatios (parsing, caching, ...). - It should be possible to call this multible times to refresh a template. - """ - raise NotImplementedError - - def render(self, **args): - """ - Render the template with the specified local variables and return an - iterator of strings (bytes). This must be thread save! - """ - raise NotImplementedError - - -class MakoTemplate(BaseTemplate): - output_encoding=None - input_encoding=None - default_filters=None - global_variables={} - - def prepare(self): - from mako.template import Template - from mako.lookup import TemplateLookup - #TODO: This is a hack... http://github.com/defnull/bottle/issues#issue/8 - mylookup = TemplateLookup(directories=map(os.path.abspath, self.lookup)+['./']) - if self.template: - self.tpl = Template(self.template, - lookup=mylookup, - output_encoding=MakoTemplate.output_encoding, - input_encoding=MakoTemplate.input_encoding, - default_filters=MakoTemplate.default_filters - ) - else: - self.tpl = Template(filename=self.filename, - lookup=mylookup, - output_encoding=MakoTemplate.output_encoding, - input_encoding=MakoTemplate.input_encoding, - default_filters=MakoTemplate.default_filters - ) - - def render(self, **args): - _defaults = MakoTemplate.global_variables.copy() - _defaults.update(args) - return self.tpl.render(**_defaults) - - -class CheetahTemplate(BaseTemplate): - def prepare(self): - from Cheetah.Template import Template - self.context = threading.local() - self.context.vars = {} - if self.template: - self.tpl = Template(source=self.template, searchList=[self.context.vars]) - else: - self.tpl = Template(file=self.filename, searchList=[self.context.vars]) - - def render(self, **args): - self.context.vars.update(args) - out = str(self.tpl) - self.context.vars.clear() - return [out] - - -class Jinja2Template(BaseTemplate): - env = None # hopefully, a Jinja environment is actually thread-safe - - def prepare(self): - if not self.env: - from jinja2 import Environment, FunctionLoader - self.env = Environment(line_statement_prefix="#", loader=FunctionLoader(self.loader)) - if self.template: - self.tpl = self.env.from_string(self.template) - else: - self.tpl = self.env.get_template(self.filename) - - def render(self, **args): - return self.tpl.render(**args).encode("utf-8") - - def loader(self, name): - if not name.endswith(".tpl"): - for path in self.lookup: - fpath = os.path.join(path, name+'.tpl') - if os.path.isfile(fpath): - name = fpath - break - f = open(name) - try: return f.read() - finally: f.close() - - -class SimpleTemplate(BaseTemplate): - re_python = re.compile(r'^\s*%\s*(?:(if|elif|else|try|except|finally|for|' - 'while|with|def|class)|(include|rebase)|(end)|(.*))') - re_inline = re.compile(r'\{\{(.*?)\}\}') - dedent_keywords = ('elif', 'else', 'except', 'finally') - - def prepare(self): - if self.template: - code = self.translate(self.template) - self.co = compile(code, '<string>', 'exec') - else: - code = self.translate(open(self.filename).read()) - self.co = compile(code, self.filename, 'exec') - - def translate(self, template): - indent = 0 - strbuffer = [] - code = [] - self.includes = dict() - 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, subtpl, end, statement = m.groups() - if keyword: - if keyword in self.dedent_keywords: - indent -= 1 - code.append(" " * indent + line[m.start(1):]) - indent += 1 - elif subtpl: - tmp = line[m.end(2):].strip().split(None, 1) - if not tmp: - code.append(' ' * indent + "_stdout.extend(_base)\n") - else: - name = tmp[0] - args = tmp[1:] and tmp[1] or '' - if name not in self.includes: - self.includes[name] = SimpleTemplate(name=name, lookup=self.lookup) - if subtpl == 'include': - code.append(' ' * indent + - "_ = _includes[%s].execute(_stdout, %s)\n" - % (repr(name), args)) - else: - code.append(' ' * indent + - "_tpl['_rebase'] = (_includes[%s], dict(%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 execute(self, stdout, **args): - args['_stdout'] = stdout - args['_includes'] = self.includes - args['_tpl'] = args - eval(self.co, args) - if '_rebase' in args: - subtpl, args = args['_rebase'] - args['_base'] = stdout[:] #copy stdout - del stdout[:] # clear stdout - return subtpl.execute(stdout, **args) - return args - def render(self, **args): - """ Render the template using keyword arguments as local variables. """ - stdout = [] - self.execute(stdout, **args) - return stdout - - - -def template(tpl, template_adapter=SimpleTemplate, **args): - ''' - Get a rendered template as a string iterator. - You can use a name, a filename or a template string as first parameter. - ''' - lookup = args.get('template_lookup', TEMPLATE_PATH) - if tpl not in TEMPLATES or DEBUG: - if "\n" in tpl or "{" in tpl or "%" in tpl or '$' in tpl: - TEMPLATES[tpl] = template_adapter(template=tpl, lookup=lookup) - elif '.' in tpl: - TEMPLATES[tpl] = template_adapter(filename=tpl, lookup=lookup) - else: - TEMPLATES[tpl] = template_adapter(name=tpl, lookup=lookup) - if not TEMPLATES[tpl]: - abort(500, 'Template (%s) not found' % tpl) - args['abort'] = abort - args['request'] = request - args['response'] = response - return TEMPLATES[tpl].render(**args) - - -def mako_template(tpl_name, **kargs): - kargs['template_adapter'] = MakoTemplate - return template(tpl_name, **kargs) - -def cheetah_template(tpl_name, **kargs): - kargs['template_adapter'] = CheetahTemplate - return template(tpl_name, **kargs) - -def jinja2_template(tpl_name, **kargs): - kargs['template_adapter'] = Jinja2Template - return template(tpl_name, **kargs) - -def view(tpl_name, **defaults): - ''' Decorator: Rendes a template for a handler. - Return a dict of template vars to fill out the template. - ''' - def decorator(func): - def wrapper(**kargs): - out = func(**kargs) - defaults.update(out) - return template(tpl_name, **defaults) - return wrapper - return decorator - -def mako_view(tpl_name, **kargs): - kargs['template_adapter'] = MakoTemplate - return view(tpl_name, **kargs) - -def cheetah_view(tpl_name, **kargs): - kargs['template_adapter'] = CheetahTemplate - return view(tpl_name, **kargs) - -def jinja2_view(tpl_name, **kargs): - kargs['template_adapter'] = Jinja2Template - return view(tpl_name, **kargs) - - - - - - - -# Database - -class BottleBucket(object): # pragma: no cover - """ 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): - if not isinstance(key, str): raise TypeError("Bottle keys must be strings") - 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() - if hasattr(self.db, 'sync'): - self.db.sync() - if hasattr(self.db, 'close'): - 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): # pragma: no cover - """ Holds multible BottleBucket instances in a thread-local way. """ - def __init__(self): - self.__dict__['open'] = {} - - def __getitem__(self, key): - warnings.warn("Please do not use bottle.db anymore. This feature is deprecated. You may use anydb directly.", DeprecationWarning) - 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 = ['./', './views/'] -TEMPLATES = {} -DEBUG = False -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', -} - -HTTP_ERROR_TEMPLATE = """ -<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> -<html> - <head> - <title>Error %(status)d: %(error_name)s</title> - </head> - <body> - <h1>Error %(status)d: %(error_name)s</h1> - <p>Sorry, the requested URL <tt>%(url)s</tt> caused an error:</p> - <pre> - %(error_message)s - </pre> - </body> -</html> -""" - -TRACEBACK_TEMPLATE = """ -<h2>Traceback:</h2> -<pre> -%s -</pre> -""" - -request = Request() -response = Response() -db = BottleDB() -local = threading.local() - -#TODO: Global and app local configuration (debug, defaults, ...) is a mess - -def debug(mode=True): - global DEBUG - DEBUG = bool(mode) - -def optimize(mode=True): - default_app().optimize = bool(mode) - - |