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

7.1.2

Download files

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

Source Distribution

responder-7.1.2.tar.gz (195.6 kB view details)

Uploaded Source

Built Distribution

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

responder-7.1.2-py3-none-any.whl (123.6 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for responder-7.1.2.tar.gz
Algorithm Hash digest
SHA256 f7fde588e05f979a24f9ccc83de3e6a15038a9479ffd9a3415c6782f8e3ba99d
MD5 462c1cb476431f784e3f132d21ced9b2
BLAKE2b-256 428934f13ca3601d1bbe16701129ab1ba7b7dcf93e603f6507ac35241e571027

See more details on using hashes here.

File details

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

File metadata

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

File hashes

Hashes for responder-7.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 67accf4aaff57d9347348dd34175b6bd31007b05c72806bb6013afa59cfcaffa
MD5 ecbce680f9eabd500921756ab4ead0a2
BLAKE2b-256 55b931e2f4155e15a990ec0850c54b563b663b8023a21bac7ac7309334ac35e1

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