1
# -*- coding: utf-8 -*-
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.
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
15
Here a simple example that creates an URL map for an application with
16
two subdomains (www and kb) and some URL rules:
20
... Rule('/', endpoint='static/index'),
21
... Rule('/about', endpoint='static/about'),
22
... Rule('/help', endpoint='static/help'),
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')
30
... ], default_subdomain='www')
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
39
Now it's possible to create a URL adapter for one of the subdomains and
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")
51
>>> c.build("static/index", force_external=True)
52
'http://www.example.com/'
54
>>> c = m.bind('example.com', subdomain='kb')
55
>>> c.build("static/about")
56
'http://www.example.com/about'
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
63
>>> c = m.bind('example.com', '/applications/example')
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`.
69
And here is how you can match URLs:
71
>>> c = m.bind('example.com')
76
>>> c = m.bind('example.com', '/', 'kb')
79
>>> c.match("/browse/42/23")
80
('kb/browse', {'id': 42, 'page': 23})
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
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`
95
:copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details.
97
:license: BSD, see LICENSE for more details.
100
from urlparse import urljoin
101
from itertools import izip
103
from werkzeug.utils import url_encode, url_quote, redirect, format_string
104
from werkzeug.exceptions import HTTPException, NotFound, MethodNotAllowed
107
_rule_re = re.compile(r'''
108
(?P<static>[^<]*) # static rule data
111
(?P<converter>[a-zA-Z_][a-zA-Z0-9_]*) # converter name
112
(?:\((?P<args>.*?)\))? # converter arguments
113
\: # variable delimiter
115
(?P<variable>[a-zA-Z][a-zA-Z0-9_]*) # variable name
118
_simple_rule_re = re.compile(r'<([^>]+)>')
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.
130
do_match = _rule_re.match
133
m = do_match(rule, pos)
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
147
remaining = rule[pos:]
148
if '>' in remaining or '<' in remaining:
149
raise ValueError('malformed url rule: %r' % rule)
150
yield None, None, remaining
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.
159
if not name in map.converters:
160
raise LookupError('the converter %r does not exist' % name)
162
storage = type('_Storage', (), {'__getitem__': lambda s, x: x})()
163
args, kwargs = eval(u'(lambda *a, **kw: (a, kw))(%s)' % args, {}, storage)
167
return map.converters[name](map, *args, **kwargs)
170
class RoutingException(Exception):
171
"""Special exceptions that require the application to redirect, notifies
172
him about missing urls etc.
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.
182
The attribute `new_url` contains the absolute destination url.
186
def __init__(self, new_url):
187
RoutingException.__init__(self, new_url)
188
self.new_url = new_url
190
def get_response(self, environ):
191
return redirect(self.new_url, 301)
194
class RequestSlash(RoutingException):
195
"""Internal exception."""
198
class BuildError(RoutingException, LookupError):
199
"""Raised if the build system cannot find a URL for an endpoint with the
203
def __init__(self, endpoint, values, method):
204
LookupError.__init__(self, endpoint, values, method)
205
self.endpoint = endpoint
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.
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`.
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()
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::
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')
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.
247
def __init__(self, subdomain, rules):
248
self.subdomain = subdomain
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
258
class Submount(RuleFactory):
259
"""Like `Subdomain` but prefixes the URL rule with a given string::
262
Rule('/', endpoint='index'),
264
Rule('/', endpoint='blog/index'),
265
Rule('/entry/<entry_slug>', endpoint='blog/show')
269
Now the rule ``'blog/show'`` matches ``/blog/entry/<entry_slug>``.
272
def __init__(self, path, rules):
273
self.path = path.rstrip('/')
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
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::
288
Rule('/', endpoint='index'),
289
EndpointPrefix('blog/', [Submount('/blog', [
290
Rule('/', endpoint='index'),
291
Rule('/entry/<entry_slug>', endpoint='show')
296
def __init__(self, prefix, rules):
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
307
class RuleTemplate(object):
308
"""Returns copies of the rules wrapped and expands string templates in
309
the endpoint, rule, defaults or subdomain sections.
311
Here a small example for such a rule template::
313
from werkzeug.routing import Map, Rule, RuleTemplate
315
resource = RuleTemplate([
316
Rule('/$name/', endpoint='$name.list'),
317
Rule('/$name/<int:id>', endpoint='$name.show')
320
url_map = Map([resource(name='user'), resource(name='page')])
322
When a rule template is called the keyword arguments are used to
323
replace the placeholders in all the string parameters.
326
def __init__(self, rules):
327
self.rules = list(rules)
329
def __call__(self, *args, **kwargs):
330
return RuleTemplateFactory(self.rules, dict(*args, **kwargs))
333
class RuleTemplateFactory(RuleFactory):
334
"""A factory that fills in template variables into rules. Used by
335
`RuleTemplate` internally.
340
def __init__(self, rules, context):
342
self.context = context
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:
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)
360
format_string(rule.rule, self.context),
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.
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.
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.
387
The converters are defined on the `Map`.
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.
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::
399
Rule('/all/', defaults={'page': 1}, endpoint='all_entries'),
400
Rule('/all/page/<int:page>', endpoint='all_entries')
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
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.
413
Can be useful if you want to have user profiles on different subdomains
414
and all subdomains are forwarded to your application::
417
Rule('/', subdomain='<username>', endpoint='user/homepage'),
418
Rule('/stats', subdomain='<username>', endpoint='user/stats')
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`.
430
Override the `Map` setting for `strict_slashes` only for this rule. If
431
not specified the `Map` setting is used.
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)
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
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)
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)
456
When the rule is matched the routing system will raise a
457
`RequestRedirect` exception with the target for the redirect.
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.
464
def __init__(self, string, defaults=None, subdomain=None, methods=None,
465
build_only=False, endpoint=None, strict_slashes=None,
467
if not string.startswith('/'):
468
raise ValueError('urls must start with a leading slash')
470
self.is_leaf = not string.endswith('/')
473
self.strict_slashes = strict_slashes
474
self.subdomain = subdomain
475
self.defaults = defaults
476
self.build_only = build_only
480
self.methods = set([x.upper() for x in methods])
481
self.endpoint = endpoint
483
self.redirect_to = redirect_to
486
if defaults is not None:
487
self.arguments = set(map(str, defaults))
489
self.arguments = set()
490
self._converters = {}
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,
501
def get_rules(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.
510
if self.map is not None:
511
raise RuntimeError('url rule %r already bound to map %r' %
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
519
rule = self.subdomain + '|' + (self.is_leaf and self.rule
520
or self.rule.rstrip('/'))
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))
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:
538
self._trace.append((False, '/'))
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 ''
546
self._regex = re.compile(regex, re.UNICODE)
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.
552
If the rule matches a dict with the converted values is returned,
553
otherwise the return value is `None`.
557
if not self.build_only:
558
m = self._regex.search(path)
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
565
if self.strict_slashes and not self.is_leaf and \
566
not groups.pop('__suffix__'):
568
# if we are not in strict slashes mode we have to remove
570
elif not self.strict_slashes:
571
del groups['__suffix__']
574
for name, value in groups.iteritems():
576
value = self._converters[name].to_python(value)
577
except ValidationError:
579
result[str(name)] = value
580
if self.defaults is not None:
581
result.update(self.defaults)
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.
592
processed = set(self.arguments)
593
for is_dynamic, data in self._trace:
596
add(self._converters[data].to_url(values[data]))
597
except ValidationError:
602
subdomain, url = (u''.join(tmp)).split('|', 1)
605
for key in set(values) - processed:
606
query_vars[key] = unicode(values[key])
608
url += '?' + url_encode(query_vars, self.map.charset,
609
sort=self.map.sort_parameters,
610
key=self.map.sort_key)
612
return subdomain, url
614
def provides_defaults_for(self, rule):
615
"""Check if this rule has defaults for a given rule.
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
623
def suitable_for(self, values, method):
624
"""Check if the dict of values has enough data for url generation.
628
if self.methods is not None and method not in self.methods:
631
valueset = set(values)
633
for key in self.arguments - set(self.defaults or ()):
634
if key not in values:
637
if self.arguments.issubset(valueset):
638
if self.defaults is None:
640
for key, value in self.defaults.iteritems():
641
if value != values[key]:
646
def match_compare(self, other):
647
"""Compare this object with another one for matching.
651
for sw, ow in izip(self._weights, other._weights):
656
if len(self._weights) > len(other._weights):
658
if len(self._weights) < len(other._weights):
660
if not other.arguments and self.arguments:
662
elif other.arguments and not self.arguments:
664
elif other.defaults is None and self.defaults is not None:
666
elif other.defaults is not None and self.defaults is None:
668
elif self.greediness > other.greediness:
670
elif self.greediness < other.greediness:
672
elif len(self.arguments) > len(other.arguments):
674
elif len(self.arguments) < len(other.arguments):
678
def build_compare(self, other):
679
"""Compare this object with another one for building.
683
if not other.arguments and self.arguments:
685
elif other.arguments and not self.arguments:
687
elif other.defaults is None and self.defaults is not None:
689
elif other.defaults is not None and self.defaults is None:
691
elif self.provides_defaults_for(other):
693
elif other.provides_defaults_for(self):
695
elif self.greediness > other.greediness:
697
elif self.greediness < other.greediness:
699
elif len(self.arguments) > len(other.arguments):
701
elif len(self.arguments) < len(other.arguments):
705
def __eq__(self, other):
706
return self.__class__ is other.__class__ and \
707
self._trace == other._trace
709
def __ne__(self, other):
710
return not self.__eq__(other)
712
def __unicode__(self):
716
charset = self.map is not None and self.map.charset or 'utf-8'
717
return unicode(self).encode(charset)
721
return '<%s (unbound)>' % self.__class__.__name__
722
charset = self.map is not None and self.map.charset or 'utf-8'
724
for is_dynamic, data in self._trace:
726
tmp.append('<%s>' % 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 '',
738
class BaseConverter(object):
739
"""Base class for all converters."""
744
def __init__(self, map):
747
def to_python(self, value):
750
def to_url(self, value):
751
return url_quote(value, self.map.charset)
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.
758
This is the default validator.
762
Rule('/pages/<page>'),
763
Rule('/<string(length=2):lang_code>')
765
:param map: the :class:`Map`.
766
:param minlength: the minimum length of the string. Must be greater
768
:param maxlength: the maximum length of the string.
769
:param length: the exact length of the string.
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)
777
if maxlength is None:
780
maxlength = int(maxlength)
781
length = '{%s,%s}' % (
785
self.regex = '[^/]' + length
788
class AnyConverter(BaseConverter):
789
"""Matches one of the items provided. Items can either be Python
790
identifiers or unicode strings::
792
Rule('/<any(about, help, imprint, u"class"):page_name>')
794
:param map: the :class:`Map`.
795
:param items: this function accepts the possible items as positional
799
def __init__(self, map, *items):
800
BaseConverter.__init__(self, map)
801
self.regex = '(?:%s)' % '|'.join([re.escape(x) for x in items])
804
class PathConverter(BaseConverter):
805
"""Like the default :class:`UnicodeConverter`, but it also matches
806
slashes. This is useful for wikis and similar applications::
808
Rule('/<path:wikipage>')
809
Rule('/<path:wikipage>/edit')
811
:param map: the :class:`Map`.
812
:param minlength: the minimum length of the string. Must be greater
814
:param maxlength: the maximum length of the string.
815
:param length: the exact length of the string.
822
class NumberConverter(BaseConverter):
823
"""Baseclass for `IntegerConverter` and `FloatConverter`.
828
def __init__(self, map, fixed_digits=0, min=None, max=None):
829
BaseConverter.__init__(self, map)
830
self.fixed_digits = fixed_digits
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()
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
850
class IntegerConverter(NumberConverter):
851
"""This converter only accepts integer values::
853
Rule('/page/<int:page>')
855
This converter does not support negative values.
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.
869
class FloatConverter(NumberConverter):
870
"""This converter only accepts floating point values::
872
Rule('/probability/<float:probability>')
874
This converter does not support negative values.
876
:param map: the :class:`Map`.
877
:param min: the minimal value.
878
:param max: the maximal value.
883
def __init__(self, map, min=None, max=None):
884
NumberConverter.__init__(self, map, 0, min, max)
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!
894
:param rules: sequence of url rules for this map.
895
:param default_subdomain: The default subdomain for rules without a
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
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`.
909
.. versionadded:: 0.5
910
`sort_parameters` and `sort_key` was added.
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):
917
self._rules_by_endpoint = {}
920
self.default_subdomain = default_subdomain
921
self.charset = charset
922
self.strict_slashes = strict_slashes
923
self.redirect_defaults = redirect_defaults
925
self.converters = DEFAULT_CONVERTERS.copy()
927
self.converters.update(converters)
929
self.sort_parameters = sort_parameters
930
self.sort_key = sort_key
932
for rulefactory in rules or ():
933
self.add(rulefactory)
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
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
949
arguments = set(arguments)
950
for rule in self._rules_by_endpoint[endpoint]:
951
if arguments.issubset(rule.arguments):
955
def iter_rules(self, endpoint=None):
956
"""Iterate over all rules or the rules of an endpoint.
958
:param endpoint: if provided only the rules for that endpoint
962
if endpoint is not None:
963
return iter(self._rules_by_endpoint[endpoint])
964
return iter(self._rules)
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.
970
:param rulefactory: a :class:`Rule` or :class:`RuleFactory`
972
for rule in rulefactory.get_rules(self):
974
self._rules.append(rule)
975
self._rules_by_endpoint.setdefault(rule.endpoint, []).append(rule)
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
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.
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
996
if subdomain is None:
997
subdomain = self.default_subdomain
998
if script_name is None:
1000
return MapAdapter(self, server_name, script_name, subdomain,
1001
url_scheme, path_info, default_method)
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
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'``.
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
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
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).
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']
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'))
1059
"""Called before matching and building to keep the compiled rules
1060
in the correct order after things changed.
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))
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.
1074
def __init__(self, map, server_name, script_name, subdomain,
1075
url_scheme, path_info, default_method):
1077
self.server_name = server_name
1078
if not script_name.endswith('/'):
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
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.
1097
Here a small example for the dispatch usage::
1099
from werkzeug import Request, Response, responder
1100
from werkzeug.routing import Map, Rule
1102
def on_index(request):
1103
return Response('Hello from the index')
1105
url_map = Map([Rule('/', endpoint='index')])
1106
views = {'index': on_index}
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)
1115
Keep in mind that this method might return exception objects, too, so
1116
use :class:`Response.force_type` to get a response object.
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.
1131
endpoint, args = self.match(path_info, method)
1132
except RequestRedirect, e:
1134
return view_func(endpoint, args)
1135
except HTTPException, e:
1136
if catch_http_exceptions:
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:
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`)
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.
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`.
1161
- you get a tuple in the form ``(endpoint, arguments)`` when there is
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
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
1172
Here is a small example for matching:
1175
... Rule('/', endpoint='index'),
1176
... Rule('/downloads/', endpoint='downloads/index'),
1177
... Rule('/downloads/<int:id>', endpoint='downloads/show')
1179
>>> urls = m.bind("example.com", "/")
1180
>>> urls.match("/", "GET")
1182
>>> urls.match("/downloads/42")
1183
('downloads/show', {'id': 42})
1185
And here is what happens on redirect and missing URLs:
1187
>>> urls.match("/downloads")
1188
Traceback (most recent call last):
1190
RequestRedirect: http://example.com/downloads/
1191
>>> urls.match("/missing")
1192
Traceback (most recent call last):
1194
NotFound: 404 Not Found
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.
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:
1211
rv = rule.match(path)
1212
except RequestSlash:
1213
raise RequestRedirect(str('%s://%s%s%s/%s/' % (
1215
self.subdomain and self.subdomain + '.' or '',
1217
self.script_name[:-1],
1218
url_quote(path_info.lstrip('/'), self.map.charset)
1222
if rule.methods is not None and method not in rule.methods:
1223
have_match_for.update(rule.methods)
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' % (
1233
subdomain and subdomain + '.' or '',
1235
self.script_name[:-1],
1236
url_quote(path.lstrip('/'), self.map.charset)
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,
1246
redirect_url = rule.redirect_to(self, **rv)
1247
raise RequestRedirect(str(urljoin('%s://%s%s%s' % (
1249
self.subdomain and self.subdomain + '.' or '',
1253
return rule.endpoint, rv
1255
raise MethodNotAllowed(valid_methods=list(have_match_for))
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.
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.
1268
self.match(path_info, method)
1269
except RequestRedirect:
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.
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.
1286
... Rule('/', endpoint='index'),
1287
... Rule('/downloads/', endpoint='downloads/index'),
1288
... Rule('/downloads/<int:id>', endpoint='downloads/show')
1290
>>> urls = m.bind("example.com", "/")
1291
>>> urls.build("index", {})
1293
>>> urls.build("downloads/show", {'id': 42})
1295
>>> urls.build("downloads/show", {'id': 42}, force_external=True)
1296
'http://example.com/downloads/42'
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.
1302
Additional values are converted to unicode and appended to the URL as
1303
URL querystring parameters:
1305
>>> urls.build("index", {'q': 'My Searchstring'})
1306
'/?q=My+Searchstring'
1308
If a rule does not exist when building a `BuildError` exception is
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.
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.
1323
method = method or self.default_method
1325
values = dict([(k, v) for k, v in values.items() if v is not None])
1329
for rule in self.map._rules_by_endpoint.get(endpoint, ()):
1330
if rule.suitable_for(values, method):
1331
rv = rule.build(values)
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' % (
1341
subdomain and subdomain + '.' or '',
1343
self.script_name[:-1],
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