0
# URL Routing
1
2
Flexible URL routing system for mapping URLs to endpoints with support for URL variables, converters, HTTP methods, and URL generation. The routing system provides both URL-to-endpoint matching and endpoint-to-URL building capabilities.
3
4
## Capabilities
5
6
### URL Map
7
8
The Map class manages a collection of URL rules and provides methods for binding to specific requests.
9
10
```python { .api }
11
class Map:
12
def __init__(self, rules=None, default_subdomain="", charset="utf-8", strict_slashes=True, merge_slashes=True, redirect_defaults=True, converters=None, sort_parameters=True, sort_key=None, host_matching=False):
13
"""
14
Create a new URL map.
15
16
Parameters:
17
- rules: Initial list of Rule objects
18
- default_subdomain: Default subdomain for rules
19
- charset: Character encoding for URLs
20
- strict_slashes: Enforce trailing slash rules
21
- merge_slashes: Merge consecutive slashes in URLs
22
- redirect_defaults: Redirect to canonical URLs
23
- converters: Custom URL converters
24
- sort_parameters: Sort URL parameters when building
25
- sort_key: Custom sort key function
26
- host_matching: Enable host-based routing
27
"""
28
29
def add(self, rule_factory):
30
"""
31
Add a rule or rule factory to the map.
32
33
Parameters:
34
- rule_factory: Rule object or RuleFactory
35
"""
36
37
def bind(self, server_name, script_name="/", subdomain=None, url_scheme="http", path_info=None, method="GET", query_args=None):
38
"""
39
Create a MapAdapter for URL matching and building.
40
41
Parameters:
42
- server_name: Server hostname
43
- script_name: SCRIPT_NAME prefix
44
- subdomain: Subdomain to match
45
- url_scheme: URL scheme (http/https)
46
- path_info: PATH_INFO for matching
47
- method: HTTP method
48
- query_args: Query arguments dict
49
50
Returns:
51
MapAdapter instance
52
"""
53
54
def bind_to_environ(self, environ, server_name=None, subdomain=None):
55
"""
56
Create MapAdapter from WSGI environ.
57
58
Parameters:
59
- environ: WSGI environment dictionary
60
- server_name: Override server name
61
- subdomain: Override subdomain
62
63
Returns:
64
MapAdapter instance
65
"""
66
67
def update(self):
68
"""Update internal routing structures after adding rules."""
69
```
70
71
### URL Rules
72
73
The Rule class defines individual URL patterns with endpoints, methods, and variable conversion.
74
75
```python { .api }
76
class Rule:
77
def __init__(self, string, defaults=None, subdomain=None, methods=None, build_only=False, endpoint=None, strict_slashes=None, merge_slashes=None, redirect_to=None, alias=False, host=None):
78
"""
79
Create a new URL rule.
80
81
Parameters:
82
- string: URL pattern (e.g., '/user/<int:id>')
83
- defaults: Default values for variables
84
- subdomain: Subdomain pattern
85
- methods: Allowed HTTP methods (defaults to GET)
86
- build_only: Only use for URL building, not matching
87
- endpoint: Endpoint name for this rule
88
- strict_slashes: Override map's strict_slashes setting
89
- merge_slashes: Override map's merge_slashes setting
90
- redirect_to: Redirect target (string or callable)
91
- alias: Whether this is an alias rule
92
- host: Host pattern for host-based routing
93
"""
94
95
def match(self, path, method="GET"):
96
"""
97
Test if this rule matches a path.
98
99
Parameters:
100
- path: URL path to test
101
- method: HTTP method
102
103
Returns:
104
Tuple of (values_dict, redirect_exception) or None
105
"""
106
107
def build(self, values, append_unknown=True):
108
"""
109
Build URL from endpoint values.
110
111
Parameters:
112
- values: Dictionary of variable values
113
- append_unknown: Append unknown values as query parameters
114
115
Returns:
116
Tuple of (path, domain)
117
"""
118
```
119
120
### Map Adapter
121
122
The MapAdapter class provides URL matching and building functionality for a specific request context.
123
124
```python { .api }
125
class MapAdapter:
126
def match(self, path_info=None, method=None, return_rule=False, query_args=None):
127
"""
128
Match a URL to an endpoint.
129
130
Parameters:
131
- path_info: URL path (uses bound path if None)
132
- method: HTTP method (uses bound method if None)
133
- return_rule: Return matched Rule object instead of endpoint
134
- query_args: Query arguments dictionary
135
136
Returns:
137
Tuple of (endpoint, values) or Rule object
138
139
Raises:
140
RequestRedirect: If redirect is required
141
MethodNotAllowed: If method is not allowed
142
NotFound: If no rule matches
143
"""
144
145
def test(self, path_info=None, method="GET"):
146
"""
147
Test if a URL matches any rule.
148
149
Parameters:
150
- path_info: URL path to test
151
- method: HTTP method
152
153
Returns:
154
True if URL matches, False otherwise
155
"""
156
157
def build(self, endpoint, values=None, method=None, force_external=False, append_unknown=True, url_scheme=None):
158
"""
159
Build URL for an endpoint.
160
161
Parameters:
162
- endpoint: Endpoint name
163
- values: Variable values dictionary
164
- method: HTTP method hint
165
- force_external: Force absolute URL
166
- append_unknown: Add unknown values as query parameters
167
- url_scheme: Override URL scheme
168
169
Returns:
170
Built URL string
171
172
Raises:
173
BuildError: If URL cannot be built
174
"""
175
176
def dispatch(self, view_func, path_info=None, method=None, catch_http_exceptions=False):
177
"""
178
Match URL and dispatch to view function.
179
180
Parameters:
181
- view_func: Function that takes (endpoint, values) and returns WSGI app
182
- path_info: URL path
183
- method: HTTP method
184
- catch_http_exceptions: Catch and return HTTP exceptions as responses
185
186
Returns:
187
WSGI application or Response
188
"""
189
```
190
191
### URL Converters
192
193
Built-in converters for URL variable types with validation and conversion.
194
195
```python { .api }
196
class BaseConverter:
197
def __init__(self, map, *args, **kwargs):
198
"""
199
Base converter class.
200
201
Parameters:
202
- map: URL map instance
203
- args: Converter arguments
204
- kwargs: Converter keyword arguments
205
"""
206
207
def to_python(self, value):
208
"""
209
Convert URL value to Python object.
210
211
Parameters:
212
- value: URL string value
213
214
Returns:
215
Converted Python object
216
217
Raises:
218
ValidationError: If conversion fails
219
"""
220
221
def to_url(self, value):
222
"""
223
Convert Python object to URL string.
224
225
Parameters:
226
- value: Python object
227
228
Returns:
229
URL-safe string
230
"""
231
232
class UnicodeConverter(BaseConverter):
233
"""Default string converter, matches any text except '/'."""
234
235
class AnyConverter(BaseConverter):
236
"""Matches any of the specified strings."""
237
def __init__(self, map, *items):
238
"""
239
Parameters:
240
- items: Valid string values
241
"""
242
243
class PathConverter(BaseConverter):
244
"""Matches strings including '/' characters."""
245
246
class IntegerConverter(BaseConverter):
247
"""Matches integers."""
248
def __init__(self, map, fixed_digits=0, min=None, max=None):
249
"""
250
Parameters:
251
- fixed_digits: Exact number of digits required
252
- min: Minimum value
253
- max: Maximum value
254
"""
255
256
class FloatConverter(BaseConverter):
257
"""Matches floating point numbers."""
258
259
class UUIDConverter(BaseConverter):
260
"""Matches UUID strings."""
261
```
262
263
### Rule Factories
264
265
Factory classes for creating multiple related rules.
266
267
```python { .api }
268
class RuleFactory:
269
"""Base class for rule factories."""
270
def get_rules(self, map):
271
"""
272
Get list of rules from this factory.
273
274
Parameters:
275
- map: URL map instance
276
277
Returns:
278
List of Rule objects
279
"""
280
281
class RuleTemplate:
282
def __init__(self, template):
283
"""
284
Create rule template.
285
286
Parameters:
287
- template: Template Rule object
288
"""
289
290
class Subdomain:
291
def __init__(self, subdomain, rules):
292
"""
293
Create rules for specific subdomain.
294
295
Parameters:
296
- subdomain: Subdomain pattern
297
- rules: List of rules or rule factories
298
"""
299
300
class Submount:
301
def __init__(self, path, rules):
302
"""
303
Mount rules under a path prefix.
304
305
Parameters:
306
- path: Path prefix
307
- rules: List of rules or rule factories
308
"""
309
310
class EndpointPrefix:
311
def __init__(self, prefix, rules):
312
"""
313
Add prefix to rule endpoints.
314
315
Parameters:
316
- prefix: Endpoint prefix string
317
- rules: List of rules or rule factories
318
"""
319
```
320
321
### Routing Exceptions
322
323
Exceptions raised during URL matching and building.
324
325
```python { .api }
326
class RoutingException(Exception):
327
"""Base exception for routing errors."""
328
329
class RequestRedirect(HTTPException, RoutingException):
330
"""Raised when a redirect is required."""
331
code = 308
332
333
def __init__(self, new_url):
334
"""
335
Parameters:
336
- new_url: Target URL for redirect
337
"""
338
339
class BuildError(RoutingException, LookupError):
340
"""Raised when URL building fails."""
341
342
def __init__(self, endpoint, values, method, adapter=None):
343
"""
344
Parameters:
345
- endpoint: Endpoint that failed to build
346
- values: Values dictionary used
347
- method: HTTP method
348
- adapter: MapAdapter instance
349
"""
350
351
class NoMatch(RoutingException):
352
"""Raised when no rule matches."""
353
354
class MethodNotAllowed(HTTPException, RoutingException):
355
"""Raised when HTTP method is not allowed."""
356
code = 405
357
```
358
359
## Usage Examples
360
361
### Basic Routing Setup
362
363
```python
364
from werkzeug.routing import Map, Rule
365
from werkzeug.wrappers import Request, Response
366
367
# Define URL rules
368
url_map = Map([
369
Rule('/', endpoint='index'),
370
Rule('/user/<username>', endpoint='user_profile'),
371
Rule('/post/<int:post_id>', endpoint='show_post'),
372
Rule('/api/users/<int:user_id>', endpoint='api_user', methods=['GET', 'POST']),
373
Rule('/download/<path:filename>', endpoint='download_file'),
374
])
375
376
def view_functions(endpoint, values):
377
"""Route endpoints to view functions."""
378
if endpoint == 'index':
379
return lambda environ, start_response: Response('Home Page')(environ, start_response)
380
elif endpoint == 'user_profile':
381
username = values['username']
382
return lambda environ, start_response: Response(f'User: {username}')(environ, start_response)
383
elif endpoint == 'show_post':
384
post_id = values['post_id']
385
return lambda environ, start_response: Response(f'Post #{post_id}')(environ, start_response)
386
# ... more endpoints
387
388
def application(environ, start_response):
389
request = Request(environ)
390
adapter = url_map.bind_to_environ(request.environ)
391
392
try:
393
endpoint, values = adapter.match()
394
wsgi_app = view_functions(endpoint, values)
395
return wsgi_app(environ, start_response)
396
except NotFound:
397
return Response('Not Found', status=404)(environ, start_response)
398
except MethodNotAllowed:
399
return Response('Method Not Allowed', status=405)(environ, start_response)
400
```
401
402
### Custom Converters
403
404
```python
405
from werkzeug.routing import BaseConverter, ValidationError
406
import re
407
408
class SlugConverter(BaseConverter):
409
"""Converter for URL-friendly slugs."""
410
411
def __init__(self, map, min_length=4, max_length=50):
412
super().__init__(map)
413
self.min_length = min_length
414
self.max_length = max_length
415
self.regex = rf'[a-z0-9-]{{{min_length},{max_length}}}'
416
417
def to_python(self, value):
418
if not re.match(r'^[a-z0-9-]+$', value):
419
raise ValidationError('Invalid slug format')
420
return value
421
422
def to_url(self, value):
423
return str(value).lower().replace(' ', '-')
424
425
# Register custom converter
426
url_map = Map([
427
Rule('/article/<slug:article_slug>', endpoint='article')
428
], converters={'slug': SlugConverter})
429
```
430
431
### URL Building
432
433
```python
434
from werkzeug.routing import Map, Rule
435
436
url_map = Map([
437
Rule('/', endpoint='index'),
438
Rule('/user/<username>', endpoint='user_profile'),
439
Rule('/post/<int:post_id>/edit', endpoint='edit_post'),
440
])
441
442
def application(environ, start_response):
443
adapter = url_map.bind_to_environ(environ)
444
445
# Build URLs in templates or redirects
446
home_url = adapter.build('index') # '/'
447
user_url = adapter.build('user_profile', {'username': 'john'}) # '/user/john'
448
edit_url = adapter.build('edit_post', {'post_id': 123}) # '/post/123/edit'
449
450
# Force external URLs
451
external_url = adapter.build('user_profile', {'username': 'john'}, force_external=True)
452
# 'http://example.com/user/john'
453
454
response_html = f'''
455
<a href="{home_url}">Home</a>
456
<a href="{user_url}">User Profile</a>
457
<a href="{edit_url}">Edit Post</a>
458
<a href="{external_url}">External Link</a>
459
'''
460
461
return Response(response_html, mimetype='text/html')(environ, start_response)
462
```
463
464
### Advanced Routing Patterns
465
466
```python
467
from werkzeug.routing import Map, Rule, Subdomain, Submount, EndpointPrefix
468
469
# Complex routing with subdomains and submounts
470
url_map = Map([
471
# Main site routes
472
Rule('/', endpoint='index'),
473
Rule('/about', endpoint='about'),
474
475
# API routes with prefix
476
Submount('/api/v1', [
477
Rule('/users', endpoint='api.list_users'),
478
Rule('/users/<int:user_id>', endpoint='api.get_user'),
479
Rule('/posts', endpoint='api.list_posts'),
480
]),
481
482
# Admin subdomain
483
Subdomain('admin', [
484
Rule('/', endpoint='admin.dashboard'),
485
Rule('/users', endpoint='admin.users'),
486
Rule('/settings', endpoint='admin.settings'),
487
]),
488
489
# API subdomain with endpoint prefix
490
Subdomain('api', [
491
EndpointPrefix('api.', [
492
Rule('/health', endpoint='health'),
493
Rule('/status', endpoint='status'),
494
])
495
]),
496
])
497
498
def application(environ, start_response):
499
adapter = url_map.bind_to_environ(environ)
500
501
try:
502
endpoint, values = adapter.match()
503
504
if endpoint.startswith('api.'):
505
# Handle API endpoints
506
return handle_api(endpoint, values)(environ, start_response)
507
elif endpoint.startswith('admin.'):
508
# Handle admin endpoints
509
return handle_admin(endpoint, values)(environ, start_response)
510
else:
511
# Handle main site
512
return handle_main(endpoint, values)(environ, start_response)
513
514
except RequestRedirect as e:
515
return Response('', status=e.code, headers=[('Location', e.new_url)])(environ, start_response)
516
```
517
518
### Method-Based Rules
519
520
```python
521
from werkzeug.routing import Map, Rule
522
from werkzeug.wrappers import Request, Response
523
524
url_map = Map([
525
Rule('/users', endpoint='users', methods=['GET', 'POST']),
526
Rule('/users/<int:user_id>', endpoint='user_detail', methods=['GET', 'PUT', 'DELETE']),
527
])
528
529
def handle_users(request, user_id=None):
530
if user_id is None:
531
# /users endpoint
532
if request.method == 'GET':
533
return Response('List all users')
534
elif request.method == 'POST':
535
return Response('Create new user')
536
else:
537
# /users/<id> endpoint
538
if request.method == 'GET':
539
return Response(f'Get user {user_id}')
540
elif request.method == 'PUT':
541
return Response(f'Update user {user_id}')
542
elif request.method == 'DELETE':
543
return Response(f'Delete user {user_id}')
544
545
def application(environ, start_response):
546
request = Request(environ)
547
adapter = url_map.bind_to_environ(request.environ)
548
549
try:
550
endpoint, values = adapter.match()
551
552
if endpoint in ('users', 'user_detail'):
553
response = handle_users(request, **values)
554
else:
555
response = Response('Not Found', status=404)
556
557
return response(environ, start_response)
558
except MethodNotAllowed as e:
559
return Response('Method Not Allowed', status=405)(environ, start_response)
560
```