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.1.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.1.0.tar.gz (328.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.1.0-py3-none-any.whl (182.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: responder-8.1.0.tar.gz
  • Upload date:
  • Size: 328.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.1.0.tar.gz
Algorithm Hash digest
SHA256 444545624da94c4f18b9c47fd3264d3343224cd25b08e190d83ef75b739d1b31
MD5 dec1149e01d8ea8d624b1bd1c4efbe25
BLAKE2b-256 895ffc760f6d1924f1be4bea3bff4d9c4357bd5dfabc95a83c2817f64c62b2df

See more details on using hashes here.

File details

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

File metadata

  • Download URL: responder-8.1.0-py3-none-any.whl
  • Upload date:
  • Size: 182.2 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.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 77da22a53627adaa0b97b723fe40983c69afef4d7f070887a4e2207cdb7ae6cc
MD5 331192cef20e70f88b0fc86c9db2ada1
BLAKE2b-256 8e7a2e1647dc3a87044aee5b68b021e5569eda4e5444727a9c652b0b48bbb93c

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