root/galaxy-central/eggs/Paste-1.6-py2.6.egg/paste/urlmap.py @ 3

リビジョン 3, 8.8 KB (コミッタ: kohda, 14 年 前)

Install Unix tools  http://hannonlab.cshl.edu/galaxy_unix_tools/galaxy.html

行番号 
1# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
2# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
3"""
4Map URL prefixes to WSGI applications.  See ``URLMap``
5"""
6
7from UserDict import DictMixin
8import re
9import os
10from paste import httpexceptions
11
12__all__ = ['URLMap', 'PathProxyURLMap']
13
14def urlmap_factory(loader, global_conf, **local_conf):
15    if 'not_found_app' in local_conf:
16        not_found_app = local_conf.pop('not_found_app')
17    else:
18        not_found_app = global_conf.get('not_found_app')
19    if not_found_app:
20        not_found_app = loader.get_app(not_found_app, global_conf=global_conf)
21    urlmap = URLMap(not_found_app=not_found_app)
22    for path, app_name in local_conf.items():
23        path = parse_path_expression(path)
24        app = loader.get_app(app_name, global_conf=global_conf)
25        urlmap[path] = app
26    return urlmap
27
28def parse_path_expression(path):
29    """
30    Parses a path expression like 'domain foobar.com port 20 /' or
31    just '/foobar' for a path alone.  Returns as an address that
32    URLMap likes.
33    """
34    parts = path.split()
35    domain = port = path = None
36    while parts:
37        if parts[0] == 'domain':
38            parts.pop(0)
39            if not parts:
40                raise ValueError("'domain' must be followed with a domain name")
41            if domain:
42                raise ValueError("'domain' given twice")
43            domain = parts.pop(0)
44        elif parts[0] == 'port':
45            parts.pop(0)
46            if not parts:
47                raise ValueError("'port' must be followed with a port number")
48            if port:
49                raise ValueError("'port' given twice")
50            port = parts.pop(0)
51        else:
52            if path:
53                raise ValueError("more than one path given (have %r, got %r)"
54                                 % (path, parts[0]))
55            path = parts.pop(0)
56    s = ''
57    if domain:
58        s = 'http://%s' % domain
59    if port:
60        if not domain:
61            raise ValueError("If you give a port, you must also give a domain")
62        s += ':' + port
63    if path:
64        if s:
65            s += '/'
66        s += path
67    return s
68
69class URLMap(DictMixin):
70
71    """
72    URLMap instances are dictionary-like object that dispatch to one
73    of several applications based on the URL.
74
75    The dictionary keys are URLs to match (like
76    ``PATH_INFO.startswith(url)``), and the values are applications to
77    dispatch to.  URLs are matched most-specific-first, i.e., longest
78    URL first.  The ``SCRIPT_NAME`` and ``PATH_INFO`` environmental
79    variables are adjusted to indicate the new context.
80   
81    URLs can also include domains, like ``http://blah.com/foo``, or as
82    tuples ``('blah.com', '/foo')``.  This will match domain names; without
83    the ``http://domain`` or with a domain of ``None`` any domain will be
84    matched (so long as no other explicit domain matches).  """
85   
86    def __init__(self, not_found_app=None):
87        self.applications = []
88        if not not_found_app:
89            not_found_app = self.not_found_app
90        self.not_found_application = not_found_app
91
92    norm_url_re = re.compile('//+')
93    domain_url_re = re.compile('^(http|https)://')
94
95    def not_found_app(self, environ, start_response):
96        mapper = environ.get('paste.urlmap_object')
97        if mapper:
98            matches = [p for p, a in mapper.applications]
99            extra = 'defined apps: %s' % (
100                ',\n  '.join(map(repr, matches)))
101        else:
102            extra = ''
103        extra += '\nSCRIPT_NAME: %r' % environ.get('SCRIPT_NAME')
104        extra += '\nPATH_INFO: %r' % environ.get('PATH_INFO')
105        extra += '\nHTTP_HOST: %r' % environ.get('HTTP_HOST')
106        app = httpexceptions.HTTPNotFound(
107            environ['PATH_INFO'],
108            comment=extra).wsgi_application
109        return app(environ, start_response)
110
111    def normalize_url(self, url, trim=True):
112        if isinstance(url, (list, tuple)):
113            domain = url[0]
114            url = self.normalize_url(url[1])[1]
115            return domain, url
116        assert (not url or url.startswith('/')
117                or self.domain_url_re.search(url)), (
118            "URL fragments must start with / or http:// (you gave %r)" % url)
119        match = self.domain_url_re.search(url)
120        if match:
121            url = url[match.end():]
122            if '/' in url:
123                domain, url = url.split('/', 1)
124                url = '/' + url
125            else:
126                domain, url = url, ''
127        else:
128            domain = None
129        url = self.norm_url_re.sub('/', url)
130        if trim:
131            url = url.rstrip('/')
132        return domain, url
133
134    def sort_apps(self):
135        """
136        Make sure applications are sorted with longest URLs first
137        """
138        def key(app_desc):
139            (domain, url), app = app_desc
140            if not domain:
141                # Make sure empty domains sort last:
142                return '\xff', -len(url)
143            else:
144                return domain, -len(url)
145        apps = [(key(desc), desc) for desc in self.applications]
146        apps.sort()
147        self.applications = [desc for (sortable, desc) in apps]
148
149    def __setitem__(self, url, app):
150        if app is None:
151            try:
152                del self[url]
153            except KeyError:
154                pass
155            return
156        dom_url = self.normalize_url(url)
157        if dom_url in self:
158            del self[dom_url]
159        self.applications.append((dom_url, app))
160        self.sort_apps()
161
162    def __getitem__(self, url):
163        dom_url = self.normalize_url(url)
164        for app_url, app in self.applications:
165            if app_url == dom_url:
166                return app
167        raise KeyError(
168            "No application with the url %r (domain: %r; existing: %s)"
169            % (url[1], url[0] or '*', self.applications))
170
171    def __delitem__(self, url):
172        url = self.normalize_url(url)
173        for app_url, app in self.applications:
174            if app_url == url:
175                self.applications.remove((app_url, app))
176                break
177        else:
178            raise KeyError(
179                "No application with the url %r" % (url,))
180
181    def keys(self):
182        return [app_url for app_url, app in self.applications]
183
184    def __call__(self, environ, start_response):
185        host = environ.get('HTTP_HOST', environ.get('SERVER_NAME')).lower()
186        if ':' in host:
187            host, port = host.split(':', 1)
188        else:
189            if environ['wsgi.url_scheme'] == 'http':
190                port = '80'
191            else:
192                port = '443'
193        path_info = environ.get('PATH_INFO')
194        path_info = self.normalize_url(path_info, False)[1]
195        for (domain, app_url), app in self.applications:
196            if domain and domain != host and domain != host+':'+port:
197                continue
198            if (path_info == app_url
199                or path_info.startswith(app_url + '/')):
200                environ['SCRIPT_NAME'] += app_url
201                environ['PATH_INFO'] = path_info[len(app_url):]
202                return app(environ, start_response)
203        environ['paste.urlmap_object'] = self
204        return self.not_found_application(environ, start_response)
205   
206           
207class PathProxyURLMap(object):
208
209    """
210    This is a wrapper for URLMap that catches any strings that
211    are passed in as applications; these strings are treated as
212    filenames (relative to `base_path`) and are passed to the
213    callable `builder`, which will return an application.
214
215    This is intended for cases when configuration files can be
216    treated as applications.
217
218    `base_paste_url` is the URL under which all applications added through
219    this wrapper must go.  Use ``""`` if you want this to not
220    change incoming URLs.
221    """
222
223    def __init__(self, map, base_paste_url, base_path, builder):
224        self.map = map
225        self.base_paste_url = self.map.normalize_url(base_paste_url)
226        self.base_path = base_path
227        self.builder = builder
228       
229    def __setitem__(self, url, app):
230        if isinstance(app, (str, unicode)):
231            app_fn = os.path.join(self.base_path, app)
232            app = self.builder(app_fn)
233        url = self.map.normalize_url(url)
234        # @@: This means http://foo.com/bar will potentially
235        # match foo.com, but /base_paste_url/bar, which is unintuitive
236        url = (url[0] or self.base_paste_url[0],
237               self.base_paste_url[1] + url[1])
238        self.map[url] = app
239
240    def __getattr__(self, attr):
241        return getattr(self.map, attr)
242
243    # This is really the only settable attribute
244    def not_found_application__get(self):
245        return self.map.not_found_application
246    def not_found_application__set(self, value):
247        self.map.not_found_application = value
248    not_found_application = property(not_found_application__get,
249                                     not_found_application__set)
250       
251   
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。