<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: caraxesthebloodwyrm02</title>
    <description>The latest articles on DEV Community by caraxesthebloodwyrm02 (@caraxesthebloodwyrm02).</description>
    <link>https://dev.to/caraxesthebloodwyrm02</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3829672%2Fc51f45a4-5a78-4979-b155-0397413f8276.png</url>
      <title>DEV Community: caraxesthebloodwyrm02</title>
      <link>https://dev.to/caraxesthebloodwyrm02</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/caraxesthebloodwyrm02"/>
    <language>en</language>
    <item>
      <title>Your Python API Calls Will Fail. Here's How to Handle It.</title>
      <dc:creator>caraxesthebloodwyrm02</dc:creator>
      <pubDate>Tue, 17 Mar 2026 15:14:49 +0000</pubDate>
      <link>https://dev.to/caraxesthebloodwyrm02/your-python-api-calls-will-fail-heres-how-to-handle-it-1pk9</link>
      <guid>https://dev.to/caraxesthebloodwyrm02/your-python-api-calls-will-fail-heres-how-to-handle-it-1pk9</guid>
      <description>&lt;p&gt;Every HTTP call you make to a third-party API will eventually fail. The endpoint will time out. The service will rate-limit you. The server will return a 503 for twelve minutes on a Tuesday afternoon.&lt;/p&gt;

&lt;p&gt;Most Python codebases handle this with a bare &lt;code&gt;try/except&lt;/code&gt; and a hope. That works until it doesn't — and when it doesn't, you get cascading failures, silent data loss, and a service that goes down without telling you why.&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://pypi.org/project/GRID-APIGUARD/" rel="noopener noreferrer"&gt;APIGuard&lt;/a&gt; to fix this. It's a small Python library (905 lines of source) that gives you three production resilience patterns — without pulling in a heavy framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three Patterns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Token Bucket Rate Limiting
&lt;/h3&gt;

&lt;p&gt;Rate limits are contracts. Break them and the API cuts you off. A token bucket enforces the contract on your side before you hit the remote server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;apiguard&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TokenBucket&lt;/span&gt;

&lt;span class="c1"&gt;# 100 requests allowed, refills at 10/second
&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TokenBucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;refill_rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;acquire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.example.com/data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Back off — you're at the limit
&lt;/span&gt;    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The implementation uses &lt;code&gt;time.monotonic()&lt;/code&gt; for sub-second precision and is thread-safe. No background threads, no timers — just math.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Circuit Breaker
&lt;/h3&gt;

&lt;p&gt;A circuit breaker tracks failures. After enough consecutive failures, it stops sending requests entirely — "opening" the circuit. This prevents your app from hammering a dead service and wasting resources (yours and theirs).&lt;/p&gt;

&lt;p&gt;It follows a three-state machine: &lt;strong&gt;CLOSED&lt;/strong&gt; (normal) → &lt;strong&gt;OPEN&lt;/strong&gt; (blocking requests) → &lt;strong&gt;HALF_OPEN&lt;/strong&gt; (testing recovery) → back to CLOSED.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;apiguard&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CircuitBreaker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CircuitOpenError&lt;/span&gt;

&lt;span class="n"&gt;breaker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CircuitBreaker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;failure_threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;# Open after 5 failures
&lt;/span&gt;    &lt;span class="n"&gt;recovery_timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;60.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;# Try again after 60 seconds
&lt;/span&gt;    &lt;span class="n"&gt;success_threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;      &lt;span class="c1"&gt;# Need 2 successes to fully close
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;breaker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call_external_api&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;CircuitOpenError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Circuit is open — don't even try
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;cached_fallback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key detail: the &lt;code&gt;success_threshold&lt;/code&gt; in HALF_OPEN state. One lucky success doesn't mean the service is back. You need consecutive successes before the breaker fully closes again.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Retry with Exponential Backoff
&lt;/h3&gt;

&lt;p&gt;Retry sounds simple. It isn't. Naive retry (same delay, unlimited attempts) turns your client into a DDoS tool. Exponential backoff with jitter spreads retry load and respects &lt;code&gt;Retry-After&lt;/code&gt; headers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;apiguard&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RetryHandler&lt;/span&gt;

&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RetryHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;max_retries&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;base_delay&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;jitter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                              &lt;span class="c1"&gt;# Randomize delay by up to 50%
&lt;/span&gt;    &lt;span class="n"&gt;retryable_status_codes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;502&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;504&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/endpoint&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retryable_status_codes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# Respects Retry-After header
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The delay formula: &lt;code&gt;base_delay * 2^(attempt - 1) * (1 + random * jitter)&lt;/code&gt;. Third attempt at base_delay=1.0 waits ~4 seconds plus jitter. This is what RFC 7231 recommends.&lt;/p&gt;

&lt;h2&gt;
  
  
  Composing All Three
&lt;/h2&gt;

&lt;p&gt;The patterns work independently, but the real value is composition. APIGuard ships a client that wires all three together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;apiguard.adapters.httpx&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AsyncRateLimitedClient&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;AsyncRateLimitedClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;# 50 requests in the bucket
&lt;/span&gt;    &lt;span class="n"&gt;refill_rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;# Refill at 5/second
&lt;/span&gt;    &lt;span class="n"&gt;max_retries&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;failure_threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;recovery_timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;30.0&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.stripe.com/v1/charges&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One client. Rate limiting, retry, and circuit breaking. The request either succeeds, retries intelligently, or fails fast with a clear error. No silent failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Integration: Replacing 623 Lines
&lt;/h2&gt;

&lt;p&gt;I built APIGuard while working on &lt;a href="https://github.com/GRID-INTELLIGENCE/GRID" rel="noopener noreferrer"&gt;GRID&lt;/a&gt;, a 190k-line Python AI framework. GRID had a custom circuit breaker implementation — 623 lines of hand-rolled state management, no rate limiting, no retry coordination.&lt;/p&gt;

&lt;p&gt;After integrating APIGuard, that 623-line file became a 40-line adapter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# GRID's FastAPI middleware using APIGuard
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;apiguard&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CircuitBreaker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BucketRegistry&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;APIGuardCircuitBreakerMiddleware&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;failure_threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recovery_timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;60.0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;breaker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CircuitBreaker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;failure_threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;failure_threshold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;recovery_timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;recovery_timeout&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__call__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;breaker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;app&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same behavior. Fewer lines. Tested independently (106 tests, 100% coverage) instead of tangled into the framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned Building It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Thread safety matters even in async code.&lt;/strong&gt; The token bucket and circuit breaker both use locks. In production, you'll have concurrent requests from multiple coroutines hitting the same bucket. Without locks, you get race conditions that silently over-consume your rate limit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Jitter is not optional.&lt;/strong&gt; Without jitter, all your retries fire at the same time (the "thundering herd" problem). Even 0.3 jitter factor spreads the load enough to matter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Retry-After&lt;/code&gt; headers are a gift.&lt;/strong&gt; Most rate-limited APIs tell you exactly when to come back. Ignoring this header and using your own backoff schedule means you'll either wait too long or retry too soon. APIGuard checks for it on every retry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep the dependency surface small.&lt;/strong&gt; APIGuard's only runtime dependency is &lt;code&gt;httpx&lt;/code&gt;. No framework opinions, no configuration files, no dependency tree. &lt;code&gt;pip install grid-apiguard&lt;/code&gt; and you're done.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Numbers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Source lines&lt;/td&gt;
&lt;td&gt;905&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test functions&lt;/td&gt;
&lt;td&gt;106&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test coverage&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Core patterns&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime dependencies&lt;/td&gt;
&lt;td&gt;1 (httpx)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python versions&lt;/td&gt;
&lt;td&gt;3.11, 3.12, 3.13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  When You Should Use This
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You call third-party APIs and don't have resilience patterns in place&lt;/li&gt;
&lt;li&gt;Your retry logic is a &lt;code&gt;while True&lt;/code&gt; loop with &lt;code&gt;time.sleep(1)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;You've been rate-limited and your solution was "catch the 429 and retry"&lt;/li&gt;
&lt;li&gt;You want circuit breaking without importing a framework&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When You Shouldn't
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You're already using Tenacity or Stamina and they work fine&lt;/li&gt;
&lt;li&gt;Your API calls are internal, low-latency, and rarely fail&lt;/li&gt;
&lt;li&gt;You need distributed circuit breaking across multiple instances (APIGuard is single-process)&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;APIGuard is on PyPI: &lt;a href="https://pypi.org/project/GRID-APIGUARD/" rel="noopener noreferrer"&gt;&lt;code&gt;pip install grid-apiguard&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source is available on request. Built solo, tested end-to-end, used in production in a 190k-line framework.&lt;/p&gt;

&lt;p&gt;If you need API integrations built with this level of failure handling, I do this work professionally — &lt;a href="https://www.upwork.com/freelancers/~019b56c816dfaf0038" rel="noopener noreferrer"&gt;Upwork profile&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>api</category>
      <category>resilience</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
