mirror of
https://github.com/kennethreitz/responder.git
synced 2026-06-05 23:00:17 +00:00
1bfd85b003
Define your API schemas with Pydantic models instead of (or alongside)
YAML docstrings and marshmallow:
from pydantic import BaseModel
class PetIn(BaseModel):
name: str
age: int = 0
class PetOut(BaseModel):
id: int
name: str
age: int
@api.route("/pets", methods=["POST"],
request_model=PetIn, response_model=PetOut)
async def create_pet(req, resp):
data = await req.media()
resp.media = {"id": 1, **data}
Also works with @api.schema("Name") decorator for registering
standalone schema components.
Pydantic models, marshmallow schemas, and YAML docstrings can all
be used together in the same API.
Also: rewrite docs with more prose, restore sidebar logo and links,
add FastAPI acknowledgment, update homepage copy.
161 tests, 95% coverage.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
235 lines
6.2 KiB
ReStructuredText
235 lines
6.2 KiB
ReStructuredText
Quick Start
|
|
===========
|
|
|
|
This guide will walk you through the basics of building a web service with
|
|
Responder. By the end, you'll know how to declare routes, handle requests,
|
|
send responses, render templates, and process background tasks.
|
|
|
|
|
|
Create a Web Service
|
|
--------------------
|
|
|
|
The first thing you need to do is declare a web service. This is the central
|
|
object that holds all your routes, middleware, and configuration::
|
|
|
|
import responder
|
|
|
|
api = responder.API()
|
|
|
|
|
|
Hello World
|
|
-----------
|
|
|
|
Next, add a route. Here, we'll make the root URL say "hello, world!"::
|
|
|
|
@api.route("/")
|
|
def hello_world(req, resp):
|
|
resp.text = "hello, world!"
|
|
|
|
Every view receives a ``req`` (request) and ``resp`` (response) object. You
|
|
don't need to return anything — just mutate the response directly.
|
|
|
|
|
|
Run the Server
|
|
--------------
|
|
|
|
Start your web service with ``api.run()``::
|
|
|
|
api.run()
|
|
|
|
This spins up a production-grade uvicorn server on port ``5042``, ready for
|
|
incoming HTTP requests.
|
|
|
|
You can customize the port with ``api.run(port=8000)``. The ``PORT``
|
|
environment variable is also honored automatically — when set, Responder
|
|
binds to ``0.0.0.0`` on that port, which is what cloud platforms like
|
|
Fly.io, Railway, and Google Cloud Run expect.
|
|
|
|
.. note::
|
|
|
|
Both sync and async views are supported. The ``async`` keyword is always
|
|
optional — use it when you need to ``await`` something.
|
|
|
|
|
|
Route Parameters
|
|
----------------
|
|
|
|
If you want dynamic URLs, use Python's familiar f-string syntax to declare
|
|
variables in your routes::
|
|
|
|
@api.route("/hello/{who}")
|
|
def hello_to(req, resp, *, who):
|
|
resp.text = f"hello, {who}!"
|
|
|
|
A ``GET`` request to ``/hello/world`` will respond with ``hello, world!``.
|
|
|
|
Route parameters are passed as keyword-only arguments (after the ``*``).
|
|
|
|
|
|
Type Convertors
|
|
^^^^^^^^^^^^^^^
|
|
|
|
You can constrain route parameters to specific types. The parameter will be
|
|
automatically converted before it reaches your view::
|
|
|
|
@api.route("/add/{a:int}/{b:int}")
|
|
async def add(req, resp, *, a, b):
|
|
resp.text = f"{a} + {b} = {a + b}"
|
|
|
|
Supported types:
|
|
|
|
- ``str`` — matches any string without slashes (default)
|
|
- ``int`` — matches digits, converts to ``int``
|
|
- ``float`` — matches decimal numbers, converts to ``float``
|
|
- ``uuid`` — matches UUID strings like ``550e8400-e29b-41d4-a716-446655440000``
|
|
- ``path`` — matches any string *including* slashes, useful for file paths
|
|
|
|
|
|
Sending Responses
|
|
-----------------
|
|
|
|
Responder gives you several ways to send data back to the client. Just set
|
|
the appropriate property on the response object.
|
|
|
|
**Text and HTML**::
|
|
|
|
resp.text = "plain text response"
|
|
resp.html = "<h1>HTML response</h1>"
|
|
|
|
**JSON** — the most common pattern for APIs. Set ``resp.media`` to any
|
|
JSON-serializable Python object::
|
|
|
|
@api.route("/hello/{who}/json")
|
|
def hello_json(req, resp, *, who):
|
|
resp.media = {"hello": who}
|
|
|
|
If the client sends an ``Accept: application/x-yaml`` header, the same data
|
|
will be returned as YAML instead. Content negotiation is automatic.
|
|
|
|
**Files** — serve a file from disk with automatic content-type detection::
|
|
|
|
resp.file("reports/annual.pdf")
|
|
|
|
**Raw bytes**::
|
|
|
|
resp.content = b"\x89PNG\r\n..."
|
|
|
|
**Status codes and headers**::
|
|
|
|
resp.status_code = 201
|
|
resp.headers["X-Custom"] = "value"
|
|
|
|
**Redirects**::
|
|
|
|
api.redirect(resp, location="/new-url")
|
|
|
|
|
|
Reading Requests
|
|
----------------
|
|
|
|
The request object gives you access to everything the client sent.
|
|
|
|
**Method and URL**::
|
|
|
|
req.method # "get", "post", etc. (lowercase)
|
|
req.full_url # "http://example.com/path?q=1"
|
|
req.url # parsed URL object
|
|
|
|
**Headers** — case-insensitive, just like you'd expect::
|
|
|
|
req.headers["Content-Type"]
|
|
req.headers["content-type"] # same thing
|
|
|
|
**Query parameters**::
|
|
|
|
# GET /search?q=python&page=2
|
|
req.params["q"] # "python"
|
|
req.params["page"] # "2"
|
|
|
|
**Path parameters** — also available on the request object::
|
|
|
|
req.path_params["user_id"] # same as the keyword argument
|
|
|
|
**Request body** — for POST/PUT/PATCH requests, you need to ``await`` the
|
|
body content::
|
|
|
|
# JSON body
|
|
data = await req.media()
|
|
|
|
# Form data
|
|
data = await req.media("form")
|
|
|
|
# File uploads
|
|
files = await req.media("files")
|
|
|
|
# Raw bytes
|
|
body = await req.content
|
|
|
|
# Raw text
|
|
text = await req.text
|
|
|
|
**Other useful properties**::
|
|
|
|
req.is_json # True if content type is JSON
|
|
req.cookies # dict of cookies
|
|
req.session # session data (dict)
|
|
req.client # (host, port) tuple
|
|
req.is_secure # True if HTTPS
|
|
|
|
|
|
Rendering Templates
|
|
-------------------
|
|
|
|
Responder includes built-in `Jinja2 <https://jinja.palletsprojects.com/>`_
|
|
support. Templates are loaded from the ``templates/`` directory by default.
|
|
|
|
The simplest way is to use ``api.template()``::
|
|
|
|
@api.route("/hello/{name}/html")
|
|
def hello_html(req, resp, *, name):
|
|
resp.html = api.template("hello.html", name=name)
|
|
|
|
You can also use the ``Templates`` class directly for more control::
|
|
|
|
from responder.templates import Templates
|
|
|
|
templates = Templates(directory="templates")
|
|
|
|
@api.route("/page")
|
|
def page(req, resp):
|
|
resp.html = templates.render("page.html", title="Hello")
|
|
|
|
Async rendering is supported too::
|
|
|
|
templates = Templates(directory="templates", enable_async=True)
|
|
resp.html = await templates.render_async("page.html", title="Hello")
|
|
|
|
You can render template strings without a file::
|
|
|
|
resp.html = api.template_string("Hello, {{ name }}!", name="world")
|
|
|
|
|
|
Background Tasks
|
|
----------------
|
|
|
|
Sometimes you want to accept a request, respond immediately, and do the
|
|
actual processing later. Responder makes this easy with background tasks::
|
|
|
|
@api.route("/incoming")
|
|
async def receive_incoming(req, resp):
|
|
data = await req.media()
|
|
|
|
@api.background.task
|
|
def process_data(data):
|
|
"""This runs in a background thread."""
|
|
import time
|
|
time.sleep(10) # simulate heavy work
|
|
|
|
process_data(data)
|
|
|
|
# Respond immediately — processing continues in the background
|
|
resp.media = {"status": "accepted"}
|
|
|
|
The ``@api.background.task`` decorator wraps any function to run in a thread
|
|
pool. The client gets an immediate response while the work continues.
|