<?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: Ujwal Vanjare</title>
    <description>The latest articles on Forem by Ujwal Vanjare (@ujwal240).</description>
    <link>https://forem.com/ujwal240</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%2F3717448%2Fc1944409-c9df-4fa3-a96a-9b4df814231e.png</url>
      <title>Forem: Ujwal Vanjare</title>
      <link>https://forem.com/ujwal240</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/ujwal240"/>
    <language>en</language>
    <item>
      <title>Scaling Astrolord: Our Journey with Serverless on AWS</title>
      <dc:creator>Ujwal Vanjare</dc:creator>
      <pubDate>Wed, 04 Feb 2026 03:23:53 +0000</pubDate>
      <link>https://forem.com/ujwal240/scaling-astrolord-our-journey-with-serverless-on-aws-2j78</link>
      <guid>https://forem.com/ujwal240/scaling-astrolord-our-journey-with-serverless-on-aws-2j78</guid>
      <description>&lt;p&gt;When we started building &lt;a href="https://astro-lord.com" rel="noopener noreferrer"&gt;Astrolord&lt;/a&gt;, we knew we had a tricky engineering problem on our hands. We were building an AI-powered astrology platform that needed to perform heavy astronomical calculations one second and stream personalized AI responses the next. We needed infrastructure that could handle a sudden spike in traffic, like during a major planetary transit, without burning a hole in our pocket during quiet hours.&lt;/p&gt;

&lt;p&gt;We decided early on to go all-in on AWS Serverless. It wasn't just about avoiding server management; it was about building an architecture that could scale to zero when no one was using it, and scale up infinitely when they were. Here is a look at how we pieced it all together.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F690st14e065ha0u7eqmi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F690st14e065ha0u7eqmi.png" alt="High Level Architecture Diagram - Showing React/S3 and FastAPI/Lambda flows" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Compute Layer: AWS Serverless
&lt;/h2&gt;

&lt;p&gt;The heart of our backend is Python. The challenge was deploying our application in a way that didn't require maintaining a fleet of servers or paying for idle time.&lt;/p&gt;

&lt;p&gt;We chose AWS Lambda for its efficiency. By adapting our web application to run in a serverless environment, we can write modern, clean code while letting AWS handle the underlying infrastructure.&lt;/p&gt;

&lt;p&gt;There were trade-offs, of course. We had to be careful with "cold starts", the initial delay when a function wakes up. We spent time optimizing our application startup to keep latency minimal. But the benefit of paying literally zero dollars when no traffic is hitting the API made it an easy choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Front Door: API Gateway
&lt;/h2&gt;

&lt;p&gt;Sitting in front of those Lambda functions is Amazon API Gateway. Connective tissue is often overlooked, but for us, this piece is critical. It acts as our secure entry point, routing requests to the correct backend services.&lt;/p&gt;

&lt;p&gt;Beyond just routing, we rely on it for stability. We configured usage plans and throttling rules directly at the gateway level. This means if a bad actor tries to hammer our API, AWS blocks them before they even wake up our compute layer, saving us money and processing power.&lt;/p&gt;

&lt;h2&gt;
  
  
  Delivering the UI: S3 and CloudFront
&lt;/h2&gt;

&lt;p&gt;For the frontend, we built a static React application. We didn't want to run a web server or manage containers just to serve HTML and JavaScript files.&lt;/p&gt;

&lt;p&gt;Instead, we use what I consider the "gold standard" for static hosting: Amazon S3 paired with CloudFront. We upload our build artifacts into an S3 bucket, which offers incredible durability. Then, CloudFront sits in front of that bucket, caching our application at edge locations all over the world.&lt;/p&gt;

&lt;p&gt;The result is that a user in Mumbai loads the app just as fast as a user in New York. We also configured strict security controls, ensuring that no one can bypass the CDN to hit our bucket directly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmj1477mqexsmtjstpeur.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmj1477mqexsmtjstpeur.png" alt="Astrolord Dashboard - The result of our React + Vite frontend" width="800" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Observability
&lt;/h2&gt;

&lt;p&gt;One thing they don't tell you about distributed systems is that debugging can be a nightmare if you aren't prepared. Since we don't have a single server to SSH into, we rely heavily on centralized logging and monitoring.&lt;/p&gt;

&lt;p&gt;We capture all standard output from our functions into CloudWatch Logs. We also set up specific metric filters to track errors and throttling events. If our error rate crosses a certain threshold, an alarm triggers and we get notified immediately. It gives us the confidence to deploy on a Friday (well, almost).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjiywon5fouw85rfq0nox.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjiywon5fouw85rfq0nox.png" alt="AWS CloudWatch Metrics - Showing Lambda invocations or low latency" width="800" height="528"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Economics of Serverless
&lt;/h2&gt;

&lt;p&gt;One of the biggest wins for us wasn't just technical, it was financial.&lt;/p&gt;

&lt;p&gt;With a traditional EC2 or container-based architecture, you are paying for capacity 24/7. Even if no one visits your site at 3 AM, that server is still running, and you are still receiving a bill.&lt;/p&gt;

&lt;p&gt;With this serverless setup, our infrastructure cost aligns perfectly with our business growth.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Zero Idle Costs&lt;/strong&gt;: When our app is quiet, our compute bill is literally $0.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Generous Free Tier&lt;/strong&gt;: AWS offers a substantial free tier for Lambda and API Gateway, meaning we effectively run our development and testing environments for free.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;No Over-Provisioning&lt;/strong&gt;: We never have to guess how many servers we need. The system just scales to meet demand, whether that's 5 users or 5,000.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why This Architecture Works for Us
&lt;/h2&gt;

&lt;p&gt;Usage patterns in our app are unpredictable. Some days are quiet; other days, everyone wants to know what the full moon means for them.&lt;/p&gt;

&lt;p&gt;This serverless architecture on AWS absorbs that volatility perfectly. We don't have to provision for peak capacity and pay for idle time. We just pay for the milliseconds of compute we actually use. It has allowed us to focus less on "keeping the lights on" and more on improving the actual product.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>python</category>
      <category>react</category>
    </item>
    <item>
      <title>Time-Travel Debugging for Python: A Complete Tutorial</title>
      <dc:creator>Ujwal Vanjare</dc:creator>
      <pubDate>Fri, 23 Jan 2026 05:10:45 +0000</pubDate>
      <link>https://forem.com/ujwal240/-time-travel-debugging-for-python-a-complete-tutorial-1dip</link>
      <guid>https://forem.com/ujwal240/-time-travel-debugging-for-python-a-complete-tutorial-1dip</guid>
      <description>&lt;h1&gt;
  
  
  Time-Travel Debugging for Python: A Complete Tutorial
&lt;/h1&gt;

&lt;p&gt;Building web applications means dealing with external APIs, databases, and the inevitable production bugs. I'm going to show you how to capture production issues and debug them locally without ever hitting those external services again.&lt;/p&gt;

&lt;p&gt;This is a complete walkthrough using Timetracer with a Starlette application. By the end, you'll have a working example and understand how to apply this to your own projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If you're new to Timetracer, you might want to check out &lt;a href="https://dev.to/ujwal240/i-got-tired-of-it-works-on-my-machine-so-i-built-a-time-travel-debugger-235h"&gt;my initial post&lt;/a&gt; about why I built this tool, or &lt;a href="https://dev.to/ujwal240/timetracer-v14-native-django-support-and-easiest-pytest-integration-5e4f"&gt;the v1.4 release post&lt;/a&gt; covering Django and pytest integration.&lt;/p&gt;

&lt;p&gt;This tutorial focuses specifically on Starlette integration and shows the complete debugging workflow with the new v1.6.0 dashboard features.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;You know that moment when a bug happens in production? You spend hours trying to reproduce it locally. You're making API calls to third-party services, dealing with rate limits, stale data, and that nagging feeling you're not testing the exact scenario that failed.&lt;/p&gt;

&lt;p&gt;Traditional debugging flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Bug reported in production&lt;/li&gt;
&lt;li&gt;Try to reproduce locally (often fails)&lt;/li&gt;
&lt;li&gt;Add logging and redeploy (slow)&lt;/li&gt;
&lt;li&gt;Hope you captured enough context (usually didn't)&lt;/li&gt;
&lt;li&gt;Repeat until fixed (hours or days)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There's a better way. With Timetracer, you capture the entire request context in production and replay it locally. Think of it as a flight recorder for your web application.&lt;/p&gt;




&lt;h2&gt;
  
  
  Setting Up the Project
&lt;/h2&gt;

&lt;p&gt;Let's build a simple API that proxies GitHub user data. First, install the dependencies:&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;starlette uvicorn httpx timetracer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a file called &lt;code&gt;app.py&lt;/code&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;starlette.applications&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Starlette&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;starlette.routing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Route&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;starlette.responses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;JSONResponse&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;httpx&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;homepage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;JSONResponse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&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;Welcome to the Starlette + Timetracer example&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;endpoints&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;/&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;/user/{username}&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;/repos/{username}&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;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path_params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;username&lt;/span&gt;&lt;span class="sh"&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;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AsyncClient&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.github.com/users/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;username&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="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;JSONResponse&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="nf"&gt;json&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;get_repos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path_params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;username&lt;/span&gt;&lt;span class="sh"&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;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AsyncClient&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;user_resp&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.github.com/users/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;username&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="n"&gt;user_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;repos_resp&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.github.com/users/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/repos&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;repos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;repos_resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;JSONResponse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;username&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;username&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_repos&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;public_repos&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;top_repos&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;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&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;stars&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stargazers_count&lt;/span&gt;&lt;span class="sh"&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;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stargazers_count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]]&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="nc"&gt;Starlette&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;routes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;homepage&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/user/{username}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;get_user&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/repos/{username}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;get_repos&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;This gives us three endpoints: a homepage, a user lookup, and a repo list. The last two hit the GitHub API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj6qgndsut4d8bjqahmbx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj6qgndsut4d8bjqahmbx.png" alt="Code Example" width="800" height="800"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The Starlette application with three endpoints&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Integrating Timetracer
&lt;/h2&gt;

&lt;p&gt;Now add Timetracer. Import the integration and call &lt;code&gt;auto_setup()&lt;/code&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;timetracer.integrations.starlette&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;auto_setup&lt;/span&gt;

&lt;span class="c1"&gt;# ... your routes ...
&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Starlette&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;routes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;homepage&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/user/{username}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;get_user&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/repos/{username}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;get_repos&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# This is the only line you need for Timetracer
&lt;/span&gt;&lt;span class="nf"&gt;auto_setup&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;plugins&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;httpx&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;That's it. One line of code. This adds middleware that captures every request and tracks all httpx calls to external APIs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Recording Requests
&lt;/h2&gt;

&lt;p&gt;Start the server in record mode:&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="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TIMETRACER_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;record
uvicorn app:app &lt;span class="nt"&gt;--reload&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your terminal should show Timetracer capturing requests:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4drra4t2z14zdvhgdc6v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4drra4t2z14zdvhgdc6v.png" alt="Terminal Record Mode" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Terminal output showing Timetracer recording requests with timing information&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now let's make some requests and see what gets captured.&lt;/p&gt;
&lt;h3&gt;
  
  
  Request 1: Homepage
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:8000/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Welcome to the Starlette + Timetracer example"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"endpoints"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/user/{username}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/repos/{username}"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&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%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9zncr9odxstots4qmowp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9zncr9odxstots4qmowp.png" alt="Homepage Response" width="800" height="393"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Browser showing the homepage JSON response&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Terminal output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;timetracer [OK] recorded GET /  id=cddb  status=200  total=9ms  deps=none
  cassette: cassettes/2026-01-23/GET__root__cddb6be9.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice &lt;code&gt;deps=none&lt;/code&gt; because this endpoint doesn't make any external calls.&lt;/p&gt;

&lt;h3&gt;
  
  
  Request 2: User Lookup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:8000/user/octocat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"login"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"octocat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The Octocat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"public_repos"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"followers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;21594&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&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%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvlrvx0mad9xyki0e570l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvlrvx0mad9xyki0e570l.png" alt="User Response" width="800" height="393"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;GitHub user data returned through our API&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Terminal output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;timetracer [OK] recorded GET /user/octocat  id=88d7  status=200  total=472ms  deps=http.client:1
  cassette: cassettes/2026-01-23/GET__user_octocat__88d76871.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This time &lt;code&gt;deps=http.client:1&lt;/code&gt; shows one external HTTP call was tracked. The duration is 472ms instead of 9ms because we're waiting for GitHub's API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Request 3: Repository List
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:8000/repos/octocat
&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%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu8y58gt92u8fld7x2l9u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu8y58gt92u8fld7x2l9u.png" alt="Repos Response" width="800" height="393"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Top repositories for the octocat user&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This endpoint makes two GitHub API calls: one for the user data and one for the repository list.&lt;/p&gt;
&lt;h3&gt;
  
  
  What Got Saved?
&lt;/h3&gt;

&lt;p&gt;Each cassette is a JSON file containing your request, response, and all external dependencies with timing information.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8xtfavn8n9i89igk0r8g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8xtfavn8n9i89igk0r8g.png" alt="Cassette JSON Structure" width="800" height="393"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Cassette file showing the captured request, response, and external API calls&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The cassette includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Request details (method, path, headers, body)&lt;/li&gt;
&lt;li&gt;Response details (status, headers, body, duration)&lt;/li&gt;
&lt;li&gt;All external dependencies (each GitHub API call with its own timing)&lt;/li&gt;
&lt;li&gt;Metadata about the session (framework, timestamp, etc.)&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Using the Dashboard
&lt;/h2&gt;

&lt;p&gt;Now for the interactive part. Timetracer includes a web dashboard to browse and analyze your captured requests.&lt;/p&gt;

&lt;p&gt;Start the dashboard server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;timetracer serve &lt;span class="nt"&gt;--dir&lt;/span&gt; cassettes &lt;span class="nt"&gt;--port&lt;/span&gt; 3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; in your browser:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcfvfdwjihspdthiaq37l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcfvfdwjihspdthiaq37l.png" alt="Dashboard Overview" width="800" height="393"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Dashboard showing all captured requests with statistics&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The dashboard shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Total requests, success count, error count&lt;/li&gt;
&lt;li&gt;Every captured request with method, path, status, duration, and dependencies&lt;/li&gt;
&lt;li&gt;Search and filter capabilities&lt;/li&gt;
&lt;li&gt;View details or replay any request&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Viewing Request Details
&lt;/h3&gt;

&lt;p&gt;Click "View" on the &lt;code&gt;/repos/octocat&lt;/code&gt; request:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3e0yws9uu0lq9bk1whky.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3e0yws9uu0lq9bk1whky.png" alt="Dashboard Detail View" width="800" height="393"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Detailed view showing request, response, and external API dependencies&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The detail view shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Request metadata: Path, method, timestamp&lt;/li&gt;
&lt;li&gt;Response: Status 200, duration 524ms&lt;/li&gt;
&lt;li&gt;Dependency Events: Both GitHub API calls with individual timings

&lt;ul&gt;
&lt;li&gt;GET &lt;a href="https://api.github.com/users/torvalds" rel="noopener noreferrer"&gt;https://api.github.com/users/torvalds&lt;/a&gt; (104ms)&lt;/li&gt;
&lt;li&gt;GET &lt;a href="https://api.github.com/users/torvalds/repos" rel="noopener noreferrer"&gt;https://api.github.com/users/torvalds/repos&lt;/a&gt; (95ms)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Ready-to-use replay command&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This view tells you exactly what happened during the request, including all external services that were called.&lt;/p&gt;
&lt;h3&gt;
  
  
  Filtering Requests
&lt;/h3&gt;

&lt;p&gt;Type "repos" in the search box:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh8mkoq456od1mniictws.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh8mkoq456od1mniictws.png" alt="Filtered Dashboard" width="800" height="393"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Dashboard filtered to show only repository-related requests&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The dashboard now shows "Showing 5 of 19 cassettes" with only the matching requests visible.&lt;/p&gt;

&lt;p&gt;You can also filter by HTTP method or status code to focus on specific types of requests.&lt;/p&gt;
&lt;h3&gt;
  
  
  Inspecting the Raw Data
&lt;/h3&gt;

&lt;p&gt;For technical inspection, the dashboard includes a Raw JSON viewer:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fddjccf2x0t9fhbf50d7d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fddjccf2x0t9fhbf50d7d.png" alt="Raw JSON Tab" width="800" height="393"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Raw JSON view showing the complete cassette structure&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This gives you direct access to the underlying cassette data, making it easy to verify exactly what state is being captured and will be replayed.&lt;/p&gt;


&lt;h2&gt;
  
  
  Debugging a Real Bug
&lt;/h2&gt;

&lt;p&gt;Now let's use Timetracer for what it's really good at: debugging production issues without touching production.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Bug Appears
&lt;/h3&gt;

&lt;p&gt;Imagine a user reports that requesting a non-existent GitHub user crashes the server with a 500 error. The problematic code:&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;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path_params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;username&lt;/span&gt;&lt;span class="sh"&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;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AsyncClient&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.github.com/users/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;username&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="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;JSONResponse&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="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;  &lt;span class="c1"&gt;# Crashes on 404
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When someone requests a user that doesn't exist, GitHub returns 404, but our code assumes success and tries to parse the error response.&lt;/p&gt;

&lt;p&gt;Even though the app crashes, Timetracer still captures the request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;timetracer [ERROR] recorded GET /user/nonexistent-user-12345  id=bad1  status=500  total=156ms  deps=http.client:1
  cassette: cassettes/2026-01-23/GET__user_nonexistent-user-12345__bad1234.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Inspecting the Error
&lt;/h3&gt;

&lt;p&gt;In the dashboard, click "View" on the failed request:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhitycmxkis4k6lqk02fl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhitycmxkis4k6lqk02fl.png" alt="Error Detail View" width="800" height="393"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Dashboard detail view showing a 404 error from GitHub API&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The detail view clearly shows that GitHub returned a 404, which propagated to our endpoint as a 500 error. You can see exactly what happened: the external API call failed, and our code didn't handle it properly.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;

&lt;p&gt;Looking at the dashboard detail view, you can see GitHub returned 404. Fix the code:&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;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path_params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;username&lt;/span&gt;&lt;span class="sh"&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;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AsyncClient&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.github.com/users/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;username&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;# Check status code before parsing
&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="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;JSONResponse&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;error&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;User not found&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;JSONResponse&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="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Getting the Replay Command
&lt;/h3&gt;

&lt;p&gt;The dashboard provides a ready-to-copy replay command:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffed3or0hsicw7py160ti.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffed3or0hsicw7py160ti.png" alt="Replay Command" width="800" height="393"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Ready-to-use replay command for testing the fix&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Just copy this command to test your fix with the exact scenario that failed in production.&lt;/p&gt;


&lt;h2&gt;
  
  
  Testing the Fix Without Network
&lt;/h2&gt;

&lt;p&gt;This is where Timetracer shows its real value. You can test the fix using the captured cassette without making any real API calls to GitHub.&lt;/p&gt;

&lt;p&gt;Stop the server and restart in replay mode:&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="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TIMETRACER_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;replay
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TIMETRACER_CASSETTE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cassettes/2026-01-23/GET__user_nonexistent-user-12345__bad1234.json
uvicorn app:app &lt;span class="nt"&gt;--reload&lt;/span&gt;
&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%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff047gpky6ldickd6xsor.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff047gpky6ldickd6xsor.png" alt="Replay Mode" width="800" height="800"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Server running in replay mode with mocked external API responses&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Make the same request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:8000/user/nonexistent-user-12345
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terminal shows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;timetracer replay GET /user/nonexistent-user-12345  mocked=1  matched=OK  runtime=5ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"User not found"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Status: 404&lt;/p&gt;

&lt;p&gt;The fix works. Notice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;No network call - the response came from the cassette&lt;/li&gt;
&lt;li&gt;Fast: 5ms instead of the original 156ms&lt;/li&gt;
&lt;li&gt;Exact scenario: Same 404 from GitHub that caused the original crash&lt;/li&gt;
&lt;li&gt;Offline: This works with no internet connection&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You just debugged and fixed a production bug without touching production or making a single external API call.&lt;/p&gt;




&lt;h2&gt;
  
  
  Performance Comparison
&lt;/h2&gt;

&lt;p&gt;Let's compare the timing differences:&lt;/p&gt;

&lt;h3&gt;
  
  
  Record Mode vs Replay Mode
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Record Duration&lt;/th&gt;
&lt;th&gt;Replay Duration&lt;/th&gt;
&lt;th&gt;Speedup&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;9ms&lt;/td&gt;
&lt;td&gt;8ms&lt;/td&gt;
&lt;td&gt;1.1x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/user/octocat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;472ms&lt;/td&gt;
&lt;td&gt;8ms&lt;/td&gt;
&lt;td&gt;59x faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/repos/octocat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;524ms&lt;/td&gt;
&lt;td&gt;10ms&lt;/td&gt;
&lt;td&gt;52x faster&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftj67pf1soolio3ra5u2w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftj67pf1soolio3ra5u2w.png" alt="Performance Comparison" width="800" height="800"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Comparison of request durations in record mode versus replay mode&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For endpoints without external calls, the times are similar. But anything that touches an external API or database becomes dramatically faster in replay mode.&lt;/p&gt;

&lt;p&gt;This isn't just about speed. It's about reliability. Tests that depend on external APIs can be flaky due to network issues, rate limiting, or changing data. Replay mode eliminates all those problems.&lt;/p&gt;


&lt;h2&gt;
  
  
  When to Use This
&lt;/h2&gt;

&lt;p&gt;I've found Timetracer most useful in these scenarios:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Debugging Production Bugs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When a user reports an issue, capture the failing request in production. Download the cassette and debug locally with the exact same conditions. No need to reproduce complex scenarios or guess at what data caused the problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Integration Testing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Tests that hit real APIs are slow and unreliable. Record your test scenarios once, then replay them. Tests run in milliseconds instead of seconds, and they never fail due to network issues or rate limiting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Offline Development&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Working on a plane or anywhere without internet? Load up cassettes with the API responses you need. Everything works normally without network access.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Performance Analysis&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The dashboard shows you exactly how long each external dependency takes. If your endpoint is slow, you can see whether it's your code or a slow external API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Preventing Regressions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you fix a bug, keep the cassette and add it to your test suite. That specific scenario is now covered forever.&lt;/p&gt;


&lt;h2&gt;
  
  
  Framework Support
&lt;/h2&gt;

&lt;p&gt;Timetracer works with:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1kl89ercgs5vvdvb1trd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1kl89ercgs5vvdvb1trd.png" alt="Framework Support" width="800" height="393"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Supported web frameworks and external service integrations&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Web Frameworks:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;FastAPI&lt;/li&gt;
&lt;li&gt;Starlette (new in v1.6.0)&lt;/li&gt;
&lt;li&gt;Flask&lt;/li&gt;
&lt;li&gt;Django&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;External Services:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;httpx and requests (HTTP clients)&lt;/li&gt;
&lt;li&gt;Motor and PyMongo (MongoDB)&lt;/li&gt;
&lt;li&gt;SQLAlchemy (SQL databases)&lt;/li&gt;
&lt;li&gt;Redis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The integration is similar across all frameworks. Usually just &lt;code&gt;auto_setup(app)&lt;/code&gt; or adding middleware.&lt;/p&gt;


&lt;h2&gt;
  
  
  Trade-offs to Consider
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Storage&lt;/strong&gt;: Each cassette is a JSON file. If you have many unique requests, you'll accumulate files. Clean up old cassettes periodically or store them in S3.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sensitive data&lt;/strong&gt;: Cassettes contain your actual request and response data. Review what's being captured, especially in production. Timetracer has built-in redaction for common sensitive fields like passwords and tokens, but verify this for your use case.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cassette maintenance&lt;/strong&gt;: API responses change over time. You'll need to re-record cassettes when your external dependencies change their response format.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not a replacement&lt;/strong&gt;: This isn't trying to replace your testing framework or mocking library. It's a debugging tool that captures production context and lets you work with it locally.&lt;/p&gt;


&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Install Timetracer:&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="c"&gt;# For Starlette&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;timetracer[starlette]

&lt;span class="c"&gt;# For FastAPI&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;timetracer[fastapi]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Integrate into your app:&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;timetracer.integrations.starlette&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;auto_setup&lt;/span&gt;
&lt;span class="nf"&gt;auto_setup&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;plugins&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;httpx&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;Run in record mode:&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="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TIMETRACER_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;record
uvicorn app:app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;View the dashboard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;timetracer serve &lt;span class="nt"&gt;--dir&lt;/span&gt; cassettes &lt;span class="nt"&gt;--port&lt;/span&gt; 3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test in replay mode:&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="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TIMETRACER_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;replay
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TIMETRACER_CASSETTE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;path/to/cassette.json
uvicorn app:app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The workflow I showed here - capturing a failing production request, viewing it in the dashboard, fixing the bug, and testing the fix in replay mode - saves hours compared to traditional debugging.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Trying to reproduce the bug&lt;/li&gt;
&lt;li&gt;Adding logging&lt;/li&gt;
&lt;li&gt;Redeploying&lt;/li&gt;
&lt;li&gt;Hoping you captured enough context&lt;/li&gt;
&lt;li&gt;Repeating until fixed&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Download the cassette&lt;/li&gt;
&lt;li&gt;View it in the dashboard&lt;/li&gt;
&lt;li&gt;Fix the code&lt;/li&gt;
&lt;li&gt;Verify the fix in replay mode&lt;/li&gt;
&lt;li&gt;Deploy with confidence&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The complete example code is on GitHub at &lt;a href="https://github.com/usv240/timetracer" rel="noopener noreferrer"&gt;github.com/usv240/timetracer&lt;/a&gt;. All 174 tests are passing, and version 1.6.0 just added Starlette support and PyMongo integration.&lt;/p&gt;

&lt;p&gt;If you work with external APIs, spend time debugging production issues, or want faster integration tests, give it a try.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Resources&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/usv240/timetracer" rel="noopener noreferrer"&gt;https://github.com/usv240/timetracer&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;PyPI: &lt;a href="https://pypi.org/project/timetracer" rel="noopener noreferrer"&gt;https://pypi.org/project/timetracer&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Documentation: &lt;a href="https://github.com/usv240/timetracer#readme" rel="noopener noreferrer"&gt;GitHub README&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Example code: See &lt;code&gt;examples/starlette_example/&lt;/code&gt; in the repo&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; #python #starlette #fastapi #debugging #testing #devtools&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Timetracer v1.4: Native Django Support and Easiest pytest Integration</title>
      <dc:creator>Ujwal Vanjare</dc:creator>
      <pubDate>Mon, 19 Jan 2026 18:32:01 +0000</pubDate>
      <link>https://forem.com/ujwal240/timetracer-v14-native-django-support-and-easiest-pytest-integration-5e4f</link>
      <guid>https://forem.com/ujwal240/timetracer-v14-native-django-support-and-easiest-pytest-integration-5e4f</guid>
      <description>&lt;p&gt;A few weeks ago, I introduced &lt;strong&gt;Timetracer&lt;/strong&gt; in my previous post: &lt;a href="https://dev.to/ujwal240/i-got-tired-of-it-works-on-my-machine-so-i-built-a-time-travel-debugger-235h"&gt;"I Got Tired of 'It Works on My Machine' So I Built a Time-Travel Debugger"&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The tool allows you to record API interactions (including dependencies like databases and external APIs) and replay them locally to debug production issues.&lt;/p&gt;

&lt;p&gt;The initial version focused on FastAPI and Flask. However, the most frequent feedback I received was the need for &lt;strong&gt;Django support&lt;/strong&gt; and better integration with &lt;strong&gt;pytest suites&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I just released v1.4.0, which adds both.&lt;/p&gt;

&lt;p&gt;Here is why this matters and how it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. No More "Enterprise Setup" Pain
&lt;/h3&gt;

&lt;p&gt;Django is often used for large, mature applications. These apps tend to have complex dependencies, specific database states, VPN-locked APIs, or legacy SOAP services that are a pain to run locally.&lt;/p&gt;

&lt;p&gt;With Timetracer's new middleware, you can record a session from a staging environment (or a colleague's machine that actually works) and replay it on your machine. You get the full app behavior without needing the full enterprise infrastructure running on &lt;code&gt;localhost&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Tests That Don't Lie
&lt;/h3&gt;

&lt;p&gt;We've all written tests with &lt;code&gt;unittest.mock&lt;/code&gt; where we guess what the API returns. Then the API changes, our mock stays the same, the test passes, but production breaks.&lt;/p&gt;

&lt;p&gt;The new pytest integration replaces brittle mocks with real, recorded interactions. Your tests run against actual data snapshots, making them far more reliable than manual mocks.&lt;/p&gt;




&lt;h2&gt;
  
  
  Django Support
&lt;/h2&gt;

&lt;p&gt;Timetracer now includes middleware that works with Django 3.2 LTS and newer. It supports both standard synchronous views and the newer async views in Django 4.1+.&lt;/p&gt;

&lt;p&gt;The integration captures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Incoming HTTP requests&lt;/li&gt;
&lt;li&gt;Outbound API calls (requests, httpx, aiohttp)&lt;/li&gt;
&lt;li&gt;Database queries (via SQLAlchemy for now, native ORM soon)&lt;/li&gt;
&lt;li&gt;Redis operations&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Setting it up
&lt;/h3&gt;

&lt;p&gt;First, install the package with Django dependencies:&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;timetracer[django,requests]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add the middleware in your &lt;code&gt;settings.py&lt;/code&gt;. It usually works best near the top of the list so it can capture everything:&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;# settings.py
&lt;/span&gt;
&lt;span class="n"&gt;MIDDLEWARE&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;timetracer.integrations.django.TimeTracerMiddleware&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# ... other middleware
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Optional configuration
&lt;/span&gt;&lt;span class="n"&gt;TIMETRACER&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;MODE&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;record&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# 'record', 'replay', or 'off'
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;CASSETTE_DIR&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;./cassettes&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;If your app makes external API calls, you should enable the plugins in your &lt;code&gt;AppConfig&lt;/code&gt; or &lt;code&gt;settings.py&lt;/code&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="c1"&gt;# myapp/apps.py or settings.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;timetracer.integrations.django&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;auto_setup&lt;/span&gt;

&lt;span class="c1"&gt;# Enable recording for the requests library
&lt;/span&gt;&lt;span class="nf"&gt;auto_setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plugins&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;requests&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;h3&gt;
  
  
  Workflow
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; Run your server: &lt;code&gt;python manage.py runserver&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt; Browse your site. Timetracer saves JSON "cassettes" for each request.&lt;/li&gt;
&lt;li&gt; To debug a specific request later, restart with &lt;code&gt;TIMETRACER_MODE=replay&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; The server will now mock all external calls using the recorded data.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  pytest Integration
&lt;/h2&gt;

&lt;p&gt;Previously, using recorded cassettes in tests required manual setup. v1.4.0 adds a pytest plugin that automatically registers fixtures when you install the package.&lt;/p&gt;

&lt;p&gt;This allows you to write integration tests that don't need live external APIs but still exercise your full stack.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using the Fixtures
&lt;/h3&gt;

&lt;p&gt;The plugin provides three main fixtures:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. timetracer_replay&lt;/strong&gt;&lt;br&gt;
Use this to run a test against a pre-recorded session. It guarantees the test runs exactly the same way every time.&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;def&lt;/span&gt; &lt;span class="nf"&gt;test_user_profile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timetracer_replay&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# 'user_profile.json' contains the recorded interaction
&lt;/span&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;timetracer_replay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cassettes/user_profile.json&lt;/span&gt;&lt;span class="sh"&gt;"&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;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;/api/users/123&lt;/span&gt;&lt;span class="sh"&gt;"&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;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="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;username&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;testuser&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. timetracer_record&lt;/strong&gt;&lt;br&gt;
Use this when writing a new test case. It captures the interaction so you can save it as a cassette.&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;def&lt;/span&gt; &lt;span class="nf"&gt;test_new_feature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timetracer_record&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# This will execute real network calls and save the result
&lt;/span&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;timetracer_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cassettes/new_feature.json&lt;/span&gt;&lt;span class="sh"&gt;"&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;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;/api/new-feature&lt;/span&gt;&lt;span class="sh"&gt;"&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;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="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. timetracer_auto&lt;/strong&gt;&lt;br&gt;
This is useful for TDD. If the cassette doesn't exist, it records. If it does exist, it replays.&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;def&lt;/span&gt; &lt;span class="nf"&gt;test_checkout_flow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timetracer_auto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Records first time, replays subsequently
&lt;/span&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;timetracer_auto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cassettes/checkout.json&lt;/span&gt;&lt;span class="sh"&gt;"&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&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/checkout&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;cart_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;abc&lt;/span&gt;&lt;span class="sh"&gt;'&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;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="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Async Support (aiohttp)
&lt;/h2&gt;

&lt;p&gt;We also added a plugin for &lt;code&gt;aiohttp&lt;/code&gt;. Building high-concurrency apps often requires async HTTP clients, and &lt;code&gt;aiohttp&lt;/code&gt; is a popular choice alongside &lt;code&gt;httpx&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Timetracer now correctly intercepts and records &lt;code&gt;aiohttp.ClientSession&lt;/code&gt; requests, capturing the full async flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;The goal remains effective local debugging. If you are using Django or pytest, I'd appreciate you trying this out and letting me know if it helps simplify your debugging workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repositories and Docs:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  GitHub: &lt;a href="https://github.com/usv240/timetracer" rel="noopener noreferrer"&gt;https://github.com/usv240/timetracer&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  PyPI: pip install timetracer&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>django</category>
      <category>pytest</category>
      <category>testing</category>
    </item>
    <item>
      <title>I Got Tired of "It Works on My Machine" So I Built a Time-Travel Debugger</title>
      <dc:creator>Ujwal Vanjare</dc:creator>
      <pubDate>Sun, 18 Jan 2026 06:17:35 +0000</pubDate>
      <link>https://forem.com/ujwal240/i-got-tired-of-it-works-on-my-machine-so-i-built-a-time-travel-debugger-235h</link>
      <guid>https://forem.com/ujwal240/i-got-tired-of-it-works-on-my-machine-so-i-built-a-time-travel-debugger-235h</guid>
      <description>&lt;p&gt;Last year, I spent three hours debugging a checkout bug that only happened in production. The external payment API was returning a slightly different response format than what we saw in staging. By the time I figured it out, I'd burned half my day and my patience.&lt;/p&gt;

&lt;p&gt;That was the last straw.&lt;/p&gt;

&lt;p&gt;I started building Timetracer that weekend. Now, when something breaks in production, I can capture the exact request, complete with every external API call, database query, and cache operation, and replay it on my laptop. No more guessing. No more "can you add some logging and redeploy?"&lt;/p&gt;

&lt;p&gt;This post is about why I built it, how it works, and why you might want to use it too.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Debugging Problem Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;Here's a scenario you've probably lived through:&lt;/p&gt;

&lt;p&gt;A customer reports that their order failed. You check the logs. You see the error. You try to reproduce it locally. But your local setup hits a different database, a sandbox payment API, and your Redis is empty. The bug doesn't happen.&lt;/p&gt;

&lt;p&gt;You add more logging. Redeploy. Wait for it to happen again. Check the new logs. Still can't reproduce it. Repeat.&lt;/p&gt;

&lt;p&gt;Or maybe you're working on a feature that integrates with a third-party API, but that API is rate-limited, or requires special credentials, or just goes down at random times. Every time you run your code, you're making real API calls. Testing becomes painful.&lt;/p&gt;

&lt;p&gt;Or you're trying to demo something to your team, but the staging environment is down. Or slow. Or someone else is testing migrations on it.&lt;/p&gt;

&lt;p&gt;These problems have something in common: your code depends on things outside your control, and that makes debugging and development unpredictable.&lt;/p&gt;




&lt;h2&gt;
  
  
  What If You Could Record Everything?
&lt;/h2&gt;

&lt;p&gt;The idea behind Timetracer is simple: when your API handles a request, record everything that happens. Not just the request and response, but every external call your code makes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;That HTTP call to Stripe? Recorded.&lt;/li&gt;
&lt;li&gt;That SELECT query to Postgres? Recorded.&lt;/li&gt;
&lt;li&gt;That Redis GET? Recorded.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of it goes into a JSON file we call a "cassette" (borrowing the term from VCR.py, which does something similar but only for HTTP).&lt;/p&gt;

&lt;p&gt;Later, when you want to debug, you load that cassette and replay the request. Timetracer intercepts all the external calls and returns the recorded responses instead of making real ones.&lt;/p&gt;

&lt;p&gt;Same input. Same external responses. Same bug. But now it's on your laptop, where you can step through the code, add breakpoints, and actually figure out what went wrong.&lt;/p&gt;




&lt;h2&gt;
  
  
  How It Actually Works
&lt;/h2&gt;

&lt;p&gt;Let's get into the technical stuff.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;

&lt;p&gt;If you're using FastAPI, adding Timetracer is two lines:&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;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;timetracer.integrations.fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;auto_setup&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;auto_setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;FastAPI&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 it. The &lt;code&gt;auto_setup&lt;/code&gt; function adds middleware that handles recording and replaying.&lt;/p&gt;

&lt;p&gt;For Flask, it's similar:&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;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;timetracer.integrations.flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;auto_setup&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;auto_setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You control the behavior with environment variables:&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="c"&gt;# Record mode - captures everything&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TIMETRACER_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;record
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TIMETRACER_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./cassettes

python app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now make some requests to your app. Each request creates a cassette file.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's In a Cassette?
&lt;/h3&gt;

&lt;p&gt;Here's a simplified version of what gets saved:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/checkout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"cart_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abc123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user_456"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"order_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"order_789"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"duration_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;342&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"events"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http.client"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.stripe.com/v1/charges"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"request_body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2999&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"usd"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"response_body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ch_xxx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"succeeded"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"duration_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;187&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"db.query"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"INSERT INTO orders (user_id, total) VALUES (?, ?)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"params"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"user_456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;29.99&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"duration_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything is there. The incoming request, the outgoing response, and every dependency call in between.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replaying
&lt;/h3&gt;

&lt;p&gt;To replay, point Timetracer at the cassette:&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="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TIMETRACER_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;replay
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TIMETRACER_CASSETTE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./cassettes/checkout_abc123.json

python app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when you hit &lt;code&gt;/checkout&lt;/code&gt; with the same request, Timetracer intercepts the Stripe call and the database query. Instead of making real calls, it returns the recorded responses.&lt;/p&gt;

&lt;p&gt;Your code runs exactly as it did in production, but locally, without needing Stripe credentials or a production database.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Plugins
&lt;/h2&gt;

&lt;p&gt;Recording HTTP calls is useful, but real applications do more than HTTP. That's why Timetracer has plugins for common dependencies.&lt;/p&gt;

&lt;h3&gt;
  
  
  HTTP Clients
&lt;/h3&gt;

&lt;p&gt;We support both httpx (async and sync) and the requests library:&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;timetracer.plugins&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;enable_httpx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;enable_requests&lt;/span&gt;

&lt;span class="nf"&gt;enable_httpx&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;    &lt;span class="c1"&gt;# For httpx calls
&lt;/span&gt;&lt;span class="nf"&gt;enable_requests&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# For requests library calls
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Databases
&lt;/h3&gt;

&lt;p&gt;SQLAlchemy queries get captured with their SQL and parameters:&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;timetracer.plugins&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;enable_sqlalchemy&lt;/span&gt;

&lt;span class="n"&gt;engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_engine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgresql://...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;enable_sqlalchemy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Caching
&lt;/h3&gt;

&lt;p&gt;Redis commands are recorded too:&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;timetracer.plugins&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;enable_redis&lt;/span&gt;

&lt;span class="nf"&gt;enable_redis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you replay, all of these return the recorded values. No real database connections. No real Redis. No real HTTP calls. Just the data from the cassette.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Dashboard
&lt;/h2&gt;

&lt;p&gt;After using Timetracer for a few weeks, I got tired of opening JSON files to find the cassette I needed. So I built a dashboard.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;timetracer serve &lt;span class="nt"&gt;--dir&lt;/span&gt; ./cassettes &lt;span class="nt"&gt;--open&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This starts a local web server with a table of all your cassettes. You can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sort by time, method, status, or duration&lt;/li&gt;
&lt;li&gt;Filter by endpoint, HTTP method, or status code&lt;/li&gt;
&lt;li&gt;Click a row to see full details&lt;/li&gt;
&lt;li&gt;View the dependency timeline&lt;/li&gt;
&lt;li&gt;See the raw JSON&lt;/li&gt;
&lt;li&gt;Replay directly from the browser&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Error responses show up with red highlighting, and slow requests (&amp;gt;1 second) get a warning indicator. If a request threw an exception, you see the full Python stack trace.&lt;/p&gt;

&lt;p&gt;It's nothing fancy, just a single HTML file with some JavaScript, but it makes browsing through cassettes way faster than grepping through JSON.&lt;/p&gt;




&lt;h2&gt;
  
  
  Dealing With Sensitive Data
&lt;/h2&gt;

&lt;p&gt;Early on, I realized that cassettes could contain passwords, API keys, and other stuff I definitely didn't want in my Git repo.&lt;/p&gt;

&lt;p&gt;So Timetracer automatically redacts sensitive data:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Headers that get stripped:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authorization&lt;/li&gt;
&lt;li&gt;Cookie&lt;/li&gt;
&lt;li&gt;Set-Cookie&lt;/li&gt;
&lt;li&gt;X-API-Key&lt;/li&gt;
&lt;li&gt;And about 20 others&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Body fields that get masked:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;password, secret, token&lt;/li&gt;
&lt;li&gt;credit_card, cvv, ssn&lt;/li&gt;
&lt;li&gt;api_key, private_key&lt;/li&gt;
&lt;li&gt;And about 100 more patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the latest release (v1.2.0), I added pattern detection for PII, emails, phone numbers, Social Security numbers, credit card numbers. The credit card detection even validates the Luhn checksum to avoid false positives.&lt;/p&gt;

&lt;p&gt;A value like &lt;code&gt;user@example.com&lt;/code&gt; becomes &lt;code&gt;[REDACTED:EMAIL]&lt;/code&gt;. A credit card number becomes &lt;code&gt;[REDACTED:CREDIT_CARD]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The goal is that cassettes should be safe to commit without thinking too hard about what's in them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real Use Cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Debugging Production Bugs
&lt;/h3&gt;

&lt;p&gt;This is the original reason I built it. Something breaks in production, you grab the cassette, replay locally, and debug with full context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Regression Testing
&lt;/h3&gt;

&lt;p&gt;Once you fix a bug, that cassette becomes a test case. You know exactly what input caused the problem and what the correct behavior should be. Run it against future code changes to make sure the bug doesn't come back.&lt;/p&gt;

&lt;h3&gt;
  
  
  Offline Development
&lt;/h3&gt;

&lt;p&gt;Working on a flight? At a coffee shop with bad WiFi? If you have cassettes from previous sessions, you can keep developing without hitting real APIs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Demos
&lt;/h3&gt;

&lt;p&gt;Recording demo scenarios means your demos work even when external services are flaky. No more "let me refresh that" during a presentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance Analysis
&lt;/h3&gt;

&lt;p&gt;The timeline command generates a waterfall chart showing how long each dependency took:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;timetracer timeline ./cassette.json &lt;span class="nt"&gt;--open&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see exactly where time is being spent. Is it the database query? The third-party API? The Redis lookup? It's all right there.&lt;/p&gt;




&lt;h2&gt;
  
  
  What About VCR.py?
&lt;/h2&gt;

&lt;p&gt;VCR.py is great. I've used it for years. But it only records HTTP calls.&lt;/p&gt;

&lt;p&gt;In a typical web application, a single API request might:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Query the database for user info&lt;/li&gt;
&lt;li&gt;Call an external API for pricing&lt;/li&gt;
&lt;li&gt;Check a cache for recent activity&lt;/li&gt;
&lt;li&gt;Write to another database&lt;/li&gt;
&lt;li&gt;Make another API call&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;VCR.py captures step 2 and 5. The rest? You're on your own.&lt;/p&gt;

&lt;p&gt;Timetracer captures all of it. One cassette has everything.&lt;/p&gt;

&lt;p&gt;Also, VCR.py is designed for test suites, you decorate individual test functions. Timetracer is designed as middleware. Every request through your app can be recorded. That makes it useful for production debugging, not just testing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Install with pip:&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;timetracer[fastapi,httpx]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or for Flask:&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;timetracer[flask,requests]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the middleware, set the environment variables, and make some requests. Your first cassettes will be in the directory you specified.&lt;/p&gt;

&lt;p&gt;The documentation has more details on configuration, the CLI tools, S3 storage, and all the other features I didn't cover here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/usv240/timetracer" rel="noopener noreferrer"&gt;https://github.com/usv240/timetracer&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;I'm using Timetracer on my own projects, and I keep finding things to improve. Some ideas on the roadmap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Better support for async database drivers&lt;/li&gt;
&lt;li&gt;GraphQL request parsing&lt;/li&gt;
&lt;li&gt;Request diffing between cassettes&lt;/li&gt;
&lt;li&gt;Maybe a VS Code extension?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you try it out and have feedback, bugs, feature requests, or just thoughts, I'd love to hear it. Open an issue on GitHub or reach out on LinkedIn.&lt;/p&gt;

&lt;p&gt;Thanks for reading. I hope Timetracer saves you some debugging time.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/usv240/timetracer" rel="noopener noreferrer"&gt;https://github.com/usv240/timetracer&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;PyPI: &lt;a href="https://pypi.org/project/timetracer/" rel="noopener noreferrer"&gt;https://pypi.org/project/timetracer/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>fastapi</category>
      <category>flask</category>
      <category>debugging</category>
    </item>
  </channel>
</rss>
