or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

data-structures.mddev-server.mdexceptions.mdhttp-utilities.mdindex.mdmiddleware.mdrequest-response.mdrouting.mdsecurity.mdtesting.mdurl-wsgi-utils.md

routing.mddocs/

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

```