<?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: Aritro Saha</title>
    <description>The latest articles on DEV Community by Aritro Saha (@halcyonpast).</description>
    <link>https://dev.to/halcyonpast</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2195560%2F37a06d82-94fb-4a25-a30f-cb7cc6adee5c.jpeg</url>
      <title>DEV Community: Aritro Saha</title>
      <link>https://dev.to/halcyonpast</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/halcyonpast"/>
    <language>en</language>
    <item>
      <title>Your overnight script died at item 5,247. Again. I built a decorator so it never does.</title>
      <dc:creator>Aritro Saha</dc:creator>
      <pubDate>Mon, 31 Aug 2026 09:09:18 +0000</pubDate>
      <link>https://dev.to/halcyonpast/your-overnight-script-died-at-item-5247-again-i-built-a-decorator-so-it-never-does-7d1</link>
      <guid>https://dev.to/halcyonpast/your-overnight-script-died-at-item-5247-again-i-built-a-decorator-so-it-never-does-7d1</guid>
      <description>&lt;p&gt;You know this loop. You've written it a hundred times:&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You kick it off against 10,000 records — CSV rows, URLs to scrape, images to resize — and go to bed. In the morning you find it crashed at &lt;strong&gt;item #5,247&lt;/strong&gt; on something malformed you never anticipated. 5,246 items of finished work, gone. Three hours, gone.&lt;/p&gt;

&lt;p&gt;So you patch the bug for that one weird row and rerun from the start. Three hours later it dies again — at item #7,913, for a &lt;em&gt;different&lt;/em&gt; reason.&lt;/p&gt;

&lt;p&gt;I got tired of this cycle, so I built &lt;a href="https://github.com/halcyon-past/quarantine" rel="noopener noreferrer"&gt;&lt;strong&gt;quarantine&lt;/strong&gt;&lt;/a&gt;. It's one decorator:&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;quarantine&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;quarantine&lt;/span&gt;

&lt;span class="nd"&gt;@quarantine&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;  &lt;span class="c1"&gt;# your normal code, unchanged
&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole integration. Bad items no longer kill your job. Each failure gets saved to a &lt;code&gt;.quarantine/&lt;/code&gt; folder — with its full traceback &lt;strong&gt;and the exact input that caused it&lt;/strong&gt; — and your loop keeps running. At the end:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;✓ 9,996 processed · ✗ 4 quarantined → .quarantine/  (run `quarantine retry` after fixing)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;quarantine-py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg5lszsr2j1dt4mxptqao.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg5lszsr2j1dt4mxptqao.gif" alt="The whole workflow in 25 seconds: the run survives 4 bad rows, quarantine list shows them, quarantine retry recovers them" width="788" height="610"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  "Can't I just use try/except?"
&lt;/h2&gt;

&lt;p&gt;Yes! In fact, try/except is exactly what quarantine uses under the hood. But here's what the hand-rolled version looks like once you make it actually &lt;em&gt;safe&lt;/em&gt;:&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="n"&gt;failed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&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="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# ❌ lost forever if the script dies later
&lt;/span&gt;        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# ❌ traceback gone — good luck debugging tomorrow
&lt;/span&gt;        &lt;span class="c1"&gt;# ❌ how do I re-run JUST these failures after I fix the bug?
&lt;/span&gt;        &lt;span class="c1"&gt;# ❌ what if 500 fail in a row because the API is down — keep going?!
&lt;/span&gt;        &lt;span class="c1"&gt;# ❌ how do I save a weird object (DataFrame row? bytes?) to look at later?
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every ❌ is a real problem you'd end up solving yourself, in every script, forever:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;try/except&lt;/code&gt; by hand&lt;/th&gt;
&lt;th&gt;&lt;code&gt;@quarantine&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Loop survives bad items&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failures survive a crash/restart&lt;/td&gt;
&lt;td&gt;❌ in RAM, gone&lt;/td&gt;
&lt;td&gt;✅ saved to disk instantly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full traceback kept for later&lt;/td&gt;
&lt;td&gt;❌ usually just printed&lt;/td&gt;
&lt;td&gt;✅ stored with the item&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The exact bad input saved&lt;/td&gt;
&lt;td&gt;❌ serialize it yourself&lt;/td&gt;
&lt;td&gt;✅ automatic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Re-run &lt;em&gt;only&lt;/em&gt; the failures&lt;/td&gt;
&lt;td&gt;❌ build it yourself&lt;/td&gt;
&lt;td&gt;✅ &lt;code&gt;quarantine retry&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debug with the real bad input&lt;/td&gt;
&lt;td&gt;❌ archaeology in logs&lt;/td&gt;
&lt;td&gt;✅ &lt;code&gt;quarantine debug 2&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Detects "everything is failing, stop"&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ halts on failure streaks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skips already-known-bad items on rerun&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;quarantine isn't a replacement for try/except. It's the ~200 lines of bookkeeping you'd have to write around it — done correctly, once.&lt;/p&gt;

&lt;p&gt;If you've worked with message queues, you'll recognize the pattern: it's a &lt;strong&gt;dead-letter queue&lt;/strong&gt;, ported from broker infrastructure to a plain Python for-loop. No broker, no server, no config. One decorator.&lt;/p&gt;

&lt;h2&gt;
  
  
  The workflow
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. See what failed:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;quarantine list
  &lt;span class="c"&gt;#  when         function   error                          input preview&lt;/span&gt;
  1  09:14:02     process    ValueError: could not convert  &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;: 8812, &lt;span class="s1"&gt;'price'&lt;/span&gt;: &lt;span class="s1"&gt;'N/A'&lt;/span&gt;, ...&lt;span class="o"&gt;}&lt;/span&gt;
  2  09:31:44     process    KeyError: &lt;span class="s1"&gt;'price'&lt;/span&gt;              &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;: 9107, ...&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Fix your code, then retry only the failures:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;quarantine retry
✓ 3 recovered · ✗ 1 still failing &lt;span class="o"&gt;(&lt;/span&gt;kept &lt;span class="k"&gt;in &lt;/span&gt;quarantine&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No rerunning the 9,996 items that already worked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Debug with the actual bad input:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;quarantine debug 2
&lt;span class="c"&gt;# opens pdb on the frame that raised, with the exact input that failed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the single biggest time-saver for me: you never have to &lt;em&gt;reproduce&lt;/em&gt; the bug. The bug's exact input is sitting on disk, waiting.&lt;/p&gt;

&lt;p&gt;There's also &lt;code&gt;quarantine stats&lt;/code&gt;, &lt;code&gt;quarantine show&lt;/code&gt;, &lt;code&gt;--json&lt;/code&gt; output for piping, and even &lt;code&gt;quarantine ui&lt;/code&gt; for a local web dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  The safety valve
&lt;/h2&gt;

&lt;p&gt;Here's a subtle failure mode of "just keep going": if 50 items fail &lt;strong&gt;in a row&lt;/strong&gt;, that's not bad data — that's your database being down. Quarantining all 10,000 items would be silly. So quarantine ships a circuit breaker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✋ 50 consecutive failures — this looks systemic, not bad data. Halting.
   Last error: ConnectionError: db.internal:5432 refused
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tune it with &lt;code&gt;@quarantine(halt_after=100)&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  No magic — just files
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;.quarantine/&lt;/code&gt; folder is plain files you can inspect yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;.quarantine/&lt;/span&gt;
&lt;span class="s"&gt;├── 0001/&lt;/span&gt;
&lt;span class="s"&gt;│   ├── input.pkl&lt;/span&gt;        &lt;span class="c1"&gt;# the exact item (pickle, JSON fallback)&lt;/span&gt;
&lt;span class="s"&gt;│   ├── input.txt&lt;/span&gt;        &lt;span class="c1"&gt;# human-readable repr&lt;/span&gt;
&lt;span class="s"&gt;│   ├── traceback.txt&lt;/span&gt;    &lt;span class="c1"&gt;# full error, exactly as it would have printed&lt;/span&gt;
&lt;span class="s"&gt;│   └── meta.json&lt;/span&gt;        &lt;span class="c1"&gt;# function, timestamp, attempt count, versions&lt;/span&gt;
&lt;span class="s"&gt;└── index.json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some design decisions I care about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Atomic writes&lt;/strong&gt; — a crash mid-save never corrupts the folder.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redaction before disk&lt;/strong&gt; — &lt;code&gt;redact=["api_key", "password"]&lt;/code&gt; means secrets never touch the filesystem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serialization fallbacks&lt;/strong&gt; — pickle → JSON → repr. Something readable is &lt;em&gt;always&lt;/em&gt; saved, even for exotic objects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dedup on rerun&lt;/strong&gt; — items already in quarantine are skipped instead of spamming duplicates.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  It scales down and up
&lt;/h2&gt;

&lt;p&gt;For quick scripts, the decorator with zero arguments is all you need. When you want more:&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="nd"&gt;@quarantine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;KeyError&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;# only quarantine these; others still crash
&lt;/span&gt;    &lt;span class="n"&gt;retries&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;backoff&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;# retry transient failures with backoff
&lt;/span&gt;    &lt;span class="n"&gt;halt_after&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;# the circuit breaker
&lt;/span&gt;    &lt;span class="n"&gt;redact&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;            &lt;span class="c1"&gt;# scrub secrets before saving
&lt;/span&gt;    &lt;span class="n"&gt;on_quarantine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;slack_alert&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;# hooks for alerting/metrics
&lt;/span&gt;    &lt;span class="nb"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;s3://bucket/prefix&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;# one shared quarantine for a fleet of workers
&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;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works on &lt;code&gt;async def&lt;/code&gt;, it's safe under threads and &lt;code&gt;multiprocessing&lt;/code&gt; (proven with real concurrent processes in the test suite), and there's a &lt;code&gt;shield()&lt;/code&gt; helper if you'd rather wrap a loop than decorate a function:&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;quarantine&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shield&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;shield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;using&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The stats that matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero runtime dependencies&lt;/strong&gt; — standard library only (S3 backend optionally adds boto3)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fully typed&lt;/strong&gt;, ships &lt;code&gt;py.typed&lt;/code&gt;, passes strict mypy&lt;/li&gt;
&lt;li&gt;Python 3.10+, tested on Linux/macOS/Windows in CI&lt;/li&gt;
&lt;li&gt;MIT licensed&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When you should NOT use it
&lt;/h2&gt;

&lt;p&gt;Honesty section:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You want the crash.&lt;/strong&gt; In a bank transfer pipeline, stopping on the first error might be correct. Continuing is a choice — make it deliberately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You're already on Celery/Kafka/Airflow.&lt;/strong&gt; Those have real dead-letter queues; use them. quarantine is for the 95% of scripts that will never justify that machinery.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failures are expected and normal&lt;/strong&gt; ("404 means skip"). Handle those with a normal &lt;code&gt;if&lt;/code&gt; — quarantine is for &lt;em&gt;unexpected&lt;/em&gt; failures you'll want to investigate.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;quarantine-py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;📦 PyPI: &lt;a href="https://pypi.org/project/quarantine-py/" rel="noopener noreferrer"&gt;https://pypi.org/project/quarantine-py/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;⭐ GitHub: &lt;a href="https://github.com/halcyon-past/quarantine" rel="noopener noreferrer"&gt;https://github.com/halcyon-past/quarantine&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📚 Docs: &lt;a href="https://quarantine-py.aritro.cloud" rel="noopener noreferrer"&gt;https://quarantine-py.aritro.cloud&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your overnight job has ever died at item 5,247 — this one's for you. I'd love to hear what you think, and issues/PRs are very welcome. What's next on the roadmap: more storage backends (GCS, Azure, Redis, Databricks).&lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
