Changelog¶
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
v4.0.0 - 2026-06-12¶
Changed¶
Breaking: Slimmed the default install from ~60 packages to ~30 by moving heavy dependencies behind extras:
pueblo[sfa-full](which pulls ins3fs,aiobotocore,aiohttp,libarchive-c, and friends) is now thecliextra.responder runstill works out of the box for local modules and file paths (app:api,myapp/core.py); only remote targets (URLs,github://, cloud storage) needpip install 'responder[cli]'grapheneandgraphql-coreare now thegraphqlextra. Install withpip install 'responder[graphql]'to useapi.graphql()
v3.12.0 - 2026-06-12¶
Added¶
Built-in metrics:
API(metrics_route="/metrics")serves request counts and latency histograms in Prometheus text format, zero dependencies. Labels use route patterns (/users/{id}) so cardinality stays bounded; error responses are recorded with their real status codesServer-side sessions:
API(session_backend=...)stores session data in a backend (MemorySessionBackend,RedisSessionBackend, or any object withget/set/delete) with only an opaque ID in the cookie — enabling revocation and unbounded session size. Handler code is unchangedQuery-parameter validation:
@api.route(..., params_model=Model)coerces and validates query strings with Pydantic (422on failure), exposes the instance asreq.state.validated_params, maps repeated keys tolistfields, and documents the parameters in the OpenAPI specresp.render(template, **context)— render a Jinja2 template as the HTML response body in one call
v3.11.0 - 2026-06-11¶
Added¶
HTTP range requests:
resp.file()andresp.stream_file()answerRange: bytes=...with206 Partial Content(suffix and open-ended ranges,416for unsatisfiable,Accept-Rangesadvertised) — enables video seeking and resumable downloadsresp.download(path, filename=...)serves files as attachments with properContent-Disposition(RFC 5987 encoding for non-ASCII names), streamed and resumableRequest timeouts:
API(request_timeout=seconds)answers overrunning handlers with504 Gateway Timeout(content-negotiated); dependency teardowns still run
Performance¶
Route resolution is cached per (method, path) with invalidation on registration — ~10% faster dispatch at 81 routes, growing with route count
v3.10.0 - 2026-06-11¶
Added¶
Trailing-slash redirects: requests that miss only by a trailing slash get a
307to the canonical path, preserving method and query string. Disable withAPI(redirect_slashes=False)Request size limits:
API(max_request_size=bytes)returns413for oversized bodies — fast-fails onContent-Lengthand enforces cumulatively for chunked/streamed uploadsAutomatic ETags:
API(auto_etag=True)adds a content-hashETagto GET responses with full304 Not Modifiedhandling; an explicitresp.etagalways winsAfter-response background tasks:
resp.background(func, *args)defers work until the client has the response (sync and async, ordered)resp.cache_control(...)helper for buildingCache-Controlheaders
Fixed¶
A
413raised while reading the body duringrequest_modelvalidation is no longer swallowed into a422
Changed¶
Trailing-slash redirects are on by default (previously such requests were 404s)
v3.9.1 - 2026-06-11¶
Added¶
Conditional request support: set
resp.etagorresp.last_modifiedand matchingIf-None-Match/If-Modified-Sincerequests automatically get304 Not Modified(RFC 7232 semantics:If-None-Matchprecedence, weak comparison, GET/HEAD only)Request body streaming:
async for chunk in req.stream()iterates large uploads without bufferingPluggable rate-limiter backends:
RateLimiter(backend=...)with the in-memory default plus a newRedisBackendfor multi-process deploymentsApplication state:
api.statenamespace, reachable from handlers viareq.api.statereq.apiis now populated with the owningAPIinstance (it was alwaysNonebefore)
Fixed¶
API(static_dir=None)crashed on every route registration — the static fallback assertion now only applies when no endpoint is givenThe static-fallback error is a
ValueErrorinstead of a bareassert
Performance¶
Request headers are parsed into the case-insensitive dict lazily, on first access (~5% faster dispatch on header-heavy requests that don’t read headers)
v3.9.0 - 2026-06-11¶
Added¶
Dependency injection for WebSocket handlers: declare path parameters and registered dependencies by name after the
wsargument. Request-scoped providers taking a parameter receive the WebSocket; generator teardown runs when the handler finishes. Handlers that only takewsare unaffectedOpenAPI 3.1 support (
openapi="3.1.0")The OpenAPI schema endpoint now serves JSON when requested via
Accept: application/json, or always whenopenapi_routeends in.jsonPath parameters are documented automatically in the OpenAPI spec from route patterns (
{id:int}→ required integer parameter)Built-in error responses (404, 405) are content-negotiated: JSON clients receive
{"error": ...}bodies instead of plain text
Fixed¶
OpenAPI paths no longer leak convertor patterns (
/users/{id:int}is now emitted as the spec-compliant/users/{id})Registering a duplicate route now raises
ValueError(previously anassertthat disappears underpython -O)Removed dead
_exception_handlersbookkeeping inAPI.exception_handler
Changed¶
mypynow passes with zero errors across the codebase (was 25);ruffis clean as welltypes-pyyamladded to thetestextra
v3.8.0 - 2026-06-11¶
Added¶
Handlers can return values: a
dict/listsetsresp.media, astrsetsresp.text, andbytessetresp.content. ReturningNonekeeps the mutate-respbehavior, so existing handlers are unaffectedApp-scoped dependencies:
@api.dependency(scope="app")resolves the provider once on first use and caches it for the application’s lifetime; generator teardown runs at shutdownAutomatic
OPTIONSresponses with anAllowheader for method-restricted routesHEADrequests are accepted whereverGETisset_cookie()gains asamesiteparameter, defaulting to"lax"The validated
request_modelinstance is now available to handlers asreq.state.validated— no need to re-parse the body
Changed¶
Requests to an existing path with an unsupported method now return
405 Method Not Allowedwith anAllowheader (previously 404)RouteGroup.before_requesthooks are now scoped to the group’s prefix (previously they silently applied to every route)
Performance¶
View signature inspection for dependency injection is cached per function
v3.7.0 - 2026-06-11¶
Added¶
Dependency injection for route handlers: register providers with
@api.dependency()(orapi.add_dependency(name, provider)) and declare them as view parameters by name. Supports sync/async functions and generators (code afteryieldruns as teardown once the response is sent). Providers accepting a parameter receive the currentRequest. Dependencies resolve at most once per request; path parameters take precedence.Per-route rate limiting via
RateLimiter.limitdecoratorWebSocket before-request hooks can now reject connections: closing the socket in a hook short-circuits the route handler
WebSocket before-request hooks may now be sync functions (run in the threadpool)
Custom formats registered on
api.formatsnow actually reach request parsing and response negotiation (previously each request got a fresh default format registry)
Fixed¶
{value:float}path convertor matched garbage like1a5(unescaped regex dot) and crashed with a 500 — now correctly returns 404Literal characters in route paths are now regex-escaped, so
/file.jsonno longer matches/fileXjsonUnbounded memory growth in
BackgroundQueue— completed futures are now pruned fromresultsreq.media("form")crashed with aTypeErrorwhen the request had noContent-TypeheaderContent negotiation returned an empty body for
Acceptheaders matching encode-incapable formats (e.g.multipart/form-data) — now falls through to JSON
Changed¶
Request.urlandRequest.paramsare now computed once and cachedFormat registries are no longer rebuilt twice per request
v3.6.2 - 2026-04-12¶
Fixed¶
GraphQL error responses now correctly return 400 status instead of always 200
OpenAPI docs UI now respects custom
openapi_routeinstead of hardcoding/schema.ymlbefore_requestsdefault type mismatch that could crash routes called outside the routerBlocking synchronous file I/O in
Response.stream_file()— now uses async I/O via anyioMemory leak in rate limiter (empty bucket keys never cleaned up)
Race condition in rate limiter
check()— added thread-safe lockingWSGI fallback catching all
TypeErrors instead of just call-signature mismatchesPydantic request/response model validation crashing on non-dict bodies
Test assertions that could never fail (
or True,< 500patterns)CaseInsensitiveDictmissing__delitem__,pop, andsetdefaultoverridesassertused for input validation in OpenAPI extension (stripped bypython -O)Potential XSS in GraphiQL template endpoint injection
Dead
or ""in media format detection logic
Changed¶
DELETErequests now participate in Pydantic request body validationSimplified status code category check to use chained comparison
Removed¶
Unused
methodparameter fromload_target()Unused Node.js setup step from CI test workflow
v3.6.1 - 2026-04-12¶
Added¶
Configurable GZip compression via
gzipparameter onAPI()(defaults toTrue)
v3.6.0 - 2026-03-24¶
Added¶
Built-in structured logging with per-request context (
enable_logging=True)api.log— always-available logger, enriched with request context when logging is enabledAutomatic access logging with timing:
GET /path → 200 (1.2ms)Request ID generation/forwarding via
X-Request-IDheadercontextvars-based request context (ID, method, path, client IP) on every log recordresponder.ext.loggingmodule:get_logger(),RequestContext,RequestContextFilter
CLAUDE.md project guide and
/releasecommandVersion number in docs sidebar
Changed¶
Comprehensive documentation improvements across all pages
Deployment: health checks, Docker Compose, Caddy, Procfile, production checklist
API reference: usage examples for every class
Feature tour: Pydantic validation, content negotiation, structured logging sections
Tutorials: modernized SQLAlchemy to
mapped_column(), fixed deprecateddatetime.utcnow(), WebSocketWebSocketDisconnecthandling, role-based auth, auth strategy guideTesting: rate limiting and WSGI mount examples
Middleware: pure ASGI middleware example
Quickstart: links to all tutorials
Sandbox: full rewrite with project layout
Docker example uses
uvinstead of pipBacklog updated: removed implemented features, replaced HTTP/2 server push with dependency injection
Removed¶
uv.lock— this is a library, not an application
v3.5.0 - 2026-03-24¶
Added¶
CI validation for Python 3.14, 3.14 free-threaded, and PyPy 3.11
Marimo notebook mounting docs and example
Type annotations for
routes.py
Changed¶
Replaced deprecated
asyncio.iscoroutinefunctionwithinspect.iscoroutinefunctionahead of Python 3.16 removalNarrowed broad
except Exceptionto specific exceptions in response model serialization and websocket chat exampleImproved GraphQL API interface with expanded test coverage
Code formatting cleanup via pyproject-fmt and ruff
Dropped Python 3.9 from CI
Fixed¶
WSGI mount returning 400 when requesting the exact mount root path
Werkzeug 3.1.7 compatibility for trusted host validation in tests
future.resultbare property access in background task test (now properly callsfuture.result())OpenAPI template packaging and static file serving
RST title underline warning breaking docs CI
Removed¶
Read the Docs configuration (docs hosted on GitHub Pages)
v3.4.0 - 2026-03-22¶
Changed¶
Upgraded to Starlette 1.0
Added comprehensive docstrings across the codebase
Expanded API reference documentation
v3.3.0 - 2026-03-22¶
Added¶
Full documentation rewrite: tutorials for REST APIs, SQLAlchemy, Flask migration
Auth, WebSocket, middleware, and configuration guides
Testing docs with prose, examples, and tips
GitHub Pages deployment for docs
Changed¶
Reworked homepage prose
Rewrote CLI and API reference docs
v3.2.0 - 2026-03-22¶
Added¶
Pydantic auto-validation:
request_modelvalidates input, returns 422 on failurePydantic auto-serialization:
response_modelstrips extra fields from responsesServer-Sent Events:
@resp.ssefor real-time streamingresp.stream_file()for streaming large files without loading into memory@api.after_request()hooksapi.group("/prefix")for route groups and API versioningapi.graphql("/path", schema=schema)one-liner GraphQL setupapi = responder.API(request_id=True)for automatic request ID generationBuilt-in rate limiter:
RateLimiter(requests=100, period=60).install(api)MessagePack format support:
await req.media("msgpack")req.is_json,req.path_params,req.clientpropertiesapi.exception_handler()decorator for custom error handlingLifespan context manager support
uuidandpathroute convertorsPEP 561
py.typedmarkerPydantic support for OpenAPI schema generation
Changed¶
Dependencies flattened:
pip install respondergets everythingCore deps reduced to starlette + uvicorn
TestClient lazy-loaded (no httpx import in production)
Before-request hooks can short-circuit by setting status code
Removed poethepoet task runner
Fixed¶
Multipart parser losing headers when parts have multiple headers
url_for()with typed route params ({id:int})resp.bodyencoding crash on bytes contentGraphQL text query missing
awaitStreaming responses not sending Content-Type headers
Python 3.9 compatibility for union type syntax
v3.0.0 - 2026-03-22¶
Added¶
Platform: Added support for Python 3.10 - Python 3.13
CLI:
responder runnow also accepts a filesystem path on its<target>argument, enabling usage on single-file applications.CLI:
responder runnow also accepts URLs.
Changed¶
Platform: Minimum Python version is now 3.9 (dropped 3.6, 3.7, 3.8)
Dependencies: Dramatically reduced core dependency count (10 → 5)
Removed
requests,requests-toolbelt,rfc3986,whitenoiseMoved
apispecandmarshmallowtoopenapioptional extraReplaced
rfc3986with stdliburllib.parseReplaced
requests-toolbeltmultipart decoder withpython-multipartReplaced deprecated
starlette.middleware.wsgiwitha2wsgiSwitched from WhiteNoise to ServeStatic
Dependencies: Pinned
starlette[full]>=0.40(was unpinned)GraphQL: Upgraded to
graphene>=3andgraphql-core>=3.1(fromgraphene<3andgraphql-server-core, which is unmaintained)GraphQL: Updated GraphiQL UI from 0.12.0 (2018) to 3.0.6 with React 18
Extensions: All of CLI-, GraphQL-, and OpenAPI-Support modules are extensions now, found within the
responder.extmodule namespace.Packaging: Migrated from
setup.pyto declarativepyproject.toml
Removed¶
Platform: Removed support for EOL Python 3.6, 3.7, 3.8
Status codes: Removed deprecated
resume_incompleteandresumealiases for HTTP 308 (marked for removal in 3.0)CLI:
responder run --buildceased to exist
Fixed¶
Routing: Fixed dispatching
static_route=Noneon Windowsuvicorn:
--debugnow maps to uvicorn’slog_level = "debug"Tests: Fixed deprecated httpx TestClient usage
v2.0.5 - 2019-12-15¶
Added¶
Update requirements to support python 3.8
v2.0.4 - 2019-11-19¶
Fixed¶
Fix static app resolving
v2.0.3 - 2019-09-20¶
Fixed¶
Fix template conflicts
v2.0.2 - 2019-09-20¶
Fixed¶
Fix template conflicts
v2.0.1 - 2019-09-20¶
Fixed¶
Fix template import
v2.0.0 - 2019-09-19¶
Changed¶
Refactor Router and Schema
v1.3.2 - 2019-08-15¶
Added¶
ASGI 3 support
CI tests for python 3.8-dev
Now requests have
statea mapping object
Deprecated¶
ASGI 2
v1.3.1 - 2019-04-28¶
Added¶
Route params Converters
Add search for documentation pages
Changed¶
Bump dependencies
v1.3.0 - 2019-02-22¶
Fixed¶
Versioning issue
Multiple cookies.
Whitenoise returns not found.
Other bugfixes.
Added¶
Stream support via
resp.stream.Cookie directives via
resp.set_cookie.Add
resp.htmlto send HTML.Other improvements.
v1.1.3 - 2019-01-12¶
Changed¶
Refactor
_route_for
Fixed¶
Resolve startup/shutdwown events
v1.2.0 - 2018-12-29¶
Added¶
Documentations
Changed¶
Use Starlette’s LifeSpan middleware
Update denpendencies
Fixed¶
Fix route.is_class_based
Fix test_500
Typos
v1.1.2 - 2018-11-11¶
Fixed¶
Minor fixes for Open API
Typos
v1.1.1 - 2018-10-29¶
Changed¶
Run sync views in a threadpoolexecutor.
v1.1.0 - 2018-10-27¶
Added¶
Support for
before_request.
v1.0.5- 2018-10-27¶
Fixed¶
Fix sessions.
v1.0.4 - 2018-10-27¶
Fixed¶
Potential bufix for cookies.
v1.0.3 - 2018-10-27¶
Fixed¶
Bugfix for redirects.
v1.0.2 - 2018-10-27¶
Changed¶
Improvement for static file hosting.
v1.0.1 - 2018-10-26¶
Changed¶
Improve cors configuration settings.
v1.0.0 - 2018-10-26¶
Changed¶
Move GraphQL support into a built-in plugin.
v0.3.3 - 2018-10-25¶
Added¶
CORS support
Changed¶
Improved exceptions.
v0.3.2 - 2018-10-25¶
Changed¶
Subtle improvements.
v0.3.1 - 2018-10-24¶
Fixed¶
Packaging fix.
v0.3.0 - 2018-10-24¶
Changed¶
Interactive Documentation endpoint.
Minor improvements.
v0.2.3 - 2018-10-24¶
Changed¶
Overall improvements.
v0.2.2 - 2018-10-23¶
Added¶
Show traceback info when background tasks raise exceptions.
v0.2.1 - 2018-10-23¶
Added¶
api.requests.
v0.2.0 - 2018-10-22¶
Added¶
WebSocket support.
v0.1.6 - 2018-10-20¶
Added¶
500 support.
v0.1.5 - 2018-10-20¶
Added¶
File upload support
Changed¶
Improvements to sequential media reading.
v0.1.4 - 2018-10-19¶
Fixed¶
Stability.
v0.1.3 - 2018-10-18¶
Added¶
Sessions support.
v0.1.2 - 2018-10-18¶
Added¶
Cookies support.
v0.1.1 - 2018-10-17¶
Changed¶
Default routes.
v0.1.0 - 2018-10-17¶
Added¶
Prototype of static application support.
v0.0.10 - 2018-10-17¶
Fixed¶
Bugfix for async class-based views.
v0.0.9 - 2018-10-17¶
Fixed¶
Bugfix for async class-based views.
v0.0.8 - 2018-10-17¶
Added¶
GraphiQL Support.
Changed¶
Improvement to route selection.
v0.0.7 - 2018-10-16¶
Changed¶
Immutable Request object.
v0.0.6 - 2018-10-16¶
Added¶
Ability to mount WSGI apps.
Supply content-type when serving up the schema.
v0.0.5 - 2018-10-15¶
Added¶
OpenAPI Schema support.
Safe load/dump yaml.
v0.0.4 - 2018-10-15¶
Added¶
Asynchronous support for data uploads.
Fixed¶
Bug fixes.
v0.0.3 - 2018-10-13¶
Fixed¶
Bug fixes.
v0.0.2 - 2018-10-13¶
Changed¶
Switch to ASGI/Starlette.
v0.0.1 - 2018-10-12¶
Added¶
Conception!