You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
<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 <ahref="../#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 <ahref="../#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>
975
975
<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>
976
976
<h2id="when-not-to-write-a-middleware">When NOT to write a middleware<aclass="headerlink" href="#when-not-to-write-a-middleware" title="Permanent link">¶</a></h2>
<aid="__codelineno-0-22" name="__codelineno-0-22" href="#__codelineno-0-22"></a><spanclass="k">await</span><spanclass="n">container</span><spanclass="o">.</span><spanclass="n">close_async</span><spanclass="p">()</span><spanclass="c1"># runs the AsyncClient.aclose finalizer</span>
851
854
</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>
852
862
<p>Breaking that down:</p>
853
863
<ul>
854
864
<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>
855
865
<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>
857
867
</ul>
858
868
<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>
859
869
<p>See the <ahref="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
<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>
1519
1522
<p>A withdrawal fails when <code>len(withdrawn_in_window) >= ceiling</code>.</p>
1520
1523
<h3id="why-a-floor-matters">Why a floor matters<aclass="headerlink" href="#why-a-floor-matters" title="Permanent link">¶</a></h3>
1521
1524
<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>
<td>Overall deadline in seconds. Must be <code>> 0</code>; <code>≤0</code> raises <code>ValueError</code>.</td>
1708
+
<td>Overall deadline in seconds. Must be a finite number <code>> 0</code>; a non-finite (<code>inf</code>/<code>nan</code>) or <code>≤0</code> value raises <code>ValueError</code>.</td>
0 commit comments