diff options
author | ianb <devnull@localhost> | 2005-05-30 20:49:51 +0000 |
---|---|---|
committer | ianb <devnull@localhost> | 2005-05-30 20:49:51 +0000 |
commit | c3338c28c1cb12ac37941be5210fc4f2d55790e8 (patch) | |
tree | 8e1afa496ea411066c454b11ea050e5c6a0a1d97 /paste/urlmap.py | |
parent | b4397d718d65e87bcb4a58d6e68a7b46df0ad032 (diff) | |
download | paste-c3338c28c1cb12ac37941be5210fc4f2d55790e8.tar.gz |
Added urlmap, which does top-level mapping to different applications, without any 'parsing' phase, typically using configuration files as application descriptors. Also added use_package function to configuration, which requires and installs the named package with easy_install.
Diffstat (limited to 'paste/urlmap.py')
-rw-r--r-- | paste/urlmap.py | 137 |
1 files changed, 137 insertions, 0 deletions
diff --git a/paste/urlmap.py b/paste/urlmap.py new file mode 100644 index 0000000..e3bdb23 --- /dev/null +++ b/paste/urlmap.py @@ -0,0 +1,137 @@ +from UserDict import DictMixin +import re +import os +import wsgilib +from docsupport import metadata + +__all__ = ['URLMap', 'PathProxyURLMap'] + +class URLMap(DictMixin): + + """ + URLMap instances are dictionary-like object that dispatch to one + of several applications based on the URL. + + The dictionary keys are URLs to match (like + ``PATH_INFO.startswith(url)``), and the values are applications to + dispatch to. URLs are matched most-specific-first, i.e., longest + URL first. The ``SCRIPT_NAME`` and ``PATH_INFO`` environmental + variables are adjusted to indicate the new context. + """ + + def __init__(self): + self.applications = [] + self.not_found_application = wsgilib.error_response_app( + '404 Not Found', 'The resource was not found') + + attr_not_found_application = metadata.Attribute(""" + An application that is run when no other application matches + the URL. (Note that the URL ``""`` is also a catch-all URL.) + By default this application returns a simple 404 Not Found + result.""") + + norm_url_re = re.compile('//+') + + def normalize_url(self, url, trim=True): + assert not url or url.startswith('/'), ( + "URL fragments must start with / (you gave %r)" % url) + url = self.norm_url_re.sub('/', url) + if trim: + return url.rstrip('/') + else: + return url + + def sort_apps(self): + """ + Make sure applications are sorted with longest URLs first + """ + self.applications.sort( + lambda a, b: cmp(len(b[0]), len(a[0]))) + + def __setitem__(self, url, app): + url = self.normalize_url(url) + if url in self: + del self[url] + self.applications.append((url, app)) + self.sort_apps() + + def __getitem__(self, url): + url = self.normalize_url(url) + for app_url, app in self.applications: + if app_url == url: + return app + raise KeyError( + "No application with the url %r" % url) + + def __delitem__(self, url): + url = self.normalize_url(url) + for app_url, app in self.applications: + if app_url == url: + self.applications.remove((app_url, url)) + break + else: + raise KeyError( + "No application with the url %r" % url) + + def keys(self): + return [app_url for app_url, app in self.applications] + + def __call__(self, environ, start_response): + path_info = environ.get('PATH_INFO') + if not path_info: + # Kind of just a fast-track for this one case. + last_url, last_app = self.applications[-1] + if not last_url: + return last_app(environ, start_response) + else: + return self.not_found_application(environ, start_response) + path_info = self.normalize_url(path_info, False) + for app_url, app in self.applications: + if (path_info == app_url + or path_info.startswith(app_url + '/')): + environ['SCRIPT_NAME'] += app_url + environ['PATH_INFO'] = path_info[len(app_url):] + return app(environ, start_response) + return self.not_found_application(environ, start_response) + + +class PathProxyURLMap(object): + + """ + This is a wrapper for URLMap that catches any strings that + are passed in as applications; these strings are treated as + filenames (relative to `base_path`) and are passed to the + callable `builder`, which will return an application. + + This is intended for cases when configuration files can be + treated as applications. + + `base_paste_url` is the URL under which all applications added through + this wrapper must go. Use ``""`` if you want this to not + change incoming URLs. + """ + + def __init__(self, map, base_paste_url, base_path, builder): + self.map = map + self.base_paste_url = self.map.normalize_url(base_paste_url) + self.base_path = base_path + self.builder = builder + + def __setitem__(self, url, app): + if isinstance(app, (str, unicode)): + app_fn = os.path.join(self.base_path, app) + app = self.builder(app_fn) + url = self.base_paste_url + url + self.map[url] = app + + def __getattr__(self, attr): + return getattr(self.map, attr) + + # This is really the only settable attribute + def not_found_application__get(self): + return self.map.not_found_application + def not_found_application__set(self, value): + self.map.not_found_application = value + not_found_application = property(not_found_application__get, + not_found_application__set) + |