assert statements are stripped with python -O. Use ValueError for
duplicate schema registration and RuntimeError for missing static
route configuration.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The parameter was accepted but never used — the function always returns
the entrypoint directly without calling any method on it.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Request validation would crash with **body if the body was a list.
Response validation would crash the same way. Now checks isinstance
before unpacking. Also added DELETE to request validation methods.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The bare except TypeError when calling mounted apps would silently
swallow any TypeError raised inside an ASGI app and incorrectly
convert it to WSGI mode. Now only falls back when the error is
about call arguments.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cleanup, length check, and append were separate non-atomic steps.
Under concurrent requests a client could exceed the limit. Added a
threading lock around the critical section.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Empty bucket keys were never removed after their timestamps expired.
Over time this accumulated an entry for every unique client IP that
ever made a request.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The async generator was using synchronous open() and read() which
blocks the event loop. Switched to anyio.open_file() for proper
async file I/O.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The default value for before_requests was [] (list) but the code calls
.get() on it which is a dict method. Changed default to match the
expected dict structure {"http": [], "ws": []}.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The docs template always fetched from /schema.yml regardless of the
user's openapi_route setting. Now uses self.openapi_route so custom
schema paths (e.g. /openapi.json) work correctly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
graphql_response() computed status_code (200 or 400) but only returned
it in an unused tuple instead of setting resp.status_code. All GraphQL
error responses were returning 200.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## About
That's just maintenance. In this case, streamline the CI configuration
for improved ergonomics and future proofing, see GH-605.
## Details
- `activate-environment: true` of newer `astral-sh/setup-uv@v7` is the
killer feature here, which wasn't available before.
- Configuring `cache-dependency-glob: pyproject.toml` isn't needed,
because the recipe already employs an excellent default configuration
that includes this and other project metadata files.
- The adjustments in `test_cli` were needed because of a different PyPy
version installed by `setup-uv`. This will yield a report to Astral
maintainers, unrelated to this patch.
## Summary
New `enable_logging=True` parameter on `responder.API()` that provides
structured, request-scoped logging using stdlib `logging` and
`contextvars`.
### What it does
- **`api.log`** — always available on every API instance. Works as a
plain logger by default; gains per-request context enrichment with
`enable_logging=True`
- **Per-request context** — every log message automatically includes
request ID, HTTP method, path, and client IP
- **Access logging** — logs every request with timing: `GET /path → 200
(1.2ms)`
- **Request ID** — generates or forwards `X-Request-ID` headers
(supersedes `request_id=True` when both are set)
- **stdlib logging** — works with any existing handler, formatter, or
log aggregator
### Usage
```python
# api.log always works — no setup required
api = responder.API()
api.log.info("starting up") # plain logger, no context
# With enable_logging=True, log messages get request context automatically
api = responder.API(enable_logging=True)
@api.route("/")
def index(req, resp):
api.log.info("handling request")
# => 2026-03-24 12:00:00 [INFO] responder.app — handling request [GET /] [req:abc123] [client:127.0.0.1]
```
For additional loggers in helper modules:
```python
from responder.ext.logging import get_logger
logger = get_logger("myapp.db")
```
### Architecture
- `responder/ext/logging.py` — self-contained module with:
- `LoggingMiddleware` — pure ASGI middleware that sets contextvars and
logs access
- `RequestContextFilter` — logging filter that injects context into
records
- `RequestContext` — read-only access to current request metadata
- `get_logger()` / `setup_logging()` — convenience functions
- `api.log` — always a valid logger; context-aware when
`enable_logging=True`, plain stdlib logger otherwise
- Wired into `API.__init__` via the `enable_logging` parameter
### Files
- `responder/ext/logging.py` — new module
- `responder/api.py` — added `enable_logging` parameter and `api.log`
- `tests/test_logging.py` — 9 tests
- `docs/source/tour.rst` — new Structured Logging section
- `docs/source/index.rst` — added to feature list
## Test plan
- [x] 9 logging tests pass
- [x] Full suite: 208 passed
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Summary
- **Auth tutorial**: fix deprecated `datetime.utcnow()` →
`datetime.now(timezone.utc)`, add role-based access control section, add
auth strategy comparison guide
- **WebSocket tutorial**: use `WebSocketDisconnect` instead of bare
`Exception` in chat example, add connection lifecycle section with close
codes, add rejected connection test example
- **SQLAlchemy tutorial**: modernize from `Column()` to
`mapped_column()` / `Mapped[]` (SQLAlchemy 2.0 style with type checker
support)
- **Deployment**: use `uv` in Docker example instead of pip, fix stale
`uv.lock` reference in production checklist
- **Quickstart**: link to all tutorials in "next steps" (was only
linking to 3 of 9)
- **Sandbox**: rewrite with project layout tree, mypy command, and
pattern-matching test examples
## Test plan
- [x] `make html` builds cleanly
- [x] All 199 tests pass
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Summary
- **Deployment guide**: health check endpoint, Docker Compose example,
Caddy reverse proxy config, Procfile pattern, production checklist
- **API reference**: quick usage examples for every class (API, Request,
Response, RouteGroup, BackgroundQueue, RateLimiter, status helpers)
- **Feature tour**: new Pydantic validation and content negotiation
sections, expanded MessagePack docs
- **Testing guide**: rate limiting and mounted WSGI app test examples,
Werkzeug 3.1.7 tip
- **Middleware tutorial**: pure ASGI middleware example (no
BaseHTTPMiddleware dependency)
- **CLI guide**: environment variables section (PORT, SECRET_KEY)
- **Homepage**: updated feature list with SSE, rate limiting, Pydantic,
content negotiation, route groups
- **Backlog**: removed already-implemented items, added current ideas
+362 lines of docs, no code changes.
## Test plan
- [x] `make html` builds cleanly with no warnings
- [x] All 199 tests pass
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Andreas Motl <andreas.motl@panodata.org>
- Call future.result() instead of bare property access in test (#596)
- Catch (ValueError, TypeError) instead of broad Exception in
response model serialization (#597)
- Catch WebSocketDisconnect instead of broad Exception in
websocket chat example (#598)
Closes#596, closes#597, closes#598
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## About
A few cosmetic adjustments aka. code formatting.
Also validate the outcome on CI/GHA.
Feel free to improve now or later at your disposal.
## Details
The updates are based on using the most recent versions of pyproject-fmt
and ruff.
Specifically, spots marked with `noqa` might need further love, also at
your disposal.
---------
Co-authored-by: Kenneth Reitz <me@kennethreitz.org>
## Summary
- When a WSGI app (e.g. Flask) is mounted at `/prefix` and a request
hits exactly `/prefix`, the path prefix was stripped to `""` instead of
`"/"`, causing frameworks like Flask to return 400.
- One-character fix: default the stripped path to `"/"` when empty.
## Test plan
- [x] `tests/test_responder.py::test_mount_wsgi_app` now passes
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Document mounting marimo ASGI apps in the feature tour
- Add examples/marimo_mount.py showing the integration
- Verified working: marimo.create_asgi_app() mounts cleanly via api.mount()
Fixes#580.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extract variables and operationName from query params and form data,
not just JSON bodies. Fixes#571.
- Add docstrings to GraphQLView class and methods. Fixes#572.
- Add 10 new GraphQL tests: variables, operationName, mutations,
context access, malformed queries, raw text, invalid variables
param. Fixes#568.
- Remove Python 3.9 from CI matrix (dropped in 3.4.0).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add comprehensive type hints to compile_path, BaseRoute, Route,
WebSocketRoute, and Router classes. Uses Starlette's Scope, Receive,
Send types and properly types the ASGI/WSGI union in Router.apps.
Fixes#566.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Include ext/openapi/docs/*.html in package_data so OpenAPI docs
themes (swagger_ui, redoc, rapidoc, elements) ship with the wheel.
Fixes#583.
- Add tests for static file serving and index.html fallback. Fixes#563.
- Bump version to 3.4.1.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add docstrings to all undocumented public methods across API, Request,
Response, Router, Route, BackgroundQueue, and related classes
- Expand api.rst with autodoc sections for RouteGroup, BackgroundQueue,
QueryDict, and RateLimiter
- Update starlette dependency to >=1.0
- Drop Python 3.9 support (required by Starlette 1.0), minimum is now 3.10
- Bump version to 3.4.0
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New documentation pages:
- Authentication: API keys, JWT tokens, session auth, custom exceptions
- WebSocket Tutorial: echo server, chat room, HTML client, data formats
- Writing Middleware: hooks vs middleware, Starlette integration, ordering
- Configuration: env vars, .env files, secret keys, debug mode, production setup
Also:
- Complete working example at end of quickstart with cross-references
- Three new example files: rest_api.py, websocket_chat.py, sse_stream.py
- Changelog updated for v3.2.0
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three new tutorial pages:
- Building a REST API: full CRUD with Pydantic validation, from scratch
- Using SQLAlchemy: async engine, lifespan setup, CRUD with ORM
- Migrating from Flask: concept mapping, quick reference table,
gradual migration via app mounting
Also rewritten:
- CLI docs: cleaner, more concise
- API reference: added prose descriptions for each section
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Every section now teaches web development concepts alongside the code:
- HTTP methods, status codes, content negotiation explained
- What ASGI is and why it matters
- How cookies, sessions, CORS, and HSTS work
- When to use WebSockets vs SSE
- Why request validation matters
- How background tasks differ from task queues
- What rate limiting protects against
- What Host header injection is
- Separation of concerns in templating
People should learn about web development while reading these docs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New sections: request validation, headers/cookies, custom error
handlers, before/after hooks, and practical tips. Every section
now explains the why, not just the how.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>