summaryrefslogtreecommitdiff
path: root/paste
diff options
context:
space:
mode:
Diffstat (limited to 'paste')
-rw-r--r--paste/default_config.conf13
-rw-r--r--paste/pyconfig.py39
-rwxr-xr-xpaste/server.py25
-rw-r--r--paste/tests/test_urlmap.py43
-rw-r--r--paste/urlmap.py137
5 files changed, 254 insertions, 3 deletions
diff --git a/paste/default_config.conf b/paste/default_config.conf
index d92dcff..812df11 100644
--- a/paste/default_config.conf
+++ b/paste/default_config.conf
@@ -11,3 +11,16 @@ host = 'localhost'
verbose = False
quiet = False
reload = False
+
+import sys
+sys_path = sys.path
+
+package_urls = {
+ 'wsgiutils': 'http://www.owlfish.com/software/wsgiutils/downloads/WSGI%20Utils-0.5.tar.gz',
+ 'ZPTKit': 'http://svn.w4py.org/ZPTKit/trunk',
+ 'ZopePageTemplates': 'http://prdownloads.sourceforge.net/zpt/ZopePageTemplates-1.4.0.tgz?download',
+ 'scgi': 'http://www.mems-exchange.org/software/files/scgi/scgi-1.2.tar.gz',
+ 'sqlobject': 'http://svn.colorstudy.com/trunk/SQLObject',
+ }
+
+app_packages = 'app-packages'
diff --git a/paste/pyconfig.py b/paste/pyconfig.py
index 342ece2..d181e3c 100644
--- a/paste/pyconfig.py
+++ b/paste/pyconfig.py
@@ -51,6 +51,8 @@ class Config(UserDict.DictMixin, object):
A dictionary-like object that represents configuration.
"""
+ builtins = {}
+
def __init__(self, with_default=False):
self.namespaces = []
if with_default:
@@ -84,7 +86,7 @@ class Config(UserDict.DictMixin, object):
def read_file(self, filename, namespace=None,
load_self=True):
- special_keys = ('__file__', 'load', 'include')
+ special_keys = ['__file__', 'load', 'include'] + self.builtins.keys()
watch_file(filename)
f = open(filename, 'rb')
content = f.read()
@@ -100,7 +102,9 @@ class Config(UserDict.DictMixin, object):
orig = namespace.copy()
namespace['__file__'] = os.path.abspath(filename)
namespace['load'] = self.make_loader(filename, namespace)
- namespace['include'] = self.make_includer(filename, namespace)
+ namespace['include'] = self.make_includer(filename, namespace)
+ for key, builder in self.builtins.items():
+ namespace[key] = builder(self, filename, namespace)
content = content.replace("\r\n","\n")
exec content in namespace
if load_self:
@@ -420,3 +424,34 @@ def make_list(option):
else:
return option
return [s.strip() for s in option.split(',')]
+
+def make_use_package(conf, relative_to, namespace):
+ import easy_install
+ from pkg_resources import DistributionNotFound, require
+ def use_package(package_spec, url=None, instdir=None, zip_ok=False,
+ multi=None, tmpdir=None):
+ if instdir is None:
+ instdir = namespace.get('app_packages', 'app-packages')
+ instdir = os.path.join(os.path.dirname(relative_to), instdir)
+ if instdir not in sys.path:
+ sys.path.insert(0, instdir)
+ if not os.path.exists(instdir):
+ os.makedirs(instdir)
+ try:
+ require(package_spec)
+ except DistributionNotFound, e:
+ pass
+ else:
+ return
+ if url is None:
+ url = namespace['package_urls'][package_spec]
+ installer = easy_install.Installer(instdir, zip_ok, multi, tmpdir)
+ try:
+ downloaded = installer.download(url)
+ installer.install_eggs(downloaded)
+ finally:
+ installer.close()
+ require(package_spec)
+ return use_package
+
+Config.builtins['use_package'] = make_use_package
diff --git a/paste/server.py b/paste/server.py
index 2db0ed7..980eb35 100755
--- a/paste/server.py
+++ b/paste/server.py
@@ -53,6 +53,7 @@ from paste import reloader
from paste import CONFIG
from paste.util import plugin
from paste import pyconfig
+from paste import urlmap
reloader_environ_key = 'WSGI_RELOADER_SHOULD_RUN'
@@ -177,6 +178,10 @@ def help():
program = sys.argv[0]
return help_message % {'program': program}
+def make_app_from_filename(filename):
+ conf = pyconfig.setup_config(filename)
+ return make_app(conf)
+
def make_app(conf):
framework_name = conf.get('framework', 'default')
framework = plugin.load_plugin_module(
@@ -185,7 +190,25 @@ def make_app(conf):
framework_name,
'_framework')
app = framework.build_application(conf)
- return app
+ mapper = conf.get('system_urlmap')
+ if mapper:
+ mapper[''] = app
+ return mapper
+ else:
+ return app
+
+def make_proxy(conf, filename, namespace):
+ mapper = conf.get('system_urlmap')
+ if mapper is None:
+ namespace['system_urlmap'] = mapper = urlmap.URLMap()
+ current_url = conf.get('base_paste_url')
+ if current_url is None:
+ namespace['base_paste_url'] = current_url = ''
+ return urlmap.PathProxyURLMap(
+ mapper, current_url, os.path.dirname(filename),
+ make_app_from_filename)
+
+pyconfig.Config.builtins['urlmap'] = make_proxy
def restart_with_reloader(conf):
if conf['verbose']:
diff --git a/paste/tests/test_urlmap.py b/paste/tests/test_urlmap.py
new file mode 100644
index 0000000..c73b017
--- /dev/null
+++ b/paste/tests/test_urlmap.py
@@ -0,0 +1,43 @@
+from paste.urlmap import *
+from fixture import *
+del setup_module
+
+def make_app(response_text):
+ def app(environ, start_response):
+ headers = [('Content-type', 'text/html')]
+ start_response('200 OK', headers)
+ return [response_text % environ]
+ return app
+
+def test_map():
+ mapper = URLMap()
+ app = TestApp(mapper)
+ text = '%s script_name="%%(SCRIPT_NAME)s" path_info="%%(PATH_INFO)s"'
+ mapper[''] = make_app(text % 'root')
+ mapper['/foo'] = make_app(text % 'foo-only')
+ mapper['/foo/bar'] = make_app(text % 'foo:bar')
+ mapper['/f'] = make_app(text % 'f-only')
+ res = app.get('/')
+ res.mustcontain('root')
+ res.mustcontain('script_name=""')
+ res.mustcontain('path_info="/"')
+ res = app.get('/blah')
+ res.mustcontain('root')
+ res.mustcontain('script_name=""')
+ res.mustcontain('path_info="/blah"')
+ res = app.get('/foo/and/more')
+ res.mustcontain('script_name="/foo"')
+ res.mustcontain('path_info="/and/more"')
+ res.mustcontain('foo-only')
+ res = app.get('/foo/bar/baz')
+ res.mustcontain('foo:bar')
+ res.mustcontain('script_name="/foo/bar"')
+ res.mustcontain('path_info="/baz"')
+ res = app.get('/fffzzz')
+ res.mustcontain('root')
+ res.mustcontain('path_info="/fffzzz"')
+ res = app.get('/f/z/y')
+ res.mustcontain('script_name="/f"')
+ res.mustcontain('path_info="/z/y"')
+ res.mustcontain('f-only')
+
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)
+