Files
responder/migration-v9.html
2026-07-14 04:14:56 +00:00

355 lines
33 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>Migrating to v9 &#8212; 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="Migrating to v8" href="migration-v8.html" />
<link rel="prev" title="Changelog" href="changes.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="migrating-to-v9">
<h1>Migrating to v9<a class="headerlink" href="#migrating-to-v9" title="Link to this heading"></a></h1>
<p>Responder 9.0 makes the v8.1 deprecation path the default behavior. Most
apps only need small cleanup: use the supported test-client helpers, pass
explicit route endpoints, and rely on precision-preserving JSON defaults.</p>
<p>The compatibility switches documented below are explicit and quiet. They are
there for apps that need to keep 8.x behavior while migrating.</p>
<p>Run your test suite with warnings surfaced to find any unrelated deprecations:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">python</span> <span class="o">-</span><span class="n">W</span> <span class="n">error</span><span class="p">::</span><span class="ne">DeprecationWarning</span> <span class="o">-</span><span class="n">m</span> <span class="n">pytest</span>
</pre></div>
</div>
<section id="typed-response-contracts-in-9-1">
<h2>Typed response contracts in 9.1<a class="headerlink" href="#typed-response-contracts-in-9-1" title="Link to this heading"></a></h2>
<p>Responder 9.1 treats every supported return annotation as an enforced response
contract. This now includes Pydantic models, dataclasses, typed dictionaries,
collections such as <code class="docutils literal notranslate"><span class="pre">list[Item]</span></code>, unions, generic pagination models, and JSON
scalars. The same type drives runtime validation and serialization, OpenAPI,
and generated-client types:</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">&quot;/items&quot;</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">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ItemOut</span><span class="p">]:</span>
<span class="k">return</span> <span class="n">rows</span>
</pre></div>
</div>
<p>Earlier releases inferred only a narrower set of shapes. A route that returned
data inconsistent with a generic or scalar annotation may now fail closed with
<code class="docutils literal notranslate"><span class="pre">500</span></code>. Fix the returned value to match the annotation, or explicitly retain
mutation-only/unchecked behavior while migrating:</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">&quot;/legacy&quot;</span><span class="p">,</span> <span class="n">response_model</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">legacy</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">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ItemOut</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">build_legacy_payload</span><span class="p">()</span>
</pre></div>
</div>
<p>Unresolvable or unsupported return annotations now produce a registration
warning naming the route and the <code class="docutils literal notranslate"><span class="pre">response_model=False</span></code> escape hatch.</p>
<p>Alternate statuses can declare their own runtime and OpenAPI contracts through
<code class="docutils literal notranslate"><span class="pre">responses=</span></code>. Return the body and status together, or assign <code class="docutils literal notranslate"><span class="pre">resp</span></code> as
usual:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">NotFound</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
<span class="n">detail</span><span class="p">:</span> <span class="nb">str</span>
<span class="nd">@api</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;/items/</span><span class="si">{item_id}</span><span class="s2">&quot;</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="n">NotFound</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="n">item_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ItemOut</span><span class="p">:</span>
<span class="n">item</span> <span class="o">=</span> <span class="n">find_item</span><span class="p">(</span><span class="n">item_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="n">NotFound</span><span class="p">(</span><span class="n">detail</span><span class="o">=</span><span class="s2">&quot;Item not found&quot;</span><span class="p">),</span> <span class="mi">404</span>
<span class="k">return</span> <span class="n">item</span>
</pre></div>
</div>
<p>Use <code class="docutils literal notranslate"><span class="pre">{&quot;model&quot;:</span> <span class="pre">NotFound,</span> <span class="pre">&quot;description&quot;:</span> <span class="pre">&quot;...&quot;}</span></code> as the response value
when the generated OpenAPI response needs a custom description, headers, or
other metadata.</p>
</section>
<section id="multipart-uploads-stream-to-disk">
<h2>Multipart uploads stream to disk<a class="headerlink" href="#multipart-uploads-stream-to-disk" title="Link to this heading"></a></h2>
<p>Multipart bodies now parse incrementally as they arrive: file parts spool to
temporary files (rolling to disk past ~1 MB) instead of being buffered in
RAM. Nothing changes in how you read uploads — <code class="docutils literal notranslate"><span class="pre">File()</span></code> markers,
<code class="docutils literal notranslate"><span class="pre">req.media(&quot;files&quot;)</span></code>, and <code class="docutils literal notranslate"><span class="pre">req.media(&quot;form&quot;)</span></code> all work as before, and
<code class="docutils literal notranslate"><span class="pre">UploadFile.save()</span></code> copies disk-to-disk in chunks.</p>
<p>Two consequences of the body no longer being buffered:</p>
<ul class="simple">
<li><p><strong>The raw body is consumed by the parse.</strong> <code class="docutils literal notranslate"><span class="pre">await</span> <span class="pre">req.content</span></code> after a
multipart parse raises a <code class="docutils literal notranslate"><span class="pre">RuntimeError</span></code> (previously it returned the full
buffered body). If a handler needs both the raw bytes and the parsed form,
await <code class="docutils literal notranslate"><span class="pre">req.content</span></code> <em>first</em> — parsing then falls back to the buffered
body, exactly like 8.x. Handlers using <code class="docutils literal notranslate"><span class="pre">File()</span></code>/<code class="docutils literal notranslate"><span class="pre">Form()</span></code> markers have
the form parsed before they run, so the raw multipart body is not available
to them at all (this matches Starlette and FastAPI).</p></li>
<li><p><strong>Text fields share the streaming parser’s limits.</strong> <code class="docutils literal notranslate"><span class="pre">req.media(&quot;form&quot;)</span></code>
on a multipart body now goes through the same parse as <code class="docutils literal notranslate"><span class="pre">media(&quot;files&quot;)</span></code>:
at most 1000 parts and 1 MB per text field (oversized or malformed bodies
get a <code class="docutils literal notranslate"><span class="pre">400</span></code>). Text fields with invalid UTF-8 are now decoded lossily
instead of being silently dropped.</p></li>
</ul>
</section>
<section id="request-bodies-are-capped-at-100-mib-by-default">
<h2>Request bodies are capped at 100 MiB by default<a class="headerlink" href="#request-bodies-are-capped-at-100-mib-by-default" title="Link to this heading"></a></h2>
<p><code class="docutils literal notranslate"><span class="pre">max_request_size</span></code> now defaults to 100 MiB instead of unlimited; larger
bodies get a <code class="docutils literal notranslate"><span class="pre">413</span></code>. The cap is enforced chunk-by-chunk on the wire, and
since multipart uploads spool to disk, raising it does not raise memory
use:</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">5</span> <span class="o">*</span> <span class="mi">1024</span><span class="o">**</span><span class="mi">3</span><span class="p">)</span> <span class="c1"># allow 5 GiB uploads</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"># pre-9.0 unlimited</span>
</pre></div>
</div>
</section>
<section id="rate-limit-errors-use-problem-details">
<h2>Rate-limit errors use Problem Details<a class="headerlink" href="#rate-limit-errors-use-problem-details" title="Link to this heading"></a></h2>
<p>With the default <code class="docutils literal notranslate"><span class="pre">API(problem_details=True)</span></code>, the rate limiter’s
<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> and fail-closed-backend <code class="docutils literal notranslate"><span class="pre">503</span> <span class="pre">Service</span>
<span class="pre">Unavailable</span></code> responses now use the same RFC 9457
<code class="docutils literal notranslate"><span class="pre">application/problem+json</span></code> envelope as framework-generated errors:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">{</span><span class="s2">&quot;type&quot;</span><span class="p">:</span> <span class="s2">&quot;about:blank&quot;</span><span class="p">,</span> <span class="s2">&quot;title&quot;</span><span class="p">:</span> <span class="s2">&quot;Too Many Requests&quot;</span><span class="p">,</span>
<span class="s2">&quot;status&quot;</span><span class="p">:</span> <span class="mi">429</span><span class="p">,</span> <span class="s2">&quot;detail&quot;</span><span class="p">:</span> <span class="s2">&quot;Rate limit exceeded.&quot;</span><span class="p">}</span>
</pre></div>
</div>
<p>Previously the body was <code class="docutils literal notranslate"><span class="pre">{&quot;error&quot;:</span> <span class="pre">&quot;rate</span> <span class="pre">limit</span> <span class="pre">exceeded&quot;}</span></code> — clients that
parse the <code class="docutils literal notranslate"><span class="pre">error</span></code> key need updating (or key off the status code and
<code class="docutils literal notranslate"><span class="pre">Retry-After</span></code>/<code class="docutils literal notranslate"><span class="pre">X-RateLimit-*</span></code> headers, which are unchanged). To keep the
legacy body shape while migrating, pass <code class="docutils literal notranslate"><span class="pre">API(problem_details=False)</span></code>,
which also keeps the legacy shape for all framework errors.</p>
</section>
<section id="trust-proxy-headers-now-rewrites-the-connection-scope">
<h2><code class="docutils literal notranslate"><span class="pre">trust_proxy_headers</span></code> now rewrites the connection scope<a class="headerlink" href="#trust-proxy-headers-now-rewrites-the-connection-scope" title="Link to this heading"></a></h2>
<p>In 8.x, <code class="docutils literal notranslate"><span class="pre">API(trust_proxy_headers=True)</span></code> only changed the client IP recorded
by <code class="docutils literal notranslate"><span class="pre">enable_logging</span></code>. It now honors the full set of forwarding headers —
RFC 7239 <code class="docutils literal notranslate"><span class="pre">Forwarded</span></code>, plus <code class="docutils literal notranslate"><span class="pre">X-Forwarded-Proto</span></code>/<code class="docutils literal notranslate"><span class="pre">-Host</span></code>/<code class="docutils literal notranslate"><span class="pre">-For</span></code> and
<code class="docutils literal notranslate"><span class="pre">X-Real-IP</span></code> — rewriting the request’s scheme, host, and client address for
every layer: HTTPS detection, redirects, URL building, trusted-host
validation, and rate-limit keys. If you enabled the flag purely for logging,
logged IPs are unchanged; the scheme/host handling simply becomes correct
behind your proxy. Leave the flag off (the default) when Responder is
directly exposed.</p>
</section>
<section id="api-session-was-removed">
<h2><code class="docutils literal notranslate"><span class="pre">api.session()</span></code> was removed<a class="headerlink" href="#api-session-was-removed" title="Link to this heading"></a></h2>
<p>The legacy test-client accessor <code class="docutils literal notranslate"><span class="pre">api.session()</span></code> has been removed. Use the
<a class="reference internal" href="api.html#responder.API.requests" title="responder.API.requests"><code class="xref py py-attr docutils literal notranslate"><span class="pre">requests</span></code></a> property instead:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">r</span> <span class="o">=</span> <span class="n">api</span><span class="o">.</span><span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;http://;/hello&quot;</span><span class="p">)</span>
</pre></div>
</div>
<p>If you relied on <code class="docutils literal notranslate"><span class="pre">session(base_url=...)</span></code> for a custom base URL, construct
the client through the supported helper:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">client</span> <span class="o">=</span> <span class="n">api</span><span class="o">.</span><span class="n">test_client</span><span class="p">(</span><span class="n">base_url</span><span class="o">=</span><span class="s2">&quot;http://testserver&quot;</span><span class="p">)</span>
</pre></div>
</div>
</section>
<section id="explicit-port-now-wins-over-port">
<h2>Explicit <code class="docutils literal notranslate"><span class="pre">port=</span></code> now wins over <code class="docutils literal notranslate"><span class="pre">PORT</span></code><a class="headerlink" href="#explicit-port-now-wins-over-port" title="Link to this heading"></a></h2>
<p>When <code class="docutils literal notranslate"><span class="pre">api.run()</span></code> / <code class="docutils literal notranslate"><span class="pre">api.serve()</span></code> receive both an explicit <code class="docutils literal notranslate"><span class="pre">port=</span></code> and
a conflicting <code class="docutils literal notranslate"><span class="pre">PORT</span></code> environment variable, the explicit argument wins:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># PORT=9000 in the environment:</span>
<span class="n">api</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">port</span><span class="o">=</span><span class="mi">8000</span><span class="p">)</span> <span class="c1"># binds 8000</span>
<span class="n">api</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">port</span><span class="o">=</span><span class="mi">9000</span><span class="p">)</span> <span class="c1"># binds 9000</span>
<span class="n">api</span><span class="o">.</span><span class="n">run</span><span class="p">()</span> <span class="c1"># binds 9000</span>
</pre></div>
</div>
<p>To keep environment-first behavior, resolve the environment yourself:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">port</span><span class="o">=</span><span class="nb">int</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;PORT&quot;</span><span class="p">,</span> <span class="mi">8000</span><span class="p">)))</span>
</pre></div>
</div>
<p>or use the legacy compatibility switch while migrating:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">port</span><span class="o">=</span><span class="mi">8000</span><span class="p">,</span> <span class="n">port_precedence</span><span class="o">=</span><span class="s2">&quot;env&quot;</span><span class="p">)</span>
</pre></div>
</div>
</section>
<section id="bare-add-route-static-fallback">
<h2>Bare <code class="docutils literal notranslate"><span class="pre">add_route()</span></code> static fallback<a class="headerlink" href="#bare-add-route-static-fallback" title="Link to this heading"></a></h2>
<p>Calling <code class="docutils literal notranslate"><span class="pre">api.add_route(route)</span></code> with no endpoint now raises by default
instead of implicitly registering a <em>default</em> route that serves
<code class="docutils literal notranslate"><span class="pre">static/index.html</span></code> for every unmatched request. Pass an endpoint
explicitly instead:</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="p">(</span><span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="s2">&quot;static&quot;</span><span class="p">)</span> <span class="o">/</span> <span class="s2">&quot;index.html&quot;</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">&quot;/&quot;</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>
<p>Static <em>assets</em> are unaffected: the <code class="docutils literal notranslate"><span class="pre">static_dir</span></code> / <code class="docutils literal notranslate"><span class="pre">static_route</span></code> mount
keeps working as-is.</p>
<p>If you need the old fallback while migrating, opt in explicitly:</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">implicit_static_fallback</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</pre></div>
</div>
</section>
<section id="decimal-serializes-as-a-json-string">
<h2><code class="docutils literal notranslate"><span class="pre">Decimal</span></code> serializes as a JSON string<a class="headerlink" href="#decimal-serializes-as-a-json-string" title="Link to this heading"></a></h2>
<p>Assigning a bare <code class="xref py py-class docutils literal notranslate"><span class="pre">decimal.Decimal</span></code> to <code class="docutils literal notranslate"><span class="pre">resp.media</span></code> now serializes it
as a JSON string, preserving precision.</p>
<p>Choose a representation explicitly when your API contract needs a number:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></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">&quot;price&quot;</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">total</span><span class="p">)}</span> <span class="c1"># exact, v9&#39;s default</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">&quot;price&quot;</span><span class="p">:</span> <span class="nb">float</span><span class="p">(</span><span class="n">total</span><span class="p">)}</span> <span class="c1"># lossy, JSON number</span>
</pre></div>
</div>
<p>or keep floats everywhere with the compatibility flag or a custom encoder:</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">json_decimal</span><span class="o">=</span><span class="s2">&quot;float&quot;</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">encoder</span><span class="p">(</span><span class="n">obj</span><span class="p">):</span>
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">decimal</span><span class="o">.</span><span class="n">Decimal</span><span class="p">):</span>
<span class="k">return</span> <span class="nb">float</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span>
<span class="k">raise</span> <span class="ne">TypeError</span> <span class="c1"># fall back to the built-in conversions</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">encoder</span><span class="o">=</span><span class="n">encoder</span><span class="p">)</span>
</pre></div>
</div>
</section>
<section id="graphql-400-with-partial-data">
<h2>GraphQL: <code class="docutils literal notranslate"><span class="pre">400</span></code> with partial data<a class="headerlink" href="#graphql-400-with-partial-data" title="Link to this heading"></a></h2>
<p>The GraphQL extension now returns HTTP <code class="docutils literal notranslate"><span class="pre">200</span></code> for execution results that
contain both <code class="docutils literal notranslate"><span class="pre">data</span></code> and <code class="docutils literal notranslate"><span class="pre">errors</span></code> (a <em>partial</em> result, e.g. one resolver
failed while others succeeded). Per the <a class="reference external" href="https://graphql.github.io/graphql-over-http/">GraphQL-over-HTTP specification</a>, these are well-formed
GraphQL responses.</p>
<p>Requests that produce <em>no</em> data (validation or request errors) keep their
<code class="docutils literal notranslate"><span class="pre">400</span></code>. Inspect the <code class="docutils literal notranslate"><span class="pre">errors</span></code> key of the response body instead of relying
on the status code alone:</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">&quot;/graph&quot;</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">result</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s2">&quot;/graph&quot;</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="p">{</span><span class="s2">&quot;query&quot;</span><span class="p">:</span> <span class="n">query</span><span class="p">})</span><span class="o">.</span><span class="n">json</span><span class="p">()</span>
<span class="k">if</span> <span class="n">result</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;errors&quot;</span><span class="p">):</span>
<span class="o">...</span> <span class="c1"># handle errors, regardless of HTTP status</span>
</pre></div>
</div>
<p>To preserve the legacy partial-data status while migrating, pass:</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">&quot;/graph&quot;</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">partial_data_status</span><span class="o">=</span><span class="mi">400</span><span class="p">)</span>
</pre></div>
</div>
</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>
<li class="toctree-l1"><a class="reference internal" href="quickstart.html">Quick Start</a></li>
<li class="toctree-l1"><a class="reference internal" href="tour.html">Feature Tour</a></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 class="current">
<li class="toctree-l1"><a class="reference internal" href="changes.html">Changelog</a></li>
<li class="toctree-l1 current"><a class="current reference internal" href="#">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="changes.html" title="previous chapter">Changelog</a></li>
<li>Next: <a href="migration-v8.html" title="next chapter">Migrating to v8</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">
&#169;2018-2026, Kenneth Reitz.
|
<a href="_sources/migration-v9.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>