Skip to content

Commit 49343e0

Browse files
author
committed
Deployed f203821 with MkDocs version: 1.6.1
1 parent 78a9c0f commit 49343e0

5 files changed

Lines changed: 33 additions & 15 deletions

File tree

dev/contributing/index.html

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -852,7 +852,7 @@ <h2 id="quick-start">Quick start<a class="headerlink" href="#quick-start" title=
852852
<div class="highlight"><pre><span></span><code><a id="__codelineno-0-1" name="__codelineno-0-1" href="#__codelineno-0-1"></a>git<span class="w"> </span>clone<span class="w"> </span>https://github.com/modern-python/httpware.git
853853
<a id="__codelineno-0-2" name="__codelineno-0-2" href="#__codelineno-0-2"></a><span class="nb">cd</span><span class="w"> </span>httpware
854854
<a id="__codelineno-0-3" name="__codelineno-0-3" href="#__codelineno-0-3"></a>just<span class="w"> </span>install<span class="w"> </span><span class="c1"># uv lock --upgrade &amp;&amp; uv sync --all-extras --frozen --group lint</span>
855-
<a id="__codelineno-0-4" name="__codelineno-0-4" href="#__codelineno-0-4"></a>just<span class="w"> </span>lint<span class="w"> </span><span class="c1"># ruff format + ruff check + ty check</span>
855+
<a id="__codelineno-0-4" name="__codelineno-0-4" href="#__codelineno-0-4"></a>just<span class="w"> </span>lint<span class="w"> </span><span class="c1"># eof-fixer + ruff format + ruff check + ty check</span>
856856
<a id="__codelineno-0-5" name="__codelineno-0-5" href="#__codelineno-0-5"></a>just<span class="w"> </span><span class="nb">test</span><span class="w"> </span><span class="c1"># pytest with coverage</span>
857857
</code></pre></div>
858858
<h2 id="development-workflow">Development workflow<a class="headerlink" href="#development-workflow" title="Permanent link">&para;</a></h2>
@@ -871,7 +871,9 @@ <h2 id="code-style">Code style<a class="headerlink" href="#code-style" title="Pe
871871
<li>Module docstrings are required; per-method docstrings only when types alone are insufficient.</li>
872872
</ul>
873873
<h2 id="architecture-invariants">Architecture invariants<a class="headerlink" href="#architecture-invariants" title="Permanent link">&para;</a></h2>
874-
<p>These are enforced by CI grep gates. Do not break them in pull requests:</p>
874+
<p>These are project invariants. The CI lint pass (<code>just lint-ci</code><code>ruff</code> + <code>ty</code>)
875+
catches what the linters can see (e.g. <code>print()</code> via ruff <code>T20</code>); the rest are
876+
enforced in code review. Do not break them in pull requests:</p>
875877
<ul>
876878
<li>No <code>httpx2._*</code> (private API) usage anywhere in the library.</li>
877879
<li>No <code>from __future__ import annotations</code>.</li>

middleware/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -971,7 +971,7 @@ <h2 id="worked-example-request-id-propagation">Worked example: request-ID propag
971971
<a id="__codelineno-2-40" name="__codelineno-2-40" href="#__codelineno-2-40"></a> <span class="p">)</span> <span class="k">as</span> <span class="n">client</span><span class="p">:</span>
972972
<a id="__codelineno-2-41" name="__codelineno-2-41" href="#__codelineno-2-41"></a> <span class="k">await</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;/users/1&quot;</span><span class="p">)</span>
973973
</code></pre></div>
974-
<p>A note on logger names: the example logs under <code>myapp.request_id</code>, NOT under <code>httpware.*</code>. The <code>httpware.*</code> namespace is reserved for events emitted by the library itself (see <a href="../#observability">Observability</a><code>httpware.retry</code> and <code>httpware.bulkhead</code> are stable contracts). Consumer middleware should use your application's own logger namespace.</p>
974+
<p>A note on logger names: the example logs under <code>myapp.request_id</code>, NOT under <code>httpware.*</code>. The <code>httpware.*</code> namespace is reserved for events emitted by the library itself (see <a href="../#observability">Observability</a><code>httpware.retry</code>, <code>httpware.bulkhead</code>, <code>httpware.circuit_breaker</code>, and <code>httpware.timeout</code> are stable contracts). Consumer middleware should use your application's own logger namespace.</p>
975975
<p>The example pairs naturally with the 0.6.0 observability events: a <code>httpware.retry</code> <code>retry.giving_up</code> log record carries a <code>url</code> attribute, and your <code>RequestIdMiddleware</code> set an <code>X-Request-Id</code> for that same call. Correlate the two in your log aggregator and you have end-to-end visibility from "this user's request" to "we gave up after N retries."</p>
976976
<h2 id="when-not-to-write-a-middleware">When NOT to write a middleware<a class="headerlink" href="#when-not-to-write-a-middleware" title="Permanent link">&para;</a></h2>
977977
<ul>

recipes/modern-di/index.html

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -844,16 +844,26 @@ <h2 id="the-minimal-wire-up">The minimal wire-up<a class="headerlink" href="#the
844844
<a id="__codelineno-0-13" name="__codelineno-0-13" href="#__codelineno-0-13"></a>
845845
<a id="__codelineno-0-14" name="__codelineno-0-14" href="#__codelineno-0-14"></a>
846846
<a id="__codelineno-0-15" name="__codelineno-0-15" href="#__codelineno-0-15"></a><span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
847-
<a id="__codelineno-0-16" name="__codelineno-0-16" href="#__codelineno-0-16"></a> <span class="k">async</span> <span class="k">with</span> <span class="n">Container</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="n">Scope</span><span class="o">.</span><span class="n">APP</span><span class="p">,</span> <span class="n">groups</span><span class="o">=</span><span class="p">[</span><span class="n">ServiceClients</span><span class="p">])</span> <span class="k">as</span> <span class="n">container</span><span class="p">:</span>
848-
<a id="__codelineno-0-17" name="__codelineno-0-17" href="#__codelineno-0-17"></a> <span class="n">client</span> <span class="o">=</span> <span class="k">await</span> <span class="n">container</span><span class="o">.</span><span class="n">resolve</span><span class="p">(</span><span class="n">AsyncClient</span><span class="p">)</span>
849-
<a id="__codelineno-0-18" name="__codelineno-0-18" href="#__codelineno-0-18"></a> <span class="n">response</span> <span class="o">=</span> <span class="k">await</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;/users/1&quot;</span><span class="p">)</span>
850-
<a id="__codelineno-0-19" name="__codelineno-0-19" href="#__codelineno-0-19"></a> <span class="nb">print</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">status_code</span><span class="p">)</span>
847+
<a id="__codelineno-0-16" name="__codelineno-0-16" href="#__codelineno-0-16"></a> <span class="n">container</span> <span class="o">=</span> <span class="n">Container</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="n">Scope</span><span class="o">.</span><span class="n">APP</span><span class="p">,</span> <span class="n">groups</span><span class="o">=</span><span class="p">[</span><span class="n">ServiceClients</span><span class="p">])</span>
848+
<a id="__codelineno-0-17" name="__codelineno-0-17" href="#__codelineno-0-17"></a> <span class="k">try</span><span class="p">:</span>
849+
<a id="__codelineno-0-18" name="__codelineno-0-18" href="#__codelineno-0-18"></a> <span class="n">client</span> <span class="o">=</span> <span class="n">container</span><span class="o">.</span><span class="n">resolve</span><span class="p">(</span><span class="n">AsyncClient</span><span class="p">)</span>
850+
<a id="__codelineno-0-19" name="__codelineno-0-19" href="#__codelineno-0-19"></a> <span class="n">response</span> <span class="o">=</span> <span class="k">await</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;/users/1&quot;</span><span class="p">)</span>
851+
<a id="__codelineno-0-20" name="__codelineno-0-20" href="#__codelineno-0-20"></a> <span class="nb">print</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">status_code</span><span class="p">)</span>
852+
<a id="__codelineno-0-21" name="__codelineno-0-21" href="#__codelineno-0-21"></a> <span class="k">finally</span><span class="p">:</span>
853+
<a id="__codelineno-0-22" name="__codelineno-0-22" href="#__codelineno-0-22"></a> <span class="k">await</span> <span class="n">container</span><span class="o">.</span><span class="n">close_async</span><span class="p">()</span> <span class="c1"># runs the AsyncClient.aclose finalizer</span>
851854
</code></pre></div>
855+
<blockquote>
856+
<p><strong>modern-di 2.x.</strong> Resolution is sync — <code>container.resolve(...)</code>, no <code>await</code>.
857+
The root container is created plainly and torn down with <code>await
858+
container.close_async()</code> (the <code>async with</code> form is for
859+
<code>build_child_container(...)</code>, not the root). On modern-di 1.x, resolution was
860+
awaited; pin accordingly if you are still on 1.x.</p>
861+
</blockquote>
852862
<p>Breaking that down:</p>
853863
<ul>
854864
<li><strong><code>Scope.APP</code></strong> ties the client to the application lifetime. One client per process; the connection pool is reused across all calls.</li>
855865
<li><strong><code>cache_settings=providers.CacheSettings(...)</code></strong> is what makes the provider a singleton. Without it, <code>Factory</code> returns a fresh <code>AsyncClient</code> on every resolve.</li>
856-
<li><strong><code>finalizer=AsyncClient.aclose</code></strong> is the unbound async method. <code>modern-di</code> detects it as a coroutine function (via <code>inspect.iscoroutinefunction</code>) and <code>await</code>s it on container teardown.</li>
866+
<li><strong><code>finalizer=AsyncClient.aclose</code></strong> is the unbound async method. <code>modern-di</code> detects the async finalizer and <code>await</code>s it on container teardown (here, on <code>close_async()</code>).</li>
857867
</ul>
858868
<p>A common first instinct here is <code>finalizer=lambda c: c.aclose()</code>. <strong>That does not work</strong> — the lambda itself is sync, so <code>modern-di</code> calls it synchronously and discards the returned coroutine unawaited. The underlying connection pool leaks. Pass the unbound async method directly, or wrap in <code>async def</code>.</p>
859869
<p>See the <a href="https://modern-di.modern-python.org/providers/factories/"><code>modern-di</code> factories docs</a> for the broader <code>CacheSettings</code> story (scopes, <code>clear_cache</code>, sync vs async finalizers).</p>
@@ -909,10 +919,13 @@ <h2 id="fix-one-wrapper-subclass-per-backend">Fix: one wrapper subclass per back
909919
<a id="__codelineno-2-27" name="__codelineno-2-27" href="#__codelineno-2-27"></a>
910920
<a id="__codelineno-2-28" name="__codelineno-2-28" href="#__codelineno-2-28"></a>
911921
<a id="__codelineno-2-29" name="__codelineno-2-29" href="#__codelineno-2-29"></a><span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
912-
<a id="__codelineno-2-30" name="__codelineno-2-30" href="#__codelineno-2-30"></a> <span class="k">async</span> <span class="k">with</span> <span class="n">Container</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="n">Scope</span><span class="o">.</span><span class="n">APP</span><span class="p">,</span> <span class="n">groups</span><span class="o">=</span><span class="p">[</span><span class="n">ServiceClients</span><span class="p">])</span> <span class="k">as</span> <span class="n">container</span><span class="p">:</span>
913-
<a id="__codelineno-2-31" name="__codelineno-2-31" href="#__codelineno-2-31"></a> <span class="n">users</span> <span class="o">=</span> <span class="k">await</span> <span class="n">container</span><span class="o">.</span><span class="n">resolve</span><span class="p">(</span><span class="n">UserApi</span><span class="p">)</span>
914-
<a id="__codelineno-2-32" name="__codelineno-2-32" href="#__codelineno-2-32"></a> <span class="n">billing</span> <span class="o">=</span> <span class="k">await</span> <span class="n">container</span><span class="o">.</span><span class="n">resolve</span><span class="p">(</span><span class="n">BillingApi</span><span class="p">)</span>
915-
<a id="__codelineno-2-33" name="__codelineno-2-33" href="#__codelineno-2-33"></a> <span class="c1"># ... use them</span>
922+
<a id="__codelineno-2-30" name="__codelineno-2-30" href="#__codelineno-2-30"></a> <span class="n">container</span> <span class="o">=</span> <span class="n">Container</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="n">Scope</span><span class="o">.</span><span class="n">APP</span><span class="p">,</span> <span class="n">groups</span><span class="o">=</span><span class="p">[</span><span class="n">ServiceClients</span><span class="p">])</span>
923+
<a id="__codelineno-2-31" name="__codelineno-2-31" href="#__codelineno-2-31"></a> <span class="k">try</span><span class="p">:</span>
924+
<a id="__codelineno-2-32" name="__codelineno-2-32" href="#__codelineno-2-32"></a> <span class="n">users</span> <span class="o">=</span> <span class="n">container</span><span class="o">.</span><span class="n">resolve</span><span class="p">(</span><span class="n">UserApi</span><span class="p">)</span>
925+
<a id="__codelineno-2-33" name="__codelineno-2-33" href="#__codelineno-2-33"></a> <span class="n">billing</span> <span class="o">=</span> <span class="n">container</span><span class="o">.</span><span class="n">resolve</span><span class="p">(</span><span class="n">BillingApi</span><span class="p">)</span>
926+
<a id="__codelineno-2-34" name="__codelineno-2-34" href="#__codelineno-2-34"></a> <span class="c1"># ... use them</span>
927+
<a id="__codelineno-2-35" name="__codelineno-2-35" href="#__codelineno-2-35"></a> <span class="k">finally</span><span class="p">:</span>
928+
<a id="__codelineno-2-36" name="__codelineno-2-36" href="#__codelineno-2-36"></a> <span class="k">await</span> <span class="n">container</span><span class="o">.</span><span class="n">close_async</span><span class="p">()</span>
916929
</code></pre></div>
917930
<p>A couple of notes:</p>
918931
<ul>

resilience/index.html

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1514,8 +1514,11 @@ <h2 id="retrybudget"><code>RetryBudget</code><a class="headerlink" href="#retryb
15141514
</tbody>
15151515
</table>
15161516
<h3 id="the-token-bucket-formula">The token-bucket formula<a class="headerlink" href="#the-token-bucket-formula" title="Permanent link">&para;</a></h3>
1517-
<div class="highlight"><pre><span></span><code><a id="__codelineno-3-1" name="__codelineno-3-1" href="#__codelineno-3-1"></a>ceiling = int(len(deposits_in_window) * percent_can_retry) + int(min_retries_per_sec * ttl)
1517+
<div class="highlight"><pre><span></span><code><a id="__codelineno-3-1" name="__codelineno-3-1" href="#__codelineno-3-1"></a>ceiling = ceil(len(deposits_in_window) * percent_can_retry) + int(min_retries_per_sec * ttl)
15181518
</code></pre></div>
1519+
<p>The percent term rounds <strong>up</strong> (<code>math.ceil</code>), so even a handful of recent
1520+
deposits permits at least one retry above the floor; the floor term truncates
1521+
(<code>int</code>).</p>
15191522
<p>A withdrawal fails when <code>len(withdrawn_in_window) &gt;= ceiling</code>.</p>
15201523
<h3 id="why-a-floor-matters">Why a floor matters<a class="headerlink" href="#why-a-floor-matters" title="Permanent link">&para;</a></h3>
15211524
<p>If the deposit rate is zero (no traffic yet), the percent term is zero — without the floor, the very first retry would be refused. The floor lets small-traffic clients still retry on isolated failures; high-traffic clients are dominated by the percent term and the floor becomes irrelevant.</p>
@@ -1702,7 +1705,7 @@ <h2 id="asynctimeout"><code>AsyncTimeout</code><a class="headerlink" href="#asyn
17021705
<tr>
17031706
<td><code>timeout</code></td>
17041707
<td><strong>REQUIRED</strong></td>
1705-
<td>Overall deadline in seconds. Must be <code>&gt; 0</code>; <code>≤0</code> raises <code>ValueError</code>.</td>
1708+
<td>Overall deadline in seconds. Must be a finite number <code>&gt; 0</code>; a non-finite (<code>inf</code>/<code>nan</code>) or <code>≤0</code> value raises <code>ValueError</code>.</td>
17061709
</tr>
17071710
</tbody>
17081711
</table>

search/search_index.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)