~ubuntu-branches/ubuntu/natty/moin/natty-updates

« back to all changes in this revision

Viewing changes to MoinMoin/support/werkzeug/routing.py

  • Committer: Bazaar Package Importer
  • Author(s): Jonas Smedegaard
  • Date: 2008-06-22 21:17:13 UTC
  • mto: This revision was merged to the branch mainline in revision 18.
  • Revision ID: james.westby@ubuntu.com-20080622211713-inlv5k4eifxckelr
ImportĀ upstreamĀ versionĀ 1.7.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- coding: utf-8 -*-
2
 
"""
3
 
    werkzeug.routing
4
 
    ~~~~~~~~~~~~~~~~
5
 
 
6
 
    When it comes to combining multiple controller or view functions (however
7
 
    you want to call them) you need a dispatcher.  A simple way would be
8
 
    applying regular expression tests on the ``PATH_INFO`` and calling
9
 
    registered callback functions that return the value then.
10
 
 
11
 
    This module implements a much more powerful system than simple regular
12
 
    expression matching because it can also convert values in the URLs and
13
 
    build URLs.
14
 
 
15
 
    Here a simple example that creates an URL map for an application with
16
 
    two subdomains (www and kb) and some URL rules:
17
 
 
18
 
    >>> m = Map([
19
 
    ...     # Static URLs
20
 
    ...     Rule('/', endpoint='static/index'),
21
 
    ...     Rule('/about', endpoint='static/about'),
22
 
    ...     Rule('/help', endpoint='static/help'),
23
 
    ...     # Knowledge Base
24
 
    ...     Subdomain('kb', [
25
 
    ...         Rule('/', endpoint='kb/index'),
26
 
    ...         Rule('/browse/', endpoint='kb/browse'),
27
 
    ...         Rule('/browse/<int:id>/', endpoint='kb/browse'),
28
 
    ...         Rule('/browse/<int:id>/<int:page>', endpoint='kb/browse')
29
 
    ...     ])
30
 
    ... ], default_subdomain='www')
31
 
 
32
 
    If the application doesn't use subdomains it's perfectly fine to not set
33
 
    the default subdomain and use the `Subdomain` rule factory.  The endpoint
34
 
    in the rules can be anything, for example import paths or unique
35
 
    identifiers.  The WSGI application can use those endpoints to get the
36
 
    handler for that URL.  It doesn't have to be a string at all but it's
37
 
    recommended.
38
 
 
39
 
    Now it's possible to create a URL adapter for one of the subdomains and
40
 
    build URLs:
41
 
 
42
 
    >>> c = m.bind('example.com')
43
 
    >>> c.build("kb/browse", dict(id=42))
44
 
    'http://kb.example.com/browse/42/'
45
 
    >>> c.build("kb/browse", dict())
46
 
    'http://kb.example.com/browse/'
47
 
    >>> c.build("kb/browse", dict(id=42, page=3))
48
 
    'http://kb.example.com/browse/42/3'
49
 
    >>> c.build("static/about")
50
 
    '/about'
51
 
    >>> c.build("static/index", force_external=True)
52
 
    'http://www.example.com/'
53
 
 
54
 
    >>> c = m.bind('example.com', subdomain='kb')
55
 
    >>> c.build("static/about")
56
 
    'http://www.example.com/about'
57
 
 
58
 
    The first argument to bind is the server name *without* the subdomain.
59
 
    Per default it will assume that the script is mounted on the root, but
60
 
    often that's not the case so you can provide the real mount point as
61
 
    second argument:
62
 
 
63
 
    >>> c = m.bind('example.com', '/applications/example')
64
 
 
65
 
    The third argument can be the subdomain, if not given the default
66
 
    subdomain is used.  For more details about binding have a look at the
67
 
    documentation of the `MapAdapter`.
68
 
 
69
 
    And here is how you can match URLs:
70
 
 
71
 
    >>> c = m.bind('example.com')
72
 
    >>> c.match("/")
73
 
    ('static/index', {})
74
 
    >>> c.match("/about")
75
 
    ('static/about', {})
76
 
    >>> c = m.bind('example.com', '/', 'kb')
77
 
    >>> c.match("/")
78
 
    ('kb/index', {})
79
 
    >>> c.match("/browse/42/23")
80
 
    ('kb/browse', {'id': 42, 'page': 23})
81
 
 
82
 
    If matching fails you get a `NotFound` exception, if the rule thinks
83
 
    it's a good idea to redirect (for example because the URL was defined
84
 
    to have a slash at the end but the request was missing that slash) it
85
 
    will raise a `RequestRedirect` exception.  Both are subclasses of the
86
 
    `HTTPException` so you can use those errors as responses in the
87
 
    application.
88
 
 
89
 
    If matching succeeded but the URL rule was incompatible to the given
90
 
    method (for example there were only rules for `GET` and `HEAD` and
91
 
    routing system tried to match a `POST` request) a `MethodNotAllowed`
92
 
    method is raised.
93
 
 
94
 
 
95
 
    :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details.
96
 
                             Thomas Johansson.
97
 
    :license: BSD, see LICENSE for more details.
98
 
"""
99
 
import re
100
 
from urlparse import urljoin
101
 
from itertools import izip
102
 
 
103
 
from werkzeug.utils import url_encode, url_quote, redirect, format_string
104
 
from werkzeug.exceptions import HTTPException, NotFound, MethodNotAllowed
105
 
 
106
 
 
107
 
_rule_re = re.compile(r'''
108
 
    (?P<static>[^<]*)                           # static rule data
109
 
    <
110
 
    (?:
111
 
        (?P<converter>[a-zA-Z_][a-zA-Z0-9_]*)   # converter name
112
 
        (?:\((?P<args>.*?)\))?                  # converter arguments
113
 
        \:                                      # variable delimiter
114
 
    )?
115
 
    (?P<variable>[a-zA-Z][a-zA-Z0-9_]*)         # variable name
116
 
    >
117
 
''', re.VERBOSE)
118
 
_simple_rule_re = re.compile(r'<([^>]+)>')
119
 
 
120
 
 
121
 
def parse_rule(rule):
122
 
    """Parse a rule and return it as generator. Each iteration yields tuples
123
 
    in the form ``(converter, arguments, variable)``. If the converter is
124
 
    `None` it's a static url part, otherwise it's a dynamic one.
125
 
 
126
 
    :internal:
127
 
    """
128
 
    pos = 0
129
 
    end = len(rule)
130
 
    do_match = _rule_re.match
131
 
    used_names = set()
132
 
    while pos < end:
133
 
        m = do_match(rule, pos)
134
 
        if m is None:
135
 
            break
136
 
        data = m.groupdict()
137
 
        if data['static']:
138
 
            yield None, None, data['static']
139
 
        variable = data['variable']
140
 
        converter = data['converter'] or 'default'
141
 
        if variable in used_names:
142
 
            raise ValueError('variable name %r used twice.' % variable)
143
 
        used_names.add(variable)
144
 
        yield converter, data['args'] or None, variable
145
 
        pos = m.end()
146
 
    if pos < end:
147
 
        remaining = rule[pos:]
148
 
        if '>' in remaining or '<' in remaining:
149
 
            raise ValueError('malformed url rule: %r' % rule)
150
 
        yield None, None, remaining
151
 
 
152
 
 
153
 
def get_converter(map, name, args):
154
 
    """Create a new converter for the given arguments or raise
155
 
    exception if the converter does not exist.
156
 
 
157
 
    :internal:
158
 
    """
159
 
    if not name in map.converters:
160
 
        raise LookupError('the converter %r does not exist' % name)
161
 
    if args:
162
 
        storage = type('_Storage', (), {'__getitem__': lambda s, x: x})()
163
 
        args, kwargs = eval(u'(lambda *a, **kw: (a, kw))(%s)' % args, {}, storage)
164
 
    else:
165
 
        args = ()
166
 
        kwargs = {}
167
 
    return map.converters[name](map, *args, **kwargs)
168
 
 
169
 
 
170
 
class RoutingException(Exception):
171
 
    """Special exceptions that require the application to redirect, notifies
172
 
    him about missing urls etc.
173
 
 
174
 
    :internal:
175
 
    """
176
 
 
177
 
 
178
 
class RequestRedirect(HTTPException, RoutingException):
179
 
    """Raise if the map requests a redirect. This is for example the case if
180
 
    `strict_slashes` are activated and an url that requires a leading slash.
181
 
 
182
 
    The attribute `new_url` contains the absolute destination url.
183
 
    """
184
 
    code = 301
185
 
 
186
 
    def __init__(self, new_url):
187
 
        RoutingException.__init__(self, new_url)
188
 
        self.new_url = new_url
189
 
 
190
 
    def get_response(self, environ):
191
 
        return redirect(self.new_url, 301)
192
 
 
193
 
 
194
 
class RequestSlash(RoutingException):
195
 
    """Internal exception."""
196
 
 
197
 
 
198
 
class BuildError(RoutingException, LookupError):
199
 
    """Raised if the build system cannot find a URL for an endpoint with the
200
 
    values provided.
201
 
    """
202
 
 
203
 
    def __init__(self, endpoint, values, method):
204
 
        LookupError.__init__(self, endpoint, values, method)
205
 
        self.endpoint = endpoint
206
 
        self.values = values
207
 
        self.method = method
208
 
 
209
 
 
210
 
class ValidationError(ValueError):
211
 
    """Validation error.  If a rule converter raises this exception the rule
212
 
    does not match the current URL and the next URL is tried.
213
 
    """
214
 
 
215
 
 
216
 
class RuleFactory(object):
217
 
    """As soon as you have more complex URL setups it's a good idea to use rule
218
 
    factories to avoid repetitive tasks.  Some of them are builtin, others can
219
 
    be added by subclassing `RuleFactory` and overriding `get_rules`.
220
 
    """
221
 
 
222
 
    def get_rules(self, map):
223
 
        """Subclasses of `RuleFactory` have to override this method and return
224
 
        an iterable of rules."""
225
 
        raise NotImplementedError()
226
 
 
227
 
 
228
 
class Subdomain(RuleFactory):
229
 
    """All URLs provided by this factory have the subdomain set to a
230
 
    specific domain. For example if you want to use the subdomain for
231
 
    the current language this can be a good setup::
232
 
 
233
 
        url_map = Map([
234
 
            Rule('/', endpoint='#select_language'),
235
 
            Subdomain('<string(length=2):lang_code>', [
236
 
                Rule('/', endpoint='index'),
237
 
                Rule('/about', endpoint='about'),
238
 
                Rule('/help', endpoint='help')
239
 
            ])
240
 
        ])
241
 
 
242
 
    All the rules except for the ``'#select_language'`` endpoint will now
243
 
    listen on a two letter long subdomain that helds the language code
244
 
    for the current request.
245
 
    """
246
 
 
247
 
    def __init__(self, subdomain, rules):
248
 
        self.subdomain = subdomain
249
 
        self.rules = rules
250
 
 
251
 
    def get_rules(self, map):
252
 
        for rulefactory in self.rules:
253
 
            for rule in rulefactory.get_rules(map):
254
 
                rule.subdomain = self.subdomain
255
 
                yield rule
256
 
 
257
 
 
258
 
class Submount(RuleFactory):
259
 
    """Like `Subdomain` but prefixes the URL rule with a given string::
260
 
 
261
 
        url_map = Map([
262
 
            Rule('/', endpoint='index'),
263
 
            Submount('/blog', [
264
 
                Rule('/', endpoint='blog/index'),
265
 
                Rule('/entry/<entry_slug>', endpoint='blog/show')
266
 
            ])
267
 
        ])
268
 
 
269
 
    Now the rule ``'blog/show'`` matches ``/blog/entry/<entry_slug>``.
270
 
    """
271
 
 
272
 
    def __init__(self, path, rules):
273
 
        self.path = path.rstrip('/')
274
 
        self.rules = rules
275
 
 
276
 
    def get_rules(self, map):
277
 
        for rulefactory in self.rules:
278
 
            for rule in rulefactory.get_rules(map):
279
 
                rule.rule = self.path + rule.rule
280
 
                yield rule
281
 
 
282
 
 
283
 
class EndpointPrefix(RuleFactory):
284
 
    """Prefixes all endpoints (which must be strings for this factory) with
285
 
    another string. This can be useful for sub applications::
286
 
 
287
 
        url_map = Map([
288
 
            Rule('/', endpoint='index'),
289
 
            EndpointPrefix('blog/', [Submount('/blog', [
290
 
                Rule('/', endpoint='index'),
291
 
                Rule('/entry/<entry_slug>', endpoint='show')
292
 
            ])])
293
 
        ])
294
 
    """
295
 
 
296
 
    def __init__(self, prefix, rules):
297
 
        self.prefix = prefix
298
 
        self.rules = rules
299
 
 
300
 
    def get_rules(self, map):
301
 
        for rulefactory in self.rules:
302
 
            for rule in rulefactory.get_rules(map):
303
 
                rule.endpoint = self.prefix + rule.endpoint
304
 
                yield rule
305
 
 
306
 
 
307
 
class RuleTemplate(object):
308
 
    """Returns copies of the rules wrapped and expands string templates in
309
 
    the endpoint, rule, defaults or subdomain sections.
310
 
 
311
 
    Here a small example for such a rule template::
312
 
 
313
 
        from werkzeug.routing import Map, Rule, RuleTemplate
314
 
 
315
 
        resource = RuleTemplate([
316
 
            Rule('/$name/', endpoint='$name.list'),
317
 
            Rule('/$name/<int:id>', endpoint='$name.show')
318
 
        ])
319
 
 
320
 
        url_map = Map([resource(name='user'), resource(name='page')])
321
 
 
322
 
    When a rule template is called the keyword arguments are used to
323
 
    replace the placeholders in all the string parameters.
324
 
    """
325
 
 
326
 
    def __init__(self, rules):
327
 
        self.rules = list(rules)
328
 
 
329
 
    def __call__(self, *args, **kwargs):
330
 
        return RuleTemplateFactory(self.rules, dict(*args, **kwargs))
331
 
 
332
 
 
333
 
class RuleTemplateFactory(RuleFactory):
334
 
    """A factory that fills in template variables into rules.  Used by
335
 
    `RuleTemplate` internally.
336
 
 
337
 
    :internal:
338
 
    """
339
 
 
340
 
    def __init__(self, rules, context):
341
 
        self.rules = rules
342
 
        self.context = context
343
 
 
344
 
    def get_rules(self, map):
345
 
        for rulefactory in self.rules:
346
 
            for rule in rulefactory.get_rules(map):
347
 
                new_defaults = subdomain = None
348
 
                if rule.defaults is not None:
349
 
                    new_defaults = {}
350
 
                    for key, value in rule.defaults.iteritems():
351
 
                        if isinstance(value, basestring):
352
 
                            value = format_string(value, self.context)
353
 
                        new_defaults[key] = value
354
 
                if rule.subdomain is not None:
355
 
                    subdomain = format_string(rule.subdomain, self.context)
356
 
                new_endpoint = rule.endpoint
357
 
                if isinstance(new_endpoint, basestring):
358
 
                    new_endpoint = format_string(new_endpoint, self.context)
359
 
                yield Rule(
360
 
                    format_string(rule.rule, self.context),
361
 
                    new_defaults,
362
 
                    subdomain,
363
 
                    rule.methods,
364
 
                    rule.build_only,
365
 
                    new_endpoint,
366
 
                    rule.strict_slashes
367
 
                )
368
 
 
369
 
 
370
 
class Rule(RuleFactory):
371
 
    """A Rule represents one URL pattern.  There are some options for `Rule`
372
 
    that change the way it behaves and are passed to the `Rule` constructor.
373
 
    Note that besides the rule-string all arguments *must* be keyword arguments
374
 
    in order to not break the application on Werkzeug upgrades.
375
 
 
376
 
    `string`
377
 
        Rule strings basically are just normal URL paths with placeholders in
378
 
        the format ``<converter(arguments):name>`` where the converter and the
379
 
        arguments are optional.  If no converter is defined the `default`
380
 
        converter is used which means `string` in the normal configuration.
381
 
 
382
 
        URL rules that end with a slash are branch URLs, others are leaves.
383
 
        If you have `strict_slashes` enabled (which is the default), all
384
 
        branch URLs that are matched without a trailing slash will trigger a
385
 
        redirect to the same URL with the missing slash appended.
386
 
 
387
 
        The converters are defined on the `Map`.
388
 
 
389
 
    `endpoint`
390
 
        The endpoint for this rule. This can be anything. A reference to a
391
 
        function, a string, a number etc.  The preferred way is using a string
392
 
        as because the endpoint is used for URL generation.
393
 
 
394
 
    `defaults`
395
 
        An optional dict with defaults for other rules with the same endpoint.
396
 
        This is a bit tricky but useful if you want to have unique URLs::
397
 
 
398
 
            url_map = Map([
399
 
                Rule('/all/', defaults={'page': 1}, endpoint='all_entries'),
400
 
                Rule('/all/page/<int:page>', endpoint='all_entries')
401
 
            ])
402
 
 
403
 
        If a user now visits ``http://example.com/all/page/1`` he will be
404
 
        redirected to ``http://example.com/all/``.  If `redirect_defaults` is
405
 
        disabled on the `Map` instance this will only affect the URL
406
 
        generation.
407
 
 
408
 
    `subdomain`
409
 
        The subdomain rule string for this rule. If not specified the rule
410
 
        only matches for the `default_subdomain` of the map.  If the map is
411
 
        not bound to a subdomain this feature is disabled.
412
 
 
413
 
        Can be useful if you want to have user profiles on different subdomains
414
 
        and all subdomains are forwarded to your application::
415
 
 
416
 
            url_map = Map([
417
 
                Rule('/', subdomain='<username>', endpoint='user/homepage'),
418
 
                Rule('/stats', subdomain='<username>', endpoint='user/stats')
419
 
            ])
420
 
 
421
 
    `methods`
422
 
        A sequence of http methods this rule applies to.  If not specified, all
423
 
        methods are allowed. For example this can be useful if you want different
424
 
        endpoints for `POST` and `GET`.  If methods are defined and the path
425
 
        matches but the method matched against is not in this list or in the
426
 
        list of another rule for that path the error raised is of the type
427
 
        `MethodNotAllowed` rather than `NotFound`.
428
 
 
429
 
    `strict_slashes`
430
 
        Override the `Map` setting for `strict_slashes` only for this rule. If
431
 
        not specified the `Map` setting is used.
432
 
 
433
 
    `build_only`
434
 
        Set this to True and the rule will never match but will create a URL
435
 
        that can be build. This is useful if you have resources on a subdomain
436
 
        or folder that are not handled by the WSGI application (like static data)
437
 
 
438
 
    `redirect_to`
439
 
        If given this must be either a string or callable.  In case of a
440
 
        callable it's called with the url adapter that triggered the match and
441
 
        the values of the URL as keyword arguments and has to return the target
442
 
        for the redirect, otherwise it has to be a string with placeholders in
443
 
        rule syntax::
444
 
 
445
 
            def foo_with_slug(adapter, id):
446
 
                # ask the database for the slug for the old id.  this of
447
 
                # course has nothing to do with werkzeug.
448
 
                return 'foo/' + Foo.get_slug_for_id(id)
449
 
 
450
 
            url_map = Map([
451
 
                Rule('/foo/<slug>', endpoint='foo'),
452
 
                Rule('/some/old/url/<slug>', redirect_to='foo/<slug>'),
453
 
                Rule('/other/old/url/<int:id>', redirect_to=foo_with_slug)
454
 
            ])
455
 
 
456
 
        When the rule is matched the routing system will raise a
457
 
        `RequestRedirect` exception with the target for the redirect.
458
 
 
459
 
        Keep in mind that the URL will be joined against the URL root of the
460
 
        script so don't use a leading slash on the target URL unless you
461
 
        really mean root of that domain.
462
 
    """
463
 
 
464
 
    def __init__(self, string, defaults=None, subdomain=None, methods=None,
465
 
                 build_only=False, endpoint=None, strict_slashes=None,
466
 
                 redirect_to=None):
467
 
        if not string.startswith('/'):
468
 
            raise ValueError('urls must start with a leading slash')
469
 
        self.rule = string
470
 
        self.is_leaf = not string.endswith('/')
471
 
 
472
 
        self.map = None
473
 
        self.strict_slashes = strict_slashes
474
 
        self.subdomain = subdomain
475
 
        self.defaults = defaults
476
 
        self.build_only = build_only
477
 
        if methods is None:
478
 
            self.methods = None
479
 
        else:
480
 
            self.methods = set([x.upper() for x in methods])
481
 
        self.endpoint = endpoint
482
 
        self.greediness = 0
483
 
        self.redirect_to = redirect_to
484
 
 
485
 
        self._trace = []
486
 
        if defaults is not None:
487
 
            self.arguments = set(map(str, defaults))
488
 
        else:
489
 
            self.arguments = set()
490
 
        self._converters = {}
491
 
        self._regex = None
492
 
        self._weights = []
493
 
 
494
 
    def empty(self):
495
 
        """Return an unbound copy of this rule.  This can be useful if you
496
 
        want to reuse an already bound URL for another map."""
497
 
        return Rule(self.rule, self.defaults, self.subdomain, self.methods,
498
 
                    self.build_only, self.endpoint, self.strict_slashes,
499
 
                    self.redirect_to)
500
 
 
501
 
    def get_rules(self, map):
502
 
        yield self
503
 
 
504
 
    def bind(self, map):
505
 
        """Bind the url to a map and create a regular expression based on
506
 
        the information from the rule itself and the defaults from the map.
507
 
 
508
 
        :internal:
509
 
        """
510
 
        if self.map is not None:
511
 
            raise RuntimeError('url rule %r already bound to map %r' %
512
 
                               (self, self.map))
513
 
        self.map = map
514
 
        if self.strict_slashes is None:
515
 
            self.strict_slashes = map.strict_slashes
516
 
        if self.subdomain is None:
517
 
            self.subdomain = map.default_subdomain
518
 
 
519
 
        rule = self.subdomain + '|' + (self.is_leaf and self.rule
520
 
                                       or self.rule.rstrip('/'))
521
 
 
522
 
        regex_parts = []
523
 
        for converter, arguments, variable in parse_rule(rule):
524
 
            if converter is None:
525
 
                regex_parts.append(re.escape(variable))
526
 
                self._trace.append((False, variable))
527
 
                self._weights.append(len(variable))
528
 
            else:
529
 
                convobj = get_converter(map, converter, arguments)
530
 
                regex_parts.append('(?P<%s>%s)' % (variable, convobj.regex))
531
 
                self._converters[variable] = convobj
532
 
                self._trace.append((True, variable))
533
 
                self._weights.append(convobj.weight)
534
 
                self.arguments.add(str(variable))
535
 
                if convobj.is_greedy:
536
 
                    self.greediness += 1
537
 
        if not self.is_leaf:
538
 
            self._trace.append((False, '/'))
539
 
 
540
 
        if not self.build_only:
541
 
            regex = r'^%s%s$' % (
542
 
                u''.join(regex_parts),
543
 
                (not self.is_leaf or not self.strict_slashes) and \
544
 
                    '(?<!/)(?P<__suffix__>/?)' or ''
545
 
            )
546
 
            self._regex = re.compile(regex, re.UNICODE)
547
 
 
548
 
    def match(self, path):
549
 
        """Check if the rule matches a given path. Path is a string in the
550
 
        form ``"subdomain|/path(method)"`` and is assembled by the map.
551
 
 
552
 
        If the rule matches a dict with the converted values is returned,
553
 
        otherwise the return value is `None`.
554
 
 
555
 
        :internal:
556
 
        """
557
 
        if not self.build_only:
558
 
            m = self._regex.search(path)
559
 
            if m is not None:
560
 
                groups = m.groupdict()
561
 
                # we have a folder like part of the url without a trailing
562
 
                # slash and strict slashes enabled. raise an exception that
563
 
                # tells the map to redirect to the same url but with a
564
 
                # trailing slash
565
 
                if self.strict_slashes and not self.is_leaf and \
566
 
                   not groups.pop('__suffix__'):
567
 
                    raise RequestSlash()
568
 
                # if we are not in strict slashes mode we have to remove
569
 
                # a __suffix__
570
 
                elif not self.strict_slashes:
571
 
                    del groups['__suffix__']
572
 
 
573
 
                result = {}
574
 
                for name, value in groups.iteritems():
575
 
                    try:
576
 
                        value = self._converters[name].to_python(value)
577
 
                    except ValidationError:
578
 
                        return
579
 
                    result[str(name)] = value
580
 
                if self.defaults is not None:
581
 
                    result.update(self.defaults)
582
 
                return result
583
 
 
584
 
    def build(self, values):
585
 
        """Assembles the relative url for that rule and the subdomain.
586
 
        If building doesn't work for some reasons `None` is returned.
587
 
 
588
 
        :internal:
589
 
        """
590
 
        tmp = []
591
 
        add = tmp.append
592
 
        processed = set(self.arguments)
593
 
        for is_dynamic, data in self._trace:
594
 
            if is_dynamic:
595
 
                try:
596
 
                    add(self._converters[data].to_url(values[data]))
597
 
                except ValidationError:
598
 
                    return
599
 
                processed.add(data)
600
 
            else:
601
 
                add(data)
602
 
        subdomain, url = (u''.join(tmp)).split('|', 1)
603
 
 
604
 
        query_vars = {}
605
 
        for key in set(values) - processed:
606
 
            query_vars[key] = unicode(values[key])
607
 
        if query_vars:
608
 
            url += '?' + url_encode(query_vars, self.map.charset,
609
 
                                    sort=self.map.sort_parameters,
610
 
                                    key=self.map.sort_key)
611
 
 
612
 
        return subdomain, url
613
 
 
614
 
    def provides_defaults_for(self, rule):
615
 
        """Check if this rule has defaults for a given rule.
616
 
 
617
 
        :internal:
618
 
        """
619
 
        return not self.build_only and self.defaults is not None and \
620
 
               self.endpoint == rule.endpoint and self != rule and \
621
 
               self.arguments == rule.arguments
622
 
 
623
 
    def suitable_for(self, values, method):
624
 
        """Check if the dict of values has enough data for url generation.
625
 
 
626
 
        :internal:
627
 
        """
628
 
        if self.methods is not None and method not in self.methods:
629
 
            return False
630
 
 
631
 
        valueset = set(values)
632
 
 
633
 
        for key in self.arguments - set(self.defaults or ()):
634
 
            if key not in values:
635
 
                return False
636
 
 
637
 
        if self.arguments.issubset(valueset):
638
 
            if self.defaults is None:
639
 
                return True
640
 
            for key, value in self.defaults.iteritems():
641
 
                if value != values[key]:
642
 
                    return False
643
 
 
644
 
        return True
645
 
 
646
 
    def match_compare(self, other):
647
 
        """Compare this object with another one for matching.
648
 
 
649
 
        :internal:
650
 
        """
651
 
        for sw, ow in izip(self._weights, other._weights):
652
 
            if sw > ow:
653
 
                return -1
654
 
            elif sw < ow:
655
 
                return 1
656
 
        if len(self._weights) > len(other._weights):
657
 
            return -1
658
 
        if len(self._weights) < len(other._weights):
659
 
            return 1
660
 
        if not other.arguments and self.arguments:
661
 
            return 1
662
 
        elif other.arguments and not self.arguments:
663
 
            return -1
664
 
        elif other.defaults is None and self.defaults is not None:
665
 
            return 1
666
 
        elif other.defaults is not None and self.defaults is None:
667
 
            return -1
668
 
        elif self.greediness > other.greediness:
669
 
            return -1
670
 
        elif self.greediness < other.greediness:
671
 
            return 1
672
 
        elif len(self.arguments) > len(other.arguments):
673
 
            return 1
674
 
        elif len(self.arguments) < len(other.arguments):
675
 
            return -1
676
 
        return 1
677
 
 
678
 
    def build_compare(self, other):
679
 
        """Compare this object with another one for building.
680
 
 
681
 
        :internal:
682
 
        """
683
 
        if not other.arguments and self.arguments:
684
 
            return -1
685
 
        elif other.arguments and not self.arguments:
686
 
            return 1
687
 
        elif other.defaults is None and self.defaults is not None:
688
 
            return -1
689
 
        elif other.defaults is not None and self.defaults is None:
690
 
            return 1
691
 
        elif self.provides_defaults_for(other):
692
 
            return -1
693
 
        elif other.provides_defaults_for(self):
694
 
            return 1
695
 
        elif self.greediness > other.greediness:
696
 
            return -1
697
 
        elif self.greediness < other.greediness:
698
 
            return 1
699
 
        elif len(self.arguments) > len(other.arguments):
700
 
            return -1
701
 
        elif len(self.arguments) < len(other.arguments):
702
 
            return 1
703
 
        return -1
704
 
 
705
 
    def __eq__(self, other):
706
 
        return self.__class__ is other.__class__ and \
707
 
               self._trace == other._trace
708
 
 
709
 
    def __ne__(self, other):
710
 
        return not self.__eq__(other)
711
 
 
712
 
    def __unicode__(self):
713
 
        return self.rule
714
 
 
715
 
    def __str__(self):
716
 
        charset = self.map is not None and self.map.charset or 'utf-8'
717
 
        return unicode(self).encode(charset)
718
 
 
719
 
    def __repr__(self):
720
 
        if self.map is None:
721
 
            return '<%s (unbound)>' % self.__class__.__name__
722
 
        charset = self.map is not None and self.map.charset or 'utf-8'
723
 
        tmp = []
724
 
        for is_dynamic, data in self._trace:
725
 
            if is_dynamic:
726
 
                tmp.append('<%s>' % data)
727
 
            else:
728
 
                tmp.append(data)
729
 
        return '<%s %r%s -> %s>' % (
730
 
            self.__class__.__name__,
731
 
            (u''.join(tmp).encode(charset)).lstrip('|'),
732
 
            self.methods is not None and ' (%s)' % \
733
 
                ', '.join(self.methods) or '',
734
 
            self.endpoint
735
 
        )
736
 
 
737
 
 
738
 
class BaseConverter(object):
739
 
    """Base class for all converters."""
740
 
    regex = '[^/]+'
741
 
    is_greedy = False
742
 
    weight = 100
743
 
 
744
 
    def __init__(self, map):
745
 
        self.map = map
746
 
 
747
 
    def to_python(self, value):
748
 
        return value
749
 
 
750
 
    def to_url(self, value):
751
 
        return url_quote(value, self.map.charset)
752
 
 
753
 
 
754
 
class UnicodeConverter(BaseConverter):
755
 
    """This converter is the default converter and accepts any string but
756
 
    only one one path segment.  Thus the string can not include a slash.
757
 
 
758
 
    This is the default validator.
759
 
 
760
 
    Example::
761
 
 
762
 
        Rule('/pages/<page>'),
763
 
        Rule('/<string(length=2):lang_code>')
764
 
 
765
 
    :param map: the :class:`Map`.
766
 
    :param minlength: the minimum length of the string.  Must be greater
767
 
                      or equal 1.
768
 
    :param maxlength: the maximum length of the string.
769
 
    :param length: the exact length of the string.
770
 
    """
771
 
 
772
 
    def __init__(self, map, minlength=1, maxlength=None, length=None):
773
 
        BaseConverter.__init__(self, map)
774
 
        if length is not None:
775
 
            length = '{%d}' % int(length)
776
 
        else:
777
 
            if maxlength is None:
778
 
                maxlength = ''
779
 
            else:
780
 
                maxlength = int(maxlength)
781
 
            length = '{%s,%s}' % (
782
 
                int(minlength),
783
 
                maxlength
784
 
            )
785
 
        self.regex = '[^/]' + length
786
 
 
787
 
 
788
 
class AnyConverter(BaseConverter):
789
 
    """Matches one of the items provided.  Items can either be Python
790
 
    identifiers or unicode strings::
791
 
 
792
 
        Rule('/<any(about, help, imprint, u"class"):page_name>')
793
 
 
794
 
    :param map: the :class:`Map`.
795
 
    :param items: this function accepts the possible items as positional
796
 
                  arguments.
797
 
    """
798
 
 
799
 
    def __init__(self, map, *items):
800
 
        BaseConverter.__init__(self, map)
801
 
        self.regex = '(?:%s)' % '|'.join([re.escape(x) for x in items])
802
 
 
803
 
 
804
 
class PathConverter(BaseConverter):
805
 
    """Like the default :class:`UnicodeConverter`, but it also matches
806
 
    slashes.  This is useful for wikis and similar applications::
807
 
 
808
 
        Rule('/<path:wikipage>')
809
 
        Rule('/<path:wikipage>/edit')
810
 
 
811
 
    :param map: the :class:`Map`.
812
 
    :param minlength: the minimum length of the string.  Must be greater
813
 
                      or equal 1.
814
 
    :param maxlength: the maximum length of the string.
815
 
    :param length: the exact length of the string.
816
 
    """
817
 
    regex = '[^/].*?'
818
 
    is_greedy = True
819
 
    weight = 50
820
 
 
821
 
 
822
 
class NumberConverter(BaseConverter):
823
 
    """Baseclass for `IntegerConverter` and `FloatConverter`.
824
 
 
825
 
    :internal:
826
 
    """
827
 
 
828
 
    def __init__(self, map, fixed_digits=0, min=None, max=None):
829
 
        BaseConverter.__init__(self, map)
830
 
        self.fixed_digits = fixed_digits
831
 
        self.min = min
832
 
        self.max = max
833
 
 
834
 
    def to_python(self, value):
835
 
        if (self.fixed_digits and len(value) != self.fixed_digits):
836
 
            raise ValidationError()
837
 
        value = self.num_convert(value)
838
 
        if (self.min is not None and value < self.min) or \
839
 
           (self.max is not None and value > self.max):
840
 
            raise ValidationError()
841
 
        return value
842
 
 
843
 
    def to_url(self, value):
844
 
        value = self.num_convert(value)
845
 
        if self.fixed_digits:
846
 
            value = ('%%0%sd' % self.fixed_digits) % value
847
 
        return str(value)
848
 
 
849
 
 
850
 
class IntegerConverter(NumberConverter):
851
 
    """This converter only accepts integer values::
852
 
 
853
 
        Rule('/page/<int:page>')
854
 
 
855
 
    This converter does not support negative values.
856
 
 
857
 
    :param map: the :class:`Map`.
858
 
    :param fixed_digits: the number of fixed digits in the URL.  If you set
859
 
                         this to ``4`` for example, the application will
860
 
                         only match if the url looks like ``/0001/``.  The
861
 
                         default is variable length.
862
 
    :param min: the minimal value.
863
 
    :param max: the maximal value.
864
 
    """
865
 
    regex = r'\d+'
866
 
    num_convert = int
867
 
 
868
 
 
869
 
class FloatConverter(NumberConverter):
870
 
    """This converter only accepts floating point values::
871
 
 
872
 
        Rule('/probability/<float:probability>')
873
 
 
874
 
    This converter does not support negative values.
875
 
 
876
 
    :param map: the :class:`Map`.
877
 
    :param min: the minimal value.
878
 
    :param max: the maximal value.
879
 
    """
880
 
    regex = r'\d+\.\d+'
881
 
    num_convert = float
882
 
 
883
 
    def __init__(self, map, min=None, max=None):
884
 
        NumberConverter.__init__(self, map, 0, min, max)
885
 
 
886
 
 
887
 
class Map(object):
888
 
    """The map class stores all the URL rules and some configuration
889
 
    parameters.  Some of the configuration values are only stored on the
890
 
    `Map` instance since those affect all rules, others are just defaults
891
 
    and can be overridden for each rule.  Note that you have to specify all
892
 
    arguments besides the `rules` as keywords arguments!
893
 
 
894
 
    :param rules: sequence of url rules for this map.
895
 
    :param default_subdomain: The default subdomain for rules without a
896
 
                              subdomain defined.
897
 
    :param charset: charset of the url. defaults to ``"utf-8"``
898
 
    :param strict_slashes: Take care of trailing slashes.
899
 
    :param redirect_defaults: This will redirect to the default rule if it
900
 
                              wasn't visited that way. This helps creating
901
 
                              unique URLs.
902
 
    :param converters: A dict of converters that adds additional converters
903
 
                       to the list of converters. If you redefine one
904
 
                       converter this will override the original one.
905
 
    :param sort_parameters: If set to `True` the url parameters are sorted.
906
 
                            See `url_encode` for more details.
907
 
    :param sort_key: The sort key function for `url_encode`.
908
 
 
909
 
    .. versionadded:: 0.5
910
 
        `sort_parameters` and `sort_key` was added.
911
 
    """
912
 
 
913
 
    def __init__(self, rules=None, default_subdomain='', charset='utf-8',
914
 
                 strict_slashes=True, redirect_defaults=True,
915
 
                 converters=None, sort_parameters=False, sort_key=None):
916
 
        self._rules = []
917
 
        self._rules_by_endpoint = {}
918
 
        self._remap = True
919
 
 
920
 
        self.default_subdomain = default_subdomain
921
 
        self.charset = charset
922
 
        self.strict_slashes = strict_slashes
923
 
        self.redirect_defaults = redirect_defaults
924
 
 
925
 
        self.converters = DEFAULT_CONVERTERS.copy()
926
 
        if converters:
927
 
            self.converters.update(converters)
928
 
 
929
 
        self.sort_parameters = sort_parameters
930
 
        self.sort_key = sort_key
931
 
 
932
 
        for rulefactory in rules or ():
933
 
            self.add(rulefactory)
934
 
 
935
 
    def is_endpoint_expecting(self, endpoint, *arguments):
936
 
        """Iterate over all rules and check if the endpoint expects
937
 
        the arguments provided.  This is for example useful if you have
938
 
        some URLs that expect a language code and others that do not and
939
 
        you want to wrap the builder a bit so that the current language
940
 
        code is automatically added if not provided but endpoints expect
941
 
        it.
942
 
 
943
 
        :param endpoint: the endpoint to check.
944
 
        :param arguments: this function accepts one or more arguments
945
 
                          as positional arguments.  Each one of them is
946
 
                          checked.
947
 
        """
948
 
        self.update()
949
 
        arguments = set(arguments)
950
 
        for rule in self._rules_by_endpoint[endpoint]:
951
 
            if arguments.issubset(rule.arguments):
952
 
                return True
953
 
        return False
954
 
 
955
 
    def iter_rules(self, endpoint=None):
956
 
        """Iterate over all rules or the rules of an endpoint.
957
 
 
958
 
        :param endpoint: if provided only the rules for that endpoint
959
 
                         are returned.
960
 
        :return: an iterator
961
 
        """
962
 
        if endpoint is not None:
963
 
            return iter(self._rules_by_endpoint[endpoint])
964
 
        return iter(self._rules)
965
 
 
966
 
    def add(self, rulefactory):
967
 
        """Add a new rule or factory to the map and bind it.  Requires that the
968
 
        rule is not bound to another map.
969
 
 
970
 
        :param rulefactory: a :class:`Rule` or :class:`RuleFactory`
971
 
        """
972
 
        for rule in rulefactory.get_rules(self):
973
 
            rule.bind(self)
974
 
            self._rules.append(rule)
975
 
            self._rules_by_endpoint.setdefault(rule.endpoint, []).append(rule)
976
 
        self._remap = True
977
 
 
978
 
    def bind(self, server_name, script_name=None, subdomain=None,
979
 
             url_scheme='http', default_method='GET', path_info=None):
980
 
        """Return a new :class:`MapAdapter` with the details specified to the
981
 
        call.  Note that `script_name` will default to ``'/'`` if not further
982
 
        specified or `None`.  The `server_name` at least is a requirement
983
 
        because the HTTP RFC requires absolute URLs for redirects and so all
984
 
        redirect exceptions raised by Werkzeug will contain the full canonical
985
 
        URL.
986
 
 
987
 
        If no path_info is passed to :meth:`match` it will use the default path
988
 
        info passed to bind.  While this doesn't really make sense for
989
 
        manual bind calls, it's useful if you bind a map to a WSGI
990
 
        environment which already contains the path info.
991
 
 
992
 
        `subdomain` will default to the `default_subdomain` for this map if
993
 
        no defined. If there is no `default_subdomain` you cannot use the
994
 
        subdomain feature.
995
 
        """
996
 
        if subdomain is None:
997
 
            subdomain = self.default_subdomain
998
 
        if script_name is None:
999
 
            script_name = '/'
1000
 
        return MapAdapter(self, server_name, script_name, subdomain,
1001
 
                          url_scheme, path_info, default_method)
1002
 
 
1003
 
    def bind_to_environ(self, environ, server_name=None, subdomain=None):
1004
 
        """Like :meth:`bind` but you can pass it an WSGI environment and it
1005
 
        will fetch the information from that directory.  Note that because of
1006
 
        limitations in the protocol there is no way to get the current
1007
 
        subdomain and real `server_name` from the environment.  If you don't
1008
 
        provide it, Werkzeug will use `SERVER_NAME` and `SERVER_PORT` (or
1009
 
        `HTTP_HOST` if provided) as used `server_name` with disabled subdomain
1010
 
        feature.
1011
 
 
1012
 
        If `subdomain` is `None` but an environment and a server name is
1013
 
        provided it will calculate the current subdomain automatically.
1014
 
        Example: `server_name` is ``'example.com'`` and the `SERVER_NAME`
1015
 
        in the wsgi `environ` is ``'staging.dev.example.com'`` the calculated
1016
 
        subdomain will be ``'staging.dev'``.
1017
 
 
1018
 
        If the object passed as environ has an environ attribute, the value of
1019
 
        this attribute is used instead.  This allows you to pass request
1020
 
        objects.  Additionally `PATH_INFO` added as a default ot the
1021
 
        :class:`MapAdapter` so that you don't have to pass the path info to
1022
 
        the match method.
1023
 
 
1024
 
        .. versionchanged:: 0.5
1025
 
            previously this method accepted a bogus `calculate_subdomain`
1026
 
            parameter that did not have any effect.  It was removed because
1027
 
            of that.
1028
 
 
1029
 
        :param environ: a WSGI environment.
1030
 
        :param server_name: an optional server name hint (see above).
1031
 
        :param subdomain: optionally the current subdomain (see above).
1032
 
        """
1033
 
        if hasattr(environ, 'environ'):
1034
 
            environ = environ.environ
1035
 
        if server_name is None:
1036
 
            if 'HTTP_HOST' in environ:
1037
 
                server_name = environ['HTTP_HOST']
1038
 
            else:
1039
 
                server_name = environ['SERVER_NAME']
1040
 
                if (environ['wsgi.url_scheme'], environ['SERVER_PORT']) not \
1041
 
                   in (('https', '443'), ('http', '80')):
1042
 
                    server_name += ':' + environ['SERVER_PORT']
1043
 
        elif subdomain is None:
1044
 
            cur_server_name = environ.get('HTTP_HOST',
1045
 
                environ['SERVER_NAME']).split(':', 1)[0].split('.')
1046
 
            real_server_name = server_name.split(':', 1)[0].split('.')
1047
 
            offset = -len(real_server_name)
1048
 
            if cur_server_name[offset:] != real_server_name:
1049
 
                raise ValueError('the server name provided (%r) does not '
1050
 
                                 'match the server name from the WSGI '
1051
 
                                 'environment (%r)' %
1052
 
                                 (environ['SERVER_NAME'], server_name))
1053
 
            subdomain = '.'.join(filter(None, cur_server_name[:offset]))
1054
 
        return Map.bind(self, server_name, environ.get('SCRIPT_NAME'),
1055
 
                        subdomain, environ['wsgi.url_scheme'],
1056
 
                        environ['REQUEST_METHOD'], environ.get('PATH_INFO'))
1057
 
 
1058
 
    def update(self):
1059
 
        """Called before matching and building to keep the compiled rules
1060
 
        in the correct order after things changed.
1061
 
        """
1062
 
        if self._remap:
1063
 
            self._rules.sort(lambda a, b: a.match_compare(b))
1064
 
            for rules in self._rules_by_endpoint.itervalues():
1065
 
                rules.sort(lambda a, b: a.build_compare(b))
1066
 
            self._remap = False
1067
 
 
1068
 
 
1069
 
class MapAdapter(object):
1070
 
    """Returned by :meth:`Map.bind` or :meth:`Map.bind_to_environ` and does
1071
 
    the URL matching and building based on runtime information.
1072
 
    """
1073
 
 
1074
 
    def __init__(self, map, server_name, script_name, subdomain,
1075
 
                 url_scheme, path_info, default_method):
1076
 
        self.map = map
1077
 
        self.server_name = server_name
1078
 
        if not script_name.endswith('/'):
1079
 
            script_name += '/'
1080
 
        self.script_name = script_name
1081
 
        self.subdomain = subdomain
1082
 
        self.url_scheme = url_scheme
1083
 
        self.path_info = path_info or u''
1084
 
        self.default_method = default_method
1085
 
 
1086
 
    def dispatch(self, view_func, path_info=None, method=None,
1087
 
                 catch_http_exceptions=False):
1088
 
        """Does the complete dispatching process.  `view_func` is called with
1089
 
        the endpoint and a dict with the values for the view.  It should
1090
 
        look up the view function, call it, and return a response object
1091
 
        or WSGI application.  http exceptions are not catched by default
1092
 
        so that applications can display nicer error messages by just
1093
 
        catching them by hand.  If you want to stick with the default
1094
 
        error messages you can pass it ``catch_http_exceptions=True`` and
1095
 
        it will catch the http exceptions.
1096
 
 
1097
 
        Here a small example for the dispatch usage::
1098
 
 
1099
 
            from werkzeug import Request, Response, responder
1100
 
            from werkzeug.routing import Map, Rule
1101
 
 
1102
 
            def on_index(request):
1103
 
                return Response('Hello from the index')
1104
 
 
1105
 
            url_map = Map([Rule('/', endpoint='index')])
1106
 
            views = {'index': on_index}
1107
 
 
1108
 
            @responder
1109
 
            def application(environ, start_response):
1110
 
                request = Request(environ)
1111
 
                urls = url_map.bind_to_environ(environ)
1112
 
                return urls.dispatch(lambda e, v: views[e](request, **v),
1113
 
                                     catch_http_exceptions=True)
1114
 
 
1115
 
        Keep in mind that this method might return exception objects, too, so
1116
 
        use :class:`Response.force_type` to get a response object.
1117
 
 
1118
 
        :param view_func: a function that is called with the endpoint as
1119
 
                          first argument and the value dict as second.  Has
1120
 
                          to dispatch to the actual view function with this
1121
 
                          information.  (see above)
1122
 
        :param path_info: the path info to use for matching.  Overrides the
1123
 
                          path info specified on binding.
1124
 
        :param method: the HTTP method used for matching.  Overrides the
1125
 
                       method specified on binding.
1126
 
        :param catch_http_exceptions: set to `True` to catch any of the
1127
 
                                      werkzeug :class:`HTTPException`\s.
1128
 
        """
1129
 
        try:
1130
 
            try:
1131
 
                endpoint, args = self.match(path_info, method)
1132
 
            except RequestRedirect, e:
1133
 
                return e
1134
 
            return view_func(endpoint, args)
1135
 
        except HTTPException, e:
1136
 
            if catch_http_exceptions:
1137
 
                return e
1138
 
            raise
1139
 
 
1140
 
    def match(self, path_info=None, method=None):
1141
 
        """The usage is simple: you just pass the match method the current
1142
 
        path info as well as the method (which defaults to `GET`).  The
1143
 
        following things can then happen:
1144
 
 
1145
 
        - you receive a `NotFound` exception that indicates that no URL is
1146
 
          matching.  A `NotFound` exception is also a WSGI application you
1147
 
          can call to get a default page not found page (happens to be the
1148
 
          same object as `werkzeug.exceptions.NotFound`)
1149
 
 
1150
 
        - you receive a `MethodNotAllowed` exception that indicates that there
1151
 
          is a match for this URL but non for the current request method.
1152
 
          This is useful for RESTful applications.
1153
 
 
1154
 
        - you receive a `RequestRedirect` exception with a `new_url`
1155
 
          attribute.  This exception is used to notify you about a request
1156
 
          Werkzeug requests by your WSGI application.  This is for example the
1157
 
          case if you request ``/foo`` although the correct URL is ``/foo/``
1158
 
          You can use the `RequestRedirect` instance as response-like object
1159
 
          similar to all other subclasses of `HTTPException`.
1160
 
 
1161
 
        - you get a tuple in the form ``(endpoint, arguments)`` when there is
1162
 
          a match.
1163
 
 
1164
 
        If the path info is not passed to the match method the default path
1165
 
        info of the map is used (defaults to the root URL if not defined
1166
 
        explicitly).
1167
 
 
1168
 
        All of the exceptions raised are subclasses of `HTTPException` so they
1169
 
        can be used as WSGI responses.  The will all render generic error or
1170
 
        redirect pages.
1171
 
 
1172
 
        Here is a small example for matching:
1173
 
 
1174
 
        >>> m = Map([
1175
 
        ...     Rule('/', endpoint='index'),
1176
 
        ...     Rule('/downloads/', endpoint='downloads/index'), 
1177
 
        ...     Rule('/downloads/<int:id>', endpoint='downloads/show')
1178
 
        ... ])
1179
 
        >>> urls = m.bind("example.com", "/")
1180
 
        >>> urls.match("/", "GET")
1181
 
        ('index', {})
1182
 
        >>> urls.match("/downloads/42")
1183
 
        ('downloads/show', {'id': 42})
1184
 
 
1185
 
        And here is what happens on redirect and missing URLs:
1186
 
 
1187
 
        >>> urls.match("/downloads")
1188
 
        Traceback (most recent call last):
1189
 
          ...
1190
 
        RequestRedirect: http://example.com/downloads/
1191
 
        >>> urls.match("/missing")
1192
 
        Traceback (most recent call last):
1193
 
          ...
1194
 
        NotFound: 404 Not Found
1195
 
 
1196
 
        :param path_info: the path info to use for matching.  Overrides the
1197
 
                          path info specified on binding.
1198
 
        :param method: the HTTP method used for matching.  Overrides the
1199
 
                       method specified on binding.
1200
 
        """
1201
 
        self.map.update()
1202
 
        if path_info is None:
1203
 
            path_info = self.path_info
1204
 
        if not isinstance(path_info, unicode):
1205
 
            path_info = path_info.decode(self.map.charset, 'ignore')
1206
 
        method = (method or self.default_method).upper()
1207
 
        path = u'%s|/%s' % (self.subdomain, path_info.lstrip('/'))
1208
 
        have_match_for = set()
1209
 
        for rule in self.map._rules:
1210
 
            try:
1211
 
                rv = rule.match(path)
1212
 
            except RequestSlash:
1213
 
                raise RequestRedirect(str('%s://%s%s%s/%s/' % (
1214
 
                    self.url_scheme,
1215
 
                    self.subdomain and self.subdomain + '.' or '',
1216
 
                    self.server_name,
1217
 
                    self.script_name[:-1],
1218
 
                    url_quote(path_info.lstrip('/'), self.map.charset)
1219
 
                )))
1220
 
            if rv is None:
1221
 
                continue
1222
 
            if rule.methods is not None and method not in rule.methods:
1223
 
                have_match_for.update(rule.methods)
1224
 
                continue
1225
 
            if self.map.redirect_defaults:
1226
 
                for r in self.map._rules_by_endpoint[rule.endpoint]:
1227
 
                    if r.provides_defaults_for(rule) and \
1228
 
                       r.suitable_for(rv, method):
1229
 
                        rv.update(r.defaults)
1230
 
                        subdomain, path = r.build(rv)
1231
 
                        raise RequestRedirect(str('%s://%s%s%s/%s' % (
1232
 
                            self.url_scheme,
1233
 
                            subdomain and subdomain + '.' or '',
1234
 
                            self.server_name,
1235
 
                            self.script_name[:-1],
1236
 
                            url_quote(path.lstrip('/'), self.map.charset)
1237
 
                        )))
1238
 
            if rule.redirect_to is not None:
1239
 
                if isinstance(rule.redirect_to, basestring):
1240
 
                    def _handle_match(match):
1241
 
                        value = rv[match.group(1)]
1242
 
                        return rule._converters[match.group(1)].to_url(value)
1243
 
                    redirect_url = _simple_rule_re.sub(_handle_match,
1244
 
                                                       rule.redirect_to)
1245
 
                else:
1246
 
                    redirect_url = rule.redirect_to(self, **rv)
1247
 
                raise RequestRedirect(str(urljoin('%s://%s%s%s' % (
1248
 
                    self.url_scheme,
1249
 
                    self.subdomain and self.subdomain + '.' or '',
1250
 
                    self.server_name,
1251
 
                    self.script_name
1252
 
                ), redirect_url)))
1253
 
            return rule.endpoint, rv
1254
 
        if have_match_for:
1255
 
            raise MethodNotAllowed(valid_methods=list(have_match_for))
1256
 
        raise NotFound()
1257
 
 
1258
 
    def test(self, path_info=None, method=None):
1259
 
        """Test if a rule would match.  Works like `match` but returns `True`
1260
 
        if the URL matches, or `False` if it does not exist.
1261
 
 
1262
 
        :param path_info: the path info to use for matching.  Overrides the
1263
 
                          path info specified on binding.
1264
 
        :param method: the HTTP method used for matching.  Overrides the
1265
 
                       method specified on binding.
1266
 
        """
1267
 
        try:
1268
 
            self.match(path_info, method)
1269
 
        except RequestRedirect:
1270
 
            pass
1271
 
        except NotFound:
1272
 
            return False
1273
 
        return True
1274
 
 
1275
 
    def build(self, endpoint, values=None, method=None, force_external=False):
1276
 
        """Building URLs works pretty much the other way round.  Instead of
1277
 
        `match` you call `build` and pass it the endpoint and a dict of
1278
 
        arguments for the placeholders.
1279
 
 
1280
 
        The `build` function also accepts an argument called `force_external`
1281
 
        which, if you set it to `True` will force external URLs. Per default
1282
 
        external URLs (include the server name) will only be used if the
1283
 
        target URL is on a different subdomain.
1284
 
 
1285
 
        >>> m = Map([
1286
 
        ...     Rule('/', endpoint='index'),
1287
 
        ...     Rule('/downloads/', endpoint='downloads/index'), 
1288
 
        ...     Rule('/downloads/<int:id>', endpoint='downloads/show')
1289
 
        ... ])
1290
 
        >>> urls = m.bind("example.com", "/")
1291
 
        >>> urls.build("index", {})
1292
 
        '/'
1293
 
        >>> urls.build("downloads/show", {'id': 42})
1294
 
        '/downloads/42'
1295
 
        >>> urls.build("downloads/show", {'id': 42}, force_external=True)
1296
 
        'http://example.com/downloads/42'
1297
 
 
1298
 
        Because URLs cannot contain non ASCII data you will always get
1299
 
        bytestrings back.  Non ASCII characters are urlencoded with the
1300
 
        charset defined on the map instance.
1301
 
 
1302
 
        Additional values are converted to unicode and appended to the URL as
1303
 
        URL querystring parameters:
1304
 
 
1305
 
        >>> urls.build("index", {'q': 'My Searchstring'})
1306
 
        '/?q=My+Searchstring'
1307
 
 
1308
 
        If a rule does not exist when building a `BuildError` exception is
1309
 
        raised.
1310
 
 
1311
 
        The build method accepts an argument called `method` which allows you
1312
 
        to specify the method you want to have an URL built for if you have
1313
 
        different methods for the same endpoint specified.
1314
 
 
1315
 
        :param endpoint: the endpoint of the URL to build.
1316
 
        :param values: the values for the URL to build.  Unhandled values are
1317
 
                       appended to the URL as query parameters.
1318
 
        :param method: the HTTP method for the rule if there are different
1319
 
                       URLs for different methods on the same endpoint.
1320
 
        :param force_external: enforce full canonical external URLs.
1321
 
        """
1322
 
        self.map.update()
1323
 
        method = method or self.default_method
1324
 
        if values:
1325
 
            values = dict([(k, v) for k, v in values.items() if v is not None])
1326
 
        else:
1327
 
            values = {}
1328
 
 
1329
 
        for rule in self.map._rules_by_endpoint.get(endpoint, ()):
1330
 
            if rule.suitable_for(values, method):
1331
 
                rv = rule.build(values)
1332
 
                if rv is not None:
1333
 
                    break
1334
 
        else:
1335
 
            raise BuildError(endpoint, values, method)
1336
 
        subdomain, path = rv
1337
 
        if not force_external and subdomain == self.subdomain:
1338
 
            return str(urljoin(self.script_name, path.lstrip('/')))
1339
 
        return str('%s://%s%s%s/%s' % (
1340
 
            self.url_scheme,
1341
 
            subdomain and subdomain + '.' or '',
1342
 
            self.server_name,
1343
 
            self.script_name[:-1],
1344
 
            path.lstrip('/')
1345
 
        ))
1346
 
 
1347
 
 
1348
 
#: the default converter mapping for the map.
1349
 
DEFAULT_CONVERTERS = {
1350
 
    'default':          UnicodeConverter,
1351
 
    'string':           UnicodeConverter,
1352
 
    'any':              AnyConverter,
1353
 
    'path':             PathConverter,
1354
 
    'int':              IntegerConverter,
1355
 
    'float':            FloatConverter
1356
 
}