diff options
Diffstat (limited to 'bottle.py')
-rw-r--r-- | bottle.py | 138 |
1 files changed, 127 insertions, 11 deletions
@@ -526,25 +526,38 @@ class Route(object): class Bottle(object): """ Each Bottle object represents a single, distinct web application and - consists of routes, callbacks, plugins and configuration. Instances are - callable WSGI applications. """ + consists of routes, callbacks, plugins, resources and configuration. + Instances are callable WSGI applications. - def __init__(self, catchall=True, autojson=True, config=None): - self.routes = [] # List of installed :class:`Route` instances. - self.router = Router() # Maps requests to :class:`Route` instances. - self.plugins = [] # List of installed plugins. + :param catchall: If true (default), handle all exceptions. Turn off to + let debugging middleware handle exceptions. + """ - self.error_handler = {} - self.config = ConfigDict(config or {}) + def __init__(self, catchall=True, autojson=True): #: If true, most exceptions are catched and returned as :exc:`HTTPError` self.catchall = catchall - #: The installed :class:`HooksPlugin`. + + #: A :cls:`ResourceManager` for application files + self.resources = ResourceManager() + + #: A :cls:`ConfigDict` for app specific configuration. + self.config = ConfigDict() + self.config.autojson = autojson + + self.routes = [] # List of installed :class:`Route` instances. + self.router = Router() # Maps requests to :class:`Route` instances. + self.error_handler = {} + + # Core plugins + self.plugins = [] # List of installed plugins. self.hooks = HooksPlugin() self.install(self.hooks) - if autojson: self.install(JSONPlugin()) + if self.config.autojson: + self.install(JSONPlugin()) #: The installed :class:`TemplatePlugin`. self.views = self.install(TemplatePlugin()) + def mount(self, prefix, app, **options): ''' Mount an application (:class:`Bottle` or plain WSGI) to a specific URL prefix. Example:: @@ -1936,6 +1949,102 @@ class WSGIFileWrapper(object): yield part +class ResourceManager(object): + ''' This class manages a list of search paths and helps to find and open + aplication-bound resources (files). + + :param base: path used to resolve relative search paths. It works as a + default for :meth:`add_path`. + :param opener: callable used to open resources. + :param cachemode: controls which lookups are cached. One of 'all', + 'found' or 'none'. + ''' + + def __init__(self, base='./', opener=open, cachemode='all'): + + self.opener = open + self.base = './' + self.cachemode = cachemode + + #: A list of search paths. See :meth:`add_path` for details. + self.path = [] + #: A list of file masks. See :meth:`add_mask` for details. + self.mask = ['%s'] + #: A cache for resolved paths. `res.cache.clear()`` clears the cache. + self.cache = {} + + def add_path(self, path, base=None, index=None): + ''' Add a path to the :attr:`path` list. + + The path is turned into an absolute and normalized form. If it + looks like a file (not ending in `/`), the filename is stripped + off. The path is not required to exist. + + Relative paths are joined with `base` or :attr:`self.base`, which + defaults to the current working directory. This comes in handy if + you resources live in a sub-folder of your module or package:: + + res.add_path('./resources/', __file__) + + The :attr:`path` list is searched in order and new paths are + added to the end of the list. The *index* parameter can change + the position (e.g. ``0`` to prepend). Adding a path a second time + moves it to the new position. + ''' + base = os.path.abspath(os.path.dirname(base or self.base)) + path = os.path.abspath(os.path.join(base, os.path.dirname(path))) + path += os.sep + if path in self.path: + self.path.remove(path) + if index is None: + self.path.append(path) + else: + self.path.insert(index, path) + self.cache.clear() + + def add_mask(self, mask, index=None): + ''' Add a new format string to the :attr:`mask` list. + + Masks are used to turn resource names into actual filenames. The + mask string must contain exactly one occurence of ``%s``, which + is replaced by the supplied resource name on lookup. This can be + used to auto-append file extentions (e.g. ``%s.ext``). + ''' + if index is None: + self.masks.append(mask) + else: + self.masks.insert(index, mask) + self.cache.clear() + + def lookup(self, name): + ''' Search for a resource and return an absolute file path, or `None`. + + The :attr:`path` list is searched in order. For each path, the + :attr:`mask` entries are tried in order. The first path that points + to an existing file is returned. Symlinks are followed. The result + is cached to speed up future lookups. ''' + if name not in self.cache or DEBUG: + for path in self.path: + for mask in self.mask: + fpath = os.path.join(path, mask%name) + if os.path.isfile(fpath): + if self.cachemode in ('all', 'found'): + self.cache[name] = fpath + return fpath + if self.cachemode == 'all': + self.cache[name] = None + return self.cache[name] + + def open(self, name, *args, **kwargs): + ''' Find a resource and return an opened file object, or raise IOError. + + Additional parameters are passed to the ``open()`` built-in. + ''' + fname = self.lookup(name) + if not fname: raise IOError("Resource %r not found." % name) + return self.opener(name, *args, **kwargs) + + @@ -2291,6 +2400,12 @@ class CherryPyServer(ServerAdapter): server.stop() +class WaitressServer(ServerAdapter): + def run(self, handler): + from waitress import serve + serve(handler, host=self.host, port=self.port) + + class PasteServer(ServerAdapter): def run(self, handler): # pragma: no cover from paste import httpserver @@ -2437,7 +2552,7 @@ class BjoernServer(ServerAdapter): class AutoServer(ServerAdapter): """ Untested. """ - adapters = [PasteServer, TwistedServer, CherryPyServer, WSGIRefServer] + adapters = [WaitressServer, PasteServer, TwistedServer, CherryPyServer, WSGIRefServer] def run(self, handler): for sa in self.adapters: try: @@ -2449,6 +2564,7 @@ server_names = { 'cgi': CGIServer, 'flup': FlupFCGIServer, 'wsgiref': WSGIRefServer, + 'waitress': WaitressServer, 'cherrypy': CherryPyServer, 'paste': PasteServer, 'fapws3': FapwsServer, |