A Router is an ordered collection of route->target pairs. It is used to efficiently match WSGI requests against a number of routes and return the first target that satisfies the request. The target may be anything, usually a string, ID or callable object. A route consists of
| 259 | |
| 260 | |
| 261 | class Router(object): |
| 262 | ''' A Router is an ordered collection of route->target pairs. It is used to |
| 263 | efficiently match WSGI requests against a number of routes and return |
| 264 | the first target that satisfies the request. The target may be anything, |
| 265 | usually a string, ID or callable object. A route consists of a path-rule |
| 266 | and a HTTP method. |
| 267 | |
| 268 | The path-rule is either a static path (e.g. `/contact`) or a dynamic |
| 269 | path that contains wildcards (e.g. `/wiki/<page>`). The wildcard syntax |
| 270 | and details on the matching order are described in docs:`routing`. |
| 271 | ''' |
| 272 | |
| 273 | default_pattern = '[^/]+' |
| 274 | default_filter = 're' |
| 275 | |
| 276 | #: The current CPython regexp implementation does not allow more |
| 277 | #: than 99 matching groups per regular expression. |
| 278 | _MAX_GROUPS_PER_PATTERN = 99 |
| 279 | |
| 280 | def __init__(self, strict=False): |
| 281 | self.rules = [] # All rules in order |
| 282 | self._groups = {} # index of regexes to find them in dyna_routes |
| 283 | self.builder = {} # Data structure for the url builder |
| 284 | self.static = {} # Search structure for static routes |
| 285 | self.dyna_routes = {} |
| 286 | self.dyna_regexes = {} # Search structure for dynamic routes |
| 287 | #: If true, static routes are no longer checked first. |
| 288 | self.strict_order = strict |
| 289 | self.filters = { |
| 290 | 're': lambda conf: |
| 291 | (_re_flatten(conf or self.default_pattern), None, None), |
| 292 | 'int': lambda conf: (r'-?\d+', int, lambda x: str(int(x))), |
| 293 | 'float': lambda conf: (r'-?[\d.]+', float, lambda x: str(float(x))), |
| 294 | 'path': lambda conf: (r'.+?', None, None)} |
| 295 | |
| 296 | def add_filter(self, name, func): |
| 297 | ''' Add a filter. The provided function is called with the configuration |
| 298 | string as parameter and must return a (regexp, to_python, to_url) tuple. |
| 299 | The first element is a string, the last two are callables or None. ''' |
| 300 | self.filters[name] = func |
| 301 | |
| 302 | rule_syntax = re.compile('(\\\\*)'\ |
| 303 | '(?:(?::([a-zA-Z_][a-zA-Z_0-9]*)?()(?:#(.*?)#)?)'\ |
| 304 | '|(?:<([a-zA-Z_][a-zA-Z_0-9]*)?(?::([a-zA-Z_]*)'\ |
| 305 | '(?::((?:\\\\.|[^\\\\>]+)+)?)?)?>))') |
| 306 | |
| 307 | def _itertokens(self, rule): |
| 308 | offset, prefix = 0, '' |
| 309 | for match in self.rule_syntax.finditer(rule): |
| 310 | prefix += rule[offset:match.start()] |
| 311 | g = match.groups() |
| 312 | if len(g[0])%2: # Escaped wildcard |
| 313 | prefix += match.group(0)[len(g[0]):] |
| 314 | offset = match.end() |
| 315 | continue |
| 316 | if prefix: |
| 317 | yield prefix, None, None |
| 318 | name, filtr, conf = g[4:7] if g[2] is None else g[1:4] |