mirror of
https://github.com/kennethreitz/responder.git
synced 2026-10-24 05:59:12 +00:00
1981 lines
237 KiB
HTML
1981 lines
237 KiB
HTML
<!DOCTYPE html>
|
||
|
||
<html lang="en" data-content_root="./">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
|
||
<title>Feature Tour — responder 9.2.0 documentation</title>
|
||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=5ecbeea2" />
|
||
<link rel="stylesheet" type="text/css" href="_static/basic.css?v=b08954a9" />
|
||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css?v=eb4102c5" />
|
||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||
<link rel="stylesheet" type="text/css" href="_static/sphinx-design.min.css?v=95c83b7e" />
|
||
<link rel="stylesheet" type="text/css" href="_static/design-elements.e5416f61bae5d36adc6d722a2b6f8cff.css?v=452a8e97" />
|
||
<script src="_static/documentation_options.js?v=e8b940be"></script>
|
||
<script src="_static/doctools.js?v=fd6eb6e6"></script>
|
||
<script src="_static/sphinx_highlight.js?v=6ffebe34"></script>
|
||
<script src="_static/clipboard.min.js?v=a7894cd8"></script>
|
||
<script src="_static/copybutton.js?v=fd10adb8"></script>
|
||
<script>
|
||
</script>
|
||
<script src="_static/design-tabs.js?v=f930bc37"></script>
|
||
<script src="_static/design-elements.bbdccc18c4abea9397628f9fea3d48c2.js?v=03c7770e"></script>
|
||
<link rel="index" title="Index" href="genindex.html" />
|
||
<link rel="search" title="Search" href="search.html" />
|
||
<link rel="next" title="Examples" href="examples.html" />
|
||
<link rel="prev" title="Quick Start" href="quickstart.html" />
|
||
|
||
<link rel="stylesheet" href="_static/custom.css" type="text/css" />
|
||
|
||
|
||
|
||
|
||
|
||
</head><body>
|
||
|
||
|
||
<div class="document">
|
||
<div class="documentwrapper">
|
||
<div class="bodywrapper">
|
||
|
||
|
||
<div class="body" role="main">
|
||
|
||
<section id="feature-tour">
|
||
<h1>Feature Tour<a class="headerlink" href="#feature-tour" title="Link to this heading">¶</a></h1>
|
||
<p>This section walks through Responder’s features in depth. Each section
|
||
explains the concept, shows working code, and explains the design choices
|
||
behind it. If you’re new to web development, this is a good place to learn
|
||
how modern web frameworks work under the hood.</p>
|
||
<section id="method-filtering">
|
||
<h2>Method Filtering<a class="headerlink" href="#method-filtering" title="Link to this heading">¶</a></h2>
|
||
<p>HTTP defines several <em>methods</em> (also called verbs) that describe what a
|
||
client wants to do with a resource. The most common are:</p>
|
||
<ul class="simple">
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">GET</span></code> — retrieve data</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">POST</span></code> — create something new</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">PUT</span></code> — replace something entirely</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">PATCH</span></code> — update part of something</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">DELETE</span></code> — remove something</p></li>
|
||
</ul>
|
||
<p>By default, a Responder route matches all methods. This is fine for simple
|
||
endpoints, but REST APIs typically map different methods to different
|
||
operations. Use the <code class="docutils literal notranslate"><span class="pre">methods</span></code> parameter to restrict a route:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/items"</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s2">"GET"</span><span class="p">])</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">list_items</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"items"</span><span class="p">:</span> <span class="p">[]}</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/items"</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s2">"POST"</span><span class="p">],</span> <span class="n">check_existing</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">create_item</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">data</span> <span class="o">=</span> <span class="k">await</span> <span class="n">req</span><span class="o">.</span><span class="n">media</span><span class="p">()</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"created"</span><span class="p">:</span> <span class="n">data</span><span class="p">}</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Note the <code class="docutils literal notranslate"><span class="pre">check_existing=False</span></code> — Responder normally prevents you from
|
||
registering two routes with the same path (to catch typos). When you
|
||
intentionally want multiple handlers for the same path with different
|
||
methods, you need to opt in.</p>
|
||
<p>Method-restricted routes get correct HTTP semantics for free:</p>
|
||
<ul class="simple">
|
||
<li><p>Requests with an unsupported method receive <code class="docutils literal notranslate"><span class="pre">405</span> <span class="pre">Method</span> <span class="pre">Not</span> <span class="pre">Allowed</span></code>
|
||
(not 404) with an <code class="docutils literal notranslate"><span class="pre">Allow</span></code> header listing what the path supports.</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">OPTIONS</span></code> requests are answered automatically with the <code class="docutils literal notranslate"><span class="pre">Allow</span></code> header.</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">HEAD</span></code> is accepted wherever <code class="docutils literal notranslate"><span class="pre">GET</span></code> is.</p></li>
|
||
</ul>
|
||
</section>
|
||
<section id="returning-values">
|
||
<h2>Returning Values<a class="headerlink" href="#returning-values" title="Link to this heading">¶</a></h2>
|
||
<p>Handlers normally communicate by mutating <code class="docutils literal notranslate"><span class="pre">resp</span></code>, but they can also just
|
||
return a value:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/users"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">list_users</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="k">return</span> <span class="p">[{</span><span class="s2">"name"</span><span class="p">:</span> <span class="s2">"alice"</span><span class="p">}]</span> <span class="c1"># same as resp.media = [...]</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/hello"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">hello</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="k">return</span> <span class="s2">"hello, world!"</span> <span class="c1"># same as resp.text = "..."</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>A <code class="docutils literal notranslate"><span class="pre">dict</span></code>, <code class="docutils literal notranslate"><span class="pre">list</span></code>, number, or boolean becomes <code class="docutils literal notranslate"><span class="pre">resp.media</span></code>, a <code class="docutils literal notranslate"><span class="pre">str</span></code>
|
||
becomes <code class="docutils literal notranslate"><span class="pre">resp.text</span></code>, and <code class="docutils literal notranslate"><span class="pre">bytes</span></code> become <code class="docutils literal notranslate"><span class="pre">resp.content</span></code>. A Pydantic model
|
||
or a dataclass instance also becomes <code class="docutils literal notranslate"><span class="pre">resp.media</span></code> — serialized natively
|
||
across JSON, YAML, and MessagePack, so a trailing <code class="docutils literal notranslate"><span class="pre">.model_dump()</span></code> is optional.
|
||
Returning <code class="docutils literal notranslate"><span class="pre">None</span></code> (the implicit default) leaves the response exactly as you
|
||
set it, so existing handlers are unaffected. Use whichever style reads
|
||
better — for quick JSON endpoints, returning the data directly is hard to
|
||
beat.</p>
|
||
<p>To set a status code, or status and headers, return a Flask-style tuple —
|
||
<code class="docutils literal notranslate"><span class="pre">body,</span> <span class="pre">status</span></code> or <code class="docutils literal notranslate"><span class="pre">body,</span> <span class="pre">status,</span> <span class="pre">headers</span></code>:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/items/{id:int}"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">get_item</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="nb">id</span><span class="p">):</span>
|
||
<span class="n">item</span> <span class="o">=</span> <span class="n">lookup</span><span class="p">(</span><span class="nb">id</span><span class="p">)</span>
|
||
<span class="k">if</span> <span class="n">item</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
|
||
<span class="k">return</span> <span class="p">{</span><span class="s2">"error"</span><span class="p">:</span> <span class="s2">"not found"</span><span class="p">},</span> <span class="mi">404</span>
|
||
<span class="k">return</span> <span class="n">item</span><span class="p">,</span> <span class="mi">200</span><span class="p">,</span> <span class="p">{</span><span class="s2">"X-Source"</span><span class="p">:</span> <span class="s2">"cache"</span><span class="p">}</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Routes are also forgiving about trailing slashes: a request to <code class="docutils literal notranslate"><span class="pre">/users/</span></code>
|
||
when only <code class="docutils literal notranslate"><span class="pre">/users</span></code> is registered (or vice versa) receives a <code class="docutils literal notranslate"><span class="pre">307</span></code>
|
||
redirect to the canonical path, preserving the method and query string.
|
||
Pass <code class="docutils literal notranslate"><span class="pre">redirect_slashes=False</span></code> to <code class="docutils literal notranslate"><span class="pre">API()</span></code> for strict matching.</p>
|
||
</section>
|
||
<section id="class-based-views">
|
||
<h2>Class-Based Views<a class="headerlink" href="#class-based-views" title="Link to this heading">¶</a></h2>
|
||
<p>Function-based views are great for simple endpoints, but sometimes you want
|
||
to group related HTTP methods together into a single resource. This is
|
||
where class-based views come in — a pattern popularized by
|
||
<a class="reference external" href="https://falconframework.org/">Falcon</a>.</p>
|
||
<p>Responder dispatches to the appropriate method handler based on the HTTP
|
||
method:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/</span><span class="si">{greeting}</span><span class="s2">"</span><span class="p">)</span>
|
||
<span class="k">class</span><span class="w"> </span><span class="nc">GreetingResource</span><span class="p">:</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">on_get</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">greeting</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">greeting</span><span class="si">}</span><span class="s2">, world!"</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">on_post</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">greeting</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"received"</span><span class="p">:</span> <span class="n">greeting</span><span class="p">}</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">on_request</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">greeting</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">"""Called on EVERY request, before the method-specific handler."""</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"X-Greeting"</span><span class="p">]</span> <span class="o">=</span> <span class="n">greeting</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The <code class="docutils literal notranslate"><span class="pre">on_request</span></code> method is called for all HTTP methods, much like
|
||
middleware scoped to a single route. Method-specific handlers (<code class="docutils literal notranslate"><span class="pre">on_get</span></code>,
|
||
<code class="docutils literal notranslate"><span class="pre">on_post</span></code>, <code class="docutils literal notranslate"><span class="pre">on_put</span></code>, <code class="docutils literal notranslate"><span class="pre">on_delete</span></code>, etc.) are called after.</p>
|
||
<p>No inheritance required — just define a class with the right method names.
|
||
This is simpler than Django’s <code class="docutils literal notranslate"><span class="pre">View</span></code> classes and more Pythonic than
|
||
framework-specific base classes.</p>
|
||
<p>Everything the type-driven request pipeline offers function views works on
|
||
class-based view methods too: a required Pydantic-typed body parameter on a
|
||
write-method handler receives the parsed, validated body (an invalid body
|
||
returns <code class="docutils literal notranslate"><span class="pre">422</span></code> before the handler runs), and <code class="docutils literal notranslate"><span class="pre">Query()</span></code>/<code class="docutils literal notranslate"><span class="pre">Header()</span></code>/
|
||
<code class="docutils literal notranslate"><span class="pre">Cookie()</span></code>/<code class="docutils literal notranslate"><span class="pre">Path()</span></code>/<code class="docutils literal notranslate"><span class="pre">Form()</span></code> markers resolve exactly as they do on
|
||
functions:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">pydantic</span><span class="w"> </span><span class="kn">import</span> <span class="n">BaseModel</span>
|
||
<span class="kn">from</span><span class="w"> </span><span class="nn">responder</span><span class="w"> </span><span class="kn">import</span> <span class="n">Query</span>
|
||
|
||
<span class="k">class</span><span class="w"> </span><span class="nc">ItemIn</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
|
||
<span class="n">name</span><span class="p">:</span> <span class="nb">str</span>
|
||
<span class="n">price</span><span class="p">:</span> <span class="nb">float</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/items"</span><span class="p">)</span>
|
||
<span class="k">class</span><span class="w"> </span><span class="nc">ItemResource</span><span class="p">:</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">on_get</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">limit</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="n">Query</span><span class="p">(</span><span class="mi">10</span><span class="p">)):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="n">fetch_items</span><span class="p">(</span><span class="n">limit</span><span class="p">)</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">on_post</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">item</span><span class="p">:</span> <span class="n">ItemIn</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="n">create_item</span><span class="p">(</span><span class="n">item</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>When <code class="docutils literal notranslate"><span class="pre">on_request</span></code> and a method handler both declare the same body model,
|
||
the body is parsed and validated once and the same instance is injected into
|
||
both.</p>
|
||
<p>Class-based views also answer <code class="docutils literal notranslate"><span class="pre">OPTIONS</span></code> automatically with <code class="docutils literal notranslate"><span class="pre">200</span></code> and an
|
||
<code class="docutils literal notranslate"><span class="pre">Allow</span></code> header listing the implemented methods — define <code class="docutils literal notranslate"><span class="pre">on_options</span></code>
|
||
(or a catch-all <code class="docutils literal notranslate"><span class="pre">on_request</span></code>) to take over. A request for an unimplemented
|
||
method still gets a <code class="docutils literal notranslate"><span class="pre">405</span></code> whose <code class="docutils literal notranslate"><span class="pre">Allow</span></code> header advertises exactly the
|
||
methods that are actually served.</p>
|
||
</section>
|
||
<section id="lifespan-events">
|
||
<h2>Lifespan Events<a class="headerlink" href="#lifespan-events" title="Link to this heading">¶</a></h2>
|
||
<p>Real applications need to set up resources when they start (database
|
||
connection pools, ML models, caches) and tear them down when they stop.
|
||
This is called the application <em>lifespan</em>.</p>
|
||
<p>The modern approach is the <em>context manager</em> pattern, where startup and
|
||
shutdown are two halves of the same block:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">contextlib</span><span class="w"> </span><span class="kn">import</span> <span class="n">asynccontextmanager</span>
|
||
|
||
<span class="nd">@asynccontextmanager</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">lifespan</span><span class="p">(</span><span class="n">app</span><span class="p">):</span>
|
||
<span class="c1"># Startup — runs before the first request</span>
|
||
<span class="nb">print</span><span class="p">(</span><span class="s2">"connecting to database..."</span><span class="p">)</span>
|
||
<span class="k">yield</span>
|
||
<span class="c1"># Shutdown — runs after the server stops</span>
|
||
<span class="nb">print</span><span class="p">(</span><span class="s2">"closing connections..."</span><span class="p">)</span>
|
||
|
||
<span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">lifespan</span><span class="o">=</span><span class="n">lifespan</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Everything before <code class="docutils literal notranslate"><span class="pre">yield</span></code> runs at startup. Everything after runs at
|
||
shutdown. If startup fails, the server won’t start. If shutdown raises,
|
||
it’s logged but the server still exits.</p>
|
||
<p>The traditional event decorator style also works:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">on_event</span><span class="p">(</span><span class="s2">"startup"</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">startup</span><span class="p">():</span>
|
||
<span class="nb">print</span><span class="p">(</span><span class="s2">"starting up"</span><span class="p">)</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">on_event</span><span class="p">(</span><span class="s2">"shutdown"</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">shutdown</span><span class="p">():</span>
|
||
<span class="nb">print</span><span class="p">(</span><span class="s2">"shutting down"</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The context manager is preferred for new code — it keeps related startup
|
||
and shutdown logic together and makes resource cleanup more explicit.</p>
|
||
<p>The two styles compose: with <code class="docutils literal notranslate"><span class="pre">lifespan=</span></code> set, <code class="docutils literal notranslate"><span class="pre">on_event("startup")</span></code>
|
||
handlers run after the context manager is entered, and
|
||
<code class="docutils literal notranslate"><span class="pre">on_event("shutdown")</span></code> handlers run before it exits.</p>
|
||
<p>For values that belong to the application as a whole, use <code class="docutils literal notranslate"><span class="pre">api.state</span></code> —
|
||
a free-form namespace reachable from any handler via <code class="docutils literal notranslate"><span class="pre">req.api.state</span></code>:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">on_event</span><span class="p">(</span><span class="s2">"startup"</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">connect</span><span class="p">():</span>
|
||
<span class="n">api</span><span class="o">.</span><span class="n">state</span><span class="o">.</span><span class="n">db</span> <span class="o">=</span> <span class="k">await</span> <span class="n">create_pool</span><span class="p">()</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/users"</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">users</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="k">await</span> <span class="n">req</span><span class="o">.</span><span class="n">api</span><span class="o">.</span><span class="n">state</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">fetch_users</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>(For resources with teardown, app-scoped dependencies below are usually
|
||
the better fit.)</p>
|
||
</section>
|
||
<section id="dependency-injection">
|
||
<h2>Dependency Injection<a class="headerlink" href="#dependency-injection" title="Link to this heading">¶</a></h2>
|
||
<p>Views often need shared resources — a database session, a config object,
|
||
the current user. Rather than reaching for globals or recomputing them in
|
||
every handler, register them as <em>dependencies</em> and declare them as view
|
||
parameters:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">dependency</span><span class="p">()</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">db</span><span class="p">():</span>
|
||
<span class="n">session</span> <span class="o">=</span> <span class="k">await</span> <span class="n">create_session</span><span class="p">()</span>
|
||
<span class="k">yield</span> <span class="n">session</span>
|
||
<span class="k">await</span> <span class="n">session</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/users/{id:int}"</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">get_user</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="nb">id</span><span class="p">,</span> <span class="n">db</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="k">await</span> <span class="n">db</span><span class="o">.</span><span class="n">fetch_user</span><span class="p">(</span><span class="nb">id</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Any view parameter (beyond <code class="docutils literal notranslate"><span class="pre">req</span></code> and <code class="docutils literal notranslate"><span class="pre">resp</span></code>) whose name matches a
|
||
registered dependency is injected automatically. Path parameters take
|
||
precedence over dependencies of the same name.</p>
|
||
<p>For one-off dependencies that belong to a single route, use <code class="docutils literal notranslate"><span class="pre">Depends</span></code>
|
||
instead of registering a global name:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">responder</span><span class="w"> </span><span class="kn">import</span> <span class="n">Depends</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">current_user</span><span class="p">(</span><span class="n">req</span><span class="p">):</span>
|
||
<span class="k">return</span> <span class="n">decode_user</span><span class="p">(</span><span class="n">req</span><span class="o">.</span><span class="n">headers</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"Authorization"</span><span class="p">))</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/me"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">me</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">user</span><span class="o">=</span><span class="n">Depends</span><span class="p">(</span><span class="n">current_user</span><span class="p">)):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"user"</span><span class="p">:</span> <span class="n">user</span><span class="p">}</span>
|
||
</pre></div>
|
||
</div>
|
||
<p><code class="docutils literal notranslate"><span class="pre">Depends</span></code> providers follow the same lifecycle rules as registered
|
||
dependencies, including generator teardown.</p>
|
||
<p><code class="docutils literal notranslate"><span class="pre">Depends</span></code> also <strong>nests</strong>: a provider’s own parameters can use <code class="docutils literal notranslate"><span class="pre">Depends</span></code>, so
|
||
dependencies compose into chains without registering each one by name:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">get_db</span><span class="p">():</span>
|
||
<span class="o">...</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">current_user</span><span class="p">(</span><span class="n">db</span><span class="o">=</span><span class="n">Depends</span><span class="p">(</span><span class="n">get_db</span><span class="p">)):</span> <span class="c1"># a provider depending on a provider</span>
|
||
<span class="k">return</span> <span class="n">load_user</span><span class="p">(</span><span class="n">db</span><span class="p">)</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/me"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">me</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">user</span><span class="o">=</span><span class="n">Depends</span><span class="p">(</span><span class="n">current_user</span><span class="p">)):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"user"</span><span class="p">:</span> <span class="n">user</span><span class="p">}</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Each provider resolves once per request and shares the same cache, and cycles
|
||
are detected and reported.</p>
|
||
<p>When a dependency is only a guard or setup step, attach it to the route instead
|
||
of adding an unused handler parameter:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/private"</span><span class="p">,</span> <span class="n">dependencies</span><span class="o">=</span><span class="p">[</span><span class="n">Depends</span><span class="p">(</span><span class="n">current_user</span><span class="p">)])</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">private</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"ok"</span><span class="p">:</span> <span class="kc">True</span><span class="p">}</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Providers can be:</p>
|
||
<ul class="simple">
|
||
<li><p><strong>Plain functions</strong> (sync or async) — the return value is injected.</p></li>
|
||
<li><p><strong>Generators</strong> (sync or async) — the yielded value is injected, and the
|
||
code after <code class="docutils literal notranslate"><span class="pre">yield</span></code> runs as teardown once the response is sent (for
|
||
WebSocket routes, when the connection closes). This is perfect for
|
||
database sessions and other resources that need cleanup. Teardown runs
|
||
even if the handler raised.</p></li>
|
||
</ul>
|
||
<p>To make a dependency request-aware, give the provider a parameter named
|
||
<code class="docutils literal notranslate"><span class="pre">req</span></code> (or <code class="docutils literal notranslate"><span class="pre">request</span></code>, or one annotated <code class="docutils literal notranslate"><span class="pre">responder.Request</span></code>) — it
|
||
receives the current request:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">dependency</span><span class="p">()</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">current_user</span><span class="p">(</span><span class="n">req</span><span class="p">):</span>
|
||
<span class="k">return</span> <span class="n">decode_token</span><span class="p">(</span><span class="n">req</span><span class="o">.</span><span class="n">headers</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"Authorization"</span><span class="p">,</span> <span class="s2">""</span><span class="p">))</span>
|
||
</pre></div>
|
||
</div>
|
||
<p><strong>Dependencies compose.</strong> A provider can depend on other providers simply
|
||
by naming them as parameters; Responder resolves the whole graph for you,
|
||
memoizes each provider so it runs at most once per request, and tears
|
||
everything down in reverse order:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">dependency</span><span class="p">()</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">config</span><span class="p">():</span>
|
||
<span class="k">return</span> <span class="n">load_config</span><span class="p">()</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">dependency</span><span class="p">()</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">db</span><span class="p">(</span><span class="n">config</span><span class="p">):</span> <span class="c1"># depends on `config`</span>
|
||
<span class="n">session</span> <span class="o">=</span> <span class="k">await</span> <span class="n">create_session</span><span class="p">(</span><span class="n">config</span><span class="o">.</span><span class="n">db_url</span><span class="p">)</span>
|
||
<span class="k">yield</span> <span class="n">session</span>
|
||
<span class="k">await</span> <span class="n">session</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/users/{id:int}"</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">get_user</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="nb">id</span><span class="p">,</span> <span class="n">db</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="k">await</span> <span class="n">db</span><span class="o">.</span><span class="n">fetch_user</span><span class="p">(</span><span class="nb">id</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Because resolution is memoized across the whole request, a shared provider
|
||
(or one asked for by both <code class="docutils literal notranslate"><span class="pre">on_request</span></code> and <code class="docutils literal notranslate"><span class="pre">on_get</span></code> in a class-based
|
||
view) runs a single time. Cyclic dependencies are detected and raise
|
||
<code class="docutils literal notranslate"><span class="pre">DependencyCycleError</span></code>.</p>
|
||
<p>The names <code class="docutils literal notranslate"><span class="pre">req</span></code>, <code class="docutils literal notranslate"><span class="pre">request</span></code>, <code class="docutils literal notranslate"><span class="pre">resp</span></code>, <code class="docutils literal notranslate"><span class="pre">response</span></code>, <code class="docutils literal notranslate"><span class="pre">ws</span></code>, and
|
||
<code class="docutils literal notranslate"><span class="pre">websocket</span></code> are reserved and can’t be used as dependency names. When
|
||
resolution goes wrong, Responder raises a catchable <code class="docutils literal notranslate"><span class="pre">DependencyError</span></code> —
|
||
specifically <code class="docutils literal notranslate"><span class="pre">DependencyCycleError</span></code>, <code class="docutils literal notranslate"><span class="pre">DependencyScopeError</span></code>, or
|
||
<code class="docutils literal notranslate"><span class="pre">DependencyResolutionError</span></code>. To register under a different name, pass it
|
||
explicitly with <code class="docutils literal notranslate"><span class="pre">@api.dependency(name="db")</span></code> or call
|
||
<code class="docutils literal notranslate"><span class="pre">api.add_dependency("db",</span> <span class="pre">provider)</span></code>.</p>
|
||
<p>For resources that should live as long as the application — connection
|
||
pools, ML models, expensive clients — use the <code class="docutils literal notranslate"><span class="pre">"app"</span></code> scope. The provider
|
||
runs once, on first use, and generator teardown is deferred until the
|
||
application shuts down:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">dependency</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="s2">"app"</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">pool</span><span class="p">():</span>
|
||
<span class="n">pool</span> <span class="o">=</span> <span class="k">await</span> <span class="n">create_pool</span><span class="p">()</span>
|
||
<span class="k">yield</span> <span class="n">pool</span>
|
||
<span class="k">await</span> <span class="n">pool</span><span class="o">.</span><span class="n">close</span><span class="p">()</span> <span class="c1"># runs at shutdown</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>App-scoped providers may compose with <em>other</em> app-scoped providers, but
|
||
they can’t depend on the request or on request-scoped providers — they
|
||
outlive any single request. A per-request database session layered on an
|
||
app-scoped connection pool is the canonical pattern; see
|
||
<a class="reference internal" href="tutorial-sqlalchemy.html"><span class="doc">Using SQLAlchemy</span></a> for a complete example.</p>
|
||
<p>WebSocket handlers participate too: declare path parameters and
|
||
dependencies by name after the <code class="docutils literal notranslate"><span class="pre">ws</span></code> argument, and they’re injected the
|
||
same way (a provider receives the socket via a <code class="docutils literal notranslate"><span class="pre">ws</span></code>/<code class="docutils literal notranslate"><span class="pre">websocket</span></code> (or
|
||
<code class="docutils literal notranslate"><span class="pre">req</span></code>/<code class="docutils literal notranslate"><span class="pre">request</span></code>) parameter or a <code class="docutils literal notranslate"><span class="pre">WebSocket</span></code> annotation):</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/ws/</span><span class="si">{room}</span><span class="s2">"</span><span class="p">,</span> <span class="n">websocket</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">chat</span><span class="p">(</span><span class="n">ws</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">room</span><span class="p">,</span> <span class="n">hub</span><span class="p">):</span>
|
||
<span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">accept</span><span class="p">()</span>
|
||
<span class="k">await</span> <span class="n">hub</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">room</span><span class="p">,</span> <span class="n">ws</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Handlers that only take <code class="docutils literal notranslate"><span class="pre">ws</span></code> keep working unchanged — names are injected
|
||
only when declared.</p>
|
||
</section>
|
||
<section id="serving-files">
|
||
<h2>Serving Files<a class="headerlink" href="#serving-files" title="Link to this heading">¶</a></h2>
|
||
<p>Web applications often need to serve files — downloads, reports, images.
|
||
Responder makes this simple with <code class="docutils literal notranslate"><span class="pre">resp.file()</span></code>, which reads a file from
|
||
disk and sets the <code class="docutils literal notranslate"><span class="pre">Content-Type</span></code> header automatically using Python’s
|
||
<code class="docutils literal notranslate"><span class="pre">mimetypes</span></code> module:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/download"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">download</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">file</span><span class="p">(</span><span class="s2">"reports/annual.pdf"</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>You can override the content type if the automatic detection isn’t right:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/image"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">image</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">file</span><span class="p">(</span><span class="s2">"photos/cat.jpg"</span><span class="p">,</span> <span class="n">content_type</span><span class="o">=</span><span class="s2">"image/jpeg"</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>For large files, use <code class="docutils literal notranslate"><span class="pre">resp.stream_file()</span></code> to avoid loading the entire
|
||
file into memory. This streams the file in chunks:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/export"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">export</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">stream_file</span><span class="p">(</span><span class="s2">"data/export.csv"</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Both <code class="docutils literal notranslate"><span class="pre">resp.file()</span></code> and <code class="docutils literal notranslate"><span class="pre">resp.stream_file()</span></code> understand HTTP range
|
||
requests: clients sending <code class="docutils literal notranslate"><span class="pre">Range:</span> <span class="pre">bytes=...</span></code> receive <code class="docutils literal notranslate"><span class="pre">206</span> <span class="pre">Partial</span>
|
||
<span class="pre">Content</span></code> with the requested slice. Multiple ranges are answered as
|
||
<code class="docutils literal notranslate"><span class="pre">multipart/byteranges</span></code>. This is what makes video seeking and resumable
|
||
downloads work — no extra code needed.</p>
|
||
<p>To prompt the browser to download rather than display, use
|
||
<code class="docutils literal notranslate"><span class="pre">resp.download()</span></code>, which streams the file (resumably) and sets
|
||
<code class="docutils literal notranslate"><span class="pre">Content-Disposition</span></code>:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/export"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">export</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">download</span><span class="p">(</span><span class="s2">"reports/annual.pdf"</span><span class="p">,</span> <span class="n">filename</span><span class="o">=</span><span class="s2">"Annual Report.pdf"</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<div class="admonition warning">
|
||
<p class="admonition-title">Warning</p>
|
||
<p>When the path comes from user input — a URL segment, a query parameter —
|
||
pass <code class="docutils literal notranslate"><span class="pre">root=</span></code> to jail file access to a directory. <code class="docutils literal notranslate"><span class="pre">resp.file</span></code>,
|
||
<code class="docutils literal notranslate"><span class="pre">resp.stream_file</span></code>, and <code class="docutils literal notranslate"><span class="pre">resp.download</span></code> resolve the path under
|
||
<code class="docutils literal notranslate"><span class="pre">root</span></code> and return <code class="docutils literal notranslate"><span class="pre">404</span></code> on any <code class="docutils literal notranslate"><span class="pre">../</span></code> or symlink that escapes it:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/files/</span><span class="si">{name}</span><span class="s2">"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">serve</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">file</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">root</span><span class="o">=</span><span class="s2">"/srv/public"</span><span class="p">)</span> <span class="c1"># cannot escape /srv/public</span>
|
||
</pre></div>
|
||
</div>
|
||
</div>
|
||
<p>Large <em>uploads</em> work the same way in reverse — iterate over the request
|
||
body in chunks instead of buffering it with <code class="docutils literal notranslate"><span class="pre">await</span> <span class="pre">req.content</span></code>:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/upload"</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s2">"POST"</span><span class="p">])</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">upload</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="k">async</span> <span class="k">with</span> <span class="k">await</span> <span class="n">anyio</span><span class="o">.</span><span class="n">open_file</span><span class="p">(</span><span class="s2">"incoming.bin"</span><span class="p">,</span> <span class="s2">"wb"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
|
||
<span class="k">async</span> <span class="k">for</span> <span class="n">chunk</span> <span class="ow">in</span> <span class="n">req</span><span class="o">.</span><span class="n">stream</span><span class="p">():</span>
|
||
<span class="k">await</span> <span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">chunk</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>For ordinary multipart uploads, a typed <code class="docutils literal notranslate"><span class="pre">File</span></code> marker gives you Starlette’s
|
||
<code class="docutils literal notranslate"><span class="pre">UploadFile</span></code> plus a Responder convenience method for saving it:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">responder</span><span class="w"> </span><span class="kn">import</span> <span class="n">File</span><span class="p">,</span> <span class="n">UploadFile</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/avatar"</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s2">"POST"</span><span class="p">])</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">avatar</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">image</span><span class="p">:</span> <span class="n">UploadFile</span> <span class="o">=</span> <span class="n">File</span><span class="p">(</span><span class="o">...</span><span class="p">)):</span>
|
||
<span class="n">path</span> <span class="o">=</span> <span class="k">await</span> <span class="n">image</span><span class="o">.</span><span class="n">save</span><span class="p">(</span><span class="s2">"/srv/uploads/avatar.bin"</span><span class="p">,</span> <span class="n">create_parents</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"saved"</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">)}</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Since 9.0, multipart bodies parse <em>incrementally</em> off the wire: file parts
|
||
spool to temporary files (rolling to disk past ~1 MB) instead of being held
|
||
in memory, so a gigabyte upload does not mean a gigabyte of RAM — raise
|
||
<code class="docutils literal notranslate"><span class="pre">max_request_size</span></code> to taste and <code class="docutils literal notranslate"><span class="pre">UploadFile.save()</span></code> moves the bytes
|
||
disk-to-disk in chunks.</p>
|
||
</section>
|
||
<section id="conditional-requests">
|
||
<h2>Conditional Requests<a class="headerlink" href="#conditional-requests" title="Link to this heading">¶</a></h2>
|
||
<p>HTTP caching saves bandwidth and server time: clients remember a validator
|
||
for the responses they’ve seen, and the server answers <code class="docutils literal notranslate"><span class="pre">304</span> <span class="pre">Not</span> <span class="pre">Modified</span></code>
|
||
— no body — when nothing changed. Set <code class="docutils literal notranslate"><span class="pre">resp.etag</span></code> or
|
||
<code class="docutils literal notranslate"><span class="pre">resp.last_modified</span></code> and Responder handles the comparison automatically:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/report"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">report</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">etag</span> <span class="o">=</span> <span class="n">compute_version_hash</span><span class="p">()</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="n">expensive_render</span><span class="p">()</span> <span class="c1"># skipped clients get a 304</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>When the request’s <code class="docutils literal notranslate"><span class="pre">If-None-Match</span></code> header matches the ETag (or
|
||
<code class="docutils literal notranslate"><span class="pre">If-Modified-Since</span></code> is at or after <code class="docutils literal notranslate"><span class="pre">last_modified</span></code>), the client
|
||
receives <code class="docutils literal notranslate"><span class="pre">304</span> <span class="pre">Not</span> <span class="pre">Modified</span></code> with empty body. <code class="docutils literal notranslate"><span class="pre">resp.last_modified</span></code>
|
||
accepts a <code class="docutils literal notranslate"><span class="pre">datetime</span></code> or a preformatted HTTP-date string:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">datetime</span><span class="w"> </span><span class="kn">import</span> <span class="n">datetime</span><span class="p">,</span> <span class="n">timezone</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/feed"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">feed</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">last_modified</span> <span class="o">=</span> <span class="n">datetime</span><span class="p">(</span><span class="mi">2026</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">15</span><span class="p">,</span> <span class="n">tzinfo</span><span class="o">=</span><span class="n">timezone</span><span class="o">.</span><span class="n">utc</span><span class="p">)</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="n">load_feed</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Per RFC 7232, <code class="docutils literal notranslate"><span class="pre">If-None-Match</span></code> takes precedence when both validators are
|
||
present, weak ETags (<code class="docutils literal notranslate"><span class="pre">W/"..."</span></code>) compare by their core value, and <code class="docutils literal notranslate"><span class="pre">304</span></code>
|
||
handling applies only to <code class="docutils literal notranslate"><span class="pre">GET</span></code> and <code class="docutils literal notranslate"><span class="pre">HEAD</span></code>.</p>
|
||
<p>The same validators also guard writes against lost updates. On
|
||
state-changing methods (anything but <code class="docutils literal notranslate"><span class="pre">GET</span></code>/<code class="docutils literal notranslate"><span class="pre">HEAD</span></code>), when <code class="docutils literal notranslate"><span class="pre">resp.etag</span></code>
|
||
or <code class="docutils literal notranslate"><span class="pre">resp.last_modified</span></code> is set, Responder evaluates the request’s
|
||
<code class="docutils literal notranslate"><span class="pre">If-Match</span></code>, <code class="docutils literal notranslate"><span class="pre">If-Unmodified-Since</span></code>, and <code class="docutils literal notranslate"><span class="pre">If-None-Match</span></code> preconditions
|
||
(RFC 9110 §13) and answers <code class="docutils literal notranslate"><span class="pre">412</span> <span class="pre">Precondition</span> <span class="pre">Failed</span></code> — carrying the
|
||
current validator in the response headers — when they don’t hold:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/items/</span><span class="si">{id}</span><span class="s2">"</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s2">"PUT"</span><span class="p">])</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">update_item</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="nb">id</span><span class="p">):</span>
|
||
<span class="n">item</span> <span class="o">=</span> <span class="n">load</span><span class="p">(</span><span class="nb">id</span><span class="p">)</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">etag</span> <span class="o">=</span> <span class="n">item</span><span class="o">.</span><span class="n">version_hash</span> <span class="c1"># the resource's current validator</span>
|
||
<span class="n">apply_changes</span><span class="p">(</span><span class="n">item</span><span class="p">,</span> <span class="k">await</span> <span class="n">req</span><span class="o">.</span><span class="n">media</span><span class="p">())</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"updated"</span><span class="p">:</span> <span class="kc">True</span><span class="p">}</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>A client that sends <code class="docutils literal notranslate"><span class="pre">If-Match:</span> <span class="pre">"<stale-tag>"</span></code> receives a <code class="docutils literal notranslate"><span class="pre">412</span></code> instead
|
||
of the success response. Per RFC 9110, <code class="docutils literal notranslate"><span class="pre">If-Match</span></code> uses <em>strong</em>
|
||
comparison (a weak <code class="docutils literal notranslate"><span class="pre">W/"..."</span></code> ETag never matches it), <code class="docutils literal notranslate"><span class="pre">If-Match:</span> <span class="pre">*</span></code>
|
||
succeeds whenever a validator is set, and <code class="docutils literal notranslate"><span class="pre">If-Unmodified-Since</span></code> is
|
||
consulted only when <code class="docutils literal notranslate"><span class="pre">If-Match</span></code> is absent. As with <code class="docutils literal notranslate"><span class="pre">304</span></code> handling, each
|
||
precondition is evaluated only when its validator is set (<code class="docutils literal notranslate"><span class="pre">resp.etag</span></code>
|
||
for the ETag preconditions, <code class="docutils literal notranslate"><span class="pre">resp.last_modified</span></code> for the date one), and
|
||
only for responses that would otherwise succeed (2xx) — a handler that
|
||
already set a <code class="docutils literal notranslate"><span class="pre">404</span></code> keeps its <code class="docutils literal notranslate"><span class="pre">404</span></code>.</p>
|
||
<div class="admonition note">
|
||
<p class="admonition-title">Note</p>
|
||
<p>Preconditions are checked when the response is sent, <em>after</em> the
|
||
handler has run. To skip the state change itself (not just the success
|
||
response), compare the precondition in the handler before mutating —
|
||
e.g. check <code class="docutils literal notranslate"><span class="pre">req.headers.get("If-Match")</span></code> against the current tag and
|
||
return early with <code class="docutils literal notranslate"><span class="pre">resp.status_code</span> <span class="pre">=</span> <span class="pre">412</span></code>.</p>
|
||
</div>
|
||
<p>Don’t want to manage validators yourself? Turn on automatic ETags and
|
||
every <code class="docutils literal notranslate"><span class="pre">GET</span></code> response gets a content-hash tag, with <code class="docutils literal notranslate"><span class="pre">304</span></code> handling for
|
||
free (note: the body is still rendered server-side; you save bandwidth,
|
||
not compute):</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">auto_etag</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>An explicitly set <code class="docutils literal notranslate"><span class="pre">resp.etag</span></code> always wins over the automatic one.</p>
|
||
<p>To control how long clients cache, set <code class="docutils literal notranslate"><span class="pre">Cache-Control</span></code> with the helper —
|
||
underscores become hyphens, <code class="docutils literal notranslate"><span class="pre">True</span></code> renders a bare directive:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">resp</span><span class="o">.</span><span class="n">cache_control</span><span class="p">(</span><span class="n">public</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">max_age</span><span class="o">=</span><span class="mi">3600</span><span class="p">)</span>
|
||
<span class="c1"># Cache-Control: public, max-age=3600</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>For common REST responses, the response object has small helpers that set the
|
||
status, headers, body, and media type together:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s2">"/items"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">create_item</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">item</span> <span class="o">=</span> <span class="n">save_item</span><span class="p">()</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">created</span><span class="p">({</span><span class="s2">"id"</span><span class="p">:</span> <span class="n">item</span><span class="o">.</span><span class="n">id</span><span class="p">},</span> <span class="n">location</span><span class="o">=</span><span class="sa">f</span><span class="s2">"/items/</span><span class="si">{</span><span class="n">item</span><span class="o">.</span><span class="n">id</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">delete</span><span class="p">(</span><span class="s2">"/items/{id:int}"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">delete_item</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="nb">id</span><span class="p">):</span>
|
||
<span class="n">delete_item_by_id</span><span class="p">(</span><span class="nb">id</span><span class="p">)</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">no_content</span><span class="p">()</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s2">"/items"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">reject_duplicate</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">problem</span><span class="p">(</span><span class="mi">409</span><span class="p">,</span> <span class="s2">"Item already exists"</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="s2">"https://example.com/conflict"</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="after-response-tasks">
|
||
<h2>After-Response Tasks<a class="headerlink" href="#after-response-tasks" title="Link to this heading">¶</a></h2>
|
||
<p>Work that shouldn’t delay the response — sending emails, recording
|
||
analytics, cache warming — can be deferred until after the client has the
|
||
bytes:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/signup"</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s2">"POST"</span><span class="p">])</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">signup</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"ok"</span><span class="p">:</span> <span class="kc">True</span><span class="p">}</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">background</span><span class="p">(</span><span class="n">send_welcome_email</span><span class="p">,</span> <span class="s2">"user@example.com"</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Sync functions run in a thread pool; async functions run on the event
|
||
loop. Multiple tasks run in the order scheduled. (For fire-and-forget work
|
||
from anywhere — not tied to a response — use <code class="docutils literal notranslate"><span class="pre">api.background</span></code> instead.)</p>
|
||
</section>
|
||
<section id="custom-error-handling">
|
||
<h2>Custom Error Handling<a class="headerlink" href="#custom-error-handling" title="Link to this heading">¶</a></h2>
|
||
<p>In production, you don’t want your users to see raw Python tracebacks.
|
||
Responder lets you register custom handlers for specific exception types,
|
||
so you can return clean, structured error responses:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">exception_handler</span><span class="p">(</span><span class="ne">ValueError</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">handle_value_error</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="n">exc</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">status_code</span> <span class="o">=</span> <span class="mi">400</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"error"</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">exc</span><span class="p">)}</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Now, any route that raises a <code class="docutils literal notranslate"><span class="pre">ValueError</span></code> will return a clean JSON
|
||
response with a 400 status code instead of a generic 500 error page.</p>
|
||
<p>This is a common pattern in API development — you define your own exception
|
||
classes for different error conditions, register handlers for each, and
|
||
your API always returns consistent, machine-readable error responses.</p>
|
||
<p>To raise an HTTP error from anywhere — a handler, a hook, a dependency —
|
||
call <code class="docutils literal notranslate"><span class="pre">abort()</span></code> instead of importing Starlette’s exceptions:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">responder</span><span class="w"> </span><span class="kn">import</span> <span class="n">abort</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/admin"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">admin</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="k">if</span> <span class="ow">not</span> <span class="n">req</span><span class="o">.</span><span class="n">session</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"is_admin"</span><span class="p">):</span>
|
||
<span class="n">abort</span><span class="p">(</span><span class="mi">403</span><span class="p">,</span> <span class="n">detail</span><span class="o">=</span><span class="s2">"Forbidden"</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p><code class="docutils literal notranslate"><span class="pre">abort()</span></code> halts the handler immediately and renders a content-negotiated
|
||
error — unlike setting <code class="docutils literal notranslate"><span class="pre">resp.status_code</span> <span class="pre">=</span> <span class="pre">403</span></code>, which doesn’t stop
|
||
execution.</p>
|
||
<p>For richer errors, raise <code class="docutils literal notranslate"><span class="pre">responder.Problem</span></code> — a raisable RFC 9457
|
||
problem-details exception carrying <code class="docutils literal notranslate"><span class="pre">type</span></code>, <code class="docutils literal notranslate"><span class="pre">title</span></code>, <code class="docutils literal notranslate"><span class="pre">instance</span></code>, and
|
||
arbitrary extension members into the rendered <code class="docutils literal notranslate"><span class="pre">application/problem+json</span></code>
|
||
payload. Because it works from anywhere an exception propagates (views,
|
||
hooks, dependencies), it makes typed problem catalogs possible without
|
||
hand-building responses in every handler:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">responder</span><span class="w"> </span><span class="kn">import</span> <span class="n">Problem</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/quota"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">quota</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="k">raise</span> <span class="n">Problem</span><span class="p">(</span>
|
||
<span class="mi">409</span><span class="p">,</span>
|
||
<span class="s2">"You have used all 100 requests for today."</span><span class="p">,</span>
|
||
<span class="n">title</span><span class="o">=</span><span class="s2">"Quota Exceeded"</span><span class="p">,</span>
|
||
<span class="nb">type</span><span class="o">=</span><span class="s2">"https://api.example.com/errors/quota-exceeded"</span><span class="p">,</span>
|
||
<span class="n">balance</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
|
||
<span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The payload flows through the same machinery as framework errors —
|
||
<code class="docutils literal notranslate"><span class="pre">problem_handler</span></code> enrichment, request IDs, and the app’s JSON encoder —
|
||
and <code class="docutils literal notranslate"><span class="pre">instance</span></code> defaults to the request path per the RFC recommendation.
|
||
Passing any of these members to <code class="docutils literal notranslate"><span class="pre">abort()</span></code> raises a <code class="docutils literal notranslate"><span class="pre">Problem</span></code> for you:
|
||
<code class="docutils literal notranslate"><span class="pre">abort(409,</span> <span class="pre">type="...",</span> <span class="pre">balance=0)</span></code>.</p>
|
||
<p>Handlers can also be registered programmatically with
|
||
<code class="docutils literal notranslate"><span class="pre">api.add_exception_handler(exc_or_status,</span> <span class="pre">handler)</span></code> (the
|
||
<code class="docutils literal notranslate"><span class="pre">@api.exception_handler</span></code> decorator delegates to it). It accepts an
|
||
exception class <em>or</em> an integer status code, so you can catch a <code class="docutils literal notranslate"><span class="pre">404</span></code>
|
||
directly. Registering against <code class="docutils literal notranslate"><span class="pre">Exception</span></code> (or <code class="docutils literal notranslate"><span class="pre">500</span></code>) installs a
|
||
catch-all for unhandled server errors — though under <code class="docutils literal notranslate"><span class="pre">debug=True</span></code> the
|
||
traceback page is shown instead:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">not_found</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="n">exc</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">status_code</span> <span class="o">=</span> <span class="mi">404</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"error"</span><span class="p">:</span> <span class="s2">"nothing here"</span><span class="p">}</span>
|
||
|
||
<span class="n">api</span><span class="o">.</span><span class="n">add_exception_handler</span><span class="p">(</span><span class="mi">404</span><span class="p">,</span> <span class="n">not_found</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Framework-generated errors use an RFC 9457-style envelope by default. Errors
|
||
such as 404, 405, validation failures, response-model validation failures, and
|
||
request timeouts use <code class="docutils literal notranslate"><span class="pre">application/problem+json</span></code> with <code class="docutils literal notranslate"><span class="pre">type</span></code>, <code class="docutils literal notranslate"><span class="pre">title</span></code>,
|
||
<code class="docutils literal notranslate"><span class="pre">status</span></code>, and <code class="docutils literal notranslate"><span class="pre">detail</span></code> fields. Validation errors also include <code class="docutils literal notranslate"><span class="pre">errors</span></code>.</p>
|
||
<p>Pass <code class="docutils literal notranslate"><span class="pre">problem_details=False</span></code> when creating the app to keep the legacy
|
||
content-negotiated error format, where JSON clients receive bodies like
|
||
<code class="docutils literal notranslate"><span class="pre">{"error":</span> <span class="pre">"Not</span> <span class="pre">Found"}</span></code> and browsers receive plain text.</p>
|
||
</section>
|
||
<section id="before-request-hooks">
|
||
<h2>Before-Request Hooks<a class="headerlink" href="#before-request-hooks" title="Link to this heading">¶</a></h2>
|
||
<p>Sometimes you need to run the same code before every request —
|
||
authentication checks, request logging, adding common headers, or setting
|
||
up per-request state. Before-request hooks let you do this without
|
||
duplicating code in every route:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="n">before_request</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">add_headers</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"X-API-Version"</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"3.2"</span>
|
||
</pre></div>
|
||
</div>
|
||
<p><strong>Short-circuiting</strong> is the really powerful part. If your hook sets
|
||
<code class="docutils literal notranslate"><span class="pre">resp.status_code</span></code>, the route handler is skipped entirely and the
|
||
response is sent immediately. This is the pattern for authentication:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="n">before_request</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">auth_check</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="k">if</span> <span class="s2">"Authorization"</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">req</span><span class="o">.</span><span class="n">headers</span><span class="p">:</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">status_code</span> <span class="o">=</span> <span class="mi">401</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"error"</span><span class="p">:</span> <span class="s2">"unauthorized"</span><span class="p">}</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>If the <code class="docutils literal notranslate"><span class="pre">Authorization</span></code> header is missing, the client gets a 401 response
|
||
and the actual route handler never runs. This is cleaner than adding
|
||
auth checks to every individual route.</p>
|
||
<p>When the hook belongs to just one endpoint, attach it directly to that route:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">require_admin</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="k">if</span> <span class="ow">not</span> <span class="n">req</span><span class="o">.</span><span class="n">session</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"is_admin"</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">status_code</span> <span class="o">=</span> <span class="mi">403</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"error"</span><span class="p">:</span> <span class="s2">"forbidden"</span><span class="p">}</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/admin"</span><span class="p">,</span> <span class="n">before</span><span class="o">=</span><span class="n">require_admin</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">admin</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"ok"</span><span class="p">:</span> <span class="kc">True</span><span class="p">}</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="after-request-hooks">
|
||
<h2>After-Request Hooks<a class="headerlink" href="#after-request-hooks" title="Link to this heading">¶</a></h2>
|
||
<p>The complement to before-request hooks. After-request hooks run after the
|
||
route handler completes but before the response is sent. They’re useful
|
||
for logging, adding response headers, or any post-processing:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">after_request</span><span class="p">()</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">log_response</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">req</span><span class="o">.</span><span class="n">method</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">req</span><span class="o">.</span><span class="n">full_url</span><span class="si">}</span><span class="s2"> -> </span><span class="si">{</span><span class="n">resp</span><span class="o">.</span><span class="n">status_code</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">after_request</span><span class="p">()</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">add_timing</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"X-Served-By"</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"responder"</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The parentheses are optional — the bare <code class="docutils literal notranslate"><span class="pre">@api.after_request</span></code> works too,
|
||
as does the bare <code class="docutils literal notranslate"><span class="pre">@api.before_request</span></code>.</p>
|
||
<p>Route-local after hooks use <code class="docutils literal notranslate"><span class="pre">after=</span></code> and run before global after hooks:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">audit</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"X-Audited"</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"1"</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/reports"</span><span class="p">,</span> <span class="n">after</span><span class="o">=</span><span class="n">audit</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">reports</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">[]</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="websocket-support">
|
||
<h2>WebSocket Support<a class="headerlink" href="#websocket-support" title="Link to this heading">¶</a></h2>
|
||
<p>HTTP is a request-response protocol — the client asks, the server answers.
|
||
But some applications need real-time, bidirectional communication: chat
|
||
apps, live dashboards, multiplayer games, collaborative editors.</p>
|
||
<p><a class="reference external" href="https://en.wikipedia.org/wiki/WebSocket">WebSockets</a> solve this by
|
||
upgrading an HTTP connection into a persistent, full-duplex channel where
|
||
both sides can send messages at any time:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/ws"</span><span class="p">,</span> <span class="n">websocket</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">websocket</span><span class="p">(</span><span class="n">ws</span><span class="p">):</span>
|
||
<span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">accept</span><span class="p">()</span>
|
||
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
|
||
<span class="n">name</span> <span class="o">=</span> <span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">receive_text</span><span class="p">()</span>
|
||
<span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">send_text</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Hello </span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">!"</span><span class="p">)</span>
|
||
<span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>You can send and receive in multiple formats:</p>
|
||
<ul class="simple">
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">send_text</span></code> / <code class="docutils literal notranslate"><span class="pre">receive_text</span></code> — plain text strings</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">send_json</span></code> / <code class="docutils literal notranslate"><span class="pre">receive_json</span></code> — JSON objects (auto-serialized)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">send_bytes</span></code> / <code class="docutils literal notranslate"><span class="pre">receive_bytes</span></code> — raw binary data</p></li>
|
||
</ul>
|
||
<p>WebSocket routes are marked with <code class="docutils literal notranslate"><span class="pre">websocket=True</span></code> in the route decorator.
|
||
They receive a <code class="docutils literal notranslate"><span class="pre">ws</span></code> object instead of <code class="docutils literal notranslate"><span class="pre">req</span></code> and <code class="docutils literal notranslate"><span class="pre">resp</span></code>.</p>
|
||
</section>
|
||
<section id="server-sent-events-sse">
|
||
<h2>Server-Sent Events (SSE)<a class="headerlink" href="#server-sent-events-sse" title="Link to this heading">¶</a></h2>
|
||
<p>SSE is a simpler alternative to WebSockets for <em>one-way</em> real-time
|
||
communication — the server pushes events to the client, but the client
|
||
can’t send messages back. This is perfect for live feeds, progress bars,
|
||
notification streams, and AI response streaming.</p>
|
||
<p>Unlike WebSockets, SSE works over plain HTTP, is automatically reconnected
|
||
by the browser, and doesn’t require any special client-side libraries. A
|
||
typed SSE route validates and serializes every event, publishes its event
|
||
schema in OpenAPI, and generates a streaming client method:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">collections.abc</span><span class="w"> </span><span class="kn">import</span> <span class="n">AsyncIterator</span>
|
||
<span class="kn">from</span><span class="w"> </span><span class="nn">pydantic</span><span class="w"> </span><span class="kn">import</span> <span class="n">BaseModel</span>
|
||
|
||
<span class="k">class</span><span class="w"> </span><span class="nc">Progress</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
|
||
<span class="n">job_id</span><span class="p">:</span> <span class="nb">int</span>
|
||
<span class="n">percent</span><span class="p">:</span> <span class="nb">int</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">sse</span><span class="p">(</span><span class="s2">"/events"</span><span class="p">,</span> <span class="n">heartbeat</span><span class="o">=</span><span class="mi">15</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">events</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">)</span> <span class="o">-></span> <span class="n">AsyncIterator</span><span class="p">[</span><span class="n">responder</span><span class="o">.</span><span class="n">SSE</span><span class="p">[</span><span class="n">Progress</span><span class="p">]]:</span>
|
||
<span class="k">async</span> <span class="k">for</span> <span class="n">update</span> <span class="ow">in</span> <span class="n">build_updates</span><span class="p">():</span>
|
||
<span class="k">yield</span> <span class="n">responder</span><span class="o">.</span><span class="n">SSE</span><span class="p">(</span>
|
||
<span class="n">Progress</span><span class="p">(</span><span class="n">job_id</span><span class="o">=</span><span class="n">update</span><span class="o">.</span><span class="n">job_id</span><span class="p">,</span> <span class="n">percent</span><span class="o">=</span><span class="n">update</span><span class="o">.</span><span class="n">percent</span><span class="p">),</span>
|
||
<span class="n">event</span><span class="o">=</span><span class="s2">"progress"</span><span class="p">,</span>
|
||
<span class="nb">id</span><span class="o">=</span><span class="n">update</span><span class="o">.</span><span class="n">sequence</span><span class="p">,</span>
|
||
<span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>On the client side, you consume SSE events with JavaScript’s built-in
|
||
<code class="docutils literal notranslate"><span class="pre">EventSource</span></code> API:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">const</span> <span class="n">source</span> <span class="o">=</span> <span class="n">new</span> <span class="n">EventSource</span><span class="p">(</span><span class="s2">"/events"</span><span class="p">);</span>
|
||
<span class="n">source</span><span class="o">.</span><span class="n">onmessage</span> <span class="o">=</span> <span class="p">(</span><span class="n">event</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
|
||
<span class="n">console</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="n">event</span><span class="o">.</span><span class="n">data</span><span class="p">);</span>
|
||
<span class="p">};</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Yield the annotated model directly when no SSE metadata is needed. Wrap it in
|
||
<a class="reference internal" href="api.html#responder.SSE" title="responder.SSE"><code class="xref py py-class docutils literal notranslate"><span class="pre">SSE</span></code></a> to set <code class="docutils literal notranslate"><span class="pre">event</span></code>, <code class="docutils literal notranslate"><span class="pre">id</span></code>, or <code class="docutils literal notranslate"><span class="pre">retry</span></code>; comment-only
|
||
events are also supported:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">yield</span> <span class="n">Progress</span><span class="p">(</span><span class="n">job_id</span><span class="o">=</span><span class="mi">7</span><span class="p">,</span> <span class="n">percent</span><span class="o">=</span><span class="mi">42</span><span class="p">)</span>
|
||
<span class="k">yield</span> <span class="n">responder</span><span class="o">.</span><span class="n">SSE</span><span class="p">(</span>
|
||
<span class="n">Progress</span><span class="p">(</span><span class="n">job_id</span><span class="o">=</span><span class="mi">7</span><span class="p">,</span> <span class="n">percent</span><span class="o">=</span><span class="mi">42</span><span class="p">),</span> <span class="n">event</span><span class="o">=</span><span class="s2">"update"</span><span class="p">,</span> <span class="nb">id</span><span class="o">=</span><span class="s2">"1"</span><span class="p">,</span> <span class="n">retry</span><span class="o">=</span><span class="mi">1500</span>
|
||
<span class="p">)</span>
|
||
<span class="k">yield</span> <span class="n">responder</span><span class="o">.</span><span class="n">SSE</span><span class="p">(</span><span class="n">comment</span><span class="o">=</span><span class="s2">"still working"</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>For long-lived streams behind proxies, pass <code class="docutils literal notranslate"><span class="pre">heartbeat=</span></code> (seconds) to emit a
|
||
keepalive comment during idle periods so the connection isn’t dropped. The
|
||
response also sets <code class="docutils literal notranslate"><span class="pre">X-Accel-Buffering:</span> <span class="pre">no</span></code> so events flush immediately:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">sse</span><span class="p">(</span><span class="s2">"/events"</span><span class="p">,</span> <span class="n">heartbeat</span><span class="o">=</span><span class="mi">15</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">events</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">)</span> <span class="o">-></span> <span class="n">AsyncIterator</span><span class="p">[</span><span class="n">Progress</span><span class="p">]:</span>
|
||
<span class="o">...</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>When the browser reconnects it sends the id of the last event it saw; read it
|
||
with <a class="reference internal" href="api.html#responder.Request.last_event_id" title="responder.Request.last_event_id"><code class="xref py py-attr docutils literal notranslate"><span class="pre">req.last_event_id</span></code></a> to resume:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">sse</span><span class="p">(</span><span class="s2">"/events"</span><span class="p">,</span> <span class="n">heartbeat</span><span class="o">=</span><span class="mi">15</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">events</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">)</span> <span class="o">-></span> <span class="n">AsyncIterator</span><span class="p">[</span><span class="n">Progress</span><span class="p">]:</span>
|
||
<span class="n">resume_from</span> <span class="o">=</span> <span class="n">req</span><span class="o">.</span><span class="n">last_event_id</span>
|
||
<span class="k">async</span> <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">feed</span><span class="p">(</span><span class="n">after</span><span class="o">=</span><span class="n">resume_from</span><span class="p">):</span>
|
||
<span class="k">yield</span> <span class="n">responder</span><span class="o">.</span><span class="n">SSE</span><span class="p">(</span><span class="n">item</span><span class="o">.</span><span class="n">payload</span><span class="p">,</span> <span class="nb">id</span><span class="o">=</span><span class="n">item</span><span class="o">.</span><span class="n">id</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Use <a class="reference internal" href="api.html#responder.API.ndjson" title="responder.API.ndjson"><code class="xref py py-meth docutils literal notranslate"><span class="pre">API.ndjson</span></code></a> for the same typed, incremental
|
||
contract over newline-delimited JSON. Each yielded item becomes one compact
|
||
JSON line with media type <code class="docutils literal notranslate"><span class="pre">application/x-ndjson</span></code>:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">ndjson</span><span class="p">(</span><span class="s2">"/exports"</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">exports</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">)</span> <span class="o">-></span> <span class="n">AsyncIterator</span><span class="p">[</span><span class="n">Progress</span><span class="p">]:</span>
|
||
<span class="k">async</span> <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">export_rows</span><span class="p">():</span>
|
||
<span class="k">yield</span> <span class="n">item</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The first immediately available item is validated before response headers are
|
||
sent, so a broken contract can still return <code class="docutils literal notranslate"><span class="pre">500</span></code>. If a later item fails,
|
||
Responder logs the contract error and terminates the stream without writing
|
||
the invalid item. Producer cancellation closes the source iterator and keeps
|
||
request-scoped dependency teardown tied to the connection lifetime.</p>
|
||
<p>For untyped protocol frames and raw streaming escape hatches, the existing
|
||
<code class="docutils literal notranslate"><span class="pre">resp.sse()</span></code> and <code class="docutils literal notranslate"><span class="pre">resp.stream()</span></code> APIs remain available.</p>
|
||
</section>
|
||
<section id="graphql">
|
||
<h2>GraphQL<a class="headerlink" href="#graphql" title="Link to this heading">¶</a></h2>
|
||
<p><a class="reference external" href="https://graphql.org/">GraphQL</a> is a query language for APIs that lets
|
||
clients request exactly the data they need — no more, no less. Instead of
|
||
multiple REST endpoints, you define a schema and let clients query it.</p>
|
||
<p>Responder includes built-in GraphQL support via
|
||
<a class="reference external" href="https://graphene-python.org/">Graphene</a>. Install it with the
|
||
<code class="docutils literal notranslate"><span class="pre">graphql</span></code> extra:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>$ uv pip install 'responder[graphql,orjson]'
|
||
</pre></div>
|
||
</div>
|
||
<p>Then set up a full GraphQL endpoint with a single method call:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">graphene</span>
|
||
|
||
<span class="k">class</span><span class="w"> </span><span class="nc">Query</span><span class="p">(</span><span class="n">graphene</span><span class="o">.</span><span class="n">ObjectType</span><span class="p">):</span>
|
||
<span class="n">hello</span> <span class="o">=</span> <span class="n">graphene</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="n">graphene</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="n">default_value</span><span class="o">=</span><span class="s2">"stranger"</span><span class="p">))</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">resolve_hello</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">info</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
|
||
<span class="k">return</span> <span class="sa">f</span><span class="s2">"Hello </span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">"</span>
|
||
|
||
<span class="n">api</span><span class="o">.</span><span class="n">graphql</span><span class="p">(</span><span class="s2">"/graphql"</span><span class="p">,</span> <span class="n">schema</span><span class="o">=</span><span class="n">graphene</span><span class="o">.</span><span class="n">Schema</span><span class="p">(</span><span class="n">query</span><span class="o">=</span><span class="n">Query</span><span class="p">))</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Visiting <code class="docutils literal notranslate"><span class="pre">/graphql</span></code> in a browser renders the
|
||
<a class="reference external" href="https://github.com/graphql/graphiql">GraphiQL</a> interactive IDE, where
|
||
you can explore your schema, write queries, and see results in real-time.
|
||
Programmatic clients can POST JSON queries to the same endpoint.</p>
|
||
<p>You can access the Responder request and response objects in your resolvers
|
||
through <code class="docutils literal notranslate"><span class="pre">info.context["request"]</span></code> and <code class="docutils literal notranslate"><span class="pre">info.context["response"]</span></code>.</p>
|
||
<p>For production, lock the endpoint down: turn off the in-browser IDE, reject
|
||
introspection queries, and cap query nesting depth to blunt
|
||
denial-of-service queries:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span><span class="o">.</span><span class="n">graphql</span><span class="p">(</span>
|
||
<span class="s2">"/graphql"</span><span class="p">,</span> <span class="n">schema</span><span class="o">=</span><span class="n">schema</span><span class="p">,</span>
|
||
<span class="n">graphiql</span><span class="o">=</span><span class="n">api</span><span class="o">.</span><span class="n">debug</span><span class="p">,</span> <span class="n">introspection</span><span class="o">=</span><span class="n">api</span><span class="o">.</span><span class="n">debug</span><span class="p">,</span> <span class="n">max_depth</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span>
|
||
<span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="openapi-documentation">
|
||
<span id="tour-openapi"></span><h2>OpenAPI Documentation<a class="headerlink" href="#openapi-documentation" title="Link to this heading">¶</a></h2>
|
||
<p><a class="reference external" href="https://www.openapis.org/">OpenAPI</a> (formerly Swagger) is the industry
|
||
standard for describing REST APIs. An OpenAPI specification lets you
|
||
auto-generate interactive documentation, client libraries, and validation
|
||
logic.</p>
|
||
<p>Responder generates OpenAPI specs from your code:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span>
|
||
<span class="n">title</span><span class="o">=</span><span class="s2">"Pet Store"</span><span class="p">,</span>
|
||
<span class="n">version</span><span class="o">=</span><span class="s2">"1.0"</span><span class="p">,</span>
|
||
<span class="n">openapi</span><span class="o">=</span><span class="s2">"3.0.2"</span><span class="p">,</span>
|
||
<span class="n">docs_route</span><span class="o">=</span><span class="s2">"/docs"</span><span class="p">,</span>
|
||
<span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>This gives you:</p>
|
||
<ul class="simple">
|
||
<li><p>An OpenAPI schema at <code class="docutils literal notranslate"><span class="pre">/schema.yml</span></code></p></li>
|
||
<li><p>Interactive Swagger UI documentation at <code class="docutils literal notranslate"><span class="pre">/docs</span></code></p></li>
|
||
</ul>
|
||
<p>OpenAPI 3.1 is supported — pass <code class="docutils literal notranslate"><span class="pre">openapi="3.1.0"</span></code>. The schema is served
|
||
as YAML by default; clients sending <code class="docutils literal notranslate"><span class="pre">Accept:</span> <span class="pre">application/json</span></code> get JSON,
|
||
and an <code class="docutils literal notranslate"><span class="pre">openapi_route</span></code> ending in <code class="docutils literal notranslate"><span class="pre">.json</span></code> (e.g. <code class="docutils literal notranslate"><span class="pre">"/schema.json"</span></code>)
|
||
serves JSON always.</p>
|
||
<p>Path parameters are documented automatically from your route patterns:
|
||
<code class="docutils literal notranslate"><span class="pre">/pets/{id:int}</span></code> produces a required integer path parameter in the spec,
|
||
with the OpenAPI-style template path (<code class="docutils literal notranslate"><span class="pre">/pets/{id}</span></code>).</p>
|
||
<p><strong>Every route appears automatically.</strong> Responder builds the spec from each
|
||
route’s methods, path parameters, body and response models, and any
|
||
<code class="docutils literal notranslate"><span class="pre">Query</span></code>/<code class="docutils literal notranslate"><span class="pre">Header</span></code>/<code class="docutils literal notranslate"><span class="pre">Cookie</span></code> markers (see <a class="reference internal" href="#pydantic-validation">Pydantic Validation</a>) — so a
|
||
route shows up with its parameters and schemas even without a line of
|
||
annotation:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/health"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">health</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"status"</span><span class="p">:</span> <span class="s2">"ok"</span><span class="p">}</span> <span class="c1"># documented as GET /health -> 200</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Validating routes (anything with a request body or typed parameters) also
|
||
get an automatic <code class="docutils literal notranslate"><span class="pre">422</span></code> in the spec. To hide a route, pass
|
||
<code class="docutils literal notranslate"><span class="pre">include_in_schema=False</span></code>; the internal schema, docs, static, and metrics
|
||
endpoints are excluded for you:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/internal"</span><span class="p">,</span> <span class="n">include_in_schema</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">internal</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="s2">"private"</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Operational responses are documented too. CSRF-protected unsafe routes show
|
||
<code class="docutils literal notranslate"><span class="pre">403</span></code>, capped request bodies show <code class="docutils literal notranslate"><span class="pre">413</span></code>, rate-limited routes show
|
||
<code class="docutils literal notranslate"><span class="pre">429</span></code> (and fail-closed backend <code class="docutils literal notranslate"><span class="pre">503</span></code>), and configured request timeouts
|
||
show <code class="docutils literal notranslate"><span class="pre">504</span></code>. With the default Problem Details contract, those responses
|
||
reference the reusable <code class="docutils literal notranslate"><span class="pre">ProblemDetails</span></code> schema.</p>
|
||
<p>Beyond that baseline, three tools let you enrich and override the generated
|
||
operations.</p>
|
||
<p><strong>Typed contracts</strong> — the recommended approach. Add a required Pydantic-typed
|
||
handler parameter for the request body and a supported return annotation (or
|
||
an explicit <code class="docutils literal notranslate"><span class="pre">response_model=</span></code>) for the response. Responder generates the
|
||
schema and validates at runtime: invalid bodies get a <code class="docutils literal notranslate"><span class="pre">422</span></code> with detailed
|
||
errors, and responses are serialized through the contract (extra fields
|
||
stripped, types enforced):</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">pydantic</span><span class="w"> </span><span class="kn">import</span> <span class="n">BaseModel</span>
|
||
|
||
<span class="k">class</span><span class="w"> </span><span class="nc">PetIn</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
|
||
<span class="n">name</span><span class="p">:</span> <span class="nb">str</span>
|
||
<span class="n">age</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">0</span>
|
||
|
||
<span class="k">class</span><span class="w"> </span><span class="nc">PetOut</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
|
||
<span class="nb">id</span><span class="p">:</span> <span class="nb">int</span>
|
||
<span class="n">name</span><span class="p">:</span> <span class="nb">str</span>
|
||
<span class="n">age</span><span class="p">:</span> <span class="nb">int</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/pets"</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s2">"POST"</span><span class="p">])</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">create_pet</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">pet</span><span class="p">:</span> <span class="n">PetIn</span><span class="p">)</span> <span class="o">-></span> <span class="n">PetOut</span><span class="p">:</span>
|
||
<span class="k">return</span> <span class="n">PetOut</span><span class="p">(</span><span class="nb">id</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="n">pet</span><span class="o">.</span><span class="n">name</span><span class="p">,</span> <span class="n">age</span><span class="o">=</span><span class="n">pet</span><span class="o">.</span><span class="n">age</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>See <a class="reference internal" href="#pydantic-validation">Pydantic Validation</a> for how these typed signatures behave at runtime.</p>
|
||
<p><strong>Decorator metadata</strong> — for operation-level OpenAPI details that should stay
|
||
near the route, pass metadata directly to the route decorator. <code class="docutils literal notranslate"><span class="pre">responses</span></code> and
|
||
examples are deep-merged with generated response schemas and framework error
|
||
responses:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">get</span><span class="p">(</span>
|
||
<span class="s2">"/pets/{id:int}"</span><span class="p">,</span>
|
||
<span class="n">response_model</span><span class="o">=</span><span class="n">PetOut</span><span class="p">,</span>
|
||
<span class="n">tags</span><span class="o">=</span><span class="p">[</span><span class="s2">"pets"</span><span class="p">],</span>
|
||
<span class="n">summary</span><span class="o">=</span><span class="s2">"Fetch a pet"</span><span class="p">,</span>
|
||
<span class="n">responses</span><span class="o">=</span><span class="p">{</span><span class="mi">404</span><span class="p">:</span> <span class="s2">"Pet not found"</span><span class="p">},</span>
|
||
<span class="n">examples</span><span class="o">=</span><span class="p">{</span>
|
||
<span class="s2">"found"</span><span class="p">:</span> <span class="p">{</span>
|
||
<span class="s2">"summary"</span><span class="p">:</span> <span class="s2">"Existing pet"</span><span class="p">,</span>
|
||
<span class="s2">"value"</span><span class="p">:</span> <span class="p">{</span><span class="s2">"id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="s2">"name"</span><span class="p">:</span> <span class="s2">"Fido"</span><span class="p">,</span> <span class="s2">"age"</span><span class="p">:</span> <span class="mi">4</span><span class="p">},</span>
|
||
<span class="p">}</span>
|
||
<span class="p">},</span>
|
||
<span class="n">openapi_extra</span><span class="o">=</span><span class="p">{</span><span class="s2">"x-codeSamples"</span><span class="p">:</span> <span class="p">[]},</span>
|
||
<span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">get_pet</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="nb">id</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"id"</span><span class="p">:</span> <span class="nb">id</span><span class="p">,</span> <span class="s2">"name"</span><span class="p">:</span> <span class="s2">"Fido"</span><span class="p">,</span> <span class="s2">"age"</span><span class="p">:</span> <span class="mi">4</span><span class="p">}</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Routes whose success status isn’t <code class="docutils literal notranslate"><span class="pre">200</span></code> can declare it with
|
||
<code class="docutils literal notranslate"><span class="pre">status_code=</span></code>. It does double duty: <code class="docutils literal notranslate"><span class="pre">resp.status_code</span></code> is pre-seeded
|
||
with it before the handler runs (so the handler only sets the body), and the
|
||
generated OpenAPI operation documents the success response under that status
|
||
instead of <code class="docutils literal notranslate"><span class="pre">200</span></code>. Declaring <code class="docutils literal notranslate"><span class="pre">status_code=204</span></code> documents no response
|
||
body. An explicit <code class="docutils literal notranslate"><span class="pre">resp.status_code</span> <span class="pre">=</span> <span class="pre">...</span></code> in the handler still wins:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/pets"</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s2">"POST"</span><span class="p">],</span> <span class="n">status_code</span><span class="o">=</span><span class="mi">201</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">create_pet</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">pet</span><span class="p">:</span> <span class="n">PetIn</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="s2">"name"</span><span class="p">:</span> <span class="n">pet</span><span class="o">.</span><span class="n">name</span><span class="p">,</span> <span class="s2">"age"</span><span class="p">:</span> <span class="n">pet</span><span class="o">.</span><span class="n">age</span><span class="p">}</span> <span class="c1"># -> 201</span>
|
||
</pre></div>
|
||
</div>
|
||
<p><strong>YAML docstrings</strong> — for fine-grained control, embed OpenAPI YAML in the
|
||
docstring; it is deep-merged <em>on top of</em> the auto-generated operation, so
|
||
you override only what you mention:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/pets"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">list_pets</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="w"> </span><span class="sd">"""A list of pets.</span>
|
||
<span class="sd"> ---</span>
|
||
<span class="sd"> get:</span>
|
||
<span class="sd"> description: Get all pets</span>
|
||
<span class="sd"> responses:</span>
|
||
<span class="sd"> 200:</span>
|
||
<span class="sd"> description: A list of pets</span>
|
||
<span class="sd"> """</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">[{</span><span class="s2">"name"</span><span class="p">:</span> <span class="s2">"Fido"</span><span class="p">}]</span>
|
||
</pre></div>
|
||
</div>
|
||
<p><strong>Marshmallow schemas</strong> — if you’re already using marshmallow:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">marshmallow</span><span class="w"> </span><span class="kn">import</span> <span class="n">Schema</span><span class="p">,</span> <span class="n">fields</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">schema</span><span class="p">(</span><span class="s2">"Pet"</span><span class="p">)</span>
|
||
<span class="k">class</span><span class="w"> </span><span class="nc">PetSchema</span><span class="p">(</span><span class="n">Schema</span><span class="p">):</span>
|
||
<span class="n">name</span> <span class="o">=</span> <span class="n">fields</span><span class="o">.</span><span class="n">Str</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>All three approaches can be mixed in the same API. You can choose from
|
||
multiple documentation themes: <code class="docutils literal notranslate"><span class="pre">swagger_ui</span></code> (default), <code class="docutils literal notranslate"><span class="pre">redoc</span></code>,
|
||
<code class="docutils literal notranslate"><span class="pre">rapidoc</span></code>, or <code class="docutils literal notranslate"><span class="pre">elements</span></code>.</p>
|
||
</section>
|
||
<section id="route-groups">
|
||
<h2>Route Groups<a class="headerlink" href="#route-groups" title="Link to this heading">¶</a></h2>
|
||
<p>As your application grows, you’ll want to organize routes logically.
|
||
Route groups let you share a URL prefix across related endpoints — a
|
||
common pattern for API versioning:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">v1</span> <span class="o">=</span> <span class="n">api</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="s2">"/v1"</span><span class="p">)</span>
|
||
|
||
<span class="nd">@v1</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/users"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">list_users</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">[]</span>
|
||
|
||
<span class="nd">@v1</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/users/{user_id:int}"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">get_user</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">user_id</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"id"</span><span class="p">:</span> <span class="n">user_id</span><span class="p">}</span>
|
||
|
||
<span class="n">v2</span> <span class="o">=</span> <span class="n">api</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="s2">"/v2"</span><span class="p">)</span>
|
||
|
||
<span class="nd">@v2</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/users"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">list_users_v2</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"users"</span><span class="p">:</span> <span class="p">[],</span> <span class="s2">"total"</span><span class="p">:</span> <span class="mi">0</span><span class="p">}</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>This keeps your code organized without affecting the routing logic.</p>
|
||
<p>Before-request hooks registered on a group only run for paths under the
|
||
group’s prefix — handy for guarding a whole API version with one check:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@v1</span><span class="o">.</span><span class="n">before_request</span><span class="p">()</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">require_key</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="k">if</span> <span class="s2">"X-Api-Key"</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">req</span><span class="o">.</span><span class="n">headers</span><span class="p">:</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">status_code</span> <span class="o">=</span> <span class="mi">401</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"error"</span><span class="p">:</span> <span class="s2">"missing API key"</span><span class="p">}</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Groups register on a live <code class="docutils literal notranslate"><span class="pre">api</span></code> instance. To declare routes in separate
|
||
modules — without an <code class="docutils literal notranslate"><span class="pre">API</span></code> at all — use the standalone
|
||
<a class="reference internal" href="api.html#responder.Router" title="responder.Router"><code class="xref py py-class docutils literal notranslate"><span class="pre">responder.Router</span></code></a> and <code class="docutils literal notranslate"><span class="pre">api.include_router()</span></code>; see <a class="reference internal" href="routers.html"><span class="doc">Composing Apps with Routers</span></a>.</p>
|
||
</section>
|
||
<section id="mounting-other-apps">
|
||
<h2>Mounting Other Apps<a class="headerlink" href="#mounting-other-apps" title="Link to this heading">¶</a></h2>
|
||
<p>Responder can mount any WSGI or ASGI application at a subroute. This is
|
||
incredibly useful for gradual migrations — you can run Flask and Responder
|
||
side by side, moving routes over one at a time:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">flask</span><span class="w"> </span><span class="kn">import</span> <span class="n">Flask</span>
|
||
|
||
<span class="n">flask_app</span> <span class="o">=</span> <span class="n">Flask</span><span class="p">(</span><span class="vm">__name__</span><span class="p">)</span>
|
||
|
||
<span class="nd">@flask_app</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">hello</span><span class="p">():</span>
|
||
<span class="k">return</span> <span class="s2">"Hello from Flask!"</span>
|
||
|
||
<span class="n">api</span><span class="o">.</span><span class="n">mount</span><span class="p">(</span><span class="s2">"/flask"</span><span class="p">,</span> <span class="n">flask_app</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Requests to <code class="docutils literal notranslate"><span class="pre">/flask/</span></code> will be handled by Flask. Everything else goes
|
||
through Responder. Both WSGI and ASGI apps are supported — Responder
|
||
wraps WSGI apps in an ASGI adapter automatically.</p>
|
||
<p>You can also mount <a class="reference external" href="https://marimo.io/">marimo</a> notebooks as
|
||
interactive dashboards within your API:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">marimo</span>
|
||
|
||
<span class="n">server</span> <span class="o">=</span> <span class="p">(</span>
|
||
<span class="n">marimo</span><span class="o">.</span><span class="n">create_asgi_app</span><span class="p">()</span>
|
||
<span class="o">.</span><span class="n">with_app</span><span class="p">(</span><span class="n">path</span><span class="o">=</span><span class="s2">"/notebooks"</span><span class="p">,</span> <span class="n">root</span><span class="o">=</span><span class="s2">"./notebooks/dashboard.py"</span><span class="p">)</span>
|
||
<span class="o">.</span><span class="n">with_app</span><span class="p">(</span><span class="n">path</span><span class="o">=</span><span class="s2">"/notebooks/analysis"</span><span class="p">,</span> <span class="n">root</span><span class="o">=</span><span class="s2">"./notebooks/analysis.py"</span><span class="p">)</span>
|
||
<span class="p">)</span>
|
||
|
||
<span class="n">api</span><span class="o">.</span><span class="n">mount</span><span class="p">(</span><span class="s2">""</span><span class="p">,</span> <span class="n">server</span><span class="o">.</span><span class="n">build</span><span class="p">())</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Notebooks are served at <code class="docutils literal notranslate"><span class="pre">/notebooks/</span></code> and <code class="docutils literal notranslate"><span class="pre">/notebooks/analysis</span></code>,
|
||
with full interactivity — reactive cells, widgets, plots, and all.</p>
|
||
</section>
|
||
<section id="cookies">
|
||
<h2>Cookies<a class="headerlink" href="#cookies" title="Link to this heading">¶</a></h2>
|
||
<p><a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies">Cookies</a> are
|
||
small pieces of data that the server asks the browser to store and send
|
||
back with every subsequent request. They’re the foundation of sessions,
|
||
authentication tokens, and user preferences on the web.</p>
|
||
<p>Reading and writing cookies is straightforward:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Read cookies from the request</span>
|
||
<span class="n">session_id</span> <span class="o">=</span> <span class="n">req</span><span class="o">.</span><span class="n">cookies</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"session_id"</span><span class="p">)</span>
|
||
|
||
<span class="c1"># Set a cookie on the response</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">cookies</span><span class="p">[</span><span class="s2">"hello"</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"world"</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>For production use, you’ll want to set security directives. The
|
||
<code class="docutils literal notranslate"><span class="pre">httponly</span></code> flag prevents JavaScript from reading the cookie (defending
|
||
against XSS attacks), and <code class="docutils literal notranslate"><span class="pre">secure</span></code> ensures it’s only sent over HTTPS:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">resp</span><span class="o">.</span><span class="n">set_cookie</span><span class="p">(</span>
|
||
<span class="s2">"token"</span><span class="p">,</span>
|
||
<span class="n">value</span><span class="o">=</span><span class="s2">"abc123"</span><span class="p">,</span>
|
||
<span class="n">max_age</span><span class="o">=</span><span class="mi">3600</span><span class="p">,</span> <span class="c1"># expires in 1 hour</span>
|
||
<span class="n">secure</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="c1"># HTTPS only</span>
|
||
<span class="n">httponly</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="c1"># no JavaScript access</span>
|
||
<span class="n">samesite</span><span class="o">=</span><span class="s2">"strict"</span><span class="p">,</span> <span class="c1"># never sent cross-site</span>
|
||
<span class="n">path</span><span class="o">=</span><span class="s2">"/"</span><span class="p">,</span>
|
||
<span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Cookies default to <code class="docutils literal notranslate"><span class="pre">SameSite=Lax</span></code>, matching modern browser behavior and
|
||
defending against CSRF. Pass <code class="docutils literal notranslate"><span class="pre">samesite="strict"</span></code> for tighter isolation,
|
||
or <code class="docutils literal notranslate"><span class="pre">samesite=None</span></code> to omit the directive entirely.</p>
|
||
</section>
|
||
<section id="cookie-based-sessions">
|
||
<h2>Cookie-Based Sessions<a class="headerlink" href="#cookie-based-sessions" title="Link to this heading">¶</a></h2>
|
||
<p>Sessions let you store per-user data across multiple requests. Responder’s
|
||
built-in sessions are cookie-based — the session data is serialized, signed
|
||
with your secret key, and stored in a cookie. The signature prevents
|
||
tampering: if someone modifies the cookie, the signature won’t match and
|
||
the data will be rejected:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/login"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">login</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">session</span><span class="p">[</span><span class="s2">"username"</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"alice"</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/profile"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">profile</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"user"</span><span class="p">:</span> <span class="n">req</span><span class="o">.</span><span class="n">session</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"username"</span><span class="p">)}</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Sessions are on by default (<code class="docutils literal notranslate"><span class="pre">sessions="auto"</span></code>). You read and write
|
||
<code class="docutils literal notranslate"><span class="pre">req.session</span></code> like a dict; <code class="docutils literal notranslate"><span class="pre">resp.session</span></code> is a write-through view of the
|
||
same data, so <code class="docutils literal notranslate"><span class="pre">resp.session</span> <span class="pre">=</span> <span class="pre">{"username":</span> <span class="pre">"alice"}</span></code> replaces it wholesale.
|
||
Pass <code class="docutils literal notranslate"><span class="pre">sessions=False</span></code> to turn the middleware off entirely (then touching
|
||
<code class="docutils literal notranslate"><span class="pre">req.session</span></code> raises a guiding <code class="docutils literal notranslate"><span class="pre">RuntimeError</span></code>).</p>
|
||
<div class="admonition warning">
|
||
<p class="admonition-title">Warning</p>
|
||
<p><strong>Set a stable secret key in production.</strong> Responder signs cookie
|
||
sessions with <code class="docutils literal notranslate"><span class="pre">secret_key</span></code> (or the <code class="docutils literal notranslate"><span class="pre">RESPONDER_SECRET_KEY</span></code> environment
|
||
variable). If neither is set, it mints a <em>random per-process</em> key and
|
||
logs a warning — fine for a quick demo, but it means every worker and
|
||
every restart gets a different key, logging users out. Generate one once:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">python</span> <span class="o">-</span><span class="n">c</span> <span class="s2">"import secrets; print(secrets.token_urlsafe(32))"</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>then pass it explicitly (or via the env var):</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">secret_key</span><span class="o">=</span><span class="s2">"<your-32+-char-random-key>"</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The old public placeholder <code class="docutils literal notranslate"><span class="pre">secret_key="NOTASECRET"</span></code> now raises
|
||
<code class="docutils literal notranslate"><span class="pre">SessionConfigError</span></code>, and <code class="docutils literal notranslate"><span class="pre">sessions=True</span></code> (strict mode) refuses to
|
||
start without a real key.</p>
|
||
</div>
|
||
<p>Session cookies are <code class="docutils literal notranslate"><span class="pre">HttpOnly</span></code> always, and <code class="docutils literal notranslate"><span class="pre">Secure</span></code> in production
|
||
(<code class="docutils literal notranslate"><span class="pre">debug=False</span></code>) by default. Behind a TLS proxy this needs no action; pass
|
||
<code class="docutils literal notranslate"><span class="pre">session_https_only=False</span></code> only when you genuinely serve plain HTTP, such
|
||
as local dev or tests over <code class="docutils literal notranslate"><span class="pre">http://</span></code>. <code class="docutils literal notranslate"><span class="pre">SameSite</span></code> defaults to <code class="docutils literal notranslate"><span class="pre">"lax"</span></code>;
|
||
<code class="docutils literal notranslate"><span class="pre">session_same_site="none"</span></code> requires a Secure cookie. Adjust the cookie
|
||
lifetime with <code class="docutils literal notranslate"><span class="pre">session_max_age</span></code> (default 14 days).</p>
|
||
<p>By default, session data lives <em>in</em> the cookie (signed to prevent
|
||
tampering). That’s simple but capped around 4KB and impossible to revoke
|
||
server-side. For logout-everywhere, large sessions, or sensitive data,
|
||
switch to server-side storage — only an opaque ID travels in the cookie (so
|
||
<code class="docutils literal notranslate"><span class="pre">secret_key</span></code> no longer matters):</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">responder.ext.sessions</span><span class="w"> </span><span class="kn">import</span> <span class="n">MemorySessionBackend</span>
|
||
|
||
<span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">session_backend</span><span class="o">=</span><span class="n">MemorySessionBackend</span><span class="p">())</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The handler code (<code class="docutils literal notranslate"><span class="pre">req.session[...]</span></code>) is identical either way. For
|
||
multi-process deployments, use <code class="docutils literal notranslate"><span class="pre">RedisSessionBackend(url=...)</span></code> (or
|
||
<code class="docutils literal notranslate"><span class="pre">AsyncRedisSessionBackend</span></code> in async apps) so all workers share the store.
|
||
Custom backends are duck-typed — any object with <code class="docutils literal notranslate"><span class="pre">get(id)</span></code>,
|
||
<code class="docutils literal notranslate"><span class="pre">set(id,</span> <span class="pre">data,</span> <span class="pre">max_age)</span></code>, and <code class="docutils literal notranslate"><span class="pre">delete(id)</span></code> works. After a login or
|
||
privilege change, call <code class="docutils literal notranslate"><span class="pre">regenerate_session(req)</span></code> (from
|
||
<code class="docutils literal notranslate"><span class="pre">responder.ext.sessions</span></code>) to rotate the session ID and defeat session
|
||
fixation. For a full login flow, see <a class="reference internal" href="tutorial-auth.html"><span class="doc">Authentication</span></a>.</p>
|
||
<p>Treat session values as JSON-shaped data (dicts, lists, strings, numbers,
|
||
booleans, <code class="docutils literal notranslate"><span class="pre">None</span></code>) so the same code works on every backend. The memory
|
||
backend stores Python objects as-is; the Redis backends serialize, and
|
||
their default codec also round-trips <code class="docutils literal notranslate"><span class="pre">datetime</span></code>, <code class="docutils literal notranslate"><span class="pre">date</span></code>, <code class="docutils literal notranslate"><span class="pre">time</span></code>,
|
||
<code class="docutils literal notranslate"><span class="pre">Decimal</span></code>, <code class="docutils literal notranslate"><span class="pre">UUID</span></code>, <code class="docutils literal notranslate"><span class="pre">set</span></code>, <code class="docutils literal notranslate"><span class="pre">frozenset</span></code>, and <code class="docutils literal notranslate"><span class="pre">bytes</span></code> values —
|
||
so <code class="docutils literal notranslate"><span class="pre">req.session["expires"]</span> <span class="pre">=</span> <span class="pre">datetime.now()</span></code> keeps working when you move
|
||
from the memory backend in development to Redis in production. Tuples come
|
||
back as lists on the Redis path (a JSON limitation). For anything richer,
|
||
pass <code class="docutils literal notranslate"><span class="pre">serializer=</span></code> to the Redis backends — any object with
|
||
<code class="docutils literal notranslate"><span class="pre">dumps(data)</span></code> and <code class="docutils literal notranslate"><span class="pre">loads(raw)</span></code> methods works.</p>
|
||
</section>
|
||
<section id="static-files">
|
||
<h2>Static Files<a class="headerlink" href="#static-files" title="Link to this heading">¶</a></h2>
|
||
<p>Most web applications serve static assets — CSS stylesheets, JavaScript
|
||
files, images, fonts. When a <code class="docutils literal notranslate"><span class="pre">static/</span></code> directory exists next to your app,
|
||
Responder serves it automatically — no configuration needed:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Place your assets in the <code class="docutils literal notranslate"><span class="pre">static/</span></code> directory and they’ll be served at
|
||
<code class="docutils literal notranslate"><span class="pre">/static/style.css</span></code>, <code class="docutils literal notranslate"><span class="pre">/static/app.js</span></code>, etc. Use <code class="docutils literal notranslate"><span class="pre">static_dir</span></code> and
|
||
<code class="docutils literal notranslate"><span class="pre">static_route</span></code> to serve a different directory or URL prefix.</p>
|
||
<p>The directory is never created for you: the implicit default <code class="docutils literal notranslate"><span class="pre">static/</span></code>
|
||
is simply skipped if it doesn’t exist, while a <code class="docutils literal notranslate"><span class="pre">static_dir</span></code> you pass
|
||
explicitly must exist — a missing one raises <code class="docutils literal notranslate"><span class="pre">FileNotFoundError</span></code> at
|
||
construction, so create the directory before pointing Responder at it.
|
||
Pass <code class="docutils literal notranslate"><span class="pre">static_dir=None</span></code> to disable static file serving entirely.</p>
|
||
<p>For single-page applications (React, Vue, Angular), you can serve
|
||
<code class="docutils literal notranslate"><span class="pre">index.html</span></code> as the default response for all unmatched routes:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">pathlib</span>
|
||
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">spa</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">html</span> <span class="o">=</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="s2">"static/index.html"</span><span class="p">)</span><span class="o">.</span><span class="n">read_text</span><span class="p">()</span>
|
||
|
||
<span class="n">api</span><span class="o">.</span><span class="n">add_route</span><span class="p">(</span><span class="s2">"/"</span><span class="p">,</span> <span class="n">spa</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="csrf-protection">
|
||
<h2>CSRF Protection<a class="headerlink" href="#csrf-protection" title="Link to this heading">¶</a></h2>
|
||
<p>Cookie sessions make browsers <em>ambient</em> authenticators: any site can make a
|
||
visitor’s browser POST to your app, cookies included. <a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/CSRF">CSRF</a>
|
||
protection defeats that by requiring a token an attacker’s page can’t read.
|
||
It’s off by default; switch it on and every unsafe request
|
||
(<code class="docutils literal notranslate"><span class="pre">POST</span></code>/<code class="docutils literal notranslate"><span class="pre">PUT</span></code>/<code class="docutils literal notranslate"><span class="pre">PATCH</span></code>/<code class="docutils literal notranslate"><span class="pre">DELETE</span></code>) must present the session’s token or
|
||
it gets a <code class="docutils literal notranslate"><span class="pre">403</span></code>:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">csrf</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>For HTML forms, pass the request into your template context and drop the
|
||
ready-made hidden input inside each <code class="docutils literal notranslate"><span class="pre"><form</span> <span class="pre">method="post"></span></code>:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">{{</span> <span class="n">req</span><span class="o">.</span><span class="n">csrf_input</span> <span class="p">}}</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>For JavaScript clients, read <code class="docutils literal notranslate"><span class="pre">req.csrf_token</span></code> (expose it in a page or a
|
||
small endpoint) and send it back in an <code class="docutils literal notranslate"><span class="pre">X-CSRF-Token</span></code> header. The token is
|
||
minted on first access, lives in the signed session, and stays stable for
|
||
the session’s lifetime.</p>
|
||
<p>Endpoints that are <em>supposed</em> to receive cross-site POSTs — webhook
|
||
receivers, token-authenticated APIs — opt out per route; conversely a single
|
||
route can opt in without the app-wide switch:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/webhooks/stripe"</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s2">"POST"</span><span class="p">],</span> <span class="n">csrf</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">stripe_webhook</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span> <span class="o">...</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/account/delete"</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s2">"POST"</span><span class="p">],</span> <span class="n">csrf</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">delete_account</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span> <span class="o">...</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The token check shares the request’s single (streaming) form parse, so a
|
||
protected multipart upload is still parsed exactly once.</p>
|
||
</section>
|
||
<section id="cors">
|
||
<h2>CORS<a class="headerlink" href="#cors" title="Link to this heading">¶</a></h2>
|
||
<p><a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">CORS</a> (Cross-
|
||
Origin Resource Sharing) is a security mechanism that controls which
|
||
websites can make requests to your API. Browsers enforce this — if your
|
||
API is at <code class="docutils literal notranslate"><span class="pre">api.example.com</span></code> and your frontend is at <code class="docutils literal notranslate"><span class="pre">app.example.com</span></code>,
|
||
the browser will block requests unless your API explicitly allows it.</p>
|
||
<p>Enable CORS and configure which origins are allowed:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">cors</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">cors_params</span><span class="o">=</span><span class="p">{</span>
|
||
<span class="s2">"allow_origins"</span><span class="p">:</span> <span class="p">[</span><span class="s2">"https://app.example.com"</span><span class="p">],</span>
|
||
<span class="s2">"allow_methods"</span><span class="p">:</span> <span class="p">[</span><span class="s2">"GET"</span><span class="p">,</span> <span class="s2">"POST"</span><span class="p">],</span>
|
||
<span class="s2">"allow_headers"</span><span class="p">:</span> <span class="p">[</span><span class="s2">"*"</span><span class="p">],</span>
|
||
<span class="s2">"allow_credentials"</span><span class="p">:</span> <span class="kc">True</span><span class="p">,</span>
|
||
<span class="s2">"max_age"</span><span class="p">:</span> <span class="mi">600</span><span class="p">,</span>
|
||
<span class="p">})</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The default policy is restrictive — you must explicitly allow each origin.
|
||
Using <code class="docutils literal notranslate"><span class="pre">["*"]</span></code> for allow_origins permits any website to call your API,
|
||
which is fine for public APIs but not for private ones.</p>
|
||
</section>
|
||
<section id="hsts">
|
||
<h2>HSTS<a class="headerlink" href="#hsts" title="Link to this heading">¶</a></h2>
|
||
<p><a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security">HSTS</a>
|
||
(HTTP Strict Transport Security) tells browsers to always use HTTPS when
|
||
communicating with your server. Once a browser sees the HSTS header, it
|
||
will refuse to connect over plain HTTP, even if the user types <code class="docutils literal notranslate"><span class="pre">http://</span></code>
|
||
in the address bar:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">enable_hsts</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>This redirects HTTP requests to HTTPS and sends a
|
||
<code class="docutils literal notranslate"><span class="pre">Strict-Transport-Security</span></code> header on responses. For a custom <code class="docutils literal notranslate"><span class="pre">max-age</span></code>
|
||
or to enable preloading, install the middleware directly:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">responder.middleware</span><span class="w"> </span><span class="kn">import</span> <span class="n">HSTSMiddleware</span>
|
||
|
||
<span class="n">api</span><span class="o">.</span><span class="n">add_middleware</span><span class="p">(</span><span class="n">HSTSMiddleware</span><span class="p">,</span> <span class="n">max_age</span><span class="o">=</span><span class="mi">63072000</span><span class="p">,</span> <span class="n">preload</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="security-headers">
|
||
<h2>Security Headers<a class="headerlink" href="#security-headers" title="Link to this heading">¶</a></h2>
|
||
<p>Opt in to common security headers on every response —
|
||
<code class="docutils literal notranslate"><span class="pre">X-Content-Type-Options:</span> <span class="pre">nosniff</span></code>, <code class="docutils literal notranslate"><span class="pre">X-Frame-Options:</span> <span class="pre">DENY</span></code>, and
|
||
<code class="docutils literal notranslate"><span class="pre">Referrer-Policy:</span> <span class="pre">strict-origin-when-cross-origin</span></code>:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">security_headers</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Add a Content-Security-Policy or Permissions-Policy, or override any header,
|
||
by passing options through:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">security_headers</span><span class="o">=</span><span class="p">{</span>
|
||
<span class="s2">"content_security_policy"</span><span class="p">:</span> <span class="s2">"default-src 'self'"</span><span class="p">,</span>
|
||
<span class="s2">"headers"</span><span class="p">:</span> <span class="p">{</span><span class="s2">"X-Frame-Options"</span><span class="p">:</span> <span class="s2">"SAMEORIGIN"</span><span class="p">},</span>
|
||
<span class="p">})</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>A header a handler sets itself is always left untouched.</p>
|
||
</section>
|
||
<section id="trusted-hosts">
|
||
<h2>Trusted Hosts<a class="headerlink" href="#trusted-hosts" title="Link to this heading">¶</a></h2>
|
||
<p>The <code class="docutils literal notranslate"><span class="pre">Host</span></code> header in an HTTP request tells the server which domain name
|
||
the client used. Attackers can forge this header to trick your application
|
||
into generating URLs to malicious domains (a class of attack called <em>Host
|
||
header injection</em>).</p>
|
||
<p>Restrict which hostnames your application accepts:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">allowed_hosts</span><span class="o">=</span><span class="p">[</span><span class="s2">"example.com"</span><span class="p">,</span> <span class="s2">"*.example.com"</span><span class="p">])</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Requests with unrecognized hosts get a <code class="docutils literal notranslate"><span class="pre">400</span> <span class="pre">Bad</span> <span class="pre">Request</span></code>. Wildcard
|
||
patterns are supported. By default, all hostnames are allowed.</p>
|
||
</section>
|
||
<section id="authentication">
|
||
<h2>Authentication<a class="headerlink" href="#authentication" title="Link to this heading">¶</a></h2>
|
||
<p><code class="docutils literal notranslate"><span class="pre">responder.ext.auth</span></code> provides Bearer, Basic, and API-key schemes. Each one
|
||
is a callable that pulls the credential from the request, verifies it, and
|
||
returns the principal your <code class="docutils literal notranslate"><span class="pre">verify</span></code> callback produced — or raises <code class="docutils literal notranslate"><span class="pre">401</span></code>
|
||
with the right <code class="docutils literal notranslate"><span class="pre">WWW-Authenticate</span></code> challenge. The most direct form is
|
||
<code class="docutils literal notranslate"><span class="pre">auth=</span></code> on a route:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">responder.ext.auth</span><span class="w"> </span><span class="kn">import</span> <span class="n">BearerAuth</span>
|
||
|
||
<span class="n">auth</span> <span class="o">=</span> <span class="n">BearerAuth</span><span class="p">(</span><span class="n">verify</span><span class="o">=</span><span class="k">lambda</span> <span class="n">token</span><span class="p">:</span> <span class="n">users</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">token</span><span class="p">))</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"/me"</span><span class="p">,</span> <span class="n">auth</span><span class="o">=</span><span class="n">auth</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">me</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">user</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"user"</span><span class="p">:</span> <span class="n">user</span><span class="p">}</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Responder enforces the scheme, registers the OpenAPI security scheme when
|
||
OpenAPI is enabled, stores the principal on <code class="docutils literal notranslate"><span class="pre">req.state.user</span></code> /
|
||
<code class="docutils literal notranslate"><span class="pre">req.state.auth</span></code>, and injects it into <code class="docutils literal notranslate"><span class="pre">user</span></code>, <code class="docutils literal notranslate"><span class="pre">principal</span></code>, or <code class="docutils literal notranslate"><span class="pre">auth</span></code>
|
||
parameters. The older explicit form still works when you want to separate
|
||
runtime dependency wiring from documentation:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">auth</span><span class="o">.</span><span class="n">register</span><span class="p">(</span><span class="n">api</span><span class="p">)</span>
|
||
<span class="n">api</span><span class="o">.</span><span class="n">add_dependency</span><span class="p">(</span><span class="s2">"user"</span><span class="p">,</span> <span class="n">auth</span><span class="p">)</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"/me"</span><span class="p">,</span> <span class="n">security</span><span class="o">=</span><span class="p">[</span><span class="s2">"bearerAuth"</span><span class="p">])</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">me</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">user</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"user"</span><span class="p">:</span> <span class="n">user</span><span class="p">}</span>
|
||
</pre></div>
|
||
</div>
|
||
<p><code class="docutils literal notranslate"><span class="pre">verify</span></code> may be sync or async; return a truthy principal to accept or a falsy
|
||
value to reject. For static secrets, pass them directly and the scheme compares
|
||
them in constant time:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">BearerAuth</span><span class="p">(</span><span class="n">tokens</span><span class="o">=</span><span class="p">[</span><span class="s2">"s3cret"</span><span class="p">])</span>
|
||
<span class="n">APIKeyAuth</span><span class="p">(</span><span class="n">keys</span><span class="o">=</span><span class="p">[</span><span class="s2">"abc123"</span><span class="p">],</span> <span class="n">name</span><span class="o">=</span><span class="s2">"X-API-Key"</span><span class="p">)</span> <span class="c1"># or location="query"</span>
|
||
<span class="n">BasicAuth</span><span class="p">(</span><span class="n">credentials</span><span class="o">=</span><span class="p">{</span><span class="s2">"alice"</span><span class="p">:</span> <span class="s2">"password"</span><span class="p">})</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Calling <code class="docutils literal notranslate"><span class="pre">register()</span></code> (or <code class="xref py py-meth docutils literal notranslate"><span class="pre">api.add_security_scheme()</span></code>) makes the scheme
|
||
appear in the OpenAPI document, so the interactive docs grow an <strong>Authorize</strong>
|
||
button. <code class="docutils literal notranslate"><span class="pre">security=</span></code> on a route marks which operations require it; pass
|
||
<code class="docutils literal notranslate"><span class="pre">default=True</span></code> to <code class="docutils literal notranslate"><span class="pre">add_security_scheme</span></code> to require a scheme everywhere.</p>
|
||
</section>
|
||
<section id="request-id">
|
||
<h2>Request ID<a class="headerlink" href="#request-id" title="Link to this heading">¶</a></h2>
|
||
<p>In distributed systems, tracing a single request across multiple services
|
||
is essential for debugging. Request IDs are unique identifiers attached to
|
||
each request — if something goes wrong, you can search your logs for that
|
||
ID and find every related event.</p>
|
||
<p>Responder can auto-generate request IDs. If the client sends an
|
||
<code class="docutils literal notranslate"><span class="pre">X-Request-ID</span></code> header (common in microservice architectures), it’s
|
||
forwarded. Otherwise, a new UUID is generated:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">request_id</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The ID appears in the <code class="docutils literal notranslate"><span class="pre">X-Request-ID</span></code> response header.</p>
|
||
<p>An inbound ID is honored only when it looks like a request ID — at most 128
|
||
characters of letters, digits, <code class="docutils literal notranslate"><span class="pre">.</span></code>, <code class="docutils literal notranslate"><span class="pre">_</span></code>, and <code class="docutils literal notranslate"><span class="pre">-</span></code>. Anything else (an
|
||
oversized header, control characters) is discarded and a fresh UUID minted
|
||
instead, so a client can’t bloat your responses or log lines.
|
||
<code class="docutils literal notranslate"><span class="pre">enable_logging=True</span></code> applies exactly the same policy and mints the same
|
||
UUID format, so switching access logging on or off never changes the IDs
|
||
your log pipeline parses.</p>
|
||
</section>
|
||
<section id="request-size-limits">
|
||
<h2>Request Size Limits<a class="headerlink" href="#request-size-limits" title="Link to this heading">¶</a></h2>
|
||
<p>Unbounded request bodies are an easy denial-of-service vector, so since 9.0
|
||
bodies are capped at <strong>100 MiB by default</strong>. Oversized requests get <code class="docutils literal notranslate"><span class="pre">413</span></code>
|
||
automatically — whether the body is read with <code class="docutils literal notranslate"><span class="pre">await</span> <span class="pre">req.content</span></code>,
|
||
<code class="docutils literal notranslate"><span class="pre">req.media()</span></code>, or streamed. Tune the cap (or disable it) per application:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">max_request_size</span><span class="o">=</span><span class="mi">10</span> <span class="o">*</span> <span class="mi">1024</span> <span class="o">*</span> <span class="mi">1024</span><span class="p">)</span> <span class="c1"># 10 MB</span>
|
||
<span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">max_request_size</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span> <span class="c1"># unlimited (pre-9.0)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The check fails fast on the <code class="docutils literal notranslate"><span class="pre">Content-Length</span></code> header when present, and
|
||
enforces the limit cumulatively for chunked uploads. Multipart uploads
|
||
stream to disk as they arrive, so a generous cap does not translate into
|
||
memory pressure.</p>
|
||
</section>
|
||
<section id="request-timeouts">
|
||
<h2>Request Timeouts<a class="headerlink" href="#request-timeouts" title="Link to this heading">¶</a></h2>
|
||
<p>A handler stuck on a slow database or unresponsive upstream shouldn’t hold
|
||
the client forever. Set an application-wide budget and overruns are
|
||
answered with <code class="docutils literal notranslate"><span class="pre">504</span> <span class="pre">Gateway</span> <span class="pre">Timeout</span></code>:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">request_timeout</span><span class="o">=</span><span class="mi">30</span><span class="p">)</span> <span class="c1"># seconds</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Dependency teardowns still run when a request times out. One caveat: a
|
||
<em>synchronous</em> handler running in the thread pool can’t be interrupted —
|
||
the client gets the 504 on time, but the thread runs to completion in the
|
||
background.</p>
|
||
<p>WebSocket handlers have a matching guard. Pass <code class="docutils literal notranslate"><span class="pre">ws_idle_timeout</span></code> to close a
|
||
connection that waits too long for the next inbound message (close code
|
||
<code class="docutils literal notranslate"><span class="pre">1001</span></code>):</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">ws_idle_timeout</span><span class="o">=</span><span class="mi">30</span><span class="p">)</span> <span class="c1"># seconds</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The deadline resets on every message received, so it bounds <em>idle</em> time between
|
||
messages rather than the total connection lifetime — a continuously active
|
||
client is never closed. It defaults to <code class="docutils literal notranslate"><span class="pre">None</span></code> (no timeout).</p>
|
||
</section>
|
||
<section id="rate-limiting">
|
||
<span id="tour-rate-limiting"></span><h2>Rate Limiting<a class="headerlink" href="#rate-limiting" title="Link to this heading">¶</a></h2>
|
||
<p>Rate limiting prevents individual clients from overwhelming your API with
|
||
too many requests. It’s essential for public APIs, and good practice even
|
||
for internal ones.</p>
|
||
<p>Responder includes a built-in rate limiter (a sliding window in memory; a
|
||
fixed window on the Redis backends):</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">responder.ext.ratelimit</span><span class="w"> </span><span class="kn">import</span> <span class="n">RateLimiter</span>
|
||
|
||
<span class="n">limiter</span> <span class="o">=</span> <span class="n">RateLimiter</span><span class="p">(</span><span class="n">requests</span><span class="o">=</span><span class="mi">100</span><span class="p">,</span> <span class="n">period</span><span class="o">=</span><span class="mi">60</span><span class="p">)</span> <span class="c1"># 100 req/min</span>
|
||
<span class="n">limiter</span><span class="o">.</span><span class="n">install</span><span class="p">(</span><span class="n">api</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>When the limit is exceeded, clients receive a <code class="docutils literal notranslate"><span class="pre">429</span> <span class="pre">Too</span> <span class="pre">Many</span> <span class="pre">Requests</span></code>
|
||
response with a <code class="docutils literal notranslate"><span class="pre">Retry-After</span></code> header. Every response includes
|
||
<code class="docutils literal notranslate"><span class="pre">X-RateLimit-Limit</span></code>, <code class="docutils literal notranslate"><span class="pre">X-RateLimit-Remaining</span></code>, and <code class="docutils literal notranslate"><span class="pre">X-RateLimit-Reset</span></code>
|
||
(seconds until the window resets) headers so clients can pace themselves.
|
||
With the default <code class="docutils literal notranslate"><span class="pre">problem_details=True</span></code> setting, over-limit and fail-closed
|
||
backend responses use the same <code class="docutils literal notranslate"><span class="pre">application/problem+json</span></code> envelope as
|
||
framework-generated errors; pass <code class="docutils literal notranslate"><span class="pre">problem_details=False</span></code> on the API to keep
|
||
the legacy <code class="docutils literal notranslate"><span class="pre">{"error":</span> <span class="pre">...}</span></code> JSON body.</p>
|
||
<p>The rate limiter is per-client, keyed by IP address by default. To key by
|
||
something else — an API key, an authenticated user id — pass <code class="docutils literal notranslate"><span class="pre">key=</span></code>, a
|
||
callable receiving the request:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">limiter</span> <span class="o">=</span> <span class="n">RateLimiter</span><span class="p">(</span>
|
||
<span class="n">requests</span><span class="o">=</span><span class="mi">100</span><span class="p">,</span> <span class="n">period</span><span class="o">=</span><span class="mi">60</span><span class="p">,</span>
|
||
<span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">req</span><span class="p">:</span> <span class="n">req</span><span class="o">.</span><span class="n">headers</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"x-api-key"</span><span class="p">,</span> <span class="s2">"anonymous"</span><span class="p">),</span>
|
||
<span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>If the key function returns <code class="docutils literal notranslate"><span class="pre">None</span></code> (or an empty string), the request
|
||
falls back to the IP-based key.</p>
|
||
<p>By default, counts live in process memory. The in-memory store tracks at most
|
||
<code class="docutils literal notranslate"><span class="pre">max_keys</span></code> distinct clients (100k by default), evicting the least-recently
|
||
seen beyond that, so a client rotating source IPs can’t grow memory without
|
||
bound. For multi-process or multi-host deployments, plug in the Redis backend so
|
||
all workers share one budget:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">responder.ext.ratelimit</span><span class="w"> </span><span class="kn">import</span> <span class="n">RateLimiter</span><span class="p">,</span> <span class="n">RedisBackend</span>
|
||
|
||
<span class="n">limiter</span> <span class="o">=</span> <span class="n">RateLimiter</span><span class="p">(</span>
|
||
<span class="n">requests</span><span class="o">=</span><span class="mi">100</span><span class="p">,</span> <span class="n">period</span><span class="o">=</span><span class="mi">60</span><span class="p">,</span>
|
||
<span class="n">backend</span><span class="o">=</span><span class="n">RedisBackend</span><span class="p">(</span><span class="n">url</span><span class="o">=</span><span class="s2">"redis://localhost:6379/0"</span><span class="p">),</span>
|
||
<span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>In async apps, reach for <code class="docutils literal notranslate"><span class="pre">AsyncRedisBackend</span></code> instead — it talks to Redis
|
||
without a thread-pool hop. It’s async-only, so drive it with
|
||
<code class="docutils literal notranslate"><span class="pre">limiter.install(api)</span></code> (or <code class="docutils literal notranslate"><span class="pre">await</span> <span class="pre">limiter.acheck(req,</span> <span class="pre">resp)</span></code>) rather than
|
||
a sync route:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">responder.ext.ratelimit</span><span class="w"> </span><span class="kn">import</span> <span class="n">RateLimiter</span><span class="p">,</span> <span class="n">AsyncRedisBackend</span>
|
||
|
||
<span class="n">limiter</span> <span class="o">=</span> <span class="n">RateLimiter</span><span class="p">(</span>
|
||
<span class="n">requests</span><span class="o">=</span><span class="mi">100</span><span class="p">,</span> <span class="n">period</span><span class="o">=</span><span class="mi">60</span><span class="p">,</span>
|
||
<span class="n">backend</span><span class="o">=</span><span class="n">AsyncRedisBackend</span><span class="p">(</span><span class="n">url</span><span class="o">=</span><span class="s2">"redis://localhost:6379/0"</span><span class="p">),</span>
|
||
<span class="p">)</span>
|
||
<span class="n">limiter</span><span class="o">.</span><span class="n">install</span><span class="p">(</span><span class="n">api</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>A word on outages: when the backend errors out (say, Redis is unreachable),
|
||
the limiter answers <code class="docutils literal notranslate"><span class="pre">503</span> <span class="pre">Service</span> <span class="pre">Unavailable</span></code> by default — nothing slips
|
||
past the limit, but rate-limited routes go down with the backend. Pass
|
||
<code class="docutils literal notranslate"><span class="pre">fail_open=True</span></code> to make the opposite trade-off: requests are let through
|
||
unmetered, with a warning logged, until the backend recovers. Choose
|
||
<code class="docutils literal notranslate"><span class="pre">fail_open=True</span></code> when availability matters more than strict enforcement
|
||
(most public APIs); keep the default when the limiter is a security control
|
||
(login attempts, expensive mutations).</p>
|
||
<p>Any object with a <code class="docutils literal notranslate"><span class="pre">hit(key,</span> <span class="pre">max_requests,</span> <span class="pre">period)</span></code> method returning
|
||
<code class="docutils literal notranslate"><span class="pre">(allowed,</span> <span class="pre">remaining)</span></code> — or <code class="docutils literal notranslate"><span class="pre">(allowed,</span> <span class="pre">remaining,</span> <span class="pre">reset_after)</span></code> to
|
||
populate <code class="docutils literal notranslate"><span class="pre">X-RateLimit-Reset</span></code> — works as a backend (<code class="docutils literal notranslate"><span class="pre">ahit</span></code> for the async
|
||
variant), so custom stores are easy to write.</p>
|
||
<p>To rate-limit a single route instead of the whole API, apply
|
||
<a class="reference internal" href="api.html#responder.ext.ratelimit.RateLimiter.limit" title="responder.ext.ratelimit.RateLimiter.limit"><code class="xref py py-meth docutils literal notranslate"><span class="pre">limit()</span></code></a> beneath <code class="docutils literal notranslate"><span class="pre">@api.route</span></code>.
|
||
Give each route its own <code class="docutils literal notranslate"><span class="pre">RateLimiter</span></code> so budgets stay independent:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">expensive_limiter</span> <span class="o">=</span> <span class="n">RateLimiter</span><span class="p">(</span><span class="n">requests</span><span class="o">=</span><span class="mi">5</span><span class="p">,</span> <span class="n">period</span><span class="o">=</span><span class="mi">60</span><span class="p">)</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/reports"</span><span class="p">)</span>
|
||
<span class="nd">@expensive_limiter</span><span class="o">.</span><span class="n">limit</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">generate_report</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="o">...</span>
|
||
</pre></div>
|
||
</div>
|
||
<div class="admonition note">
|
||
<p class="admonition-title">Note</p>
|
||
<p>Prefer <code class="docutils literal notranslate"><span class="pre">limiter.install(api)</span></code> or <code class="docutils literal notranslate"><span class="pre">@limiter.limit</span></code> over calling
|
||
<code class="docutils literal notranslate"><span class="pre">limiter.check(req,</span> <span class="pre">resp)</span></code> from a hand-written before-request hook.
|
||
All three enforce identically, but only the first two are visible to the
|
||
OpenAPI generator — a manual <code class="docutils literal notranslate"><span class="pre">check()</span></code> call is arbitrary code, so the
|
||
generated schema won’t list the <code class="docutils literal notranslate"><span class="pre">429</span></code>/<code class="docutils literal notranslate"><span class="pre">503</span></code> responses for it.</p>
|
||
</div>
|
||
</section>
|
||
<section id="metrics">
|
||
<h2>Metrics<a class="headerlink" href="#metrics" title="Link to this heading">¶</a></h2>
|
||
<p>Production services need visibility into traffic and latency. Responder
|
||
ships a zero-dependency metrics endpoint in Prometheus text format:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">metrics_route</span><span class="o">=</span><span class="s2">"/metrics"</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Every request is recorded as a counter
|
||
(<code class="docutils literal notranslate"><span class="pre">responder_requests_total{method,path,status}</span></code>), a latency histogram
|
||
(<code class="docutils literal notranslate"><span class="pre">responder_request_duration_seconds</span></code>), and an in-flight gauge
|
||
(<code class="docutils literal notranslate"><span class="pre">responder_requests_in_flight</span></code> — requests currently being handled, the
|
||
series capacity dashboards want). Labels use the route <em>pattern</em>
|
||
(<code class="docutils literal notranslate"><span class="pre">/users/{id}</span></code>), not the raw path, so cardinality stays bounded; requests
|
||
matching no route are labelled <code class="docutils literal notranslate"><span class="pre">unmatched</span></code>. Point Prometheus, Grafana
|
||
Alloy, or any compatible scraper at the endpoint and you have dashboards.</p>
|
||
<p>The histogram’s bucket bounds default to 5ms–10s
|
||
(<code class="xref py py-data docutils literal notranslate"><span class="pre">responder.ext.metrics.BUCKETS</span></code>). If your latency profile is
|
||
different — 30-second report endpoints, sub-millisecond cache hits — pass
|
||
your own ascending bounds via <code class="docutils literal notranslate"><span class="pre">metrics_buckets=</span></code> (or construct a
|
||
<code class="xref py py-class docutils literal notranslate"><span class="pre">MetricsCollector</span></code> with <code class="docutils literal notranslate"><span class="pre">buckets=</span></code>
|
||
directly):</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span>
|
||
<span class="n">metrics_route</span><span class="o">=</span><span class="s2">"/metrics"</span><span class="p">,</span>
|
||
<span class="n">metrics_buckets</span><span class="o">=</span><span class="p">(</span><span class="mf">0.1</span><span class="p">,</span> <span class="mf">0.5</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">,</span> <span class="mf">5.0</span><span class="p">,</span> <span class="mf">30.0</span><span class="p">),</span>
|
||
<span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="health-checks">
|
||
<h2>Health Checks<a class="headerlink" href="#health-checks" title="Link to this heading">¶</a></h2>
|
||
<p>Orchestrators (Kubernetes, load balancers) want a readiness endpoint that
|
||
reflects whether the app’s dependencies are actually reachable. Register
|
||
checks and Responder aggregates them:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">health_route</span><span class="o">=</span><span class="s2">"/health"</span><span class="p">)</span>
|
||
|
||
<span class="n">api</span><span class="o">.</span><span class="n">add_health_check</span><span class="p">(</span><span class="s2">"db"</span><span class="p">,</span> <span class="k">lambda</span><span class="p">:</span> <span class="n">database</span><span class="o">.</span><span class="n">ping</span><span class="p">())</span>
|
||
<span class="n">api</span><span class="o">.</span><span class="n">add_health_check</span><span class="p">(</span><span class="s2">"cache"</span><span class="p">,</span> <span class="n">check_redis</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>A check passes unless it returns <code class="docutils literal notranslate"><span class="pre">False</span></code> or raises. The endpoint returns
|
||
<code class="docutils literal notranslate"><span class="pre">200</span></code> with <code class="docutils literal notranslate"><span class="pre">{"status":</span> <span class="pre">"ok",</span> <span class="pre">"checks":</span> <span class="pre">{...}}</span></code> when all pass, and <code class="docutils literal notranslate"><span class="pre">503</span></code>
|
||
otherwise — with each check’s status (and the error detail for any that
|
||
raised). Checks may be sync or async, and the route is excluded from the
|
||
OpenAPI schema. (<code class="docutils literal notranslate"><span class="pre">add_health_check</span></code> adds the route at <code class="docutils literal notranslate"><span class="pre">/health</span></code> on first
|
||
use if you didn’t set <code class="docutils literal notranslate"><span class="pre">health_route</span></code>.)</p>
|
||
</section>
|
||
<section id="structured-logging">
|
||
<h2>Structured Logging<a class="headerlink" href="#structured-logging" title="Link to this heading">¶</a></h2>
|
||
<p>Production applications need structured, searchable logs. Responder
|
||
includes built-in logging that automatically attaches request context
|
||
— request ID, HTTP method, path, and client IP — to every log message
|
||
emitted during request handling:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">enable_logging</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>This gives you:</p>
|
||
<ul>
|
||
<li><p><strong>Access logging</strong> with timing for every request:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>2026-03-24 12:00:00 [INFO] responder.access — GET /users → 200 (1.2ms)
|
||
</pre></div>
|
||
</div>
|
||
</li>
|
||
<li><p><strong>A logger on the API instance</strong> — use <code class="docutils literal notranslate"><span class="pre">api.log</span></code> anywhere in
|
||
your routes. Request context (ID, method, path, client IP) is
|
||
attached automatically:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/users/{user_id:int}"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">get_user</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">user_id</span><span class="p">):</span>
|
||
<span class="n">api</span><span class="o">.</span><span class="n">log</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">"fetching user </span><span class="si">%d</span><span class="s2">"</span><span class="p">,</span> <span class="n">user_id</span><span class="p">)</span>
|
||
<span class="c1"># => [INFO] responder.app -- fetching user 42 [GET /users/42] [req:a1b2c3] [client:10.0.0.1]</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"id"</span><span class="p">:</span> <span class="n">user_id</span><span class="p">}</span>
|
||
</pre></div>
|
||
</div>
|
||
</li>
|
||
<li><p><strong>Request IDs</strong> generated automatically (or forwarded from the
|
||
<code class="docutils literal notranslate"><span class="pre">X-Request-ID</span></code> header) and included in responses.</p></li>
|
||
</ul>
|
||
<p>The logging uses Python’s standard <code class="docutils literal notranslate"><span class="pre">logging</span></code> module, so it works with
|
||
any handler — files, syslog, JSON formatters, Datadog, Sentry, whatever
|
||
you already use.</p>
|
||
<p>For additional loggers (e.g. in helper modules), use <code class="docutils literal notranslate"><span class="pre">get_logger</span></code>:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">responder.ext.logging</span><span class="w"> </span><span class="kn">import</span> <span class="n">get_logger</span>
|
||
<span class="n">logger</span> <span class="o">=</span> <span class="n">get_logger</span><span class="p">(</span><span class="s2">"myapp.db"</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>You can also access the current request context directly:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">responder.ext.logging</span><span class="w"> </span><span class="kn">import</span> <span class="n">RequestContext</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/debug"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">debug</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span>
|
||
<span class="s2">"request_id"</span><span class="p">:</span> <span class="n">RequestContext</span><span class="o">.</span><span class="n">get_request_id</span><span class="p">(),</span>
|
||
<span class="s2">"client_ip"</span><span class="p">:</span> <span class="n">RequestContext</span><span class="o">.</span><span class="n">get_client_ip</span><span class="p">(),</span>
|
||
<span class="p">}</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>When <code class="docutils literal notranslate"><span class="pre">enable_logging=True</span></code> is set, it supersedes <code class="docutils literal notranslate"><span class="pre">request_id=True</span></code>
|
||
— the logging middleware handles request IDs itself, so you don’t get
|
||
duplicate headers.</p>
|
||
<p>For debugging route control flow, pass <code class="docutils literal notranslate"><span class="pre">trace_dispatch=True</span></code>. Responder emits
|
||
debug logs for the documented dispatch stages: before hooks, auth,
|
||
dependencies, handler execution, and after hooks:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">enable_logging</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">trace_dispatch</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="pydantic-validation">
|
||
<h2>Pydantic Validation<a class="headerlink" href="#pydantic-validation" title="Link to this heading">¶</a></h2>
|
||
<p><a class="reference external" href="https://docs.pydantic.dev/">Pydantic</a> models integrate directly with
|
||
Responder’s routing. Add a required Pydantic-typed body parameter to validate
|
||
incoming data and annotate the return value to control the outgoing shape:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">pydantic</span><span class="w"> </span><span class="kn">import</span> <span class="n">BaseModel</span>
|
||
|
||
<span class="k">class</span><span class="w"> </span><span class="nc">ItemIn</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
|
||
<span class="n">name</span><span class="p">:</span> <span class="nb">str</span>
|
||
<span class="n">price</span><span class="p">:</span> <span class="nb">float</span>
|
||
|
||
<span class="k">class</span><span class="w"> </span><span class="nc">ItemOut</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
|
||
<span class="nb">id</span><span class="p">:</span> <span class="nb">int</span>
|
||
<span class="n">name</span><span class="p">:</span> <span class="nb">str</span>
|
||
<span class="n">price</span><span class="p">:</span> <span class="nb">float</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/items"</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s2">"POST"</span><span class="p">])</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">create_item</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">item</span><span class="p">:</span> <span class="n">ItemIn</span><span class="p">)</span> <span class="o">-></span> <span class="n">ItemOut</span><span class="p">:</span>
|
||
<span class="k">return</span> <span class="n">ItemOut</span><span class="p">(</span><span class="nb">id</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="o">**</span><span class="n">item</span><span class="o">.</span><span class="n">model_dump</span><span class="p">())</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>When a write-method handler has a required Pydantic-typed body parameter:</p>
|
||
<ul class="simple">
|
||
<li><p>Valid requests are parsed, validated, and injected as a model instance.</p></li>
|
||
<li><p>Invalid requests get an automatic <code class="docutils literal notranslate"><span class="pre">422</span> <span class="pre">Unprocessable</span> <span class="pre">Entity</span></code> response with
|
||
detailed error messages — you don’t write any validation code.</p></li>
|
||
</ul>
|
||
<p>When a response contract is inferred or explicitly set:</p>
|
||
<ul class="simple">
|
||
<li><p>The response is serialized through the model before being sent</p></li>
|
||
<li><p>Extra fields are stripped automatically</p></li>
|
||
<li><p>Type coercion happens at the boundary</p></li>
|
||
</ul>
|
||
<p>For individual query parameters, headers, and cookies, declare them as
|
||
keyword-only arguments with a <em>marker</em> default. Responder reads the value,
|
||
coerces it to the annotated type, and injects it — returning <code class="docutils literal notranslate"><span class="pre">422</span></code> if a
|
||
required value is missing or won’t coerce:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">responder</span><span class="w"> </span><span class="kn">import</span> <span class="n">Query</span><span class="p">,</span> <span class="n">Header</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/search"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">search</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span>
|
||
<span class="n">q</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Query</span><span class="p">(</span><span class="o">...</span><span class="p">),</span>
|
||
<span class="n">limit</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="n">Query</span><span class="p">(</span><span class="mi">10</span><span class="p">),</span>
|
||
<span class="n">tags</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="n">Query</span><span class="p">([]),</span>
|
||
<span class="n">user_agent</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Header</span><span class="p">(</span><span class="s2">"unknown"</span><span class="p">)):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"q"</span><span class="p">:</span> <span class="n">q</span><span class="p">,</span> <span class="s2">"limit"</span><span class="p">:</span> <span class="n">limit</span><span class="p">,</span> <span class="s2">"tags"</span><span class="p">:</span> <span class="n">tags</span><span class="p">}</span>
|
||
</pre></div>
|
||
</div>
|
||
<ul>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">Query(...)</span></code> (an Ellipsis) marks the value <strong>required</strong>; <code class="docutils literal notranslate"><span class="pre">Query(10)</span></code>
|
||
makes it optional with that default.</p></li>
|
||
<li><p>The annotation drives coercion: <code class="docutils literal notranslate"><span class="pre">"5"</span></code> becomes <code class="docutils literal notranslate"><span class="pre">5</span></code>, and a sequence
|
||
type like <code class="docutils literal notranslate"><span class="pre">list[str]</span></code> collects repeated keys (<code class="docutils literal notranslate"><span class="pre">?tags=a&tags=b</span></code>).</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">Header</span></code> looks up the header named after the parameter, converting
|
||
underscores to dashes (<code class="docutils literal notranslate"><span class="pre">user_agent</span></code> → <code class="docutils literal notranslate"><span class="pre">user-agent</span></code>); pass <code class="docutils literal notranslate"><span class="pre">alias=</span></code>
|
||
for an explicit name. <code class="docutils literal notranslate"><span class="pre">Cookie</span></code> reads cookies, and <code class="docutils literal notranslate"><span class="pre">Path</span></code> re-validates
|
||
or renames a path segment:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">responder</span><span class="w"> </span><span class="kn">import</span> <span class="n">Path</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/users/{uid:int}"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">get_user</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">user_id</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="o">...</span><span class="p">,</span> <span class="n">alias</span><span class="o">=</span><span class="s2">"uid"</span><span class="p">)):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"id"</span><span class="p">:</span> <span class="n">user_id</span><span class="p">}</span>
|
||
</pre></div>
|
||
</div>
|
||
</li>
|
||
</ul>
|
||
<p>All four markers are exported from the top level
|
||
(<code class="docutils literal notranslate"><span class="pre">from</span> <span class="pre">responder</span> <span class="pre">import</span> <span class="pre">Query,</span> <span class="pre">Header,</span> <span class="pre">Cookie,</span> <span class="pre">Path</span></code>) and feed the
|
||
generated OpenAPI <code class="docutils literal notranslate"><span class="pre">parameters</span></code>.</p>
|
||
<p>To validate the whole query string as a single model instead, use
|
||
<code class="docutils literal notranslate"><span class="pre">params_model</span></code> — values are coerced, defaults apply, repeated keys map to
|
||
<code class="docutils literal notranslate"><span class="pre">list</span></code> fields, invalid queries get a <code class="docutils literal notranslate"><span class="pre">422</span></code>, and the parameters appear in
|
||
your OpenAPI spec:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">SearchParams</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
|
||
<span class="n">q</span><span class="p">:</span> <span class="nb">str</span>
|
||
<span class="n">limit</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">10</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/search"</span><span class="p">,</span> <span class="n">params_model</span><span class="o">=</span><span class="n">SearchParams</span><span class="p">)</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">search</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">params</span> <span class="o">=</span> <span class="n">req</span><span class="o">.</span><span class="n">state</span><span class="o">.</span><span class="n">validated_params</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="k">await</span> <span class="n">find</span><span class="p">(</span><span class="n">params</span><span class="o">.</span><span class="n">q</span><span class="p">,</span> <span class="n">limit</span><span class="o">=</span><span class="n">params</span><span class="o">.</span><span class="n">limit</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The body and the response validate from type hints too. On
|
||
<code class="docutils literal notranslate"><span class="pre">POST</span></code>/<code class="docutils literal notranslate"><span class="pre">PUT</span></code>/<code class="docutils literal notranslate"><span class="pre">PATCH</span></code>/<code class="docutils literal notranslate"><span class="pre">DELETE</span></code>, a keyword-only parameter annotated
|
||
with a Pydantic model (and no default) receives the parsed, validated body —
|
||
and a supported return annotation becomes the response contract:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/items"</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s2">"POST"</span><span class="p">])</span>
|
||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">create_item</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">item</span><span class="p">:</span> <span class="n">ItemIn</span><span class="p">)</span> <span class="o">-></span> <span class="n">ItemOut</span><span class="p">:</span>
|
||
<span class="k">return</span> <span class="n">ItemOut</span><span class="p">(</span><span class="nb">id</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="n">item</span><span class="o">.</span><span class="n">name</span><span class="p">,</span> <span class="n">price</span><span class="o">=</span><span class="n">item</span><span class="o">.</span><span class="n">price</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>An invalid or non-object body returns <code class="docutils literal notranslate"><span class="pre">422</span></code> before your handler runs. The
|
||
<code class="docutils literal notranslate"><span class="pre">-></span> <span class="pre">ItemOut</span></code> return annotation validates and coerces the outgoing value and
|
||
strips undeclared fields; if the payload violates the contract it fails closed
|
||
(a <code class="docutils literal notranslate"><span class="pre">500</span></code> in production, or re-raises under <code class="docutils literal notranslate"><span class="pre">debug=True</span></code>) rather than
|
||
leaking a malformed response. Validation runs after after-request hooks, so the
|
||
contract describes what is actually sent. Opt out with
|
||
<code class="docutils literal notranslate"><span class="pre">@api.route(...,</span> <span class="pre">response_model=False)</span></code>.</p>
|
||
<p>Inference uses Pydantic’s <code class="docutils literal notranslate"><span class="pre">TypeAdapter</span></code> support boundary. Models,
|
||
dataclasses, typed dictionaries, <code class="docutils literal notranslate"><span class="pre">list[ItemOut]</span></code>, <code class="docutils literal notranslate"><span class="pre">Page[ItemOut]</span></code>, unions,
|
||
and JSON scalar types all drive runtime validation, OpenAPI, and generated
|
||
clients. A union like <code class="docutils literal notranslate"><span class="pre">ItemOut</span> <span class="pre">|</span> <span class="pre">ErrorOut</span></code> emits <code class="docutils literal notranslate"><span class="pre">anyOf</span></code>/<code class="docutils literal notranslate"><span class="pre">oneOf</span></code> and
|
||
validates the selected branch. String and bytes annotations default to
|
||
<code class="docutils literal notranslate"><span class="pre">text/plain</span></code> and <code class="docutils literal notranslate"><span class="pre">application/octet-stream</span></code>; other inferred contracts use
|
||
<code class="docutils literal notranslate"><span class="pre">application/json</span></code>. Explicit <code class="docutils literal notranslate"><span class="pre">response_model=</span></code> remains available when the
|
||
signature cannot or should not carry the contract.</p>
|
||
<div class="admonition note">
|
||
<p class="admonition-title">Note</p>
|
||
<p>A raw ORM object needs a model configured with <code class="docutils literal notranslate"><span class="pre">from_attributes=True</span></code>, or
|
||
an explicit <code class="docutils literal notranslate"><span class="pre">ItemOut.model_validate(obj)</span></code> before it is returned.</p>
|
||
</div>
|
||
<p>This is the recommended way to build validated REST APIs with Responder.
|
||
See the <a class="reference internal" href="tutorial-rest.html"><span class="doc">Building a REST API</span></a> for a complete walkthrough.</p>
|
||
</section>
|
||
<section id="pagination">
|
||
<h2>Pagination<a class="headerlink" href="#pagination" title="Link to this heading">¶</a></h2>
|
||
<p><code class="docutils literal notranslate"><span class="pre">responder.ext.pagination</span></code> provides a generic <code class="docutils literal notranslate"><span class="pre">Page</span></code> envelope and a
|
||
<code class="docutils literal notranslate"><span class="pre">paginate</span></code> helper for list endpoints. Pair them with the typed <code class="docutils literal notranslate"><span class="pre">Query</span></code>
|
||
markers and a <code class="docutils literal notranslate"><span class="pre">Page[Model]</span></code> response model:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">responder</span><span class="w"> </span><span class="kn">import</span> <span class="n">Query</span>
|
||
<span class="kn">from</span><span class="w"> </span><span class="nn">responder.ext.pagination</span><span class="w"> </span><span class="kn">import</span> <span class="n">Page</span><span class="p">,</span> <span class="n">paginate</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"/items"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">list_items</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span>
|
||
<span class="n">page</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="n">Query</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">ge</span><span class="o">=</span><span class="mi">1</span><span class="p">),</span>
|
||
<span class="n">size</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="n">Query</span><span class="p">(</span><span class="mi">20</span><span class="p">,</span> <span class="n">ge</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">le</span><span class="o">=</span><span class="mi">100</span><span class="p">))</span> <span class="o">-></span> <span class="n">Page</span><span class="p">[</span><span class="n">Item</span><span class="p">]:</span>
|
||
<span class="k">return</span> <span class="n">paginate</span><span class="p">(</span><span class="n">db</span><span class="o">.</span><span class="n">all</span><span class="p">(),</span> <span class="n">page</span><span class="o">=</span><span class="n">page</span><span class="p">,</span> <span class="n">size</span><span class="o">=</span><span class="n">size</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The response is an envelope with <code class="docutils literal notranslate"><span class="pre">items</span></code>, <code class="docutils literal notranslate"><span class="pre">total</span></code>, <code class="docutils literal notranslate"><span class="pre">page</span></code>, <code class="docutils literal notranslate"><span class="pre">size</span></code>, and
|
||
<code class="docutils literal notranslate"><span class="pre">pages</span></code>, and OpenAPI documents it as an inline object referencing your element
|
||
model. <code class="docutils literal notranslate"><span class="pre">paginate</span></code> slices an in-memory collection by default; when you page in
|
||
the database yourself, pass the already-sliced rows plus the overall
|
||
<code class="docutils literal notranslate"><span class="pre">total=</span></code>:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">rows</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">query</span><span class="p">(</span><span class="n">limit</span><span class="o">=</span><span class="n">size</span><span class="p">,</span> <span class="n">offset</span><span class="o">=</span><span class="p">(</span><span class="n">page</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">*</span> <span class="n">size</span><span class="p">)</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="n">paginate</span><span class="p">(</span><span class="n">rows</span><span class="p">,</span> <span class="n">page</span><span class="o">=</span><span class="n">page</span><span class="p">,</span> <span class="n">size</span><span class="o">=</span><span class="n">size</span><span class="p">,</span> <span class="n">total</span><span class="o">=</span><span class="n">db</span><span class="o">.</span><span class="n">count</span><span class="p">())</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Many REST consumers navigate by response <em>headers</em> instead of the body
|
||
envelope — the GitHub-style <span class="target" id="index-0"></span><a class="rfc reference external" href="https://datatracker.ietf.org/doc/html/rfc8288.html"><strong>RFC 8288</strong></a> <code class="docutils literal notranslate"><span class="pre">Link</span></code> header plus
|
||
<code class="docutils literal notranslate"><span class="pre">X-Total-Count</span></code>. Call <code class="docutils literal notranslate"><span class="pre">set_pagination_headers</span></code> to emit both:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">responder.ext.pagination</span><span class="w"> </span><span class="kn">import</span> <span class="n">paginate</span><span class="p">,</span> <span class="n">set_pagination_headers</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"/items"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">list_items</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span>
|
||
<span class="n">page</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="n">Query</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">ge</span><span class="o">=</span><span class="mi">1</span><span class="p">),</span>
|
||
<span class="n">size</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="n">Query</span><span class="p">(</span><span class="mi">20</span><span class="p">,</span> <span class="n">ge</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">le</span><span class="o">=</span><span class="mi">100</span><span class="p">))</span> <span class="o">-></span> <span class="n">Page</span><span class="p">[</span><span class="n">Item</span><span class="p">]:</span>
|
||
<span class="n">result</span> <span class="o">=</span> <span class="n">paginate</span><span class="p">(</span><span class="n">db</span><span class="o">.</span><span class="n">all</span><span class="p">(),</span> <span class="n">page</span><span class="o">=</span><span class="n">page</span><span class="p">,</span> <span class="n">size</span><span class="o">=</span><span class="n">size</span><span class="p">)</span>
|
||
<span class="n">set_pagination_headers</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="n">result</span><span class="p">)</span>
|
||
<span class="k">return</span> <span class="n">result</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The <code class="docutils literal notranslate"><span class="pre">Link</span></code> header carries <code class="docutils literal notranslate"><span class="pre">rel="first"</span></code>/<code class="docutils literal notranslate"><span class="pre">"prev"</span></code>/<code class="docutils literal notranslate"><span class="pre">"next"</span></code>/<code class="docutils literal notranslate"><span class="pre">"last"</span></code>
|
||
URLs built from the request’s own URL — every other query parameter
|
||
(filters, sort specs) is preserved; only <code class="docutils literal notranslate"><span class="pre">page</span></code> and <code class="docutils literal notranslate"><span class="pre">size</span></code> are
|
||
rewritten. <code class="docutils literal notranslate"><span class="pre">prev</span></code> is omitted on the first page and <code class="docutils literal notranslate"><span class="pre">next</span></code> on the last.</p>
|
||
<section id="sorting-and-filtering">
|
||
<h3>Sorting and Filtering<a class="headerlink" href="#sorting-and-filtering" title="Link to this heading">¶</a></h3>
|
||
<p><code class="docutils literal notranslate"><span class="pre">responder.ext.query</span></code> rounds out list endpoints with <code class="docutils literal notranslate"><span class="pre">sort_items</span></code> and
|
||
<code class="docutils literal notranslate"><span class="pre">filter_items</span></code> — in-memory helpers (dicts or objects, no ORM coupling) that
|
||
pair with the typed markers and <code class="docutils literal notranslate"><span class="pre">paginate</span></code>:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">responder.ext.query</span><span class="w"> </span><span class="kn">import</span> <span class="n">filter_items</span><span class="p">,</span> <span class="n">sort_items</span>
|
||
|
||
<span class="nd">@api</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"/items"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">list_items</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span>
|
||
<span class="n">status</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Query</span><span class="p">(</span><span class="kc">None</span><span class="p">),</span>
|
||
<span class="n">sort</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Query</span><span class="p">(</span><span class="s2">"name"</span><span class="p">),</span>
|
||
<span class="n">page</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="n">Query</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">ge</span><span class="o">=</span><span class="mi">1</span><span class="p">),</span>
|
||
<span class="n">size</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="n">Query</span><span class="p">(</span><span class="mi">20</span><span class="p">,</span> <span class="n">ge</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">le</span><span class="o">=</span><span class="mi">100</span><span class="p">))</span> <span class="o">-></span> <span class="n">Page</span><span class="p">[</span><span class="n">Item</span><span class="p">]:</span>
|
||
<span class="n">rows</span> <span class="o">=</span> <span class="n">filter_items</span><span class="p">(</span><span class="n">db</span><span class="o">.</span><span class="n">all</span><span class="p">(),</span> <span class="p">{</span><span class="s2">"status"</span><span class="p">:</span> <span class="n">status</span><span class="p">})</span>
|
||
<span class="n">rows</span> <span class="o">=</span> <span class="n">sort_items</span><span class="p">(</span><span class="n">rows</span><span class="p">,</span> <span class="n">sort</span><span class="p">,</span> <span class="n">allowed</span><span class="o">=</span><span class="p">{</span><span class="s2">"name"</span><span class="p">,</span> <span class="s2">"created_at"</span><span class="p">})</span>
|
||
<span class="k">return</span> <span class="n">paginate</span><span class="p">(</span><span class="n">rows</span><span class="p">,</span> <span class="n">page</span><span class="o">=</span><span class="n">page</span><span class="p">,</span> <span class="n">size</span><span class="o">=</span><span class="n">size</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p><code class="docutils literal notranslate"><span class="pre">filter_items</span></code> applies <code class="docutils literal notranslate"><span class="pre">field</span> <span class="pre">==</span> <span class="pre">value</span></code> equality and skips entries whose
|
||
value is <code class="docutils literal notranslate"><span class="pre">None</span></code> (so optional markers pass straight through). <code class="docutils literal notranslate"><span class="pre">sort_items</span></code>
|
||
reads a <code class="docutils literal notranslate"><span class="pre">name,-created</span></code> spec (<code class="docutils literal notranslate"><span class="pre">-</span></code> = descending, multiple keys allowed);
|
||
always pass <code class="docutils literal notranslate"><span class="pre">allowed=</span></code> for a client-supplied <code class="docutils literal notranslate"><span class="pre">sort</span></code> so users can’t order by
|
||
arbitrary attributes — an out-of-list field returns <code class="docutils literal notranslate"><span class="pre">400</span></code>.</p>
|
||
</section>
|
||
</section>
|
||
<section id="content-negotiation">
|
||
<span id="tour-content-negotiation"></span><h2>Content Negotiation<a class="headerlink" href="#content-negotiation" title="Link to this heading">¶</a></h2>
|
||
<p>Responder automatically negotiates the response format based on the
|
||
client’s <code class="docutils literal notranslate"><span class="pre">Accept</span></code> header. Set <code class="docutils literal notranslate"><span class="pre">resp.media</span></code> to a Python object and
|
||
the right thing happens:</p>
|
||
<ul class="simple">
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">Accept:</span> <span class="pre">application/json</span></code> (default) → JSON</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">Accept:</span> <span class="pre">application/yaml</span></code> → YAML (the legacy <code class="docutils literal notranslate"><span class="pre">application/x-yaml</span></code>
|
||
is accepted too)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">Accept:</span> <span class="pre">application/x-msgpack</span></code> → MessagePack</p></li>
|
||
</ul>
|
||
<p>This means a single endpoint serves multiple formats without any
|
||
conditional logic in your code:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/data"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">data</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"key"</span><span class="p">:</span> <span class="s2">"value"</span><span class="p">}</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Clients get the format they ask for:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>$ curl http://localhost:5042/data
|
||
{"key": "value"}
|
||
|
||
$ curl -H "Accept: application/yaml" http://localhost:5042/data
|
||
key: value
|
||
</pre></div>
|
||
</div>
|
||
<p>When the <code class="docutils literal notranslate"><span class="pre">Accept</span></code> header ranks several supported formats, Responder
|
||
serves the one the client prefers — the highest q-value wins, per
|
||
RFC 9110 §12.5.1 — rather than the first format registered:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>$ curl -H "Accept: application/yaml;q=0.9, application/json;q=0.1" \
|
||
http://localhost:5042/data
|
||
key: value
|
||
</pre></div>
|
||
</div>
|
||
<p>Without an <code class="docutils literal notranslate"><span class="pre">Accept</span></code> header, or when everything ties (e.g. <code class="docutils literal notranslate"><span class="pre">*/*</span></code>),
|
||
JSON remains the default. To apply the same ranking yourself — say, to
|
||
choose between representations you render by hand — use
|
||
<code class="docutils literal notranslate"><span class="pre">req.preferred_media_type()</span></code>, which returns the candidate the client
|
||
prefers (ties keep your order) or <code class="docutils literal notranslate"><span class="pre">None</span></code> when it accepts none of them:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/export"</span><span class="p">)</span>
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">export</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||
<span class="n">preferred</span> <span class="o">=</span> <span class="n">req</span><span class="o">.</span><span class="n">preferred_media_type</span><span class="p">([</span><span class="s2">"application/json"</span><span class="p">,</span> <span class="s2">"text/csv"</span><span class="p">])</span>
|
||
<span class="k">if</span> <span class="n">preferred</span> <span class="o">==</span> <span class="s2">"text/csv"</span><span class="p">:</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="n">render_csv</span><span class="p">()</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">mimetype</span> <span class="o">=</span> <span class="s2">"text/csv"</span>
|
||
<span class="k">else</span><span class="p">:</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="n">render_dict</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="faster-json-with-orjson">
|
||
<h2>Faster JSON with orjson<a class="headerlink" href="#faster-json-with-orjson" title="Link to this heading">¶</a></h2>
|
||
<p>The recommended Responder installation includes
|
||
<a class="reference external" href="https://github.com/ijl/orjson">orjson</a>:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>$ pip install "responder[orjson]"
|
||
</pre></div>
|
||
</div>
|
||
<p>Responder detects it automatically and uses it to encode JSON responses —
|
||
typically 3-10x faster than the standard library, which matters for large
|
||
payloads that would otherwise serialize on the event loop. No code changes
|
||
are needed: <code class="docutils literal notranslate"><span class="pre">resp.media</span></code> and custom <code class="docutils literal notranslate"><span class="pre">API(encoder=...)</span></code> hooks keep working
|
||
exactly as before. Request decoding remains on the standard library to
|
||
preserve arbitrary-precision integers and compatibility with <code class="docutils literal notranslate"><span class="pre">NaN</span></code> and
|
||
<code class="docutils literal notranslate"><span class="pre">Infinity</span></code>. The legacy <code class="docutils literal notranslate"><span class="pre">API(json_ensure_ascii=True)</span></code> mode also stays on
|
||
the standard library, since orjson emits UTF-8 only.</p>
|
||
<p>Free-threaded CPython builds (<code class="docutils literal notranslate"><span class="pre">3.14t</span></code>/<code class="docutils literal notranslate"><span class="pre">3.15t</span></code>) can install the base
|
||
<code class="docutils literal notranslate"><span class="pre">responder</span></code> package. If orjson is unavailable, Responder transparently
|
||
falls back to the standard-library encoder.</p>
|
||
<p>Output is byte-identical to the stdlib encoder except for whitespace
|
||
(orjson emits compact <code class="docutils literal notranslate"><span class="pre">,</span></code>/<code class="docutils literal notranslate"><span class="pre">:</span></code> separators) and one corner: float
|
||
<code class="docutils literal notranslate"><span class="pre">nan</span></code>/<code class="docutils literal notranslate"><span class="pre">inf</span></code> values serialize as <code class="docutils literal notranslate"><span class="pre">null</span></code> instead of the non-standard
|
||
<code class="docutils literal notranslate"><span class="pre">NaN</span></code>/<code class="docutils literal notranslate"><span class="pre">Infinity</span></code> literals. Payloads orjson cannot handle (such as
|
||
integers beyond 64 bits) automatically fall back to the standard library,
|
||
so nothing that serialized before stops working.</p>
|
||
</section>
|
||
<section id="messagepack">
|
||
<h2>MessagePack<a class="headerlink" href="#messagepack" title="Link to this heading">¶</a></h2>
|
||
<p><a class="reference external" href="https://msgpack.org/">MessagePack</a> is a binary serialization format
|
||
that’s more compact and faster to parse than JSON. It’s useful for
|
||
high-throughput APIs, IoT devices, and anywhere bandwidth matters.</p>
|
||
<p>Responder supports MessagePack alongside JSON and YAML:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Decode a MessagePack request body</span>
|
||
<span class="n">data</span> <span class="o">=</span> <span class="k">await</span> <span class="n">req</span><span class="o">.</span><span class="n">media</span><span class="p">(</span><span class="s2">"msgpack"</span><span class="p">)</span>
|
||
|
||
<span class="c1"># Respond with MessagePack</span>
|
||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"result"</span><span class="p">:</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]}</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Content negotiation works automatically — clients can send
|
||
<code class="docutils literal notranslate"><span class="pre">Accept:</span> <span class="pre">application/x-msgpack</span></code> to receive MessagePack responses
|
||
instead of JSON. You can also explicitly decode MessagePack request
|
||
bodies by passing <code class="docutils literal notranslate"><span class="pre">"msgpack"</span></code> to <code class="docutils literal notranslate"><span class="pre">req.media()</span></code>.</p>
|
||
</section>
|
||
</section>
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
<div class="sphinxsidebar" role="navigation" aria-label="Main">
|
||
<div class="sphinxsidebarwrapper"><p class="logo">
|
||
<a href="index.html">
|
||
<img class="logo" src="_static/responder.png" />
|
||
</a>
|
||
</p>
|
||
<p>
|
||
<strong>Responder</strong> — a familiar HTTP service framework for Python.
|
||
<br />
|
||
<small>v9.2.0</small>
|
||
</p>
|
||
<h3>Useful Links</h3>
|
||
<ul>
|
||
<li><a href="https://github.com/kennethreitz/responder">Responder @ GitHub</a></li>
|
||
<li><a href="https://pypi.org/project/responder/">Responder @ PyPI</a></li>
|
||
<li><a href="https://github.com/kennethreitz/responder/issues">Issue Tracker</a></li>
|
||
</ul><h3>Navigation</h3>
|
||
<p class="caption" role="heading"><span class="caption-text">User Guide</span></p>
|
||
<ul class="current">
|
||
<li class="toctree-l1"><a class="reference internal" href="quickstart.html">Quick Start</a></li>
|
||
<li class="toctree-l1 current"><a class="current reference internal" href="#">Feature Tour</a><ul>
|
||
<li class="toctree-l2"><a class="reference internal" href="#method-filtering">Method Filtering</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#returning-values">Returning Values</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#class-based-views">Class-Based Views</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#lifespan-events">Lifespan Events</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#dependency-injection">Dependency Injection</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#serving-files">Serving Files</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#conditional-requests">Conditional Requests</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#after-response-tasks">After-Response Tasks</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#custom-error-handling">Custom Error Handling</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#before-request-hooks">Before-Request Hooks</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#after-request-hooks">After-Request Hooks</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#websocket-support">WebSocket Support</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#server-sent-events-sse">Server-Sent Events (SSE)</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#graphql">GraphQL</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#openapi-documentation">OpenAPI Documentation</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#route-groups">Route Groups</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#mounting-other-apps">Mounting Other Apps</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#cookies">Cookies</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#cookie-based-sessions">Cookie-Based Sessions</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#static-files">Static Files</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#csrf-protection">CSRF Protection</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#cors">CORS</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#hsts">HSTS</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#security-headers">Security Headers</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#trusted-hosts">Trusted Hosts</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#authentication">Authentication</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#request-id">Request ID</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#request-size-limits">Request Size Limits</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#request-timeouts">Request Timeouts</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#rate-limiting">Rate Limiting</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#metrics">Metrics</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#health-checks">Health Checks</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#structured-logging">Structured Logging</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#pydantic-validation">Pydantic Validation</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#pagination">Pagination</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#content-negotiation">Content Negotiation</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#faster-json-with-orjson">Faster JSON with orjson</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#messagepack">MessagePack</a></li>
|
||
</ul>
|
||
</li>
|
||
<li class="toctree-l1"><a class="reference internal" href="examples.html">Examples</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="routers.html">Composing Apps with Routers</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="guide-config.html">Configuration</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="clientgen.html">Generated Clients</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="deployment.html">Deployment</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="testing.html">Testing</a></li>
|
||
</ul>
|
||
<p class="caption" role="heading"><span class="caption-text">Tutorials</span></p>
|
||
<ul>
|
||
<li class="toctree-l1"><a class="reference internal" href="tutorial-rest.html">Building a REST API</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="tutorial-sqlalchemy.html">Using SQLAlchemy</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="tutorial-auth.html">Authentication</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="tutorial-websockets.html">WebSocket Tutorial</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="tutorial-middleware.html">Writing Middleware</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="tutorial-flask.html">Migrating from Flask</a></li>
|
||
</ul>
|
||
<p class="caption" role="heading"><span class="caption-text">Reference</span></p>
|
||
<ul>
|
||
<li class="toctree-l1"><a class="reference internal" href="api.html">API Reference</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="cli.html">Command Line Interface</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="runtime-contracts.html">Runtime Contracts</a></li>
|
||
</ul>
|
||
<p class="caption" role="heading"><span class="caption-text">Project</span></p>
|
||
<ul>
|
||
<li class="toctree-l1"><a class="reference internal" href="changes.html">Changelog</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="migration-v9.html">Migrating to v9</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="migration-v8.html">Migrating to v8</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="migration-v7.html">Migrating to v7</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="migration-v6.html">Migrating to v6</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="migration-v5.html">Migrating to v5</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="sandbox.html">Sandbox</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="backlog.html">Backlog</a></li>
|
||
</ul>
|
||
|
||
<div class="relations">
|
||
<h3>Related Topics</h3>
|
||
<ul>
|
||
<li><a href="index.html">Documentation overview</a><ul>
|
||
<li>Previous: <a href="quickstart.html" title="previous chapter">Quick Start</a></li>
|
||
<li>Next: <a href="examples.html" title="next chapter">Examples</a></li>
|
||
</ul></li>
|
||
</ul>
|
||
</div>
|
||
<search id="searchbox" style="display: none" role="search">
|
||
<h3 id="searchlabel">Quick search</h3>
|
||
<div class="searchformwrapper">
|
||
<form class="search" action="search.html" method="get">
|
||
<input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
|
||
<input type="submit" value="Go" />
|
||
</form>
|
||
</div>
|
||
</search>
|
||
<script>document.getElementById('searchbox').style.display = "block"</script>
|
||
</div>
|
||
</div>
|
||
<div class="clearer"></div>
|
||
</div>
|
||
<div class="footer">
|
||
©2018-2026, Kenneth Reitz.
|
||
|
||
|
|
||
<a href="_sources/tour.rst.txt"
|
||
rel="nofollow">Page source</a>
|
||
</div>
|
||
|
||
|
||
<a href="https://github.com/kennethreitz/responder" class="github">
|
||
<img src="_static/forkme_right_darkblue.png" alt="Fork me on GitHub" class="github"/>
|
||
</a>
|
||
|
||
|
||
|
||
</body>
|
||
</html> |