kennethreitz 36abcb402e Batch 7: fix regressions found by adversarial review; bump to 4.1.0
Adversarial multi-agent review of the full diff surfaced real issues
(several in my own additions); all fixed and regression-tested:

- response_model: only validate dict/model bodies and only for actual
  Pydantic models — `response_model=list` (a documented marker) no longer
  500s, and list bodies pass through untouched as in v4.0.
- Open-redirect guard: normalize backslashes and leading control/whitespace
  before classifying, so `/\evil.com` & friends are blocked (browsers treat
  `\` as `/`).
- Server sessions: reverted the dirty-tracking/deepcopy optimization — it
  broke sliding backend TTL (premature logout) and crashed on
  non-deepcopyable MemoryBackend values. Back to v4.0 always-write semantics.
- Reverted the ServerErrorMiddleware-outermost reorder: it dropped
  X-Request-ID and mislogged status on 500s. Restored v4.0 ordering.
- req.params: decode the scope query string as UTF-8 (not latin-1) so raw
  non-ASCII queries aren't mojibaked; keeps the no-urlparse speedup.
- GraphQL max_depth: follow fragment spreads so depth can't be smuggled via
  chained fragments (cycles broken; operations measured as roots).
- Type-hint body injection: gate to POST/PUT/PATCH/DELETE and skip params
  that declare a default, so GET and optional-model handlers keep working.

Bumps version to 4.1.0 and finalizes the changelog.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 02:59:18 -04:00
2018-10-11 13:57:55 -04:00
2024-10-24 02:27:32 +02:00

Responder

A familiar HTTP Service Framework for Python, powered by Starlette.

import responder

api = responder.API()

@api.route("/{greeting}")
async def greet_world(req, resp, *, greeting):
    resp.text = f"{greeting}, world!"

if __name__ == "__main__":
    api.run()
$ pip install responder

That's it. Supports Python 3.10+.

The Basics

  • resp.text sends back text. resp.html sends back HTML. resp.content sends back bytes.
  • resp.media sends back JSON (or YAML, with content negotiation).
  • resp.file("path.pdf") serves a file with automatic content-type detection.
  • req.headers is case-insensitive. req.params gives you query parameters.
  • Both sync and async views work — the async is optional.

Highlights

# Type-safe route parameters
@api.route("/users/{user_id:int}")
async def get_user(req, resp, *, user_id):
    resp.media = {"id": user_id}

# HTTP method filtering
@api.route("/items", methods=["POST"])
async def create_item(req, resp):
    data = await req.media()
    resp.media = {"created": data}

# Class-based views
@api.route("/things/{id}")
class ThingResource:
    def on_get(self, req, resp, *, id):
        resp.media = {"id": id}
    def on_post(self, req, resp, *, id):
        resp.text = "created"

# Before-request hooks (auth, rate limiting, etc.)
@api.route(before_request=True)
def check_auth(req, resp):
    if not req.headers.get("Authorization"):
        resp.status_code = 401
        resp.media = {"error": "unauthorized"}

# Custom error handling
@api.exception_handler(ValueError)
async def handle_error(req, resp, exc):
    resp.status_code = 400
    resp.media = {"error": str(exc)}

# Lifespan events
from contextlib import asynccontextmanager

@asynccontextmanager
async def lifespan(app):
    print("starting up")
    yield
    print("shutting down")

api = responder.API(lifespan=lifespan)

# GraphQL
import graphene
api.graphql("/graphql", schema=graphene.Schema(query=Query))

# WebSockets
@api.route("/ws", websocket=True)
async def websocket(ws):
    await ws.accept()
    while True:
        name = await ws.receive_text()
        await ws.send_text(f"Hello {name}!")

# Mount WSGI/ASGI apps
from flask import Flask
flask_app = Flask(__name__)
api.mount("/flask", flask_app)

# Background tasks
@api.route("/work")
def do_work(req, resp):
    @api.background.task
    def process():
        import time; time.sleep(10)
    process()
    resp.media = {"status": "processing"}

Built-in OpenAPI docs, cookie-based sessions, gzip compression, static file serving, Jinja2 templates, and a production uvicorn server.

Route convertors: str, int, float, uuid, path.

Documentation

https://responder.kennethreitz.org

Languages
Python 99.7%
HTML 0.2%