Skip to main content

A familiar HTTP Service Framework for Python.

Project description

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.11+.

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.
  • File(...) uploads use streamed UploadFile objects; await file.save(path) writes them to disk.
  • 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}

# Route-local hooks
def require_json(req, resp):
    if not req.is_json:
        resp.status_code = 415
        resp.media = {"error": "JSON required"}

@api.post("/events", before=require_json)
async def events(req, resp):
    resp.media = await req.media()

# Local dependencies
from responder import Depends

def current_user(req):
    return req.headers.get("X-User")

@api.get("/me")
def me(req, resp, *, user=Depends(current_user)):
    resp.media = {"user": user}

# Side-effect dependencies
@api.get("/ready", dependencies=[Depends(current_user)])
def ready(req, resp):
    resp.media = {"ready": True}

# Route-level auth with OpenAPI security
from responder.ext.auth import BearerAuth

auth = BearerAuth(tokens=["s3cret"])

@api.get("/private", auth=auth)
def private(req, resp, *, user):
    resp.media = {"user": user}

# Named policies keep route auth intent reusable
admin = api.policy("admin", auth.requires("admin"))

@api.get("/admin", auth=admin)
def admin(req, resp, *, user):
    resp.media = {"user": user}

# App-level auth with public route opt-out
secured_api = responder.API(auth=auth)

@secured_api.get("/health", auth=None)
def health(req, resp):
    resp.media = {"ok": True}

# Optional auth accepts anonymous requests but still rejects bad credentials
optional_auth = auth.optional()

@api.get("/maybe", auth=optional_auth)
def maybe(req, resp, *, user):
    resp.media = {"user": user}

# 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)}

# Problem-details enrichment
def problem_handler(payload, request, exc):
    payload["type"] = f"https://example.com/problems/{payload['status']}"
    payload["instance"] = request.url.path

api = responder.API(problem_handler=problem_handler, request_id=True)

# 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. Install responder[server] to add Granian for production ASGI serving.

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

Framework errors use RFC 9457-style application/problem+json responses by default; pass problem_details=False to keep the legacy error format. Pass problem_handler= to enrich those payloads; request IDs are included when request_id=True or structured logging is enabled. OpenAPI documents the shared ProblemDetails schema, and generated clients expose it as APIError.problem. Handlers can send their own problem details with resp.problem(...), and use resp.created(...) / resp.no_content() for common REST responses.

For a polished, practical app, see examples/todo.py; for a tiny local-command example, see examples/fortunes.py. For the full contract fixture, see examples/atelier.py; the test suite imports it, validates its OpenAPI document, and drives a generated client against it.

Documentation

https://responder.kennethreitz.org

Project details


Release history Release notifications | RSS feed

This version

8.2.0

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

responder-8.2.0.tar.gz (333.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

responder-8.2.0-py3-none-any.whl (184.8 kB view details)

Uploaded Python 3

File details

Details for the file responder-8.2.0.tar.gz.

File metadata

  • Download URL: responder-8.2.0.tar.gz
  • Upload date:
  • Size: 333.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.6

File hashes

Hashes for responder-8.2.0.tar.gz
Algorithm Hash digest
SHA256 ae7c3f302b7ae0ab2a3cecda8b3ad755b95d55240aeb05385c745216508a5d14
MD5 bd43c48167af679d63ecfb02a9ae8e90
BLAKE2b-256 b9aaf1136465e897612cfc1db9fd2f1f73f4c36db207777a6f495f6cb808b5fc

See more details on using hashes here.

File details

Details for the file responder-8.2.0-py3-none-any.whl.

File metadata

  • Download URL: responder-8.2.0-py3-none-any.whl
  • Upload date:
  • Size: 184.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.6

File hashes

Hashes for responder-8.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 985c84354c9104e0ca6f0177b52022b9e8e5de575dee50dfd014ed763eb7f979
MD5 2926d0d066c3356d69b18c9874e56e69
BLAKE2b-256 5a7665f7f9e897f6816cdb15da8283dd8326b476c9c4ab602389e187ccf7027a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page