NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

routing.py (24620B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\carbon\common\lib\werkzeug\routing.py
      2 import re
      3 from pprint import pformat
      4 from urlparse import urljoin
      5 from itertools import izip
      6 from werkzeug.urls import url_encode, url_quote
      7 from werkzeug.utils import redirect, format_string
      8 from werkzeug.exceptions import HTTPException, NotFound, MethodNotAllowed
      9 from werkzeug._internal import _get_environ
     10 _rule_re = re.compile('\n    (?P<static>[^<]*)                           # static rule data\n    <\n    (?:\n        (?P<converter>[a-zA-Z_][a-zA-Z0-9_]*)   # converter name\n        (?:\\((?P<args>.*?)\\))?                  # converter arguments\n        \\:                                      # variable delimiter\n    )?\n    (?P<variable>[a-zA-Z][a-zA-Z0-9_]*)         # variable name\n    >\n', re.VERBOSE)
     11 _simple_rule_re = re.compile('<([^>]+)>')
     12 
     13 def parse_rule(rule):
     14     pos = 0
     15     end = len(rule)
     16     do_match = _rule_re.match
     17     used_names = set()
     18     while pos < end:
     19         m = do_match(rule, pos)
     20         if m is None:
     21             break
     22         data = m.groupdict()
     23         if data['static']:
     24             yield (None, None, data['static'])
     25         variable = data['variable']
     26         converter = data['converter'] or 'default'
     27         if variable in used_names:
     28             raise ValueError('variable name %r used twice.' % variable)
     29         used_names.add(variable)
     30         yield (converter, data['args'] or None, variable)
     31         pos = m.end()
     32 
     33     if pos < end:
     34         remaining = rule[pos:]
     35         if '>' in remaining or '<' in remaining:
     36             raise ValueError('malformed url rule: %r' % rule)
     37         yield (None, None, remaining)
     38 
     39 
     40 def get_converter(map, name, args):
     41     if name not in map.converters:
     42         raise LookupError('the converter %r does not exist' % name)
     43     if args:
     44         storage = type('_Storage', (), {'__getitem__': lambda s, x: x})()
     45         args, kwargs = eval(u'(lambda *a, **kw: (a, kw))(%s)' % args, {}, storage)
     46     else:
     47         args = ()
     48         kwargs = {}
     49     return map.converters[name](map, *args, **kwargs)
     50 
     51 
     52 class RoutingException(Exception):
     53     pass
     54 
     55 
     56 class RequestRedirect(HTTPException, RoutingException):
     57     code = 301
     58 
     59     def __init__(self, new_url):
     60         RoutingException.__init__(self, new_url)
     61         self.new_url = new_url
     62 
     63     def get_response(self, environ):
     64         return redirect(self.new_url, 301)
     65 
     66 
     67 class RequestSlash(RoutingException):
     68     pass
     69 
     70 
     71 class BuildError(RoutingException, LookupError):
     72 
     73     def __init__(self, endpoint, values, method):
     74         LookupError.__init__(self, endpoint, values, method)
     75         self.endpoint = endpoint
     76         self.values = values
     77         self.method = method
     78 
     79 
     80 class ValidationError(ValueError):
     81     pass
     82 
     83 
     84 class RuleFactory(object):
     85 
     86     def get_rules(self, map):
     87         raise NotImplementedError()
     88 
     89 
     90 class Subdomain(RuleFactory):
     91 
     92     def __init__(self, subdomain, rules):
     93         self.subdomain = subdomain
     94         self.rules = rules
     95 
     96     def get_rules(self, map):
     97         for rulefactory in self.rules:
     98             for rule in rulefactory.get_rules(map):
     99                 rule = rule.empty()
    100                 rule.subdomain = self.subdomain
    101                 yield rule
    102 
    103 
    104 class Submount(RuleFactory):
    105 
    106     def __init__(self, path, rules):
    107         self.path = path.rstrip('/')
    108         self.rules = rules
    109 
    110     def get_rules(self, map):
    111         for rulefactory in self.rules:
    112             for rule in rulefactory.get_rules(map):
    113                 rule = rule.empty()
    114                 rule.rule = self.path + rule.rule
    115                 yield rule
    116 
    117 
    118 class EndpointPrefix(RuleFactory):
    119 
    120     def __init__(self, prefix, rules):
    121         self.prefix = prefix
    122         self.rules = rules
    123 
    124     def get_rules(self, map):
    125         for rulefactory in self.rules:
    126             for rule in rulefactory.get_rules(map):
    127                 rule = rule.empty()
    128                 rule.endpoint = self.prefix + rule.endpoint
    129                 yield rule
    130 
    131 
    132 class RuleTemplate(object):
    133 
    134     def __init__(self, rules):
    135         self.rules = list(rules)
    136 
    137     def __call__(self, *args, **kwargs):
    138         return RuleTemplateFactory(self.rules, dict(*args, **kwargs))
    139 
    140 
    141 class RuleTemplateFactory(RuleFactory):
    142 
    143     def __init__(self, rules, context):
    144         self.rules = rules
    145         self.context = context
    146 
    147     def get_rules(self, map):
    148         for rulefactory in self.rules:
    149             for rule in rulefactory.get_rules(map):
    150                 new_defaults = subdomain = None
    151                 if rule.defaults is not None:
    152                     new_defaults = {}
    153                     for key, value in rule.defaults.iteritems():
    154                         if isinstance(value, basestring):
    155                             value = format_string(value, self.context)
    156                         new_defaults[key] = value
    157 
    158                 if rule.subdomain is not None:
    159                     subdomain = format_string(rule.subdomain, self.context)
    160                 new_endpoint = rule.endpoint
    161                 if isinstance(new_endpoint, basestring):
    162                     new_endpoint = format_string(new_endpoint, self.context)
    163                 yield Rule(format_string(rule.rule, self.context), new_defaults, subdomain, rule.methods, rule.build_only, new_endpoint, rule.strict_slashes)
    164 
    165 
    166 class Rule(RuleFactory):
    167 
    168     def __init__(self, string, defaults = None, subdomain = None, methods = None, build_only = False, endpoint = None, strict_slashes = None, redirect_to = None):
    169         if not string.startswith('/'):
    170             raise ValueError('urls must start with a leading slash')
    171         self.rule = string
    172         self.is_leaf = not string.endswith('/')
    173         self.map = None
    174         self.strict_slashes = strict_slashes
    175         self.subdomain = subdomain
    176         self.defaults = defaults
    177         self.build_only = build_only
    178         if methods is None:
    179             self.methods = None
    180         else:
    181             self.methods = set([ x.upper() for x in methods ])
    182             if 'HEAD' not in self.methods and 'GET' in self.methods:
    183                 self.methods.add('HEAD')
    184         self.endpoint = endpoint
    185         self.greediness = 0
    186         self.redirect_to = redirect_to
    187         if defaults is not None:
    188             self.arguments = set(map(str, defaults))
    189         else:
    190             self.arguments = set()
    191         self._trace = self._converters = self._regex = self._weights = None
    192 
    193     def empty(self):
    194         defaults = None
    195         if self.defaults is not None:
    196             defaults = dict(self.defaults)
    197         return Rule(self.rule, defaults, self.subdomain, self.methods, self.build_only, self.endpoint, self.strict_slashes, self.redirect_to)
    198 
    199     def get_rules(self, map):
    200         yield self
    201 
    202     def refresh(self):
    203         self.bind(self.map, rebind=True)
    204 
    205     def bind(self, map, rebind = False):
    206         if self.map is not None and not rebind:
    207             raise RuntimeError('url rule %r already bound to map %r' % (self, self.map))
    208         self.map = map
    209         if self.strict_slashes is None:
    210             self.strict_slashes = map.strict_slashes
    211         if self.subdomain is None:
    212             self.subdomain = map.default_subdomain
    213         rule = self.subdomain + '|' + (self.is_leaf and self.rule or self.rule.rstrip('/'))
    214         self._trace = []
    215         self._converters = {}
    216         self._weights = []
    217         regex_parts = []
    218         for converter, arguments, variable in parse_rule(rule):
    219             if converter is None:
    220                 regex_parts.append(re.escape(variable))
    221                 self._trace.append((False, variable))
    222                 self._weights.append(len(variable))
    223             else:
    224                 convobj = get_converter(map, converter, arguments)
    225                 regex_parts.append('(?P<%s>%s)' % (variable, convobj.regex))
    226                 self._converters[variable] = convobj
    227                 self._trace.append((True, variable))
    228                 self._weights.append(convobj.weight)
    229                 self.arguments.add(str(variable))
    230                 if convobj.is_greedy:
    231                     self.greediness += 1
    232 
    233         if not self.is_leaf:
    234             self._trace.append((False, '/'))
    235         if not self.build_only:
    236             regex = '^%s%s$' % (u''.join(regex_parts), (not self.is_leaf or not self.strict_slashes) and '(?<!/)(?P<__suffix__>/?)' or '')
    237             self._regex = re.compile(regex, re.UNICODE)
    238 
    239     def match(self, path):
    240         if not self.build_only:
    241             m = self._regex.search(path)
    242             if m is not None:
    243                 groups = m.groupdict()
    244                 if self.strict_slashes and not self.is_leaf and not groups.pop('__suffix__'):
    245                     raise RequestSlash()
    246                 elif not self.strict_slashes:
    247                     del groups['__suffix__']
    248                 result = {}
    249                 for name, value in groups.iteritems():
    250                     try:
    251                         value = self._converters[name].to_python(value)
    252                     except ValidationError:
    253                         return
    254 
    255                     result[str(name)] = value
    256 
    257                 if self.defaults is not None:
    258                     result.update(self.defaults)
    259                 return result
    260 
    261     def build(self, values, append_unknown = True):
    262         tmp = []
    263         add = tmp.append
    264         processed = set(self.arguments)
    265         for is_dynamic, data in self._trace:
    266             if is_dynamic:
    267                 try:
    268                     add(self._converters[data].to_url(values[data]))
    269                 except ValidationError:
    270                     return
    271 
    272                 processed.add(data)
    273             else:
    274                 add(data)
    275 
    276         subdomain, url = u''.join(tmp).split('|', 1)
    277         if append_unknown:
    278             query_vars = MultiDict(values)
    279             for key in processed:
    280                 if key in query_vars:
    281                     del query_vars[key]
    282 
    283             if query_vars:
    284                 url += '?' + url_encode(query_vars, self.map.charset, sort=self.map.sort_parameters, key=self.map.sort_key)
    285         return (subdomain, url)
    286 
    287     def provides_defaults_for(self, rule):
    288         return not self.build_only and self.defaults is not None and self.endpoint == rule.endpoint and self != rule and self.arguments == rule.arguments
    289 
    290     def suitable_for(self, values, method = None):
    291         if method is not None:
    292             if self.methods is not None and method not in self.methods:
    293                 return False
    294         valueset = set(values)
    295         for key in self.arguments - set(self.defaults or ()):
    296             if key not in values:
    297                 return False
    298 
    299         if self.arguments.issubset(valueset):
    300             if self.defaults is None:
    301                 return True
    302             for key, value in self.defaults.iteritems():
    303                 if value != values[key]:
    304                     return False
    305 
    306         return True
    307 
    308     def match_compare(self, other):
    309         for sw, ow in izip(self._weights, other._weights):
    310             if sw > ow:
    311                 return -1
    312             if sw < ow:
    313                 return 1
    314 
    315         if len(self._weights) > len(other._weights):
    316             return -1
    317         if len(self._weights) < len(other._weights):
    318             return 1
    319         if not other.arguments and self.arguments:
    320             return 1
    321         if other.arguments and not self.arguments:
    322             return -1
    323         if other.defaults is None and self.defaults is not None:
    324             return 1
    325         if other.defaults is not None and self.defaults is None:
    326             return -1
    327         if self.greediness > other.greediness:
    328             return -1
    329         if self.greediness < other.greediness:
    330             return 1
    331         if len(self.arguments) > len(other.arguments):
    332             return 1
    333         if len(self.arguments) < len(other.arguments):
    334             return -1
    335         return 1
    336 
    337     def build_compare(self, other):
    338         if not other.arguments and self.arguments:
    339             return -1
    340         if other.arguments and not self.arguments:
    341             return 1
    342         if other.defaults is None and self.defaults is not None:
    343             return -1
    344         if other.defaults is not None and self.defaults is None:
    345             return 1
    346         if self.provides_defaults_for(other):
    347             return -1
    348         if other.provides_defaults_for(self):
    349             return 1
    350         if self.greediness > other.greediness:
    351             return -1
    352         if self.greediness < other.greediness:
    353             return 1
    354         if len(self.arguments) > len(other.arguments):
    355             return -1
    356         if len(self.arguments) < len(other.arguments):
    357             return 1
    358         return -1
    359 
    360     def __eq__(self, other):
    361         return self.__class__ is other.__class__ and self._trace == other._trace
    362 
    363     def __ne__(self, other):
    364         return not self.__eq__(other)
    365 
    366     def __unicode__(self):
    367         return self.rule
    368 
    369     def __str__(self):
    370         charset = self.map is not None and self.map.charset or 'utf-8'
    371         return unicode(self).encode(charset)
    372 
    373     def __repr__(self):
    374         if self.map is None:
    375             return '<%s (unbound)>' % self.__class__.__name__
    376         charset = self.map is not None and self.map.charset or 'utf-8'
    377         tmp = []
    378         for is_dynamic, data in self._trace:
    379             if is_dynamic:
    380                 tmp.append('<%s>' % data)
    381             else:
    382                 tmp.append(data)
    383 
    384         return '<%s %r%s -> %s>' % (self.__class__.__name__,
    385          u''.join(tmp).encode(charset).lstrip('|'),
    386          self.methods is not None and ' (%s)' % ', '.join(self.methods) or '',
    387          self.endpoint)
    388 
    389 
    390 class BaseConverter(object):
    391     regex = '[^/]+'
    392     is_greedy = False
    393     weight = 100
    394 
    395     def __init__(self, map):
    396         self.map = map
    397 
    398     def to_python(self, value):
    399         return value
    400 
    401     def to_url(self, value):
    402         return url_quote(value, self.map.charset)
    403 
    404 
    405 class UnicodeConverter(BaseConverter):
    406 
    407     def __init__(self, map, minlength = 1, maxlength = None, length = None):
    408         BaseConverter.__init__(self, map)
    409         if length is not None:
    410             length = '{%d}' % int(length)
    411         else:
    412             if maxlength is None:
    413                 maxlength = ''
    414             else:
    415                 maxlength = int(maxlength)
    416             length = '{%s,%s}' % (int(minlength), maxlength)
    417         self.regex = '[^/]' + length
    418 
    419 
    420 class AnyConverter(BaseConverter):
    421 
    422     def __init__(self, map, *items):
    423         BaseConverter.__init__(self, map)
    424         self.regex = '(?:%s)' % '|'.join([ re.escape(x) for x in items ])
    425 
    426 
    427 class PathConverter(BaseConverter):
    428     regex = '[^/].*?'
    429     is_greedy = True
    430     weight = 50
    431 
    432 
    433 class NumberConverter(BaseConverter):
    434 
    435     def __init__(self, map, fixed_digits = 0, min = None, max = None):
    436         BaseConverter.__init__(self, map)
    437         self.fixed_digits = fixed_digits
    438         self.min = min
    439         self.max = max
    440 
    441     def to_python(self, value):
    442         if self.fixed_digits and len(value) != self.fixed_digits:
    443             raise ValidationError()
    444         value = self.num_convert(value)
    445         if self.min is not None and value < self.min or self.max is not None and value > self.max:
    446             raise ValidationError()
    447         return value
    448 
    449     def to_url(self, value):
    450         value = self.num_convert(value)
    451         if self.fixed_digits:
    452             value = '%%0%sd' % self.fixed_digits % value
    453         return str(value)
    454 
    455 
    456 class IntegerConverter(NumberConverter):
    457     regex = '\\d+'
    458     num_convert = int
    459 
    460 
    461 class FloatConverter(NumberConverter):
    462     regex = '\\d+\\.\\d+'
    463     num_convert = float
    464 
    465     def __init__(self, map, min = None, max = None):
    466         NumberConverter.__init__(self, map, 0, min, max)
    467 
    468 
    469 class Map(object):
    470     default_converters = None
    471 
    472     def __init__(self, rules = None, default_subdomain = '', charset = 'utf-8', strict_slashes = True, redirect_defaults = True, converters = None, sort_parameters = False, sort_key = None):
    473         self._rules = []
    474         self._rules_by_endpoint = {}
    475         self._remap = True
    476         self.default_subdomain = default_subdomain
    477         self.charset = charset
    478         self.strict_slashes = strict_slashes
    479         self.redirect_defaults = redirect_defaults
    480         self.converters = self.default_converters.copy()
    481         if converters:
    482             self.converters.update(converters)
    483         self.sort_parameters = sort_parameters
    484         self.sort_key = sort_key
    485         for rulefactory in rules or ():
    486             self.add(rulefactory)
    487 
    488     def is_endpoint_expecting(self, endpoint, *arguments):
    489         self.update()
    490         arguments = set(arguments)
    491         for rule in self._rules_by_endpoint[endpoint]:
    492             if arguments.issubset(rule.arguments):
    493                 return True
    494 
    495         return False
    496 
    497     def iter_rules(self, endpoint = None):
    498         if endpoint is not None:
    499             return iter(self._rules_by_endpoint[endpoint])
    500         return iter(self._rules)
    501 
    502     def add(self, rulefactory):
    503         for rule in rulefactory.get_rules(self):
    504             rule.bind(self)
    505             self._rules.append(rule)
    506             self._rules_by_endpoint.setdefault(rule.endpoint, []).append(rule)
    507 
    508         self._remap = True
    509 
    510     def bind(self, server_name, script_name = None, subdomain = None, url_scheme = 'http', default_method = 'GET', path_info = None):
    511         if subdomain is None:
    512             subdomain = self.default_subdomain
    513         if script_name is None:
    514             script_name = '/'
    515         return MapAdapter(self, server_name, script_name, subdomain, url_scheme, path_info, default_method)
    516 
    517     def bind_to_environ(self, environ, server_name = None, subdomain = None):
    518         environ = _get_environ(environ)
    519         if server_name is None:
    520             if 'HTTP_HOST' in environ:
    521                 server_name = environ['HTTP_HOST']
    522             else:
    523                 server_name = environ['SERVER_NAME']
    524                 if (environ['wsgi.url_scheme'], environ['SERVER_PORT']) not in (('https', '443'), ('http', '80')):
    525                     server_name += ':' + environ['SERVER_PORT']
    526         elif subdomain is None:
    527             wsgi_server_name = environ.get('HTTP_HOST', environ['SERVER_NAME'])
    528             cur_server_name = wsgi_server_name.split(':', 1)[0].split('.')
    529             real_server_name = server_name.split(':', 1)[0].split('.')
    530             offset = -len(real_server_name)
    531             if cur_server_name[offset:] != real_server_name:
    532                 raise ValueError('the server name provided (%r) does not match the server name from the WSGI environment (%r)' % (server_name, wsgi_server_name))
    533             subdomain = '.'.join(filter(None, cur_server_name[:offset]))
    534         return Map.bind(self, server_name, environ.get('SCRIPT_NAME'), subdomain, environ['wsgi.url_scheme'], environ['REQUEST_METHOD'], environ.get('PATH_INFO'))
    535 
    536     def update(self):
    537         if self._remap:
    538             self._rules.sort(lambda a, b: a.match_compare(b))
    539             for rules in self._rules_by_endpoint.itervalues():
    540                 rules.sort(lambda a, b: a.build_compare(b))
    541 
    542             self._remap = False
    543 
    544     def __repr__(self):
    545         rules = self.iter_rules()
    546         return '%s([%s])' % (self.__class__.__name__, pformat(list(rules)))
    547 
    548 
    549 class MapAdapter(object):
    550 
    551     def __init__(self, map, server_name, script_name, subdomain, url_scheme, path_info, default_method):
    552         self.map = map
    553         self.server_name = server_name
    554         if not script_name.endswith('/'):
    555             script_name += '/'
    556         self.script_name = script_name
    557         self.subdomain = subdomain
    558         self.url_scheme = url_scheme
    559         self.path_info = path_info or u''
    560         self.default_method = default_method
    561 
    562     def dispatch(self, view_func, path_info = None, method = None, catch_http_exceptions = False):
    563         try:
    564             try:
    565                 endpoint, args = self.match(path_info, method)
    566             except RequestRedirect as e:
    567                 return e
    568 
    569             return view_func(endpoint, args)
    570         except HTTPException as e:
    571             if catch_http_exceptions:
    572                 return e
    573             raise
    574 
    575     def match(self, path_info = None, method = None, return_rule = False):
    576         self.map.update()
    577         if path_info is None:
    578             path_info = self.path_info
    579         if not isinstance(path_info, unicode):
    580             path_info = path_info.decode(self.map.charset, 'ignore')
    581         method = (method or self.default_method).upper()
    582         path = u'%s|/%s' % (self.subdomain, path_info.lstrip('/'))
    583         have_match_for = set()
    584         for rule in self.map._rules:
    585             try:
    586                 rv = rule.match(path)
    587             except RequestSlash:
    588                 raise RequestRedirect(str('%s://%s%s%s/%s/' % (self.url_scheme,
    589                  self.subdomain and self.subdomain + '.' or '',
    590                  self.server_name,
    591                  self.script_name[:-1],
    592                  url_quote(path_info.lstrip('/'), self.map.charset))))
    593 
    594             if rv is None:
    595                 continue
    596             if rule.methods is not None and method not in rule.methods:
    597                 have_match_for.update(rule.methods)
    598                 continue
    599             if self.map.redirect_defaults:
    600                 for r in self.map._rules_by_endpoint[rule.endpoint]:
    601                     if r.provides_defaults_for(rule) and r.suitable_for(rv, method):
    602                         rv.update(r.defaults)
    603                         subdomain, path = r.build(rv)
    604                         raise RequestRedirect(str('%s://%s%s%s/%s' % (self.url_scheme,
    605                          subdomain and subdomain + '.' or '',
    606                          self.server_name,
    607                          self.script_name[:-1],
    608                          url_quote(path.lstrip('/'), self.map.charset))))
    609 
    610             if rule.redirect_to is not None:
    611                 if isinstance(rule.redirect_to, basestring):
    612 
    613                     def _handle_match(match):
    614                         value = rv[match.group(1)]
    615                         return rule._converters[match.group(1)].to_url(value)
    616 
    617                     redirect_url = _simple_rule_re.sub(_handle_match, rule.redirect_to)
    618                 else:
    619                     redirect_url = rule.redirect_to(self, **rv)
    620                 raise RequestRedirect(str(urljoin('%s://%s%s%s' % (self.url_scheme,
    621                  self.subdomain and self.subdomain + '.' or '',
    622                  self.server_name,
    623                  self.script_name), redirect_url)))
    624             if return_rule:
    625                 return (rule, rv)
    626             return (rule.endpoint, rv)
    627 
    628         if have_match_for:
    629             raise MethodNotAllowed(valid_methods=list(have_match_for))
    630         raise NotFound()
    631 
    632     def test(self, path_info = None, method = None):
    633         try:
    634             self.match(path_info, method)
    635         except RequestRedirect:
    636             pass
    637         except NotFound:
    638             return False
    639 
    640         return True
    641 
    642     def _partial_build(self, endpoint, values, method, append_unknown):
    643         if method is None:
    644             rv = self._partial_build(endpoint, values, self.default_method, append_unknown)
    645             if rv is not None:
    646                 return rv
    647         for rule in self.map._rules_by_endpoint.get(endpoint, ()):
    648             if rule.suitable_for(values, method):
    649                 rv = rule.build(values, append_unknown)
    650                 if rv is not None:
    651                     return rv
    652 
    653     def build(self, endpoint, values = None, method = None, force_external = False, append_unknown = True):
    654         self.map.update()
    655         if values:
    656             if isinstance(values, MultiDict):
    657                 values = dict(((k, v) for k, v in values.iteritems(multi=True) if v is not None))
    658             else:
    659                 values = dict(((k, v) for k, v in values.iteritems() if v is not None))
    660         else:
    661             values = {}
    662         rv = self._partial_build(endpoint, values, method, append_unknown)
    663         if rv is None:
    664             raise BuildError(endpoint, values, method)
    665         subdomain, path = rv
    666         if not force_external and subdomain == self.subdomain:
    667             return str(urljoin(self.script_name, path.lstrip('/')))
    668         return str('%s://%s%s%s/%s' % (self.url_scheme,
    669          subdomain and subdomain + '.' or '',
    670          self.server_name,
    671          self.script_name[:-1],
    672          path.lstrip('/')))
    673 
    674 
    675 DEFAULT_CONVERTERS = {'default': UnicodeConverter,
    676  'string': UnicodeConverter,
    677  'any': AnyConverter,
    678  'path': PathConverter,
    679  'int': IntegerConverter,
    680  'float': FloatConverter}
    681 from werkzeug.datastructures import ImmutableDict, MultiDict
    682 Map.default_converters = ImmutableDict(DEFAULT_CONVERTERS)