<?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>Forem: Sayak Naskar</title>
    <description>The latest articles on Forem by Sayak Naskar (@hacky1997).</description>
    <link>https://forem.com/hacky1997</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%2F239636%2F2b82750c-ea74-4b32-8f6e-eed76fc4be28.jpeg</url>
      <title>Forem: Sayak Naskar</title>
      <link>https://forem.com/hacky1997</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/hacky1997"/>
    <language>en</language>
    <item>
      <title>Stop Writing Fake Pytest Fixtures and Capture Real API Responses Safely using Snapfix</title>
      <dc:creator>Sayak Naskar</dc:creator>
      <pubDate>Mon, 13 Apr 2026 19:52:18 +0000</pubDate>
      <link>https://forem.com/hacky1997/stop-writing-fake-pytest-fixtures-and-capture-real-api-responses-safely-using-snapfix-4pk5</link>
      <guid>https://forem.com/hacky1997/stop-writing-fake-pytest-fixtures-and-capture-real-api-responses-safely-using-snapfix-4pk5</guid>
      <description>&lt;h2&gt;
  
  
  So, What Snapfix Does
&lt;/h2&gt;

&lt;p&gt;Wraps any Python function with &lt;code&gt;@capture&lt;/code&gt;, intercepts the return value when&lt;br&gt;
called once against staging, serializes it (preserving 15+ Python types&lt;br&gt;
including &lt;code&gt;datetime&lt;/code&gt;, &lt;code&gt;Decimal&lt;/code&gt;, &lt;code&gt;UUID&lt;/code&gt;, &lt;code&gt;bytes&lt;/code&gt;), scrubs 22 default PII field patterns by name, then writes a valid &lt;code&gt;@pytest.fixture&lt;/code&gt; file. Also ships: a&lt;br&gt;
CLI (&lt;code&gt;list&lt;/code&gt;, &lt;code&gt;show&lt;/code&gt;, &lt;code&gt;diff&lt;/code&gt;, &lt;code&gt;audit&lt;/code&gt;, &lt;code&gt;verify&lt;/code&gt;, &lt;code&gt;clear&lt;/code&gt;), a pytest plugin with&lt;br&gt;
auto-discovery, snapshot diffing to detect API schema drift, and a&lt;br&gt;
regex-based second-pass PII scanner.&lt;/p&gt;
&lt;h3&gt;
  
  
  Who It Is For
&lt;/h3&gt;

&lt;p&gt;Python backend/API developers who write pytest and integrate with third-party APIs (Stripe, Twilio, internal microservices). Also, QA engineers are inheriting messy test suites, and any dev who has ever committed a real email address to git by accident.&lt;/p&gt;
&lt;h3&gt;
  
  
  Problem It actually Solves
&lt;/h3&gt;

&lt;p&gt;There are two compounding problems - &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Hand-written fixtures are the wrong shape that reflect what you &lt;em&gt;guessed&lt;/em&gt; the API returns, not what it actually returns.&lt;/li&gt;
&lt;li&gt;The fastest path to a realistic fixture is pasting a live response, which ships PII into git history permanently.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  GitHub Link
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;https://github.com/hacky1997/snapfix&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Example Use
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@capture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stripe_invoice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scrub&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;metadata&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;Call once in dev/staging → review generated file → commit → remove decorator.&lt;/p&gt;
&lt;h3&gt;
  
  
  Key Differentiators from VCR / pytest-recording / responses
&lt;/h3&gt;

&lt;p&gt;Those tools mock at the HTTP layer and replay requests. Snapfix captures&lt;br&gt;
&lt;em&gt;Python objects&lt;/em&gt; after your own deserialization logic runs. It cares about the&lt;br&gt;
shape of the data &lt;em&gt;your code returns&lt;/em&gt;, not the raw HTTP bytes. It also has no&lt;br&gt;
opinion on networking at all — works for database results, gRPC, internal&lt;br&gt;
function calls, anything.&lt;/p&gt;



&lt;p&gt;Last year, I found a real customer email address inside a test fixture in our repository. It had been there for eight months. Sitting in &lt;code&gt;git log&lt;/code&gt;, immovable, committed by someone who copied a live Stripe response to debug a billing edge case and forgot to clean it up before pushing.&lt;/p&gt;

&lt;p&gt;That's how &lt;code&gt;snapfix&lt;/code&gt; started.&lt;/p&gt;


&lt;h3&gt;
  
  
  The Problem Nobody Talks About (But Every Team Has)
&lt;/h3&gt;

&lt;p&gt;When you're building against a third-party API like Stripe, Twilio, or an internal microservice, you eventually need realistic test data. There are two obvious options, and both are quietly broken.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1: Write fake fixtures by hand.&lt;/strong&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="nd"&gt;@pytest.fixture&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fake_invoice&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&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;in_test_abc123&lt;/span&gt;&lt;span class="sh"&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;status&lt;/span&gt;&lt;span class="sh"&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;paid&lt;/span&gt;&lt;span class="sh"&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;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;14999&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# is this cents or dollars? Let me guess
&lt;/span&gt;        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;email&lt;/span&gt;&lt;span class="sh"&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;alice@example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# is this actually how Stripe nests this?
&lt;/span&gt;        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks fine... Runs green... Ships to production and breaks. Because the real Stripe response has &lt;code&gt;amount_due&lt;/code&gt;, not &lt;code&gt;amount&lt;/code&gt;. And &lt;code&gt;customer&lt;/code&gt; is sometimes a string ID and sometimes a nested object, depending on your &lt;code&gt;expand&lt;/code&gt; parameters. And there are seventeen other fields you didn't know about that your code silently relies on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 2: Paste a real response.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is what most developers actually do under deadline pressure. You &lt;code&gt;print()&lt;/code&gt; the live response, paste it into a fixture, push it. Fast, accurate, and a compliance violation waiting to be discovered.&lt;/p&gt;

&lt;p&gt;GitHub reported 39 million leaked secrets across public repositories in 2024.&lt;br&gt;
Stripe API keys and customer PII travel this exact path: production API → developer debug session → test fixture → &lt;code&gt;git commit&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git history&lt;/code&gt; does not forget. Even after you delete the file, it remains in the history permanently unless you run &lt;code&gt;git filter-branch&lt;/code&gt;, a disruptive operation most teams never do.&lt;/p&gt;


&lt;h3&gt;
  
  
  There Is a Third Option
&lt;/h3&gt;

&lt;p&gt;Capture the real object. Strip the sensitive fields automatically. Write the fixture for you.&lt;/p&gt;

&lt;p&gt;That's &lt;code&gt;snapfix&lt;/code&gt;. One decorator, one staging run, one review step, done.&lt;br&gt;
&lt;/p&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;snapfix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  How It Works in Practice
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Step 1 — Add the decorator
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# myapp/billing.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;snapfix&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;capture&lt;/span&gt;

&lt;span class="nd"&gt;@capture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stripe_invoice_paid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# the fixture name
&lt;/span&gt;    &lt;span class="n"&gt;scrub&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;metadata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;     &lt;span class="c1"&gt;# extra fields beyond the 22 sensitive defaults
&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;fetch_invoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoice_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;invoice&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;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;invoice_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;expand&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;customer&lt;/span&gt;&lt;span class="sh"&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;lines.data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_dict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;@capture&lt;/code&gt; is transparent — it never changes what your function returns, never swallows exceptions, and becomes a complete no-op when &lt;code&gt;SNAPFIX_ENABLED=false&lt;/code&gt; is set (which you should set in production).&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 2 — Call it once against staging
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"
import asyncio
from myapp.billing import fetch_invoice
asyncio.run(fetch_invoice('in_1OkRealInvoiceId'))
"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see this in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  snapfix ✓ fixture written: tests/fixtures/snapfix_stripe_invoice_paid.py
  snapfix scrubbed: customer.email, customer.address, metadata.internal_ref
  snapfix review before committing — value-level PII is not detected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 3 — Look at what was written
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Generated by snapfix — do not edit manually
# Captured    : 2026-03-24T14:22:01
# Source      : myapp.billing.fetch_invoice (myapp/billing.py)
# Scrubbed    : customer.email, customer.address, metadata.internal_ref
#
# ⚠  Review before committing: value-level PII is not detected.
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;snapfix&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;reconstruct&lt;/span&gt;

&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;stripe_invoice_paid&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;reconstruct&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&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;in_1OkRealInvoiceId&lt;/span&gt;&lt;span class="sh"&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;object&lt;/span&gt;&lt;span class="sh"&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;invoice&lt;/span&gt;&lt;span class="sh"&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;amount_due&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;14999&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;amount_paid&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;14999&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;amount_remaining&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;currency&lt;/span&gt;&lt;span class="sh"&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;usd&lt;/span&gt;&lt;span class="sh"&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;status&lt;/span&gt;&lt;span class="sh"&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;paid&lt;/span&gt;&lt;span class="sh"&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;customer_email&lt;/span&gt;&lt;span class="sh"&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;***SCRUBBED***&lt;/span&gt;&lt;span class="sh"&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;created&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&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;__snapfix_type__&lt;/span&gt;&lt;span class="sh"&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;datetime&lt;/span&gt;&lt;span class="sh"&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;value&lt;/span&gt;&lt;span class="sh"&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;2026-03-01T09:00:00&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;  &lt;span class="c1"&gt;# datetime
&lt;/span&gt;        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;lines&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&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;object&lt;/span&gt;&lt;span class="sh"&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;list&lt;/span&gt;&lt;span class="sh"&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;data&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&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;amount&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;14999&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&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;Pro plan (March 2026)&lt;/span&gt;&lt;span class="sh"&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;has_more&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;total_count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&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;metadata&lt;/span&gt;&lt;span class="sh"&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;***SCRUBBED***&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;# Scrubbed:&lt;/code&gt; header lists every field that was removed. The &lt;code&gt;reconstruct()&lt;/code&gt;&lt;br&gt;
call converts type markers back to real Python objects — that &lt;code&gt;datetime&lt;/code&gt; marker&lt;br&gt;
becomes an actual &lt;code&gt;datetime.datetime&lt;/code&gt;, not a string, when your test runs.&lt;/p&gt;
&lt;h4&gt;
  
  
  Step 4 — Review, commit, and remove the decorator
&lt;/h4&gt;

&lt;p&gt;This is the step most tools skip. Snapfix makes it explicit: it's your code,&lt;br&gt;
you review it before it goes anywhere. Once committed, remove &lt;code&gt;@capture&lt;/code&gt; from&lt;br&gt;
your function. The fixture is self-contained.&lt;/p&gt;
&lt;h4&gt;
  
  
  Step 5 — Write tests against the actual data shape
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_invoice_is_paid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stripe_invoice_paid&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;stripe_invoice_paid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;paid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_invoice_amount_in_cents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stripe_invoice_paid&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Stripe always returns integer cents — this is what the real API sends
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;stripe_invoice_paid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount_paid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;14999&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stripe_invoice_paid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount_paid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;int&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;test_pii_is_scrubbed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stripe_invoice_paid&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Documents your compliance behavior explicitly
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;stripe_invoice_paid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;***SCRUBBED***&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_created_is_real_datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stripe_invoice_paid&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
    &lt;span class="c1"&gt;# reconstruct() restored the type — not a string
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stripe_invoice_paid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;created&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What Gets Scrubbed Automatically
&lt;/h3&gt;

&lt;p&gt;Snapfix scrubs any field whose &lt;strong&gt;name contains&lt;/strong&gt; one of 22 default substrings&lt;br&gt;
(case-insensitive, substring match):&lt;/p&gt;

&lt;p&gt;&lt;code&gt;email&lt;/code&gt; · &lt;code&gt;password&lt;/code&gt; · &lt;code&gt;token&lt;/code&gt; · &lt;code&gt;secret&lt;/code&gt; · &lt;code&gt;api_key&lt;/code&gt; · &lt;code&gt;access_token&lt;/code&gt; ·&lt;br&gt;
&lt;code&gt;refresh_token&lt;/code&gt; · &lt;code&gt;ssn&lt;/code&gt; · &lt;code&gt;credit_card&lt;/code&gt; · &lt;code&gt;card_number&lt;/code&gt; · &lt;code&gt;cvv&lt;/code&gt; · &lt;code&gt;phone&lt;/code&gt; ·&lt;br&gt;
&lt;code&gt;mobile&lt;/code&gt; · &lt;code&gt;dob&lt;/code&gt; · &lt;code&gt;date_of_birth&lt;/code&gt; · &lt;code&gt;address&lt;/code&gt; · &lt;code&gt;ip_address&lt;/code&gt; · &lt;code&gt;auth&lt;/code&gt; ·&lt;br&gt;
&lt;code&gt;bearer&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;customer_email&lt;/code&gt; gets scrubbed (contains &lt;code&gt;email&lt;/code&gt;), &lt;code&gt;billing_phone_number&lt;/code&gt;&lt;br&gt;
gets scrubbed (contains &lt;code&gt;phone&lt;/code&gt;), &lt;code&gt;retry_count&lt;/code&gt; does not.&lt;/p&gt;

&lt;p&gt;You add your own with &lt;code&gt;scrub=["metadata", "customer_id", "tax_ref"]&lt;/code&gt;.&lt;/p&gt;


&lt;h3&gt;
  
  
  Types That Survive the Round-Trip
&lt;/h3&gt;

&lt;p&gt;This was the part I cared most about getting right. Real API responses contain&lt;br&gt;
&lt;code&gt;datetime&lt;/code&gt; objects, &lt;code&gt;Decimal&lt;/code&gt; for money, &lt;code&gt;UUID&lt;/code&gt; for IDs. If your fixture&lt;br&gt;
converts these to strings, your tests are testing the wrong types.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What you get back&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;datetime.datetime&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;datetime.datetime&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;decimal.Decimal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;decimal.Decimal&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uuid.UUID&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;uuid.UUID&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;bytes&lt;/code&gt; / &lt;code&gt;bytearray&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;bytes&lt;/code&gt; / &lt;code&gt;bytearray&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pathlib.Path&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pathlib.Path&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;set&lt;/code&gt; / &lt;code&gt;frozenset&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;set&lt;/code&gt; / &lt;code&gt;frozenset&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dataclass&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;dict&lt;/code&gt; (via &lt;code&gt;dataclasses.asdict()&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pydantic model&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;dict&lt;/code&gt; (via &lt;code&gt;.model_dump()&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;circular reference&lt;/td&gt;
&lt;td&gt;sentinel dict (capture still completes)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;tuple&lt;/code&gt; becomes &lt;code&gt;list&lt;/code&gt; on roundtrip — JSON has no tuple type. This is&lt;br&gt;
documented and intentional.&lt;/p&gt;


&lt;h3&gt;
  
  
  The Second Layer of Defense: &lt;code&gt;snapfix audit&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Field-name scrubbing has a gap: &lt;code&gt;payload["tags"][0]&lt;/code&gt; containing an email won't&lt;br&gt;
be caught by name matching. So Snapfix ships a second-pass regex scanner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;snapfix audit

&lt;span class="c"&gt;# snapfix audit — tests/fixtures/&lt;/span&gt;
&lt;span class="c"&gt;# ────────────────────────────────────────────────────────────&lt;/span&gt;
&lt;span class="c"&gt;#   Files scanned : 8&lt;/span&gt;
&lt;span class="c"&gt;#   Findings      : 0&lt;/span&gt;
&lt;span class="c"&gt;#   Status        : ✓ PASSED — no PII patterns detected&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It checks for email patterns, US phone numbers, SSNs, credit card numbers (Visa/MC/Amex/Discover), AWS access keys, and long API-key-like strings. Lines already containing &lt;code&gt;***SCRUBBED***&lt;/code&gt;, &lt;code&gt;example.com&lt;/code&gt;, &lt;code&gt;test@&lt;/code&gt;, &lt;code&gt;placeholder&lt;/code&gt;, or &lt;code&gt;dummy&lt;/code&gt; are skipped to keep false positives low.&lt;/p&gt;

&lt;p&gt;Add it as a pre-commit hook:&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="c1"&gt;# .pre-commit-config.yaml&lt;/span&gt;
&lt;span class="na"&gt;repos&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local&lt;/span&gt;
    &lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;snapfix-audit&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;snapfix PII audit&lt;/span&gt;
        &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;snapfix audit --strict&lt;/span&gt;
        &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;system&lt;/span&gt;
        &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^tests/fixtures/snapfix_.*\.py$&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Catching API Schema Changes: &lt;code&gt;snapfix diff&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;When you upgrade your Stripe SDK and re-capture a fixture, Snapfix automatically compares to the previous capture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;snapfix diff stripe_invoice_paid

&lt;span class="c"&gt;# ✗  Changes detected in 'stripe_invoice_paid':&lt;/span&gt;
&lt;span class="c"&gt;# --- stripe_invoice_paid (previous)&lt;/span&gt;
&lt;span class="c"&gt;# +++ stripe_invoice_paid (current)&lt;/span&gt;
&lt;span class="c"&gt;# @@ ...&lt;/span&gt;
&lt;span class="c"&gt;#  lines.data[0].amount: 14999&lt;/span&gt;
&lt;span class="c"&gt;# +lines.data[0].amount_excluding_tax: 14999&lt;/span&gt;
&lt;span class="c"&gt;# +lines.data[0].tax_amounts: []&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stripe added two fields in a recent API version. Your CI catches this before production does. Run &lt;code&gt;snapfix diff&lt;/code&gt; after every dependency upgrade that touches an external API.&lt;/p&gt;




&lt;h3&gt;
  
  
  Pytest Plugin: Zero Config Required
&lt;/h3&gt;

&lt;p&gt;Snapfix registers itself as a pytest plugin when you install it. No&lt;br&gt;
&lt;code&gt;conftest.py&lt;/code&gt; changes. Generated fixture files are auto-discovered and show up in &lt;code&gt;pytest --collect-only&lt;/code&gt; and &lt;code&gt;pytest --fixtures&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pytest &lt;span class="nt"&gt;--snapfix-capture&lt;/span&gt;    &lt;span class="c"&gt;# enable capture for this session&lt;/span&gt;
pytest &lt;span class="nt"&gt;--no-snapfix-capture&lt;/span&gt; &lt;span class="c"&gt;# explicitly disable&lt;/span&gt;
pytest &lt;span class="nt"&gt;--snapfix-dir&lt;/span&gt; /path  &lt;span class="c"&gt;# write to a different directory&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Capture auto-disables in CI environments (&lt;code&gt;CI=true&lt;/code&gt;) unless you explicitly pass &lt;code&gt;--snapfix-capture&lt;/code&gt;. This prevents staging calls from running in your automated test pipeline.&lt;/p&gt;




&lt;h3&gt;
  
  
  Works With Anything That Returns a Python Object
&lt;/h3&gt;

&lt;p&gt;Snapfix doesn't touch HTTP. It intercepts function return values. That means it works for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stripe, Twilio, Sendgrid, any REST client&lt;/li&gt;
&lt;li&gt;gRPC response objects&lt;/li&gt;
&lt;li&gt;SQLAlchemy query results (serialize the dict form)&lt;/li&gt;
&lt;li&gt;Internal microservice responses&lt;/li&gt;
&lt;li&gt;Any function your codebase owns that calls something external&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Honest Limitations
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;PII detection is field-name only by default.&lt;/strong&gt; An email stored under &lt;code&gt;payload["user_tags"][0]&lt;/code&gt; won't be caught by the scrubber — only by &lt;code&gt;snapfix audit&lt;/code&gt;. Always review the generated file before committing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not for production traffic.&lt;/strong&gt; Use it against staging or development only.&lt;br&gt;
The warning is in the generated file header.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Python 3.10+ required.&lt;/strong&gt; This is a hard constraint, not a soft preference.&lt;br&gt;
If your team is on 3.9, it won't install.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;tuple&lt;/code&gt; becomes &lt;code&gt;list&lt;/code&gt; on roundtrip.&lt;/strong&gt; JSON's type system. Documented.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enum class not preserved.&lt;/strong&gt; &lt;code&gt;reconstruct()&lt;/code&gt; returns &lt;code&gt;.value&lt;/code&gt;, not the enum&lt;br&gt;
instance. Cast manually if your tests check enum identity.&lt;/p&gt;


&lt;h3&gt;
  
  
  Try It in 3 Minutes
&lt;/h3&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;snapfix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Add &lt;code&gt;@capture("my_api_response")&lt;/code&gt; to one function that calls a third-party API&lt;/li&gt;
&lt;li&gt;Call that function once in your dev or staging environment&lt;/li&gt;
&lt;li&gt;Open the generated file in &lt;code&gt;tests/fixtures/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Read the &lt;code&gt;# Scrubbed:&lt;/code&gt; header — verify what was stripped&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;snapfix audit&lt;/code&gt; for a second-pass check&lt;/li&gt;
&lt;li&gt;Commit the fixture, remove the decorator&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you have existing fixtures, run &lt;code&gt;snapfix verify&lt;/code&gt; to confirm they're all&lt;br&gt;
healthy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;snapfix verify
&lt;span class="c"&gt;# ✓  snapfix_stripe_invoice_paid.py  [stripe_invoice_paid]&lt;/span&gt;
&lt;span class="c"&gt;# ✓  snapfix_stripe_subscription.py  [stripe_subscription]&lt;/span&gt;
&lt;span class="c"&gt;# Status : ✓ ALL VALID&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;⭐ Star the repo if this solves a real problem for you. Open an issue if something doesn't work. I read everyone.&lt;/p&gt;

&lt;p&gt;If you've ever found something sensitive in a test file, tell me about it in&lt;br&gt;
the comments.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>python</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I build my voice based email for blind fellows</title>
      <dc:creator>Sayak Naskar</dc:creator>
      <pubDate>Wed, 20 May 2020 05:55:02 +0000</pubDate>
      <link>https://forem.com/hacky1997/i-build-my-voice-based-email-for-blind-fellows-1en7</link>
      <guid>https://forem.com/hacky1997/i-build-my-voice-based-email-for-blind-fellows-1en7</guid>
      <description>&lt;p&gt;I've been coding as a hobby since I was in 1st year of my engineering. Actually I am a security enthusiastic guy. I started building little projects and open-sourcing them and ended up building a bit bigger projects. From development side, one project was build when I was in 2nd year.&lt;/p&gt;

&lt;p&gt;So, have a look on &lt;a href="https://github.com/hacky1997/voice-based-email-for-blind" rel="noopener noreferrer"&gt;Voice Email&lt;/a&gt;, a project for blind people and for all students who are interested to develop and build new one.&lt;/p&gt;

&lt;p&gt;As this project was almost 3 years ago project I was not sure that it will gain a bit popular among other university students. But, due to lack of time I was not able to modify the old one, surely I will do that soon.&lt;/p&gt;

&lt;p&gt;If you're curious and want to learn more about how the launch went then please have a look on my &lt;a href="https://github.com/hacky1997" rel="noopener noreferrer"&gt;Github&lt;/a&gt; page or you hit me up by sending emails.&lt;/p&gt;

</description>
      <category>octograd2020</category>
      <category>devgrad2020</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
