pallets/werkzeug
github.com/pallets/werkzeug ↗ · 273 files · 1.6 MB
Werkzeug werkzeug German noun: "tool". Etymology: werk ("work"), zeug ("stuff") Werkzeug is a comprehensive [WSGI][] web application library.
Overview
Werkzeug is a Python WSGI toolkit for application and framework developers, providing request parsing, response generation, URL routing, HTTP utilities, composable middleware, and development tools. Its architecture separates protocol logic from WSGI stream handling, allowing consumers to use individual utilities or higher-level Request and Response objects. At commit 594452f6a4fe4de38a544962fbf04bfc9d37fbc2, the package identifies as 3.2.0.dev and includes compatibility transitions toward Werkzeug 4.0.
System map
The main runtime boundary is the WSGI application callable, with separate adapters for request input and response output. Routing and HTTP utilities can operate independently of an application framework. Durable storage and rendering integrations are selected by consuming applications, as illustrated by the Redis-backed Shortly example. Packaging, CI, and documentation are configured through Flit, uv/tox, GitHub Actions, and Sphinx.
- The package-level API re-exports run_simple, Client, Request, and Response; README demonstrates a Request.application-decorated callable served by run_simple.
- Version 3.2.0.dev requires Python >=3.11 and MarkupSafe >=3.0.3; watchdog >=6 is an optional extra.
- Sans-I/O Request represents method, URL, and headers; the WSGI Request constructor derives those values from environ and retains the original environment.
- Map owns the state-machine matcher, endpoint-indexed rules, converter registry, and remapping lock; MapAdapter.match translates matcher outcomes into HTTP routing exceptions.
- Request-local resources include cached body bytes and parsed upload streams; Local separately stores context-scoped dictionaries in ContextVar.
- Shortly supplies its own Redis client, autoescaping Jinja environment, and route map; Redis keys hold URL targets, reverse mappings, identifiers, and click counts.
- Flit builds the package, while tox uses a uv lock runner to build a wheel and invoke pytest; dedicated tox environments run mypy, pre-commit, and Sphinx.
- Tag pushes trigger uv build, artifact upload, draft GitHub release creation, and PyPI publication using an id-token-enabled publish environment; deployment docs direct applications to dedicated production WSGI servers.
Feature catalog
The developer-facing features cover HTTP request interpretation, response construction, routing, file transfer, security helpers, and testing. Middleware adds application composition and operational diagnostics. Development tooling includes a server, reloader, and browser debugger. The inspected example demonstrates user-facing URL creation, redirects, and click statistics.
- Request exposes query parameters and cookies as immutable multidicts, form fields through lazy parsing, and JSON parsing with force, silent, and cache controls.
- Response supports streaming WSGI output and suppresses bodies for HEAD, informational responses, 204, and 304; cookie setters expose Secure, HttpOnly, SameSite, and Partitioned attributes.
- Routing supports string, any, path, integer, float, and UUID converters, canonical redirects, method restrictions, and distinct WebSocket matching.
- send_file provides attachment naming, MIME inference, cache metadata, conditional responses, ranges, and X-Sendfile; SharedDataMiddleware maps URL prefixes to filesystem or package resources.
- Security helpers generate scrypt or PBKDF2 password hashes, compare stored hashes with hmac.compare_digest, and validate untrusted URL path components with safe_join.
- Middleware can mount WSGI applications by path, rewrite trusted forwarded metadata, proxy HTTP requests, lint protocol behavior, and profile requests with optional output files.
- Development serving selects single-process, threaded, or forked servers, optionally adds debugging/static middleware, and supports automatic reloading; Client simulates WSGI requests and follows redirects without a server.
- Shortly offers POST / to create a short URL, /<short_id> to increment clicks and redirect, and /<short_id>+ to display target and click-count details; missing identifiers produce its custom 404 page.
Data flow
Incoming HTTP data becomes a WSGI environ before Request interprets it. Body access passes through stream limits, then form or JSON parsers whose results are owned by the request instance. Response turns application data and headers into a byte iterable and start_response call. File persistence, outbound proxy traffic, and Redis writes are explicit boundaries rather than implicit framework services.
- WSGIRequestHandler.make_environ decodes request paths, constructs CGI/WSGI fields, normalizes headers, ignores underscore-bearing header names, and wraps chunked input in DechunkedInput with wsgi.input_terminated.
- Request converts WSGI path strings and query bytes into Sans-I/O metadata; args uses parse_qsl with blank values preserved and invalid-byte quoting behavior.
- get_input_stream rejects a declared length above max_content_length and, for server-terminated input, applies a LimitedStream when a maximum is configured.
- FormDataParser dispatches by MIME type: multipart uses a required boundary and MultiPartParser, URL-encoded bodies are read and decoded in full, and unsupported types return empty form/file collections with the stream.
- MultipartDecoder events drive text accumulation or file-stream writes; completed file streams are rewound and wrapped in FileStorage, while default upload streams use SpooledTemporaryFile.
- get_data optionally caches raw body bytes; get_json consumes those bytes through json_module.loads and maintains separate silent/non-silent cache entries.
- Response.get_wsgi_response combines final headers and its body iterator; __call__ invokes start_response and returns that iterator, with normal iteration wrapped in ClosingIterator.
- Persistence destinations are caller-selected: FileStorage.save copies an upload into a supplied path or stream, while Shortly.insert_url writes Redis target/reverse mappings after allocating a counter-based identifier.
Behavior traces
The investigation followed development startup, URL creation and persistence, short-link resolution, multipart parsing, file serving, and test-client execution through their actual call sites. Each trace records execution order and meaningful failure branches. These are source-level walkthroughs, not runtime observations. The Redis-backed example provides the clearest complete application flow.
- Startup: Shortly.create_app constructs Shortly, optionally wraps wsgi_app with SharedDataMiddleware, then its __main__ block calls run_simple with debugger and reloader enabled.
- Server startup continues through run_simple → optional DebuggedApplication → make_server → socket inheritance and WERKZEUG_SERVER_FD assignment → run_with_reloader or serve_forever; threaded plus multiple processes is rejected.
- Create-link flow: Shortly.__call__ → wsgi_app → Request → dispatch_request → bind_to_environ → match → on_new_url; valid POST input calls insert_url and redirects to details, while an invalid scheme re-renders the form.
- Persistence flow: insert_url first reads reverse-url:<url>; an existing mapping is reused, otherwise Redis INCR last-url-id → base36_encode → SET url-target:<id> → SET reverse-url:<url> establishes the new mapping.
- Follow-link flow: routing calls on_follow_short_link → Redis GET target → NotFound if absent, otherwise INCR click-count → redirect; dispatch_request turns NotFound into error_404 and Response emits the final WSGI output.
- Upload flow: Request.form → _load_form_data → _parse_form_data → FormDataParser.parse → _parse_multipart → MultiPartParser.parse → MultipartDecoder events; files become FileStorage and parser failures close streams through its context manager.
- Download flow: send_from_directory → safe_join → regular-file check → send_file → open/stat → wrap_file and Response → apply_conditions → apply_range; conditional-processing errors close the response before propagating.
- Test-client flow: Client.open obtains a Request from EnvironBuilder, calls Client.run_wsgi_app to attach and update cookies, constructs TestResponse, and optionally follows redirects while closing intermediate responses and detecting loops.
Testing signals
Tests and CI were inspected, not executed; no passing result or coverage percentage is established. Pytest covers protocol utilities and adapters, with separate middleware and Sans-I/O test files plus subprocess-based live-server fixtures. The inspected assertions address parsing limits, routing trust, JSON failures, file transfer, security helpers, and redirect behavior. This inventory is bounded, and example persistence/concurrency behavior was not established by the inspected tests.
- Pytest discovers tests under tests/, treats warnings as errors, and declares a dev_server marker; tox's configured test command is pytest -v --tb=short with forwarded positional arguments.
- TestFormParser asserts early total-length rejection, multipart field-memory rejection, part-count rejection, and URL-encoded exemption from max_form_parts; a separate test checks memory limits across multiple decoder events.
- Routing tests reject disallowed WebSocket methods and verify Request.trusted_hosts during Map.bind_to_environ; ProxyFix tests exercise the resulting request root, route building, matching, and redirect location.
- JSON tests assert successful request/response decoding, 415 for an unsupported content type, silent and forced parsing behavior, 400 for malformed data, and body consumption when caching is disabled.
- File tests assert conditional 304 responses, cache-age behavior, ETag options, and traversal rejection; range tests assert 206 output, clamping, 416 failures, first-range-only behavior, and POST exemption.
- Security tests assert scrypt/PBKDF2 formats, salt variability, and unsafe-path rejection; Client tests reject external redirect following and redirect loops. These assertions exist but were not executed in this investigation.
- DevServerClient launches a subprocess and reserves ports; live-server tests include HTTP, TLS, reloading, and Unix sockets, with platform skips and a Windows-CI skip for a reloader test.
- Inspected CI config runs locked uv/tox tests across Python 3.11–3.15, free-threaded 3.14, PyPy, Windows, and macOS, plus mypy and pre-commit jobs; docs-only/README changes skip the tests workflow, and no execution or example Redis concurrency coverage is established here.
Risk map
The important trust boundaries are request-body resource consumption, debugger command execution, forwarded headers, filesystem paths, and outbound proxy connections. The observations below describe source behavior and configuration responsibilities, not confirmed vulnerabilities. Compatibility branches and documentation drift also deserve attention during maintenance. The example's persistence race is an explicitly labeled hypothesis requiring separate verification.
- Request.max_content_length defaults to None, get_data reads the body into memory, and URL-encoded parsing does not apply multipart memory/part limits; applications or upstream servers must establish a total-body limit.
- The debugger intentionally evaluates Python commands: dispatch requires evalex, a known frame, matching command secret, and PIN trust, then execute_command checks host trust before frame.eval; production deployment docs prohibit the development debugger/server.
- ProxyFix's trusted counts select values from the right of forwarded-header lists; trusting client-controlled positions can alter remote address, scheme, host, or mount prefix.
- safe_join checks path names rather than symlinks or filesystem trust; send_from_directory requires a trusted base directory, while FileStorage.save opens a supplied path directly in write mode.
- ProxyMiddleware creates outbound HTTP/HTTPS connections to configured targets, forwards filtered request headers and body chunks, and turns OSError into BadGateway; target and TLS configuration are sensitive integration boundaries.
- The reloader spawns the current Python interpreter with inherited environment and descriptors, setting WERKZEUG_RUN_MAIN; run_simple transfers the server descriptor through WERKZEUG_SERVER_FD.
- Hypothesis: concurrent Shortly.insert_url calls for the same URL can both miss the reverse mapping and allocate separate identifiers because GET, INCR, and the two SET operations are separate calls; this was not reproduced. (inferred)
- Security documentation has a concrete default mismatch: generate_password_hash's PBKDF2 description says 600000 iterations, while DEFAULT_PBKDF2_ITERATIONS and test_pbkdf2 use 1000000; callers should rely on the actual stored method string.
Extension points
Extension primarily uses Python classes, factories, and WSGI callable composition. Routing offers explicit converter and rule-factory interfaces. Request parsing and JSON behavior can be overridden, although several historical hooks are being deprecated. Server, reloader, static-resource, and file-response factories expose additional integration seams.
- Custom route converters plug into Map(converters=...) or its instance registry; BaseConverter exposes regex, weight, part_isolating, to_python, and to_url.
- RuleFactory subclasses implement get_rules; Map.add binds every yielded rule, registers matchable rules with the matcher, and indexes all rules by endpoint.
- Subdomain and Submount compose nested rule factories by copying rules and modifying their subdomain or path prefix.
- Request._parse_form_data(stream) is the new parsing override point; form_data_parser_class, make_form_data_parser, and _get_file_stream remain compatibility hooks marked for Werkzeug 4.0 removal.
- Multipart parsing accepts a stream_factory that receives filename, content type, and length metadata; its returned writable stream owns uploaded bytes until FileStorage exposes them.
- Request JSON behavior delegates to json_module.loads and on_json_loading_failed; replacing these changes decoding or error handling while retaining force/silent/cache control.
- WSGI callable composition supports mounted applications through DispatcherMiddleware and static resource exports through SharedDataMiddleware; Shortly.create_app demonstrates swapping the wrapped wsgi_app.
- make_server accepts a custom request_handler; reloader selection uses the reloader_loops table, choosing stat or watchdog for auto; send_file accepts a response_class and callable max_age.
Contributor handoff
Start with the public WSGI contract, then inspect the relevant protocol, adapter, and test layers together. Preserve stream ownership, cleanup, route-building parity, and documented compatibility behavior. Tests and CI were inspected, not executed; the brief establishes neither passing checks nor exhaustive coverage. Future implementation work should use the existing locked tooling only when execution is separately authorized.
- Read README's callable example and docs/levels before editing public APIs: the project explicitly supports both Request/Response wrappers and lower-level utility functions.
- For body changes, inspect Request, FormDataParser, MultipartDecoder, and get_input_stream together; multipart limits and server-terminated stream handling are separate controls.
- Preserve upload cleanup and cached-body reparsing: Request.close closes parsed file objects, cached input is re-exposed through BytesIO, and MultiPartParser closes created streams on errors.
- Routing edits must account for both matcher state and endpoint builders: Rule.compile installs generated build functions, while Map.update sorts matcher and endpoint rules under its remapping lock.
- Keep Werkzeug 3.2 compatibility paths explicit: parser hooks and configurable storage classes emit deprecation warnings, and tests treat warnings as errors unless deliberately handled.
- When execution is authorized, use the configured uv/tox pytest workflow with the smallest relevant test-file positional argument first; tests and CI were inspected here, not executed, and test existence supplies no passing result.
- Follow existing style and verification configuration: Ruff uses single-line imports and excludes examples, mypy is strict over src, pre-commit runs repository hooks, and the docs tox environment builds Sphinx with warnings as errors.
- Distinguish runnable demonstrations from supported library boundaries: Shortly requires Redis/Jinja and enables development debugging, while deployment docs require production WSGI hosting; bounded example inspection does not establish persistence integration coverage.
Languages
| Language | Files | Share of source |
|---|---|---|
| Python | 142 | 98% |
| HTML | 41 | docs/config |
| CSS | 8 | docs/config |
| YAML | 8 | docs/config |
| Markdown | 5 | docs/config |
| JSON | 1 | docs/config |
| JavaScript | 1 | 1% |
| Shell | 1 | 1% |
Start reading here
Probable source entrypoint: pyproject.toml — inferred from the directory tree and manifest, not the upstream docs.
Source roots
src/ (58 files)
Source layout — src/
| Path | Files |
|---|---|
src/werkzeug/ | 58 |
Repository structure
| Top-level path | Files |
|---|---|
examples/ | 104 |
src/ | 58 |
tests/ | 47 |
docs/ | 44 |
.github/ | 9 |
.devcontainer/ | 2 |
Docs found: README.md
Package
pyproject.toml — werkzeug v3.2.0.dev, 1 declared dependencies.
watchdog
CI & config entrypoints
README.md— Repository readme
Structural health signals
Average score 8.1/10 across 96 analyzed source files; 11 hotspots surfaced.
src/werkzeug/wrappers/request.py— 888 lines, score 5.2 (Large file, Deep nesting)src/werkzeug/http.py— 1524 lines, score 5.7 (Large file, Deep nesting)src/werkzeug/datastructures/structures.py— 926 lines, score 5.7 (Large file, Deep nesting)
These are deterministic review leads — size, churn, and structural signals — not proof of a bug in this project.
Recent history
594452f6a4fe2026-10-01 — put sign in correct place for small negative floatf7e37f0bf5102026-09-27 — drop python 3.10 (#3311)2f60c2039d672026-09-27 — drop python 3.107ad32e85d0502026-09-27 — Merge branch 'stable'85dd69bda8f72026-09-27 — release version 3.1.9 (#3310)