<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>ashwch RSS</title>
        <link>https://ashwch.com/</link>
        <description>Writing by Ashwini Chaudhary.</description>
        <lastBuildDate>Sat, 28 Feb 2026 05:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>Astro</generator>
        <language>en</language>
        <copyright>© 2026 Ashwini Chaudhary</copyright>
        <atom:link href="https://ashwch.com/rss.xml" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[No Code by Hand]]></title>
            <link>https://ashwch.com/no-code-by-hand-agentic-platform-acceleration.html</link>
            <guid isPermaLink="false">tag:ashwch.com,2026-02-28:/no-code-by-hand-agentic-platform-acceleration.html</guid>
            <pubDate>Sat, 28 Feb 2026 05:00:00 GMT</pubDate>
            <description><![CDATA[CI dropped from 37 minutes to 9, at 35% lower cost. What we learned about mixing Claude and Codex, clearing the hidden queue, and where the real multiplier came from.]]></description>
            <content:encoded><![CDATA[<p><em>CI dropped from 37 minutes to 9, at 35% lower cost. Here is what we learned about the hidden queue, mixing Claude and Codex, and where the real multiplier came from.</em></p>
<hr>
<h2 id="the-hidden-queue">The hidden queue</h2>
<p>Every engineering team has a version of this story.</p>
<p>Your roadmap looks healthy. Feature velocity looks fine. Sprint retros are tolerable. But your engineers are still losing chunks of every day to things that were never planned: waiting 16 minutes for CI to tell them a typo failed linting. Manually running six database commands because the snapshot process lives in someone’s Slack history. Bumping a dependency because Dependabot fired another alert at 3pm on a Friday.</p>
<p>That is the hidden queue. It is not on your roadmap, it is not in your sprints, and it is costing you more than most feature work.</p>
<p>Most teams do not ignore this queue on purpose. It just keeps losing in prioritization because each item feels small in isolation. “Fix the flaky test” is never going to beat “Ship the dashboard redesign” in a planning meeting. But those small items compound. A 16-minute CI wait happens 20 times a day across 5 engineers, and that adds up to over 26 hours of dead time per week - time where nobody is building anything or even thinking about the problem, just staring at a spinner.</p>
<p>Late last year, we changed one rule: <strong>if something keeps burning team time, it is roadmap work</strong> - not “tech debt we’ll get to someday,” but prioritized work with owners and metrics.</p>
<p>In the 90-day window from <strong>December 1, 2025</strong> through <strong>February 28, 2026</strong>, that rule turned into <strong>57 major non-product changesets</strong> across backend, frontend, and monolith tooling repos: <strong>56 major PRs and 1 major direct commit</strong>, touching <strong>1,303 files</strong> with <strong>120,929 additions</strong> and <strong>31,170 deletions</strong>.</p>
<figure>
  <img src="https://ashwch.com/images/articles/no-code-by-hand/changeset-mix.svg" alt="Bar chart showing major non-product changesets by repo type: backend, frontend, monolith, and direct commits.">
  <figcaption>Non-product engineering changesets in the 90-day window, broken down by repo type.</figcaption>
</figure>
<p>The point of those numbers is that a whole class of work moved from “someday” to “done” - and we did it without pausing product delivery.</p>
<hr>
<h2 id="what-we-shipped-this-quarter">What we shipped this quarter</h2>
<p>Here is the breadth of what moved in those 90 days, all in parallel:</p>
<ul>
<li><strong>CI architecture</strong> - Rewired how we decide what checks to run, when, and at what cost</li>
<li><strong>Testing platform</strong> - Built E2E and integration test coverage in phases, replaced flaky waits with deterministic ones</li>
<li><strong>Local development</strong> - Made git worktrees reliable so agents and engineers can run multiple branches in parallel without collisions</li>
<li><strong>Operations safety</strong> - Took processes that lived in people’s heads (snapshots, support shells, admin actions) and gave them proper guardrails and audit trails</li>
<li><strong>Security remediation</strong> - Closed CVEs and Dependabot alerts in planned waves instead of random fire-drills</li>
<li><strong>Debt retirement</strong> - Squashed tangled migration graphs, removed dead Slack/Teams integrations</li>
<li><strong>Open-source skills</strong> - Turned repeating engineering patterns into reusable agent skills and open-sourced them</li>
</ul>
<figure>
  <img src="https://ashwch.com/images/articles/no-code-by-hand/workstream-coverage.svg" alt="Horizontal bar chart showing major non-product changesets split across multiple workstreams.">
  <figcaption>CI was one stream inside a much broader platform quarter.</figcaption>
</figure>
<p>Most teams can point to one thing they improved. “We made CI faster.” “We added more tests.” Seven high-friction systems in parallel while keeping review quality, rollback safety, and product delivery intact is a different kind of claim. So the obvious question is: how?</p>
<hr>
<h2 id="why-this-was-possible-now-and-not-a-year-ago">Why this was possible now and not a year ago</h2>
<p><a href="https://x.com/karpathy/status/2026731645169185220">Karpathy put it bluntly</a>: coding agents basically did not work before December 2025, and they basically work now. He described giving an agent a full system setup task - SSH keys, vLLM, web dashboard, systemd services - and it came back 30 minutes later with everything working. He did not touch anything.</p>
<p>That matched what we were seeing. As recently as October 2025, the same tools felt like expensive autocomplete. By December, a new generation of models had shipped and changed that completely. <a href="https://mitchellh.com/writing/my-ai-adoption-journey">Mitchell Hashimoto</a> went through the same arc - from skeptic to building his entire Ghostty workflow around agents, with the key insight that you have to separate planning from execution and let the agent self-correct. <a href="https://x.com/paulg/status/2022604692178522562">Paul Graham</a> framed the implication: “In the AI age, taste will become even more important. When anyone can make anything, the big differentiator is what you choose to make.” <a href="https://simonwillison.net/guides/agentic-engineering-patterns/hoard-things-you-know-how-to-do/">Simon Willison</a> figured out the compounding angle: “Coding agents mean we only ever need to figure out a useful trick once.”</p>
<p>They were all talking about the same shift from different angles, but none of them say loudly enough what actually caused it: <strong>the models got qualitatively better.</strong></p>
<p>Claude Opus 4.5, Opus 4.6, Codex 5.2, Codex 5.3 - these are not incremental improvements. They can hold entire systems in context, reason about architectural tradeoffs, catch multi-tenant security patterns, and produce code that is reviewable on the first pass. That kind of jump is what made No Code by Hand our default working mode since December.</p>
<p>Our team learned the differences through daily use, not benchmarks. We got opinionated fast, and our honest assessment does not look like any vendor’s marketing page:</p>
<p><strong>Claude models</strong> (Opus 4.5, Opus 4.6, Sonnet) are our workhorse for execution. They are fast - genuinely fast, even at high thinking levels. They are great at quick fixes, running skills that require less deliberation, verifying and formatting large numbers of files, and chewing through well-scoped tasks at speed. The <a href="https://github.com/anthropics/claude-agent-sdk-python">Claude Agent SDK</a> (available in both Python and TypeScript) is mature and well-designed - it gives you the full agent runtime with tools, subagent orchestration, MCP support, and granular permissions out of the box. If you are building custom workflows, that SDK matters a lot.</p>
<p>The tradeoff: Claude models can be eager. They will take shortcuts on medium to large features, skip files, and miss critical business logic if you are not watching. They also consume tokens fast - you can burn through a significant budget in a short session. You get results quickly, but you end up verifying more.</p>
<p><strong>Codex models</strong> (Codex 5.2, Codex 5.3) are where we go for depth. Planning, architectural reasoning, thorough code review, anything where you need the model to actually think through the full problem before writing code. Execution is slower, but the output is more satisfying - it will not skip critical business logic or take shortcuts the way Claude sometimes does. It respects the complexity of what you are asking it to do.</p>
<p>The tradeoff: Codex is weaker with tool use compared to Claude models. The SDK story is thinner - there is a <a href="https://developers.openai.com/codex/sdk/">Codex SDK</a> (TypeScript only), but it is an orchestration wrapper for controlling a local Codex agent, not a general-purpose agent-building runtime like Claude’s. No Python equivalent. Token limits, on the other hand, are very hard to hit - you can run long, deep sessions without worrying about context exhaustion.</p>
<p><strong>In practice, we mix both.</strong> A lot of our best work comes from running Claude and Codex together, or multiple instances of the same model, each with a clean context scoped to a specific piece of the problem. One model plans while another executes, or one reviews what the other wrote. We split large tasks across separate sessions so no single context gets polluted with too much state.</p>
<p>With Codex specifically, we use <strong>steer mode</strong> and <strong>queue mode</strong> very tactically depending on the task:</p>
<ul>
<li><strong>Steer mode for ambiguous work.</strong> Anything that involves architectural decisions, multi-tenant security logic, or unfamiliar parts of the codebase - we steer. Example: when building the admin safety model (the system that prevents admins from locking themselves out), we started Codex in steer mode, pointed it at the permission hierarchy, course-corrected twice when it tried to flatten the role graph, and guided it through the edge cases where a superadmin demotes themselves. The final implementation was clean because we were in the loop at every decision point instead of reviewing a finished mess.</li>
<li><strong>Queue mode for well-scoped, parallelizable work.</strong> When the task is clear and the scope is bounded, queue mode lets us fire off multiple Codex tasks and review them as they land. Example: during the CI migration, we queued separate tasks for each workflow file rewrite - linting pipeline, test matrix, deployment gates - each with its own clean context. Five tasks running in parallel, each one scoped tightly enough that the model did not need steering.</li>
<li><strong>The tactical switch.</strong> We start most sessions in steer mode. If the first few interactions show the model understands the problem and the scope is narrowing, we switch to queue mode and let it run. If it starts drifting, we pull it back to steer. We re-evaluate steer vs queue mode throughout the session based on drift, clarity, and risk.</li>
</ul>
<p>Picking the right model for the right task - and knowing when to combine them - is itself a skill that takes time to develop, and teams that treat all models as interchangeable are missing most of the value.</p>
<p><strong>The biggest shift is how these models want to be used.</strong> Earlier generations worked best when you gave them a prompt and left them alone. The current generation is different - they are built for interactive steering. You set direction, check in as it works, course-correct when it drifts, and guide it through the ambiguous parts rather than hoping it guesses right. OpenAI made this explicit with <a href="https://openai.com/index/introducing-gpt-5-3-codex/">Codex 5.3</a>: steer mode - where you interact with the model while it works without losing context - shipped as a <a href="https://github.com/openai/codex/pull/10690">stable feature</a>, enabled by default. Anthropic’s <a href="https://www.anthropic.com/news/claude-opus-4-6">Claude Opus 4.6</a> moved in the same direction with adaptive thinking, where the model adjusts its reasoning depth based on what you are asking and responds to mid-turn guidance.</p>
<p>This matters more than it sounds. <a href="https://ampcode.com/news/gpt-5.3-codex">AmpCode put it well</a>: “the agent is no longer the bottleneck. Our ability to tell it what to do is.” A year ago the workflow was write a prompt, wait, review what comes back. Now it is closer to pair programming - you stay in the loop, steer the model toward the right answer as it works, and course-correct in real time rather than filtering through wrong answers afterward. Teams that still treat agents as black boxes they throw tasks at are working against how these models are actually designed.</p>
<p>Our team has taken this to its logical conclusion: we have pretty much ditched our IDEs entirely. We work strictly in terminals - Claude Code, Codex CLI, git, and shell scripts. The models are better at navigating codebases than any file tree sidebar ever was, and the terminal is where steering actually happens - no GUI layer sitting between you and the agent. <a href="https://the-decoder.com/andrej-karpathy-says-programming-is-unrecognizable-now-that-ai-agents-actually-work/">Karpathy put it bluntly</a>: “You’re not typing computer code into an editor like the way things were since computers were invented. That era is over.” What replaced it is spinning up agents, giving them tasks in English, and managing their work in parallel - but with what he calls “high-level direction, judgement, taste, oversight, iteration, and hints and ideas.” That is steering, not autopilot.</p>
<p>None of this means the models write perfect code or replace engineering judgment. What changed is how cheap it became to try something, evaluate it, and try again. When a rewrite takes 20 minutes instead of a day, you can try three approaches and pick the best one. You can afford to be thorough instead of cutting corners because iteration itself got cheap.</p>
<p>That context matters for everything that follows. The hidden queue shrank because each iteration got about 10x cheaper, so we could clear items that used to stay deferred. So we started with the one system where we could prove it.</p>
<hr>
<h2 id="ci-the-system-we-could-actually-measure">CI: the system we could actually measure</h2>
<p>We started with CI because it was the one system where we had clean telemetry and clear before/after boundaries. You can argue about whether a refactor “improved code quality.” You cannot argue about whether a workflow went from 942 seconds to 675 seconds. The numbers are right there.</p>
<h3 id="the-real-problem-was-architectural">The real problem was architectural</h3>
<p>Before the redesign, our CI pipeline had a fundamental coupling problem. One heavy workflow path tried to answer two completely different questions at the same time:</p>
<ol>
<li><em>Should expensive checks even run on this change?</em> (A typo in a README does not need database-level integration tests.)</li>
<li><em>Is this change safe to ship?</em> (A migration change absolutely does.)</li>
</ol>
<p>When those two questions are coupled, you get the worst of both worlds. Low-risk changes overpay - a CSS fix waits 16 minutes for checks it does not need. High-risk changes wait behind noise - a database migration sits in the same queue as a copy change.</p>
<h3 id="what-we-actually-changed">What we actually changed</h3>
<p>The fix was separating qualification from execution - figuring out which checks should run before actually running them:</p>
<ul>
<li><strong>Fast checks moved earlier.</strong> Linting, type checking, import validation, and test placement checks all moved into a GitHub Actions workflow that runs in under 45 seconds. If these fail, nothing downstream even starts.</li>
<li><strong>Scope classification.</strong> A file-change classifier now evaluates every PR: does this change touch backend code? Database schemas? Frontend only? Docs only? Changes that are irrelevant to deep checks skip them entirely.</li>
<li><strong>Content-aware caching.</strong> Instead of time-based cache invalidation, caches are now keyed on actual content hashes. Same code = same cache, regardless of when you last ran.</li>
<li><strong>Runtime image optimization.</strong> The CircleCI runtime image was rebuilt to eliminate bootstrap overhead - the time between “job starts” and “your code actually runs.”</li>
</ul>
<h3 id="the-numbers">The numbers</h3>
<p>On the heavy path (CircleCI test-and-check workflow), the broad pre/post window around the runtime-image milestone gave the strongest signal:</p>
<ul>
<li><strong>Median workflow wall time</strong>: 942.5s → 675.0s, a <strong>28.4% reduction</strong> (<code>n=218</code> before, <code>n=149</code> after)</li>
<li><strong>Deepest check lane</strong> (Deep DB/RLS checks): 332.0s → 95.5s, a <strong>71.2% reduction</strong></li>
</ul>
<figure>
  <img src="https://ashwch.com/images/articles/no-code-by-hand/ci-heavy-lane-pre-post.svg" alt="Bar chart comparing heavy CI workflow median runtime before and after the runtime image shift.">
  <figcaption>Heavy validation median runtime: 942.5s → 675.0s, a 28.4% reduction.</figcaption>
</figure>
<p>Those are the headline numbers, but they are only half the story - because the first win came with an uncomfortable tradeoff.</p>
<hr>
<h2 id="why-we-kept-going-after-the-first-win">Why we kept going after the first win</h2>
<p>After the first round of CI improvements, we had an intermediate phase where runtime improved but credit consumption actually got <em>worse</em>. We were running more things in parallel, which made the wall clock faster, but the total compute bill went up.</p>
<p>Most blog posts would stop here, post the runtime chart, and declare victory. We did not, and honestly this next part matters more than the numbers above.</p>
<p>We kept running the loop: <strong>hypothesis → implementation → measurement → tradeoff review → tune or rollback → standardize</strong>. That loop is where cheap correction cycles made the real difference. When rewriting a CI configuration takes 20 minutes instead of a day, you can afford to try three approaches and pick the best one.</p>
<h3 id="three-phases-of-tradeoff">Three phases of tradeoff</h3>
<p>The three-phase view shows the full path, including the uncomfortable middle:</p>
<ul>
<li><strong>Baseline</strong>: 37.91 min critical path, 359 mean credits per paired run</li>
<li><strong>Parallel phase</strong>: 10.63 min critical path (great!), 456 credits (worse - more parallel jobs)</li>
<li><strong>Current phase</strong>: 9.08 min critical path, 232 credits (both better than baseline)</li>
</ul>
<p>Relative to baseline, the current mode is <strong>76% faster</strong> and <strong>35% cheaper</strong>. The current sample in this phase comparison is still early (<code>n=6</code>), so we treat it as directional and use the larger pre/post windows for stronger claims.</p>
<figure>
  <img src="https://ashwch.com/images/articles/no-code-by-hand/ci-three-phases-runtime-credits.svg" alt="Two-panel chart comparing critical-path runtime and combined credits across baseline, parallel, and current CI phases.">
  <figcaption>Three CI phases: faster runtime and lower compute cost in the current mode.</figcaption>
</figure>
<h3 id="modeled-time-savings">Modeled time savings</h3>
<p>The waiting-time model across this window estimates <strong>21.39 hours</strong> of developer waiting time recovered, broken into three categories:</p>
<ul>
<li><strong>11.07h</strong> from faster heavy lanes (every CI run finishes sooner)</li>
<li><strong>6.38h</strong> from early-stop on failed fast checks (if linting fails in 30 seconds, you do not wait 15 minutes for full tests to also fail)</li>
<li><strong>3.94h</strong> from skipping irrelevant deep runs (a docs-only change skips database checks entirely)</li>
</ul>
<p>This is a model, not direct causal proof. But 21 hours of waiting time eliminated over a few weeks is a meaningful quality-of-life improvement for a small team.</p>
<figure>
  <img src="https://ashwch.com/images/articles/no-code-by-hand/ci-waiting-time-model.svg" alt="Stacked bar chart showing modeled waiting-time reductions from three CI improvements.">
  <figcaption>Modeled waiting-time reduction: 21.39 hours across three improvement categories.</figcaption>
</figure>
<hr>
<h2 id="every-ci-job-not-just-the-workflow">Every CI job, not just the workflow</h2>
<p>The workflow-level numbers tell one story. The job-level numbers tell a more interesting one.</p>
<p>The “Deep DB/RLS checks” job - the single most expensive check in our pipeline, the one that spins up a full database and runs row-level security validations - went from <strong>332s to 95.5s</strong>. That is a <strong>71% reduction</strong> on the job that matters most for safety confidence.</p>
<figure>
  <img src="https://ashwch.com/images/articles/no-code-by-hand/ci-deep-check-lane.svg" alt="Paired horizontal bars showing before/after runtimes for each CI job.">
  <figcaption>Job-level improvements after the runtime image shift. The deepest check lane dropped 71%.</figcaption>
</figure>
<p>That improvement came primarily from the runtime image rebuild. The old image was doing expensive setup work (installing system packages, configuring database connections, bootstrapping test fixtures) on every single run. The new image bakes all of that into the image layer, so the job starts with everything already in place.</p>
<p>It sounds boring, but it changes daily life. At 5.5 minutes you context-switch to something else and lose focus. At 1.5 minutes you just wait for it and stay in the flow. And then we looked at the trigger configuration and found something we should have caught months ago.</p>
<hr>
<h2 id="we-were-running-the-same-checks-twice">We were running the same checks twice</h2>
<p>Here is a problem that is embarrassingly common and embarrassingly wasteful: GitHub fires both a <code>push</code> event and a <code>pull_request</code> event when you push to a branch with an open PR. If your CI triggers on both, you run the same checks twice on the same code.</p>
<p>Before we fixed this, the ratio was absurd: <strong>399 push-triggered runs</strong> vs <strong>22 PR-triggered runs</strong> on non-deploy branches. An 18:1 ratio of redundant work.</p>
<p>After dedup (<code>#2713</code>): <strong>9 push runs</strong> vs <strong>36 PR runs</strong>. A 0.25:1 ratio. Only meaningful work runs.</p>
<figure>
  <img src="https://ashwch.com/images/articles/no-code-by-hand/ci-dedup-effect.svg" alt="Before and after comparison of push vs PR triggered CI runs showing dramatic reduction in redundant runs.">
  <figcaption>Before dedup: 18x more push runs than PR runs. After: only meaningful runs execute.</figcaption>
</figure>
<p>A few hours of investigation, hundreds of compute-hours saved over a quarter. And it is exactly the kind of thing that never gets prioritized because “CI is working fine” - as long as nobody looks at the bill.</p>
<p>With CI finally fast and honest, we could tackle the thing it depends on most: whether the tests themselves were worth trusting.</p>
<hr>
<h2 id="the-test-suite-we-could-not-trust">The test suite we could not trust</h2>
<p>The testing work this quarter was about building a testing <em>system</em> that could be trusted by default and maintained without heroics, not just adding more test files.</p>
<h3 id="why-phased-rollout-matters">Why phased rollout matters</h3>
<p>The most common failure mode for test initiatives is the big-bang rewrite: someone decides “we need 80% coverage,” writes hundreds of tests in a week, and three months later half of them are flaky, ignored, or commented out. We did the opposite.</p>
<figure>
  <img src="https://ashwch.com/images/articles/no-code-by-hand/testing-platform-phases.svg" alt="Phased testing platform diagram showing progression from infrastructure to deterministic stabilization.">
  <figcaption>Testing rolled out in phases instead of one risky rewrite.</figcaption>
</figure>
<p>The sequence:</p>
<ol>
<li><strong>Infrastructure first.</strong> Test runner configuration, fixture management, database seeding, environment isolation - the kind of work nobody wants to do but everything else depends on.</li>
<li><strong>Role-based E2E flows.</strong> Tests organized by user role - HR admin, manager, employee - because that is how our permission model works. Testing by role catches the bugs that actually happen in production: “this works for admins but breaks for regular users.”</li>
<li><strong>Unit and integration depth.</strong> Once the E2E skeleton was stable, we filled in unit and integration tests underneath. The E2E tests act as a safety net while you add lower-level coverage.</li>
<li><strong>Deterministic stabilization.</strong> The final phase was eliminating flakiness. And that brings us to one of the most universally hated problems in software engineering.</li>
</ol>
<h3 id="the-sleep2000-problem">The <code>sleep(2000)</code> problem</h3>
<p>If you have ever maintained E2E tests, you know this pain intimately. The test clicks a button, then waits. How long should it wait?</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="javascript"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// This is a guess</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">await</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> page.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">click</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'#submit'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">await</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> sleep</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">2000</span><span style="color:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">expect</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(page.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">url</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()).</span><span style="color:#6F42C1;--shiki-dark:#B392F0">toBe</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'/dashboard'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">);</span></span></code></pre>
<p>Two seconds works on your laptop. It fails on CI because the server is slower. You bump it to 3 seconds. Now the test passes but the suite takes 40 minutes. Someone adds <code>sleep(5000)</code> “just to be safe.” Now it takes an hour. And it still flakes on Mondays when the CI machines are loaded.</p>
<p>The fix is simple to explain and tedious to actually do: go through every test and replace fixed waits with waits that check whether the thing actually happened.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="javascript"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// This is an assertion about behavior</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">await</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> page.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">click</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'#submit'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">await</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> page.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">waitForURL</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'/dashboard'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">);</span></span></code></pre>
<p>The second version waits exactly as long as needed - 200ms on a fast run, 3 seconds on a slow one - and fails immediately with a clear error if the URL never changes. That one shift improved reliability, review trust, and debugging speed because failures started telling you what actually went wrong instead of “timeout after 5000ms.”</p>
<p>A few of the replaced waits started failing consistently rather than flaking - which turned out to be a gift, because the generous sleeps had been hiding genuine performance issues in the application. Fixing the tests ended up fixing the app.</p>
<p>None of that would have mattered, though, if the environment running those tests kept colliding with itself.</p>
<hr>
<h2 id="why-two-agents-could-not-run-at-the-same-time">Why two agents could not run at the same time</h2>
<p>If you are doing agentic coding seriously, Worktrees are the base layer for agentic coding; without isolated checkouts, parallel execution breaks down.</p>
<p>An AI coding agent needs its own isolated checkout to work in. It cannot share your working directory - it would stomp on your files, your running servers, your local state. Git worktrees solve this cleanly: each worktree is a separate working copy of the repo with its own branch, its own file state, and its own index. You can have an agent working on a refactor in one worktree while you are reviewing a PR in another and a second agent is writing tests in a third.</p>
<p>But worktrees only deliver that promise if your local development setup is worktree-aware. Ours was not. And until we fixed it, every other improvement in this post would have been bottlenecked by a broken local environment.</p>
<h3 id="what-was-actually-breaking">What was actually breaking</h3>
<p>Every worktree tries to spin up its own local dev environment. Without isolation, that means:</p>
<ul>
<li>Branch A starts Docker with the default port mapping: <code>localhost:8000</code> for the API, <code>localhost:3000</code> for the frontend.</li>
<li>An agent opens a worktree for Branch B. It also wants <code>localhost:8000</code> and <code>localhost:3000</code>.</li>
<li>Port collision. One of them fails silently or grabs a random port. The agent’s frontend is now talking to your backend running different code. Or your server crashes because the port is taken. Either way, someone wastes 20 minutes debugging a port-conflict issue that looks like an app bug and burns 20 minutes of debugging.</li>
<li>Meanwhile, Branch A’s <code>.env</code> file says <code>API_URL=http://localhost:8000</code>. Branch B’s <code>.env</code> says the same thing. The frontend in one worktree makes API calls to a server that is running code from a completely different branch.</li>
</ul>
<p>This blocked the entire agentic workflow. If worktrees are unreliable, agents cannot work in parallel, and the speed advantage you are counting on just collapses.</p>
<figure>
  <img src="https://ashwch.com/images/articles/no-code-by-hand/local-dev-isolation.svg" alt="Before and after diagram of local development showing worktree port collisions replaced by deterministic port isolation.">
  <figcaption>Before: shared ports collide across worktrees. After: deterministic isolation per worktree.</figcaption>
</figure>
<h3 id="the-fix">The fix</h3>
<p>The primary checkout behaves exactly like before - same ports, same config, no surprises for the human developer. But every non-primary worktree gets <strong>deterministic port isolation</strong>: ports are derived from the worktree path, so they are stable (the same worktree always gets the same ports) and unique (no two worktrees ever collide).</p>
<p>We added root-level wrappers - <code>just up</code>, <code>just down</code>, <code>just sync-api-env</code> - so that starting, stopping, and syncing environment state became a single predictable command regardless of which worktree you are in. The frontend environment file automatically picks up the correct backend URL for whichever worktree is running. An agent can <code>just up</code> in its worktree and get a fully working, fully isolated local environment without any human intervention.</p>
<p>This unlocked everything else. Once worktrees were reliable, we could run multiple agents in parallel on different tasks, each with its own branch, its own server, its own test environment. Most of the speedup came from running three or four agents in parallel without collisions.</p>
<p>But that speed advantage hits a wall the first time one of those agents needs to run a database snapshot.</p>
<hr>
<h2 id="operations-when-the-process-lives-in-someones-head">Operations: when the process lives in someone’s head</h2>
<p>Every engineering team has critical processes that work perfectly as long as the right person is online and remembers the exact sequence of commands - which is really just a single point of failure wearing a human costume.</p>
<p>This quarter we found three of these in our own stack and rebuilt them.</p>
<h3 id="database-snapshots">Database snapshots</h3>
<p>Before: creating a database snapshot for a customer or for staging required running six commands in exact order. Dump the SQL. Package it. Upload to the right bucket. Restore on the target. Create the snapshot record. Notify the team in Slack. Miss a step and you get a corrupt snapshot. Run them out of order and you overwrite something. The whole process lived in one engineer’s head and a Slack message from eight months ago.</p>
<figure>
  <img src="https://ashwch.com/images/articles/no-code-by-hand/snapshot-automation.svg" alt="Before: 6 manual steps in a chain. After: one command with built-in error handling.">
  <figcaption>Snapshot creation went from 6 manual steps to one command with preflight checks and Slack notification.</figcaption>
</figure>
<p>After: <code>./manage.sh snapshot create</code>. One command. It runs preflight checks (is the source database accessible? Is there enough disk space?), executes the full pipeline with error handling at every step, and posts a Slack notification when it is done or if it fails. Any engineer can run it. No tribal knowledge required.</p>
<h3 id="admin-actions">Admin actions</h3>
<p>Before: high-impact admin actions (think: merging two user accounts, resetting organization data, modifying subscription state) were one-click operations with no confirmation step. Click the button, the mutation happens immediately, no undo. One accidental click on the wrong row in a moment of pressure = irreversible data loss.</p>
<figure>
  <img src="https://ashwch.com/images/articles/no-code-by-hand/admin-safety-model.svg" alt="Before: click and pray. After: plan, confirm, execute with eligibility checks.">
  <figcaption>Admin actions moved from one-click mutations to a plan → confirm → execute model with eligibility checks.</figcaption>
</figure>
<p>After: high-impact actions follow a three-step model. First, <strong>review the impact</strong> - the system shows you exactly what will change and who is affected. Second, <strong>confirm the scope</strong> - you explicitly acknowledge what you are about to do. Third, <strong>execute</strong> - only after passing eligibility checks and risk assessment. Going from “click and pray” to “plan, confirm, execute” is how you turn potential incidents into boring Tuesdays.</p>
<h3 id="support-shell-workflows">Support shell workflows</h3>
<p>Support shells - the tools your team uses to diagnose and fix customer issues in production - got a significant upgrade. Instead of an engineer manually running queries and eyeballing results, the shell now surfaces relevant context, suggests likely fixes, and handles the common reconciliation patterns automatically. The shell keeps engineering judgment in the loop while shortening the path from incident report to root-cause diagnosis.</p>
<figure>
  <img src="https://ashwch.com/images/articles/no-code-by-hand/ops-safety-surfaces.svg" alt="Grid showing operational surfaces moved from person-dependent choreography to guarded control planes.">
  <figcaption>Operations power paths rebuilt as safer, repeatable, auditable surfaces.</figcaption>
</figure>
<p>There is no product launch here and nobody is going to tweet about it, but this is the work that keeps incidents smaller, response times tighter, and makes sure the process still works when the person who “knows how snapshots work” is on vacation. It also removed the last excuse for not touching the security backlog.</p>
<hr>
<h2 id="security-from-random-interrupts-to-planned-waves">Security: from random interrupts to planned waves</h2>
<p>Here is how security and dependency work usually goes: Dependabot opens an alert. Someone sees it, maybe today, maybe next week. They bump the version, run the tests, fix whatever breaks, open a PR. It gets reviewed between other work. Meanwhile three more alerts have fired. The PRs conflict with each other. Merge churn increases. Review quality drops because everyone is context-switching.</p>
<p>We replaced that pattern with a <strong>wave-based remediation model</strong>:</p>
<ul>
<li><strong>Wave 1</strong>: Patch-level security updates. Django, Pillow, SimpleJWT, pyasn1. Low-risk, high-confidence bumps.</li>
<li><strong>Wave 2</strong>: Cryptography chain. <code>cryptography</code> and <code>weasyprint</code> updates that cascade through the dependency tree. Medium risk, needs careful testing.</li>
<li><strong>Wave 3</strong>: Deep transitive dependencies. Refreshing the protobuf chain through Google’s dependency graph. Higher complexity, so we isolated it into its own review cycle.</li>
</ul>
<figure>
  <img src="https://ashwch.com/images/articles/no-code-by-hand/security-dependabot-program.svg" alt="Layered security remediation diagram showing staged dependency waves, workflow hardening, and release-integrated closure checks.">
  <figcaption>Security moved from reactive interrupts to a layered, wave-based remediation program.</figcaption>
</figure>
<p>The security work went beyond dependency bumping. In the same window, we also:</p>
<ul>
<li><strong>Hardened CI input handling.</strong> GitHub Actions workflows can receive untrusted input from PR titles, branch names, and commit messages. If those inputs flow unsanitized into shell commands, that is a command injection vulnerability <em>in your CI pipeline</em>. We audited and hardened those paths.</li>
<li><strong>Reduced PII exposure in telemetry.</strong> Logging and error tracking payloads were carrying more personally identifiable information than they needed to. We scrubbed those payloads down to what is actually useful for debugging without exposing user data.</li>
</ul>
<p>The practical result is that security and dependency work stopped being an interrupt. We close issues faster, in planned batches with proper review scope, and the rest of the team can actually plan their week without random fire-drills derailing it.</p>
<p>The security waves also forced us to confront what we had been putting off: the migration graph and a pile of dead integration code that had been making every other change harder than it needed to be.</p>
<hr>
<h2 id="debt-the-art-of-safe-subtraction">Debt: the art of safe subtraction</h2>
<p>Deleting code is easy. Deleting the <em>right</em> code without breaking something in production takes a lot more care.</p>
<h3 id="migration-topology">Migration topology</h3>
<p>Our Django migration graph had accumulated cruft over time - the way they always do. Branches, merges, dependencies that used to make sense but now just add confusion. New developers would look at the migration graph and have no idea what was load-bearing and what was historical accident.</p>
<figure>
  <img src="https://ashwch.com/images/articles/no-code-by-hand/migration-squash.svg" alt="Before: tangled migration graph with cross-dependencies. After: clean, linear migration chain.">
  <figcaption>Migration topology simplified from a tangled graph to clean lineage.</figcaption>
</figure>
<p>We used a two-step squash strategy:</p>
<ol>
<li><strong>Introduce replacement lineage.</strong> New squashed migrations replace the old chain, but the old migrations stay around for compatibility. Both paths work.</li>
<li><strong>Archive and prune.</strong> Once the new lineage is stable and deployed, remove the old chain. The graph is clean.</li>
</ol>
<p>This approach respects the reality that migrations are one of the scariest parts of a Django application. You do not yolo a migration squash. You prove the new path works while the old path is still available as a fallback.</p>
<h3 id="dead-integration-code">Dead integration code</h3>
<p>We also retired deprecated Slack and Teams integration paths - but only after proving they were genuinely unused or had working replacements. “Dead” integration code is never truly dead. It still shows up in code reviews (“is this still used?”), expands the upgrade surface (“this import broke after the SDK update”), and confuses new contributors (“should I be using this API or the other one?”).</p>
<p>Removing it deliberately - with evidence that nothing was actually using it - means every future engineer who touches that part of the codebase has one less thing to be confused about.</p>
<p>By this point we had noticed a pattern: the cleanup workflows, the review checklists, the remediation sequences - we kept solving similar problems the same way. Why were we still rebuilding them from scratch each time?</p>
<hr>
<h2 id="the-biggest-multiplier-was-not-a-model">The biggest multiplier was not a model</h2>
<p>If I had to pick the single biggest multiplier from this quarter, it would be this section, and it connects directly to why model selection matters.</p>
<p>Here is what kept happening: an engineer figures out a really effective way to use Opus for a thorough code review, or they discover that Codex handles Dependabot triage perfectly if you give it the right structure. It works great for them. Then it lives in their head, or in a DM, or in a prompt they copy-paste from a notes app. That is useful for one person, but it does not spread.</p>
<p>We took a different approach: we turned those patterns into reusable agent skills and commands, and open-sourced the whole layer in <a href="https://github.com/DiversioTeam/agent-skills-marketplace">DiversioTeam/agent-skills-marketplace</a>. Right now that repo includes <strong>15 plugins, 17 skills, and 37 commands</strong> covering:</p>
<ul>
<li><strong>Code review</strong> - Hyper-pedantic Django review skills that check for multi-tenant safety, migration correctness, and security patterns</li>
<li><strong>Commit and PR hygiene</strong> - Atomic commit helpers that enforce pre-commit hooks, lint gates, and clean commit messages</li>
<li><strong>Dependabot remediation</strong> - Wave-based triage and execution for both frontend and backend dependency management</li>
<li><strong>Release operations</strong> - PR creation, version bumping, merge conflict resolution, and release publishing</li>
<li><strong>Docs generation</strong> - Repository documentation, API docs from Bruno files, AGENTS.md canonical formats</li>
</ul>
<figure>
  <img src="https://ashwch.com/images/articles/no-code-by-hand/skills-compounding-flywheel.svg" alt="Compounding flywheel diagram for open-source skills: pattern discovery, codification, team reuse, and baseline uplift.">
  <figcaption>Open-source skills turn individual technique into team-level memory.</figcaption>
</figure>
<p>This is where <a href="https://simonwillison.net/guides/agentic-engineering-patterns/hoard-things-you-know-how-to-do/">Simon Willison’s insight</a> lands hardest: “Coding agents mean we only ever need to figure out a useful trick once.” That is exactly what skills do. Someone figures out the best way to run a security audit with Opus. It becomes a skill. Now every engineer on the team runs security audits at that level, automatically.</p>
<p>Instead of “someone has a strong prompt in a DM,” these became shared tools that anyone on the team can run, inspect, and improve. They live in version control with clear entry points, not in someone’s clipboard.</p>
<p>This matters for two reasons:</p>
<ol>
<li><strong>Reviews stop depending on who is doing them.</strong> When every code review uses the same skill with the same guardrails, the floor rises. You stop getting thorough reviews from one person and surface-level reviews from another.</li>
<li><strong>Onboarding gets faster.</strong> New engineers do not need to discover the team’s workflow through osmosis. They install the skills and inherit months of accumulated process knowledge on day one.</li>
</ol>
<p>This is why I keep saying agentic coding is an execution multiplier, but also a <strong>learning multiplier</strong>. The model’s capability matters, but what decides whether gains stick across the team or disappear when one person goes on vacation is whether you have a skill system.</p>
<p>We are not the only ones who figured this out. The ecosystem is moving fast - <a href="https://github.com/anthropics/claude-code/tree/main/plugins">Anthropic ships official plugins for Claude Code</a>, <a href="https://github.com/vercel-labs/agent-skills/">Vercel published a library of agent skills</a>, and individual developers are releasing genuinely useful tools like <a href="https://github.com/nicobailon/visual-explainer">visual-explainer</a> that solve problems we would have built ourselves. More and more companies and individuals are open-sourcing skills that slot right into existing agent workflows, and our team is actively pulling the best ones into how we work. The value of a skill system goes up the more people contribute to it - not just inside your team, but across the industry.</p>
<p>We did not see what the skills layer had actually done until we stepped back and looked at the quarter as a whole.</p>
<hr>
<h2 id="how-it-all-compounded">How it all compounded</h2>
<p>What makes this quarter more than a list of separate improvements is that the workstreams were not independent. They fed into each other, and each one made the next one easier to pull off.</p>
<figure>
  <img src="https://ashwch.com/images/articles/no-code-by-hand/reinforcement-system-map.svg" alt="System map showing how CI, testing, local development, ops safety, security, and skills codification reinforce each other.">
  <figcaption>Direct and indirect reinforcement loops across the quarter's workstreams.</figcaption>
</figure>
<p>Here is how the loops worked:</p>
<ul>
<li><strong>CI speed → testing velocity.</strong> Faster CI feedback meant testing improvements were cheaper to validate. You could iterate on a test three times in an hour instead of once.</li>
<li><strong>Testing quality → CI signal.</strong> Better tests with deterministic waits produced cleaner CI signal. Fewer flaky failures meant the CI results were actually trustworthy, which made scope-aware gating and cost optimization possible.</li>
<li><strong>Worktree isolation → everything.</strong> Reliable worktrees meant agents and engineers could iterate on every other stream in parallel without breaking each other’s environments.</li>
<li><strong>Skills codification → all of the above.</strong> Once patterns were encoded into reusable skills, we stopped relearning the same lessons. New contributors inherited the workflow directly.</li>
</ul>
<p>We did not just do many things in one quarter - we ended up building a system where the improvements reinforced each other. Better CI feedback made testing more productive, which made CI signal more trustworthy, which made it safe to skip unnecessary checks, which freed up time to codify the patterns, which meant the next improvement started from a higher baseline.</p>
<p>If you are looking at this and thinking “we have the same hidden queue,” here is where to start.</p>
<hr>
<h2 id="what-you-can-actually-copy">What you can actually copy</h2>
<p>If you want to apply this without copying our stack, copy the mechanics:</p>
<ol>
<li>
<p><strong>Make the hidden queue visible and measure it.</strong> Track every time an engineer waits, context-switches, or does manual repetitive work for one week. Add up the hours. Then pick one loop, instrument it, fix it, and measure the before and after. Start wherever your data is cleanest.</p>
</li>
<li>
<p><strong>Separate qualification from execution.</strong> In any expensive system - CI, deployments, data pipelines - cheap checks should decide whether expensive checks run. A 30-second linter should gate a 15-minute integration test, not run alongside it.</p>
</li>
<li>
<p><strong>Ship in phases, not big bangs.</strong> Every improvement in this post was shipped incrementally. If Phase 2 breaks, you roll back Phase 2. Phase 1 still works.</p>
</li>
<li>
<p><strong>Make worktrees work.</strong> If your team is using AI coding agents, fix your local dev setup first. Deterministic port isolation, environment syncing, single-command startup. Without this, you are bottlenecking every other improvement.</p>
</li>
<li>
<p><strong>Learn the models and mix them.</strong> Claude is fast and great for execution but will take shortcuts on complex work. Codex is slower but more thorough. Run them together - one plans, one executes, one reviews.</p>
</li>
<li>
<p><strong>Turn prompts into skills.</strong> When someone on your team figures out an effective AI-assisted workflow, do not let it stay in their head. Encode it. Version it. Share it. The gap between “one person is fast” and “the whole team is fast” is a skill system.</p>
</li>
</ol>
<p>These patterns are general-purpose and work whether you are running a monolith, microservices, or something in between. The models make it cheaper to try things and iterate. The patterns are what make the results last.</p>
<hr>
<h2 id="attention-is-still-the-bottleneck">Attention is still the bottleneck</h2>
<p>If you take one thing from this post, do not let it be “they wrote less code.” We wrote and deleted a lot of code - 120,929 lines added, 31,170 deleted.</p>
<p>What changed is where we <strong>spent our attention</strong>.</p>
<p>The repetitive plumbing - CI configuration, test infrastructure, environment wiring, dependency management, snapshot steps - is now cheaper to do. That frees up the team to focus on boundaries, failure modes, control surfaces, and review quality.</p>
<p>We still write code and still need engineers; we are just spending more of that scarce attention on work that actually needs human judgment.</p>]]></content:encoded>
            <author>Ashwini Chaudhary</author>
            <category>ai</category>
            <category>agentic-engineering</category>
            <category>ci-cd</category>
            <category>developer-experience</category>
            <category>platform-engineering</category>
            <category>team-learning</category>
            <category>security</category>
            <category>testing</category>
            <category>workflow</category>
        </item>
        <item>
            <title><![CDATA[Protecting Our Own Tenant in a Multi-Tenant SaaS]]></title>
            <link>https://ashwch.com/protecting-our-own-tenant-in-a-multi-tenant-saas.html</link>
            <guid isPermaLink="false">tag:ashwch.com,2025-11-24:/protecting-our-own-tenant-in-a-multi-tenant-saas.html</guid>
            <pubDate>Mon, 24 Nov 2025 05:00:00 GMT</pubDate>
            <description><![CDATA[How we treat Diversio's own tenant inside Optimo as the hardest one to reach, using layered controls across Django admin, approvals, Postgres RLS, IAM and automation.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>If your own company is a tenant in your own product, it should be the hardest one to access, not the easiest.</p>
</blockquote>
<p>At <a href="https://diversio.com">Diversio</a>, one of the products we build is <a href="https://optimoteams.com">Optimo</a>, a multi-tenant SaaS that helps HR and people teams work with sensitive employee and organizational data.</p>
<p>Most Optimo organizations belong to customers.</p>
<p>One belongs to us.</p>
<p>That internal Diversio org inside Optimo holds our own employee and company data. It lives in the same production cluster as customer tenants, and our engineers and Client Success team use the same product surfaces to support both, always through per-tenant and role-based guardrails.</p>
<p>Customer orgs already have their own access boundaries; this post is about holding our own org to an even higher bar so it is the hardest one to reach, not the easiest.</p>
<p>Even with those existing guardrails, it is easy for internal tooling to drift toward a de facto “god mode” for your own org if you never make the extra constraints explicit. We wanted to push in the opposite direction. We wanted everyone at Diversio to be comfortable with how their data is handled, while still letting a very small engineering team move quickly for customers.</p>
<p>This post walks through how we got there.</p>
<hr>
<h2 id="table-of-contents">Table of Contents</h2>
<ol>
<li><a href="#what-youll-learn">What you’ll learn</a></li>
<li><a href="#why-our-own-org-is-special">Why our own org is special</a></li>
<li><a href="#layer-1-an-org-scoped-admin-mixin">Layer 1: An org-scoped admin mixin</a></li>
<li><a href="#layer-2-a-totp-gated-workflow-for-sensitive-access">Layer 2: A TOTP-gated workflow for sensitive access</a></li>
<li><a href="#layer-3-postgres-rls-and-the-sensitive-flag">Layer 3: Postgres RLS and the sensitive flag</a></li>
<li><a href="#layer-4-iam-and-support-shells">Layer 4: IAM and support shells</a></li>
<li><a href="#automation-and-a-five-person-team">Automation and a five-person team</a></li>
<li><a href="#gaps-and-future-work">Gaps and future work</a></li>
<li><a href="#the-bottom-line">The bottom line</a></li>
</ol>
<hr>
<h2 id="what-youll-learn">What you’ll learn</h2>
<p>By the end of this post, you will see how to:</p>
<ul>
<li>Treat your own company’s tenant as the hardest one to reach in a multi-tenant SaaS.</li>
<li>Use an org-scoped admin mixin to get “row-level security” at the application layer.</li>
<li>Wrap sensitive access behind a small TOTP-gated workflow instead of ad-hoc toggles.</li>
<li>Lean on Postgres Row-Level Security (RLS) and a single support-shell role to protect production data.</li>
<li>Use CI to keep your RLS and access model from silently drifting.</li>
<li>Get all of this working with a five-person engineering team without grinding development to a halt.</li>
</ul>
<p>If you are building a multi-tenant application and your own company is one of the tenants, this post gives you a concrete set of patterns you can reuse or adapt.</p>
<hr>
<h2 id="why-our-own-org-is-special">Why our own org is special</h2>
<p>When we say “our own org”, we mean the internal Diversio organization that lives alongside customer orgs inside Optimo.</p>
<p>It is special for a few reasons:</p>
<ul>
<li>It includes our own employee data and internal company information.</li>
<li>It exercises almost every feature in Optimo, often before customers see it.</li>
<li>It is the easiest tenant for us to accidentally treat as “less important”, because we are so close to it.</li>
</ul>
<p>This pattern is not unique to us. GitHub runs on GitHub, Slack lives inside Slack, and Stripe’s teams build and operate on top of their own payments APIs and tools. Claude Code is another good example: a lot of what makes it feel sharp comes from Anthropic dogfooding it heavily<sup><a href="#user-content-fn-dogfood" id="user-content-fnref-dogfood" data-footnote-ref="" aria-describedby="footnote-label">1</a></sup>, and we lean on it in our own repos too. Using your own tools at full blast is one of the fastest ways to find sharp edges before customers do.</p>
<p>Optimo is the same. Our own org is where we try new workflows, push edge cases and shortcut a lot of “demo tenant” friction. That is great for product quality, but it also means the easiest tenant for us to poke at is the one tenant we should be most careful with.</p>
<p>We wanted to set a very simple rule:</p>
<blockquote>
<p>If a control is good enough for a sensitive customer, it should apply to Diversio too.</p>
</blockquote>
<p>At the same time, we did not want a giant “break everything” toggle in one admin screen that quietly bypassed all of this for engineers on call.</p>
<p>The result is a layered design:</p>
<ol>
<li>A clear notion of which organizations each admin may see at the application layer.</li>
<li>A small, TOTP-gated workflow to grant and remove sensitive access.</li>
<li>Postgres RLS that treats sensitive orgs as a first-class concept.</li>
<li>IAM and support shells that default engineers into safe roles.</li>
<li>Automation that keeps all of these in sync as the system changes.</li>
</ol>
<p>The rest of this post goes layer by layer.</p>
<hr>
<h2 id="layer-1-an-org-scoped-admin-mixin">Layer 1: An org-scoped admin mixin</h2>
<p>Most internal users at Diversio interact with data through Django admin and a handful of internal consoles. That is where we started.</p>
<p>We already had the usual multi-tenant pieces:</p>
<ul>
<li>An <code>Organization</code> model.</li>
<li>Internal staff users who can act on behalf of organizations.</li>
<li>A boolean flag that means “this organization is sensitive”.</li>
</ul>
<p>The problem was that each Django admin class had to remember to do the right thing. It is easy to get one view right, then forget the filter on another.</p>
<p>So we pulled the logic into a reusable organization-scoped admin mixin (internally, <code>OptimoOrgScopedAdminMixin</code>).</p>
<p>At a high level, every staff user has:</p>
<ul>
<li>A global profile (are they active staff, what kind of staff).</li>
<li>Per-org assignments (which customers or regions they are responsible for).</li>
<li>A flag indicating whether they are allowed to see any sensitive orgs at all.</li>
</ul>
<p>A central helper answers a single question:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">def</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> get_allowed_org_ids</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(user) -> set[</span><span style="color:#005CC5;--shiki-dark:#79B8FF">int</span><span style="color:#24292E;--shiki-dark:#E1E4E8">]:</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">    # Look at profile, per-org permissions and the sensitive flag.</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">    # Return the set of organization IDs this user may see.</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">    ...</span></span></code></pre>
<p>The admin mixin uses this helper to:</p>
<ul>
<li>Filter list views down to the organizations you are allowed to see.</li>
<li>Enforce object-level permissions (“can I view, change or delete this row”).</li>
<li>Narrow foreign keys and dropdowns so you cannot accidentally link records to orgs you do not have access to.</li>
</ul>
<p>A simplified version looks like this:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">class</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> OrgScopedAdmin</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ModelAdmin</span><span style="color:#24292E;--shiki-dark:#E1E4E8">):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    def</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> get_queryset</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(self, request):</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">        qs </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> super</span><span style="color:#24292E;--shiki-dark:#E1E4E8">().get_queryset(request)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">        allowed </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> get_allowed_org_ids(request.user)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> qs.filter(</span><span style="color:#E36209;--shiki-dark:#FFAB70">organization_id__in</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8">allowed)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    def</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> has_view_permission</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(self, request, obj</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF">None</span><span style="color:#24292E;--shiki-dark:#E1E4E8">):</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">        base </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> super</span><span style="color:#24292E;--shiki-dark:#E1E4E8">().has_view_permission(request, obj)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        if</span><span style="color:#D73A49;--shiki-dark:#F97583"> not</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> base </span><span style="color:#D73A49;--shiki-dark:#F97583">or</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> obj </span><span style="color:#D73A49;--shiki-dark:#F97583">is</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> None</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">            return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> base</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> obj.organization_id </span><span style="color:#D73A49;--shiki-dark:#F97583">in</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> get_allowed_org_ids(request.user)</span></span></code></pre>
<p>Two important choices here:</p>
<ul>
<li>Sensitive orgs (including Diversio) are excluded by default, even for superusers.</li>
<li>Admin views opt in to the mixin and get the same behaviour, instead of reinventing access rules one screen at a time.</li>
</ul>
<p>For an engineer with strong customer-facing permissions:</p>
<ul>
<li>Django admin is still powerful for the customer orgs they are assigned to.</li>
<li>The Diversio org simply does not show up unless a separate process has granted that level of access.</li>
</ul>
<p>This is our first layer of “row-level security”: an application-level filter baked into admin, not an afterthought in each view.</p>
<hr>
<h2 id="layer-2-a-totp-gated-workflow-for-sensitive-access">Layer 2: A TOTP-gated workflow for sensitive access</h2>
<p>The admin mixin enforces “what you can see” for a given profile.</p>
<p>The next question is: how does someone get a profile that is allowed to see the Diversio org at all?</p>
<p>We knew what we did not want:</p>
<ul>
<li>A checkbox in Django admin that anyone with enough permissions could toggle.</li>
<li>A permanent “god mode” role that people forget they are still holding.</li>
</ul>
<p>Instead, we built a small, TOTP-gated workflow.</p>
<p>Each staff profile has a boolean that means:</p>
<blockquote>
<p>This person may see sensitive orgs like Diversio, in addition to their normal org assignments.</p>
</blockquote>
<p>That boolean is visible in admin, but it is read-only. The only way to change it is a CLI command that:</p>
<ul>
<li>Takes a target user.</li>
<li>Takes a TOTP code from an approver (CEO, CTO or a small set of delegates).</li>
<li>Logs who did what and when.</li>
</ul>
<p>In pseudocode:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">def</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> require_sensitive_approval</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(code: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">str</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) -> </span><span style="color:#005CC5;--shiki-dark:#79B8FF">None</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    if</span><span style="color:#D73A49;--shiki-dark:#F97583"> not</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> verify_totp(code):  </span><span style="color:#6A737D;--shiki-dark:#6A737D"># shared helper wired to env secrets</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        raise</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ApprovalError(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Invalid TOTP code"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">def</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> enable_sensitive_access</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(staff_user, code: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">str</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) -> </span><span style="color:#005CC5;--shiki-dark:#79B8FF">None</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    require_sensitive_approval(code)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    staff_user.can_see_sensitive_orgs </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> True</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    staff_user.save(</span><span style="color:#E36209;--shiki-dark:#FFAB70">update_fields</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8">[</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"can_see_sensitive_orgs"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">])</span></span></code></pre>
<p>The outcome looks like this in practice:</p>
<ul>
<li>Sensitive access is rare and is usually time-boxed to a specific investigation.</li>
<li>It is deliberate: someone with real authority has to approve it with a physical device.</li>
<li>It is auditable: we can answer “who could see the Diversio org in April” without guessing.</li>
</ul>
<p>This is the human-side guardrail that complements the org-scoped admin mixin.</p>
<hr>
<h2 id="layer-3-postgres-rls-and-the-sensitive-flag">Layer 3: Postgres RLS and the sensitive flag</h2>
<p>Application-level scoping and TOTP workflows are necessary, but not sufficient. Engineers also work close to the database: SQL shells, diagnostics, ad-hoc queries and migrations.</p>
<p>We wanted Postgres itself to agree that the Diversio org is special.</p>
<p>We do two things here.</p>
<h3 id="31-guard-the-sensitive-flag-itself">3.1 Guard the sensitive flag itself</h3>
<p>First, we treat the “this org is sensitive” flag as something you do not get to flip casually.</p>
<ul>
<li>Normal application roles and ad-hoc DB connections cannot change it.</li>
<li>Only a narrow path, tied to the TOTP-gated CLI, can set or clear it.</li>
<li>The database checks the current role and a session variable before allowing updates to that column.</li>
</ul>
<p>The goal is simple:</p>
<ul>
<li>Nobody “experimenting” in a production shell can quietly downgrade the Diversio org from <code>is_sensitive = TRUE</code> to <code>FALSE</code>.</li>
<li>Even if a bug slips through at the app layer, the database is still able to say “no” on the one write that really matters.</li>
</ul>
<h3 id="32-postgres-rls-for-support-shells">3.2 Postgres RLS for support shells</h3>
<p>The second piece is Postgres Row-Level Security (RLS), which we apply to a dedicated support database role, the role engineers use in our production support shells.</p>
<p>For that support role, RLS policies enforce:</p>
<ul>
<li>On org-scoped tables, queries only see rows where the organization is not sensitive.</li>
<li>Rows belonging to sensitive orgs (including Diversio) are filtered out by the database itself.</li>
<li>These rules apply to both reads and writes.</li>
</ul>
<p>In simplified SQL:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="sql"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">ALTER</span><span style="color:#D73A49;--shiki-dark:#F97583"> TABLE</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> org_events </span><span style="color:#D73A49;--shiki-dark:#F97583">ENABLE</span><span style="color:#D73A49;--shiki-dark:#F97583"> ROW</span><span style="color:#D73A49;--shiki-dark:#F97583"> LEVEL</span><span style="color:#D73A49;--shiki-dark:#F97583"> SECURITY</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">CREATE</span><span style="color:#D73A49;--shiki-dark:#F97583"> POLICY</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> support_only_non_sensitive</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">  ON</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> org_events</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">  FOR</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ALL</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">  TO</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> support_shell_role</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">  USING</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    organization_id </span><span style="color:#D73A49;--shiki-dark:#F97583">IN</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">      SELECT</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> id</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">      FROM</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> organizations</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">      WHERE</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> is_sensitive </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> FALSE</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    )</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  );</span></span></code></pre>
<p>If you connect to production using the support shell and run:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="sql"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">SELECT</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#D73A49;--shiki-dark:#F97583"> FROM</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> org_events</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">WHERE</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> organization_id </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">  SELECT</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> id </span><span style="color:#D73A49;--shiki-dark:#F97583">FROM</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> organizations </span><span style="color:#D73A49;--shiki-dark:#F97583">WHERE</span><span style="color:#D73A49;--shiki-dark:#F97583"> name</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'Diversio'</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">);</span></span></code></pre>
<p>Postgres itself refuses to show those rows to the <code>support_shell_role</code>, regardless of what the application code might have done.</p>
<h3 id="33-an-rls-registry-and-ci-checks">3.3 An RLS registry and CI checks</h3>
<p>We have a lot of org-scoped tables in Optimo:</p>
<ul>
<li>Some with a direct <code>organization_id</code> column.</li>
<li>Some where the org needs to be derived via a join (through a user, an employee, a workspace and so on).</li>
</ul>
<p>Managing RLS for all of them by hand is a great way to make mistakes.</p>
<p>So we maintain a small Python “registry” that lists:</p>
<ul>
<li>Which tables are org-scoped.</li>
<li>How to determine the organization for each table.</li>
<li>Which tables are intentionally excluded.</li>
</ul>
<p>On top of that registry, we have a management command that:</p>
<ul>
<li>Enables RLS on those tables.</li>
<li>Ensures policies exist for the support role.</li>
<li>Creates a helper function like <code>org_is_not_sensitive(org_id uuid)</code> for <code>USING</code> and <code>WITH CHECK</code> clauses.</li>
<li>Scans the schema for any new org-scoped tables that are not in the registry yet.</li>
<li>Fails loudly if it finds gaps.</li>
</ul>
<p>We run this command automatically in CircleCI:</p>
<ul>
<li>Apply migrations to a fresh database.</li>
<li>Run the RLS command in “apply plus verify” mode.</li>
<li>If someone adds an org-scoped table and forgets to update the registry, the build fails.</li>
</ul>
<p>Conceptually, the CI step looks like this:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="yaml"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">- </span><span style="color:#22863A;--shiki-dark:#85E89D">run</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">    name</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">Validate support-shell RLS</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">    command</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#D73A49;--shiki-dark:#F97583">|</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">      python manage.py migrate --noinput</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">      python manage.py check_support_shell_rls --apply --verbosity 2</span></span></code></pre>
<p>RLS becomes an executable spec, not a fragile manual checklist.</p>
<hr>
<h2 id="layer-4-iam-and-support-shells-that-default-to-safe">Layer 4: IAM and support shells that default to safe</h2>
<p>The final layer is how we actually connect to production.</p>
<p>Like many teams, we used to have engineers with:</p>
<ul>
<li>Broad AWS permissions.</li>
<li>The ability to run <code>ecs exec</code> into production containers.</li>
<li>Access to secrets wired to powerful database roles.</li>
</ul>
<p>That is convenient, but it does not align with “the Diversio org should be the hardest tenant to reach”.</p>
<p>We reshaped things so that sensitive power is separated and explicit.</p>
<h3 id="41-sensitive-secrets-are-separated">4.1 Sensitive secrets are separated</h3>
<p>Anything that directly powers sensitive workflows, for example:</p>
<ul>
<li>Special database URLs.</li>
<li>TOTP secrets.</li>
<li>Break-glass admin credentials.</li>
</ul>
<p>lives in its own secret, encrypted under a dedicated KMS key. Only specific permission sets and automation roles can read it.</p>
<h3 id="42-permission-sets-map-to-real-personas">4.2 Permission sets map to real personas</h3>
<p>We distinguish a normal “production engineer” persona from a “break-glass administrator”.</p>
<p>The normal production role:</p>
<ul>
<li>Cannot talk directly to the main database role.</li>
<li>Cannot read master database secrets.</li>
<li>Cannot read sensitive secrets.</li>
<li>Can still manage non-sensitive app configuration and run diagnostics.</li>
</ul>
<h3 id="43-support-shells-are-the-default">4.3 Support shells are the default</h3>
<p>Engineers connect to production via a dedicated support-shell service that:</p>
<ul>
<li>Runs the same Optimo container image as the main app.</li>
<li>Uses the restricted support database role by default.</li>
<li>Is wired so that a normal production engineer always lands in a support shell, not on a fully privileged backend container.</li>
</ul>
<p>A small wrapper script ties it together:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">permission_set</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8">$(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">detect_aws_permission_set</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> [ </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">$permission_set</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "ProdEngineer"</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ]; </span><span style="color:#D73A49;--shiki-dark:#F97583">then</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  target_service</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"support-shell"</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">else</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  target_service</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"backend"</span><span style="color:#6A737D;--shiki-dark:#6A737D">  # break-glass roles only</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">fi</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">open_ecs_exec_shell</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="color:#24292E;--shiki-dark:#E1E4E8">$target_service</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span></span></code></pre>
<p>If the support-shell configuration is missing or broken, the script refuses to open a shell instead of silently falling back to something more powerful.</p>
<p>Combined with RLS, this gives us:</p>
<ul>
<li>A realistic production shell for debugging customer issues.</li>
<li>A database that still hides rows for the Diversio org from the support role.</li>
<li>A clear, auditable path to more power when it is genuinely needed.</li>
</ul>
<hr>
<h2 id="automation-and-a-five-person-team">Automation and a five-person team</h2>
<p>At this point, it is fair to ask:</p>
<blockquote>
<p>Is this too much machinery for a five-person engineering team?</p>
</blockquote>
<p>It would be, if it were all manual.</p>
<p>What makes it work is that the moving parts share a few patterns:</p>
<ul>
<li>One admin mixin for org scoping, reused everywhere.</li>
<li>One TOTP-gated path for sensitive access, instead of scattered toggles.</li>
<li>One RLS registry and command that define what the database should enforce.</li>
<li>One CI step that runs migrations, bootstraps RLS and fails when something drifts.</li>
</ul>
<p>The extra work happens once, up front. After that:</p>
<ul>
<li>New admin views inherit the mixin and automatically respect org scoping.</li>
<li>New engineers follow the same shell story and get the same protections.</li>
<li>New org-scoped tables are caught by the RLS command if we forget to register them.</li>
</ul>
<p>The system enforces the policy for us, instead of us trying to remember every rule in our heads.</p>
<hr>
<h2 id="gaps-and-future-work">Gaps and future work</h2>
<p>We are happy with the direction, but this is not “done”.</p>
<p>Some gaps we have deliberately left open for future iterations:</p>
<ul>
<li>
<p><strong>RLS mostly protects the support role today.</strong><br>
We would like to extend RLS to more roles and tables where it makes sense, without tanking performance or complicating migrations.</p>
</li>
<li>
<p><strong>Many read paths still lean on application logic.</strong><br>
The admin mixin does a lot, and the database does a lot for support shells, but other read paths still rely on Django-level scoping. We would like more database-side guardrails around the most sensitive slices of the Diversio org.</p>
</li>
<li>
<p><strong>Sensitive access observability is basic.</strong><br>
We log the right events and can reconstruct them, but we would like:</p>
<ul>
<li>A small dashboard that graphs sensitive access over time.</li>
<li>Simple alerts when approvals spike or come from unexpected places.</li>
</ul>
</li>
<li>
<p><strong>Approvals are very CLI-shaped.</strong><br>
The TOTP flow is fine for engineers, less so for non-engineers. A small internal UI for approvers (“who is asking to see Diversio, why, and for how long”) would make this much more approachable.</p>
</li>
<li>
<p><strong>We do not “game day” this enough.</strong><br>
We test the pieces individually. We would like regular drills where we intentionally try to break assumptions about the Diversio org in a safe environment and see how the system responds.</p>
</li>
</ul>
<p>These are good problems to have. They are the kind of problems you get to think about only after you have built the basics.</p>
<hr>
<h2 id="the-bottom-line">The bottom line</h2>
<p>Letting your own company be a tenant in your own product is a great dogfooding story: your own team runs real workflows through the product every day. It is also a serious security challenge.</p>
<p>Our approach at Diversio has been to:</p>
<ul>
<li>Treat the Diversio org as the most demanding customer we have.</li>
<li>Give the application a clear notion of which orgs each admin may see, via an org-scoped mixin.</li>
<li>Give the database a clear notion of which rows each role may see, via RLS and a registry.</li>
<li>Let CI and automation keep those contracts honest as Optimo evolves.</li>
<li>Build human workflows (approvals, TOTP) into the design from the start.</li>
</ul>
<p>The key point is not that we use Django or Postgres or AWS specifically. The key point is that we use layers:</p>
<ul>
<li>UI and admin behaviour.</li>
<li>Approval workflows.</li>
<li>Database policies.</li>
<li>Cloud IAM and shell tooling.</li>
<li>Automation to glue it together.</li>
</ul>
<p>Together, these give us something stronger than “trust us, we will be careful” and they do it in a way that a small, focused team can maintain while still shipping features.</p>
<p><em>Thanks to <a href="https://www.linkedin.com/in/ashishsiwal/">Ashish Siwal</a> for reviewing this post and helping solidify the implementation.</em></p>
<section data-footnotes="" class="footnotes"><h2 class="sr-only" id="footnote-label">Footnotes</h2>
<ol>
<li id="user-content-fn-dogfood">
<p>“Dogfooding” just means using your own product internally for real, day-to-day work, so you hit the same pain points and edge cases your customers do. <a href="#user-content-fnref-dogfood" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref">↩</a></p>
</li>
</ol>
</section>]]></content:encoded>
            <author>Ashwini Chaudhary</author>
            <category>security</category>
            <category>django</category>
            <category>postgres</category>
            <category>rls</category>
            <category>aws</category>
            <category>multi-tenant</category>
            <category>saas</category>
            <category>optimo</category>
            <category>diversio</category>
            <category>permissions</category>
            <category>production</category>
            <category>iam</category>
        </item>
        <item>
            <title><![CDATA[Insights from "Everything I Know About Good System Design"]]></title>
            <link>https://ashwch.com/insights-from-everything-i-know-about-good-system-design.html</link>
            <guid isPermaLink="false">tag:ashwch.com,2025-08-17:/insights-from-everything-i-know-about-good-system-design.html</guid>
            <pubDate>Sun, 17 Aug 2025 04:00:00 GMT</pubDate>
            <description><![CDATA[My thoughts and practical experiences applying Sean Goedecke's system design principles - from state management to database schemas, with real-world lessons learned over 10+ years.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>The hard part about software design is state. If you’re storing any kind of information for any amount of time, you have a lot of tricky decisions to make about how you save, store and serve it.</p>
</blockquote>
<p>I recently read Sean Goedecke’s excellent piece on <a href="https://www.seangoedecke.com/good-system-design/">system design principles</a>, and it crystallized many lessons I’ve learned over 10+ years of building systems. Here are my key takeaways and how they’ve played out in practice.</p>
<hr>
<h2 id="table-of-contents">Table of Contents</h2>
<ol>
<li><a href="#state-the-root-of-all-complexity">State: The Root of All Complexity</a></li>
<li><a href="#database-design-get-the-schema-right">Database Design: Get the Schema Right</a></li>
<li><a href="#query-efficiency-let-the-database-work">Query Efficiency: Let the Database Work</a></li>
<li><a href="#background-processing-fast-vs-slow-operations">Background Processing: Fast vs. Slow Operations</a></li>
<li><a href="#caching-choose-carefully">Caching: Choose Carefully</a></li>
<li><a href="#hot-paths-zero-margin-for-error">Hot Paths: Zero Margin for Error</a></li>
<li><a href="#logging-and-error-handling">Logging and Error Handling</a></li>
<li><a href="#graceful-failure-the-hardest-problem">Graceful Failure: The Hardest Problem</a></li>
<li><a href="#the-pattern-recognition">The Pattern Recognition</a></li>
</ol>
<hr>
<h2 id="state-the-root-of-all-complexity">State: The Root of All Complexity</h2>
<p>State makes everything tricky. The more state we have, the harder it becomes to reason about our systems and keep track of responsibilities.</p>
<p>On the flip side, statelessness relieves us of worrying about systems getting “stuck” or needing complex guardrails to prevent corruption.</p>
<blockquote>
<p>You should try and minimize the amount of stateful components in any system.</p>
</blockquote>
<blockquote>
<p>What this means in practice is having one service that knows about the state - i.e. it talks to a database - and other services that do stateless things. Avoid having five different services all write to the same table.</p>
</blockquote>
<p>I’ll admit this isn’t something I actively think about - I might be doing it unconsciously in some cases and missing it entirely in others. But I want to keep an eye on this pattern in future projects.</p>
<h2 id="database-design-get-the-schema-right">Database Design: Get the Schema Right</h2>
<blockquote>
<p>Schema design should be flexible, because once you have thousands or millions of records, it can be an enormous pain to change the schema.
However, if you make it too flexible (e.g. by sticking everything in a “value” JSON column, or using “keys” and “values” tables to track arbitrary data) you load a ton of complexity into the application code (and likely buy some very awkward performance constraints).</p>
</blockquote>
<p>I’ve always spent significant time on database design when building new systems, and it’s consistently paid off. The JSON trap is real - while convenient initially, JSON fields create several problems:</p>
<ul>
<li>Hard to query effectively</li>
<li>Difficult to enforce schemas</li>
<li>Serialization/deserialization overhead</li>
<li>Network transfer costs for large payloads</li>
<li>Additional validation complexity</li>
</ul>
<p>That said, I’ve found JSON fields useful for storing external API payloads and validation errors - places where the structure truly varies.</p>
<h3 id="index-strategy">Index Strategy</h3>
<blockquote>
<p>If you expect your table to ever be more than a few rows, you should put indexes on it.</p>
</blockquote>
<p>I think the author didn’t intend it, but <strong>index as you go</strong>. If your table isn’t showing slow queries yet, don’t index prematurely. You can always add indices later and optimize them based on actual query patterns.</p>
<p>I’m a big fan of PostgreSQL’s <code>explain analyze</code> - and with AI tools, analyzing query plans has become even more accessible.</p>
<h2 id="query-efficiency-let-the-database-work">Query Efficiency: Let the Database Work</h2>
<blockquote>
<p>When querying the database, <em>query the database</em>. It’s almost always more efficient to get the database to do the work than to do it yourself. For instance, if you need data from multiple tables, <code>JOIN</code> them instead of making separate queries and stitching them together in-memory. Particularly if you’re using an ORM, beware accidentally making queries in an inner loop. That’s an easy way to turn a <code>select id, name from table</code> to a <code>select id from table</code> and a hundred <code>select name from table where id = ?</code>.</p>
</blockquote>
<p>As a longtime Django user who’s been reviewing Django code for 10+ years, I’ve seen the N+1 query problem countless times. It’s crucial to educate your team on ORM fundamentals and maintain documentation of good vs. bad practices.</p>
<p>Having a central document helps during code reviews - it’s hard to repeatedly explain the “why” with the same depth. A shared resource makes linking to best practices much easier.</p>
<h2 id="background-processing-fast-vs-slow-operations">Background Processing: Fast vs. Slow Operations</h2>
<p>The goal is a fast request-response cycle for the best user experience. Operations that can’t be completed quickly should move to background processing, with users notified when complete.</p>
<p>This seems obvious, but determining what belongs in the background requires careful consideration of user expectations and system constraints.</p>
<h2 id="caching-choose-carefully">Caching: Choose Carefully</h2>
<p>Be selective about what and when to cache. Otherwise, you’ll face the much harder problem of cache invalidation. The best solution is reducing the number of places using cache.</p>
<p>Cache invalidation remains one of the hardest problems in computer science for good reason.</p>
<h2 id="hot-paths-zero-margin-for-error">Hot Paths: Zero Margin for Error</h2>
<p>I define hot paths as system components where downtime significantly affects our SLA - critical numbers, authentication, or features that directly impact business and users.</p>
<p>These systems require extra care:</p>
<ul>
<li>World-class unit tests and coverage</li>
<li>Attention to unhappy paths</li>
<li>More conservative change management</li>
</ul>
<p>Take Instagram: it’s more important for signed-in users to see their feed than for signup to work perfectly.</p>
<h2 id="logging-and-error-handling">Logging and Error Handling</h2>
<p>Beyond traditional logging, we utilize logs in unit tests to ensure they’re not removed carelessly during updates. This makes log changes visible during code review.</p>
<p>We also return custom error codes with responses - they help identify issues without log diving and assist both customers and developers debugging problems.</p>
<p>For critical errors sent to monitoring tools like Sentry, we use custom exception names to quickly spot issue locations based on error messages.</p>
<h2 id="graceful-failure-the-hardest-problem">Graceful Failure: The Hardest Problem</h2>
<p>This ties back to state management. Building resilient state machines requires balancing when to fail, retry frequency, state resets, and maintaining manageable state complexity.</p>
<p>It’s one of the trickiest aspects of system design - fighting between reliability and simplicity.</p>
<h2 id="the-pattern-recognition">The Pattern Recognition</h2>
<p>After years of building systems, these principles become pattern recognition. The key is being intentional about applying them rather than hoping they happen by accident.</p>
<p>Sean’s post does an excellent job distilling complex system design into actionable principles. The challenge is remembering to apply them when you’re deep in implementation details.</p>
<hr>
<p>What patterns have you noticed in your own system design work? I’d love to hear about principles that have served you well over time.</p>]]></content:encoded>
            <author>Ashwini Chaudhary</author>
            <category>system-design</category>
            <category>software-architecture</category>
            <category>database-design</category>
            <category>state-management</category>
            <category>performance</category>
            <category>best-practices</category>
        </item>
        <item>
            <title><![CDATA[The Monolith That Made AI Actually Useful]]></title>
            <link>https://ashwch.com/the-monolith-that-made-ai-actually-useful.html</link>
            <guid isPermaLink="false">tag:ashwch.com,2025-08-07:/the-monolith-that-made-ai-actually-useful.html</guid>
            <pubDate>Thu, 07 Aug 2025 04:00:00 GMT</pubDate>
            <description><![CDATA[How we solved context switching across multiple repositories by building a monolith using git submodules, making both humans and AI 10x more effective with our codebase.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Picture this: You’re debugging an issue that spans your React frontend, Django backend, and Terraform infrastructure. Three terminal windows. Three different repos. Three different contexts. By the time you trace the bug from UI to API to database, you’ve lost half your morning just to context switching.</p>
</blockquote>
<p>We solved this at Diversio. The solution made both humans and AI 10x more effective with our codebase.</p>
<hr>
<h2 id="table-of-contents">Table of Contents</h2>
<ol>
<li><a href="#what-youll-learn">What you’ll learn</a></li>
<li><a href="#the-problem-we-were-facing">The Problem We Were Facing</a></li>
<li><a href="#our-solution-git-submodules-done-right">Our Solution: Git Submodules Done Right</a></li>
<li><a href="#the-ai-advantage">The AI Advantage</a></li>
<li><a href="#advanced-implementation">Advanced Implementation</a></li>
<li><a href="#mcp-integration-and-team-benefits">MCP Integration and Team Benefits</a></li>
<li><a href="#the-technical-details">The Technical Details</a></li>
<li><a href="#implementation-strategy">Implementation Strategy</a></li>
<li><a href="#the-bottom-line">The Bottom Line</a></li>
</ol>
<hr>
<h2 id="what-youll-learn">What you’ll learn</h2>
<ul>
<li><strong>Structure a monolith</strong> repository with git submodules for maximum efficiency</li>
<li><strong>Master git worktrees</strong> for parallel development without context switching</li>
<li><strong>Automate worktree creation</strong> with our Python scripts (links included!)</li>
<li><strong>Reduce context switching</strong> by 90% and watch productivity soar</li>
<li><strong>Supercharge AI tools</strong> with complete codebase context—making them actually useful</li>
<li><strong>Implement immediately</strong> with practical tips and working code</li>
</ul>
<p>If you’re tired of juggling multiple repos and losing context between features, this guide shows you exactly how we solved it. If you’re already using git submodules, you’ll learn how to level up with worktrees and automation.</p>
<hr>
<h2 id="the-problem-we-were-facing">The Problem We Were Facing</h2>
<p>Every feature we built touched multiple repositories:</p>
<ul>
<li>Clone three separate repos</li>
<li>Keep versions in sync</li>
<li>Jump between directories constantly</li>
<li>Lose context when debugging cross-service issues</li>
</ul>
<p>But here’s the real kicker. AI tools like Claude Code couldn’t see our full system. They’d suggest fixes that made sense for one service but broke two others. They had fragments, not the full picture.</p>
<h2 id="our-solution-git-submodules-done-right">Our Solution: Git Submodules Done Right</h2>
<p>We built a monolith using git submodules that gives us the best of both worlds:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="plaintext"><code><span class="line"><span>diversio-monolith/</span></span>
<span class="line"><span>├── frontend/              # React application</span></span>
<span class="line"><span>├── backend/               # Django API</span></span>
<span class="line"><span>├── design-system/         # Shared UI components</span></span>
<span class="line"><span>├── optimo-frontend/       # Optimo product app</span></span>
<span class="line"><span>├── diversio-serverless/   # AWS Lambda functions</span></span>
<span class="line"><span>├── infrastructure/        # Terraform definitions</span></span>
<span class="line"><span>├── terraform-modules/     # Reusable infrastructure</span></span>
<span class="line"><span>└── scripts/               # Development automation</span></span></code></pre>
<p>Each directory is its own git repository. But they all live in one workspace. One clone gets you everything.</p>
<h3 id="from-days-to-hours">From Days to Hours</h3>
<p>Our product manager <a href="https://www.linkedin.com/in/samuel-bonin/">Sam</a> now redesigns features in hours, not days. He pulls the latest code, runs migrations, and has AI analyze our Bruno API collections. Remember when we <a href="%7Bfilename%7D/articles/from-postman-to-bruno-how-ai-changed-our-api-workflow.md">migrated from Postman to Bruno</a>? That decision pays huge dividends in a monolith.</p>
<p>The workflow looks like this:</p>
<ol>
<li>Pull latest commits across all services</li>
<li>Run migrations with full database context</li>
<li>AI reads organized API documentation</li>
<li>Create comprehensive designs with complete system understanding</li>
<li>Implement changes knowing exactly what will be affected</li>
</ol>
<p>What used to take hours of repository hopping now happens in a single flow.</p>
<h2 id="the-ai-advantage">The AI Advantage</h2>
<p>Here’s where it gets interesting. By giving AI tools complete system context, we unlocked capabilities that were impossible before:</p>
<ul>
<li><strong>Cross-repository analysis</strong>: Claude Code traces API calls from frontend to backend to infrastructure</li>
<li><strong>Better architectural decisions</strong>: Suggestions consider the entire stack</li>
<li><strong>System-wide debugging</strong>: Issues that span services become traceable</li>
<li><strong>Smarter refactoring</strong>: Changes account for all dependencies</li>
</ul>
<p>The AI isn’t guessing anymore. It knows.</p>
<h2 id="advanced-implementation">Advanced Implementation</h2>
<h3 id="git-worktrees-the-secret-sauce">Git Worktrees: The Secret Sauce</h3>
<p>The real magic happens with git worktrees. Instead of switching branches and losing context, we work on multiple features simultaneously:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Create a new worktree for a feature</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">uv</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> run</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> scripts/create_worktree.py</span></span></code></pre>
<p>Our automation handles everything:</p>
<ul>
<li>Interactive branch selection across all repositories</li>
<li>Smart filtering for hundreds of branches</li>
<li>Automatic submodule initialization</li>
<li>Proper conflict resolution</li>
<li>Copies .env files (because git worktrees don’t do this by default, and nobody wants to debug missing environment variables)</li>
</ul>
<p>Check out our automation scripts:</p>
<ul>
<li><a href="https://gist.github.com/ashwch/79177b4af7f2ea482418d6e9934d4787"><strong>create_worktree.py</strong></a> - Interactive worktree creator with submodule support</li>
<li><a href="https://gist.github.com/ashwch/909ea473250e8c8a937a8a4aa4a4dc72"><strong>update_submodules.py</strong></a> - Automated submodule updater with branch configuration</li>
</ul>
<p>Each developer can have multiple features in progress, each in its own directory, with full system context preserved.</p>
<h3 id="custom-ai-agents-specialized-tools-for-every-task">Custom AI Agents: Specialized Tools for Every Task</h3>
<p>We built specialized Claude Code agents that understand our codebase deeply:</p>
<ul>
<li><strong>Frontend PR Specialist</strong>: Analyzes React/TypeScript changes with component architecture visualizations</li>
<li><strong>Backend PR Specialist</strong>: Reviews Django changes with database schema analysis</li>
<li><strong>Infrastructure PR Specialist</strong>: Validates Terraform changes with cost and security impact assessments</li>
<li><strong>Integration Specialist</strong>: Traces data flows across frontend, backend, and infrastructure</li>
<li><strong>Data Migration Specialist</strong>: Handles complex data transformations and migrations</li>
<li><strong>Testing Automation Specialist</strong>: Writes comprehensive test suites following our patterns</li>
</ul>
<p>Each agent has deep knowledge of its domain. They don’t just suggest generic fixes. They understand our specific patterns, our conventions, our tech stack.</p>
<p>We even built meta agents that manage other agents:</p>
<ul>
<li><strong>Subagent Creator</strong>: Designs new focused agents with clear boundaries</li>
<li><strong>Subagent Reviewer</strong>: Reviews and optimizes agent definitions to prevent overlaps</li>
</ul>
<p>These meta agents save hours when adding or updating agents. No more manual agent configuration. The AI helps manage the AI.</p>
<p>The best part? These agents are shared across the entire team through the monolith. When one engineer creates an agent, everyone benefits immediately.</p>
<h2 id="mcp-integration-and-team-benefits">MCP Integration and Team Benefits</h2>
<p>We integrated Model Context Protocol (MCP) servers that give Claude Code direct access to:</p>
<ul>
<li><strong>CircleCI</strong>: Build status, test results, deployment logs</li>
<li><strong>Context7</strong>: Up-to-date documentation for any library</li>
<li><strong>Gemini AI</strong>: Research and code analysis</li>
<li><strong>Playwright</strong>: Browser automation for testing</li>
</ul>
<p>These integrations work seamlessly across the entire monolith, giving AI unprecedented visibility into our development pipeline.</p>
<h3 id="benefits-across-the-team">Benefits Across the Team</h3>
<p><strong>Product Managers</strong> get complete system visibility for rapid prototyping. API documentation and database schemas always accessible.</p>
<p><strong>New Engineers</strong> get a single command setup. Clone one repo, get everything with working examples.</p>
<p><strong>Senior Engineers</strong> can do system-wide refactoring. Make changes across repos with full context.</p>
<p><strong>AI Tools</strong> get complete codebase analysis. Suggestions that actually work.</p>
<p><strong>DevOps teams</strong> see infrastructure definitions alongside the code they deploy.</p>
<h2 id="the-technical-details">The Technical Details</h2>
<p><strong>Submodule Management</strong>: Each submodule points to a specific commit. Changes need commits in both the submodule and the monolith. This preserves atomicity while enabling coordination.</p>
<p><strong>Branch Independence</strong>: Work on different branches in each submodule simultaneously. Perfect for features that need coordinated changes. And crucially, pull requests still go to individual repositories, maintaining our existing review processes and CI/CD pipelines.</p>
<p><strong>Automated Tooling</strong>: Our Python scripts handle branch management, worktree creation, and submodule updates. No manual coordination needed.</p>
<p><strong>API Documentation as Code</strong>: Bruno’s file-based approach means API collections live with the code they test. They’re discoverable, version-controlled, and immediately accessible to both humans and AI. The AI can read, understand, and even suggest API changes based on the actual implementation.</p>
<h2 id="implementation-strategy">Implementation Strategy</h2>
<p>AI tools are becoming primary development partners. But these tools are only as good as the context you give them. By structuring our codebase for maximum AI comprehension, we created an environment where both humans and AI work at their full potential.</p>
<p>The results: 10x faster feature development, 90% fewer integration bugs, and architectural decisions that consider the full system impact.</p>
<h3 id="getting-started">Getting Started</h3>
<p>If you’re working with multiple related repositories, you can implement this:</p>
<ol>
<li>Create a monolith repository</li>
<li>Add existing repos as git submodules</li>
<li>Build automation scripts for common workflows</li>
<li>Configure MCP servers for your tools</li>
<li>Train your team on git worktrees</li>
<li>Move API documentation to file-based tools like Bruno</li>
</ol>
<p>The investment pays off immediately. No more context switching, better AI assistance, and a development experience that scales with complexity.</p>
<h2 id="the-bottom-line">The Bottom Line</h2>
<p>This isn’t just about tooling. It’s about creating an environment where high-quality work happens quickly and naturally.</p>
<p>When your product managers can redesign systems confidently, your engineers can refactor safely across services, and your AI tools provide system-aware suggestions, you’ve built more than a development environment.</p>
<p>You’ve built a competitive advantage.</p>
<hr>
<p>Want to dive deeper into Git worktrees? Check out my comprehensive guide: <a href="https://gist.github.com/ashwch/946ad983977c9107db7ee9abafeb95bd">Git Worktrees: From Zero to Hero</a>. It covers everything from first principles to advanced workflows with submodules.</p>
<p><em>Thanks to <a href="https://www.linkedin.com/in/samuel-bonin/">Samuel Bonin</a> and <a href="https://www.linkedin.com/in/amalraj-offl/">Amal Raj</a> for reviewing this post.</em></p>]]></content:encoded>
            <author>Ashwini Chaudhary</author>
            <category>git-worktrees</category>
            <category>development-workflow</category>
            <category>ai-tools</category>
            <category>monorepo</category>
            <category>claude-code</category>
            <category>mcp</category>
            <category>devops</category>
            <category>productivity</category>
            <category>bruno</category>
        </item>
        <item>
            <title><![CDATA[Postman to Bruno: A Weekend Migration That Transformed Our API Workflow]]></title>
            <link>https://ashwch.com/from-postman-to-bruno-how-ai-changed-our-api-workflow.html</link>
            <guid isPermaLink="false">tag:ashwch.com,2025-07-13:/from-postman-to-bruno-how-ai-changed-our-api-workflow.html</guid>
            <pubDate>Sun, 13 Jul 2025 04:00:00 GMT</pubDate>
            <description><![CDATA[We migrated our entire Postman collection to Bruno over a weekend and leveraged Claude Code to automate API documentation, reducing documentation time by 90% and catching breaking changes at review time.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>We moved our entire Postman collection to Bruno over a weekend and let Claude Code chew on the new files by Monday morning. By lunchtime, API docs were writing themselves.</p>
</blockquote>
<hr>
<h2 id="table-of-contents">Table of Contents</h2>
<ol>
<li><a href="#what-youll-learn">What you’ll learn</a></li>
<li><a href="#why-we-relied-on-postman-for-so-long--where-it-falls-short">Why we relied on Postman for so long &#x26; where it falls short</a></li>
<li><a href="#migration-basics">Migration basics</a></li>
<li><a href="#scripts-that-saved-us-hours">Scripts that saved us hours</a></li>
<li><a href="#common-migration-patterns">Common Migration Patterns</a></li>
<li><a href="#the-ai-integration-revolution">The AI Integration Revolution</a></li>
<li><a href="#the-unexpected-benefits">The Unexpected Benefits</a></li>
<li><a href="#getting-your-team-started">Getting Your Team Started</a></li>
<li><a href="#what-this-means-for-engineering-teams">What This Means for Engineering Teams</a></li>
</ol>
<hr>
<h2 id="what-youll-learn">What you’ll learn</h2>
<ul>
<li><strong>Migrate</strong> a Postman collection (requests <em>and</em> environments) to Bruno in a few hours.</li>
<li><strong>Keep docs in‑lockstep</strong> with your codebase; no more stale Postman descriptions.</li>
<li><strong>Let AI write the boring bits:</strong> type <code>/bruno-api path/to/request.bru</code> and get ready‑to‑ship docs, TypeScript types, and React Query hooks.</li>
<li><strong>Catch breaking changes at review time,</strong> not after deploy night.</li>
</ul>
<p>If you’re exploring AI‑first tooling and looking to streamline your API workflow, this guide walks you through our practical migration step‑by‑step. If you’re already using Bruno, you’ll learn how to help improve your existing workflow further using AI.</p>
<hr>
<h2 id="why-we-relied-on-postman-for-so-long--where-it-falls-short">Why we relied on Postman for so long &#x26; where it falls short</h2>
<p>At <a href="https://diversio.com/">Diversio</a>, Postman had been our all‑purpose API toolkit since the company’s first endpoint shipped in 2018. Every engineer has owned a collection or three; shared environments lived in the cloud; QA and PMs could fire requests without touching the codebase.</p>
<p><strong>Why Postman Works for Us (So Far)</strong></p>
<ul>
<li>
<p><em>Scriptable</em>: Pre‑request scripts spun up test users, refreshed auth tokens, and handled auth with a single click.</p>
</li>
<li>
<p><em>Variable templating</em>: <code>{{base_url}}</code>, <code>{{auth_token}}</code>, and other vars kept requests DRY across local and cloud setups.</p>
</li>
<li>
<p><em>Chained workflows</em>: Scripts set environment variables that downstream requests consumed, so multi‑step API flows ran end‑to‑end without anyone hand‑editing the shared collection.</p>
</li>
<li>
<p><em>Performance‑testing ready</em>: The same collections powered our performance tests, giving us baseline latency numbers without duplicating effort.</p>
</li>
<li>
<p><em>Full‑stack friendly</em>: Frontend and backend engineers could debug and iterate on the same requests without context‑switching or extra tooling.</p>
</li>
<li>
<p><em>Collaborative</em>: Share links meant non‑engineers could poke endpoints in seconds.</p>
</li>
</ul>
<h3 id="why-it-started-to-hurt">Why it started to hurt</h3>
<p>As our API surface has increased and now we are working on more and more features at once(thanks to agentic coding), the workflow that once felt effortless began eating hours and hurting our productivity.</p>
<ul>
<li>
<p><strong>Manual sync tax</strong> – Every new endpoint meant combing through multiple collections to wire up scripts, tests, and examples by hand.</p>
</li>
<li>
<p><strong>Docs drift</strong> – Descriptions hid in Postman’s UI; unless someone remembered to update them, they slipped out of date.</p>
</li>
<li>
<p><strong>Invisible breaking changes</strong> – Because collections lived in Postman’s cloud, reviewers never saw contract updates during code review.</p>
</li>
<li>
<p><strong>Meeting creep</strong> – We still ended up on calls to reconcile mismatched examples and edge‑case behaviours. A lot of time spent in huddles and stand-ups discussing APIs that we couldn’t document well inside of Postman.</p>
</li>
</ul>
<p>Postman still <em>works</em> but it has just slowed us down. Any change meant updating code <em>and</em> a JSON export no one liked opening. That lag became the bottleneck.</p>
<p><a href="https://www.usebruno.com/">Bruno</a>’s Git‑friendly plain‑text format, and its ability to embed full Markdown docs, looked like a way out. The best part? AI agents can read <code>.bru</code> files like normal code, so automation suddenly became trivial and APIs are now part of our codebase and included in diffs during code reviews.</p>
<h2 id="migration-basics">Migration basics</h2>
<h3 id="1-script-syntax">1. Script syntax</h3>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="js"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Postman</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">var</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> json </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> JSON</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">parse</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(responseBody);</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">var</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> token </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> json[</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"access_token"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">];</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">pm.environment.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">set</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"auth_token"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, token);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Bruno</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">var</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> json </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> res.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">getBody</span><span style="color:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">var</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> token </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> json.access_token;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">bru.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">setEnvVar</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"auth_token"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, token);</span></span></code></pre>
<h3 id="2-base64-helpers">2. Base64 helpers</h3>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="js"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Postman</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> payload</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> JSON</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">parse</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">atob</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(token.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">split</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'.'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)[</span><span style="color:#005CC5;--shiki-dark:#79B8FF">1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">]));</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Bruno (Buffer.from works in Bruno's Node.js environment)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> payload</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> JSON</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">parse</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(Buffer.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">from</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(token.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">split</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'.'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)[</span><span style="color:#005CC5;--shiki-dark:#79B8FF">1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">], </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'base64'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">).</span><span style="color:#6F42C1;--shiki-dark:#B392F0">toString</span><span style="color:#24292E;--shiki-dark:#E1E4E8">());</span></span></code></pre>
<h3 id="3-environment-files">3. Environment files</h3>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="plaintext"><code><span class="line"><span>vars {</span></span>
<span class="line"><span>  base_url: http://localhost:8000</span></span>
<span class="line"><span>  api_key: {{process.env.API_KEY}}</span></span>
<span class="line"><span>}</span></span></code></pre>
<h3 id="4-inline-markdown-docs">4. In‑line Markdown docs</h3>
<p>Bruno lets every request double as a mini README:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="plaintext"><code><span class="line"><span>docs {</span></span>
<span class="line"><span>  # User Authentication</span></span>
<span class="line"><span>  </span></span>
<span class="line"><span>  `POST` `/api/v2/auth/login`</span></span>
<span class="line"><span></span></span>
<span class="line"><span>  ## Overview</span></span>
<span class="line"><span></span></span>
<span class="line"><span>  Returns JWT tokens.</span></span>
<span class="line"><span></span></span>
<span class="line"><span>  ⚡ **Rate Limit**: 5/min per IP</span></span>
<span class="line"><span></span></span>
<span class="line"><span>  ...</span></span>
<span class="line"><span>}</span></span></code></pre>
<p>Rich tables, code fences, emojis etc can be included. Because it’s Markdown, Claude Code and other AI tools parse it effortlessly.</p>
<h3 id="5-organizing-your-bruno-collections">5. Organizing your Bruno collections</h3>
<p>After migration, we organized our Bruno files by feature rather than by API version. Here’s our structure:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="text"><code><span class="line"><span>bruno/</span></span>
<span class="line"><span>├── .env                  # Local secrets (git-ignored)</span></span>
<span class="line"><span>├── .env.example          # Template for team members</span></span>
<span class="line"><span>├── .gitignore           # Ensures .env stays local</span></span>
<span class="line"><span>├── environments/</span></span>
<span class="line"><span>│   ├── local.bru</span></span>
<span class="line"><span>│   ├── staging.bru</span></span>
<span class="line"><span>│   └── production.bru</span></span>
<span class="line"><span>├── auth/</span></span>
<span class="line"><span>│   ├── login.bru</span></span>
<span class="line"><span>│   ├── refresh_token.bru</span></span>
<span class="line"><span>│   └── logout.bru</span></span>
<span class="line"><span>├── users/</span></span>
<span class="line"><span>│   ├── get_profile.bru</span></span>
<span class="line"><span>│   ├── update_profile.bru</span></span>
<span class="line"><span>│   └── list_users.bru</span></span>
<span class="line"><span>├── analytics/</span></span>
<span class="line"><span>│   ├── dashboard_metrics.bru</span></span>
<span class="line"><span>│   └── export_reports.bru</span></span>
<span class="line"><span>└── integrations/</span></span>
<span class="line"><span>    ├── stripe/</span></span>
<span class="line"><span>    │   └── create_payment.bru</span></span>
<span class="line"><span>    └── webhooks/</span></span>
<span class="line"><span>        └── incoming_webhooks.bru</span></span></code></pre>
<p><strong>Security tip</strong>: Always add <code>.env</code> to your <code>.gitignore</code>. Create a <code>.env.example</code> with dummy values so team members know what environment variables to set:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># .env.example</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">API_BASE_URL</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">http://localhost:8000</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">API_KEY</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">your-api-key-here</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">JWT_TOKEN</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">will-be-set-by-login-script</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">DEFAULT_COMPANY_ID</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">1234</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">TEST_USERNAME</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">testuser</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">TEST_PASSWORD</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">testpass</span></span></code></pre>
<p>Each <code>.bru</code> file can include documentation, pre/post scripts, and environment variable references. This structure makes it easy to:</p>
<ul>
<li>Find related endpoints quickly</li>
<li>Review API changes in PRs</li>
<li>Generate documentation by feature area</li>
<li>Manage permissions at the folder level</li>
<li>Keep sensitive data out of version control</li>
</ul>
<hr>
<h2 id="scripts-that-saved-us-hours">Scripts that saved us hours</h2>
<h3 id="environment-converter-python">Environment converter (Python)</h3>
<p>The script is available in this GitHub gist: <a href="https://gist.github.com/ashwch/317ce7d35dd605187bedf39e6b7858a8">migrate_postman_envs.py</a></p>
<h4 id="command">Command</h4>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">$</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> uv</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> run</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> migrate_postman_envs.py</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ./postman_environments/</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ./bruno_environments/</span></span></code></pre>
<h4 id="output">Output</h4>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="text"><code><span class="line"><span>🔄 Processing 10 file(s)...</span></span>
<span class="line"><span></span></span>
<span class="line"><span>✅ Converted webhook_env.json → ./bruno_environments/webhook_env.bru</span></span>
<span class="line"><span>✅ Converted local_env.json → ./bruno_environments/local_env.bru</span></span>
<span class="line"><span>✅ Converted production.json → ./bruno_environments/production.bru</span></span>
<span class="line"><span>✅ Converted staging.json → ./bruno_environments/staging.bru</span></span>
<span class="line"><span>✅ Converted development.json → ./bruno_environments/development.bru</span></span>
<span class="line"><span>✅ Converted test_env.json → ./bruno_environments/test_env.bru</span></span>
<span class="line"><span>✅ Converted qa_env.json → ./bruno_environments/qa_env.bru</span></span>
<span class="line"><span>✅ Converted sandbox.json → ./bruno_environments/sandbox.bru</span></span>
<span class="line"><span>✅ Converted integration.json → ./bruno_environments/integration.bru</span></span>
<span class="line"><span>✅ Converted demo_env.json → ./bruno_environments/demo_env.bru</span></span>
<span class="line"><span></span></span>
<span class="line"><span>✨ Done! Converted 10/10 file(s)</span></span></code></pre>
<h4 id="validator-command">Validator Command</h4>
<p>The script can be found here: <a href="https://gist.github.com/ashwch/cd87eb1574b1b88d21ddef1508a186f6">validate_bruno_files.py</a></p>
<h3 id="command-1">Command</h3>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">$</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> uv</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> run</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> validate_bruno_files.py</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ./bruno_environments/</span></span></code></pre>
<h3 id="output-1">Output</h3>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="text"><code><span class="line"><span>🔍 Validating 10 Bruno file(s)...</span></span>
<span class="line"><span></span></span>
<span class="line"><span>✅ demo_env.bru</span></span>
<span class="line"><span>✅ development.bru</span></span>
<span class="line"><span>✅ integration.bru</span></span>
<span class="line"><span>✅ local_dev.bru</span></span>
<span class="line"><span>✅ performance_test.bru</span></span>
<span class="line"><span>✅ production.bru</span></span>
<span class="line"><span>✅ qa_testing.bru</span></span>
<span class="line"><span>✅ sandbox.bru</span></span>
<span class="line"><span>✅ staging.bru</span></span>
<span class="line"><span>✅ user_acceptance.bru</span></span>
<span class="line"><span></span></span>
<span class="line"><span>📊 Summary: 10/10 file(s) valid</span></span></code></pre>
<h3 id="request-migration-script">Request Migration Script</h3>
<p>For the actual requests, here’s a simple bash script:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">#!/bin/bash</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># convert_postman_scripts.sh</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Common replacements</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">sed</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 's/JSON\.parse(responseBody)/res.getBody()/g'</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> *</span><span style="color:#032F62;--shiki-dark:#9ECBFF">.bru</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">sed</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 's/atob(/Buffer.from(/g'</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> *</span><span style="color:#032F62;--shiki-dark:#9ECBFF">.bru</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">sed</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 's/pm\.environment\.set(/bru.setEnvVar(/g'</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> *</span><span style="color:#032F62;--shiki-dark:#9ECBFF">.bru</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Fix dictionary access patterns</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">sed</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -i</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 's/jsonData\["\([^"]*\)"\]/jsonData.\1/g'</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> *</span><span style="color:#032F62;--shiki-dark:#9ECBFF">.bru</span></span></code></pre>
<p><strong>Pro tip</strong>: Run this on a copy first. Some replacements might need manual review, especially if you have complex string patterns.</p>
<h2 id="common-migration-patterns">Common Migration Patterns</h2>
<h3 id="response-validation">Response Validation</h3>
<p>Add defensive checks when migrating:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="javascript"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Old Postman way (often broke with null responses)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">var</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> id </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> JSON</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">parse</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(responseBody)[</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"data"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">][</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"id"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">];</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Better Bruno pattern</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">var</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> response </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> res.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">getBody</span><span style="color:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> (response </span><span style="color:#D73A49;--shiki-dark:#F97583">&#x26;&#x26;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> response.data </span><span style="color:#D73A49;--shiki-dark:#F97583">&#x26;&#x26;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> response.data.id) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  bru.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">setEnvVar</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"resource_id"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, response.data.id);</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">} </span><span style="color:#D73A49;--shiki-dark:#F97583">else</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  console.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">error</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Unexpected response structure:"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, response);</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<h3 id="test-migration">Test Migration</h3>
<p>If you have Postman tests:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="javascript"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Postman test</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">pm.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">test</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Status code is 200"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#D73A49;--shiki-dark:#F97583">function</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> () {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    pm.response.to.have.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">status</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">200</span><span style="color:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">});</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Bruno test</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">test</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Status code is 200"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#D73A49;--shiki-dark:#F97583">function</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> () {</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    expect</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(res.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">getStatus</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()).to.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">equal</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">200</span><span style="color:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">});</span></span></code></pre>
<h2 id="the-ai-integration-revolution">The AI Integration Revolution</h2>
<p>Here’s where things get exciting. I created a custom Claude <a href="https://docs.anthropic.com/en/docs/claude-code/slash-commands">slash command</a> that analyzes our Bruno files and generates comprehensive documentation by inspecting our Django codebase.</p>
<h3 id="the-bruno-api-command">The <code>/bruno-api</code> Command</h3>
<p>Instead of maintaining documentation scripts, we taught Claude to understand our codebase. Here’s how the actual command works:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="text"><code><span class="line"><span># Custom Claude Command: /bruno-api</span></span>
<span class="line"><span></span></span>
<span class="line"><span>When the user types `/bruno-api [bruno-file-path]`, you will:</span></span>
<span class="line"><span></span></span>
<span class="line"><span>1. **Parse the Bruno File**</span></span>
<span class="line"><span>   - Extract the HTTP method, endpoint URL, headers, and body structure</span></span>
<span class="line"><span>   - Identify authentication requirements (Bearer token, API key, etc.)</span></span>
<span class="line"><span>   - Note any pre/post-request scripts for context</span></span>
<span class="line"><span></span></span>
<span class="line"><span>2. **Reverse Engineer the Backend**</span></span>
<span class="line"><span>   - Use the endpoint URL to find the Django URL pattern:</span></span>
<span class="line"><span>     path('api/v2/users/', UserViewSet.as_view())</span></span>
<span class="line"><span>     re_path(r'^api/v1/reports/(?P&#x3C;pk>\d+)/$', ReportDetailView.as_view())</span></span>
<span class="line"><span>   - Locate the corresponding view/viewset class</span></span>
<span class="line"><span>   - For Django Ninja endpoints, find the router and operation functions</span></span>
<span class="line"><span></span></span>
<span class="line"><span>3. **Deep Code Analysis**</span></span>
<span class="line"><span>   - Extract serializer fields, types, validation rules</span></span>
<span class="line"><span>   - Identify permission classes and authentication requirements</span></span>
<span class="line"><span>   - Trace through the view method to understand:</span></span>
<span class="line"><span>     - Query parameters and filtering</span></span>
<span class="line"><span>     - Data transformations</span></span>
<span class="line"><span>     - External service calls</span></span>
<span class="line"><span>     - Error conditions</span></span>
<span class="line"><span></span></span>
<span class="line"><span>4. **Generate Comprehensive Documentation**</span></span>
<span class="line"><span>   Including:</span></span>
<span class="line"><span>   - Full API endpoint documentation</span></span>
<span class="line"><span>   - TypeScript interfaces for request/response</span></span>
<span class="line"><span>   - React Query hooks with error handling</span></span>
<span class="line"><span>   - Authentication requirements</span></span>
<span class="line"><span>   - Business logic notes (caching, rate limits, etc.)</span></span>
<span class="line"><span>   - Common error scenarios</span></span></code></pre>
<p>Note: For brevity, we have excluded a lot of details like <code>allowed-tools</code>, <code>Context</code> etc from the command above. But these and other internal project specific details are present in our <code>/bruno-api</code> command.</p>
<h3 id="how-it-analyzes-your-code">How It Analyzes Your Code</h3>
<p>The sophistication comes from how Claude connects all the pieces:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="text"><code><span class="line"><span># Claude's Analysis Process:</span></span>
<span class="line"><span></span></span>
<span class="line"><span>1. Bruno file says: GET /api/v2/analytics/inclusion-scores/</span></span>
<span class="line"><span>2. Find in urls.py: path('api/v2/analytics/inclusion-scores/', InclusionScoresView.as_view())</span></span>
<span class="line"><span>3. Find InclusionScoresView class</span></span>
<span class="line"><span>4. Analyze the get() method:</span></span>
<span class="line"><span>   - What serializer? InclusionScoresSerializer</span></span>
<span class="line"><span>   - What permissions? IsAuthenticated + HasAnalyticsAccess</span></span>
<span class="line"><span>   - What does it do? Aggregates survey data with demographic breakdowns</span></span>
<span class="line"><span>5. Check serializer fields and validation</span></span>
<span class="line"><span>6. Find related models and business logic</span></span>
<span class="line"><span>7. Generate complete, accurate documentation</span></span></code></pre>
<p><strong>The magic</strong>: This is more than just brittle script parsing AST. Claude Code understands our code semantically, follows imports, and comprehends business logic and can inspect multiple aspects of an API.</p>
<h3 id="real-example-output">Real Example Output</h3>
<p><strong>Input:</strong> Simple Bruno file from Postman migration - <a href="https://gist.github.com/ashwch/2a9d05024ac43479782092acbd4eae8f">see this basic file</a> (just endpoint + auth)</p>
<p><strong>Command:</strong> <code>/bruno-api bruno/analytics/user_metrics.bru</code></p>
<p><strong>Output:</strong> Claude analyzes the Django codebase and generates comprehensive documentation. Here’s a small sample:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// TypeScript Interfaces (auto-generated from Django serializers)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">interface</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> UserMetricsResponse</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">  count</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> number</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">  next</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> string</span><span style="color:#D73A49;--shiki-dark:#F97583"> |</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> null</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">  results</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Array</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&#x3C;{</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">    user_id</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> string</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">    email</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> string</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">    last_active</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> string</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">    total_sessions</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> number</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">    sessions_this_month</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> number</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">    avg_session_duration</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> string</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">    status</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'active'</span><span style="color:#D73A49;--shiki-dark:#F97583"> |</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'inactive'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">    role</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> string</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">    department</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> string</span><span style="color:#D73A49;--shiki-dark:#F97583"> |</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> null</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  }>;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// React Query Hook (with error handling derived from Django views)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">export</span><span style="color:#D73A49;--shiki-dark:#F97583"> const</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> useUserMetrics</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="color:#E36209;--shiki-dark:#FFAB70">companyId</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> string</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">params</span><span style="color:#D73A49;--shiki-dark:#F97583">?:</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> UserMetricsParams</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) </span><span style="color:#D73A49;--shiki-dark:#F97583">=></span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">  return</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> useQuery</span><span style="color:#24292E;--shiki-dark:#E1E4E8">({</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    queryKey: [</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'user-metrics'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, companyId, params],</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    queryFn</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#D73A49;--shiki-dark:#F97583">async</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> () </span><span style="color:#D73A49;--shiki-dark:#F97583">=></span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">      const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> response</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#D73A49;--shiki-dark:#F97583"> await</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> apiClient.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">get</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">        `/api/v2/companies/${</span><span style="color:#24292E;--shiki-dark:#E1E4E8">companyId</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}/user-metrics/`</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">        { params }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">      );</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">      return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> response.data;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    },</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    enabled: </span><span style="color:#D73A49;--shiki-dark:#F97583">!!</span><span style="color:#24292E;--shiki-dark:#E1E4E8">companyId,</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    retry</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: (</span><span style="color:#E36209;--shiki-dark:#FFAB70">failureCount</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">error</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> any</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) </span><span style="color:#D73A49;--shiki-dark:#F97583">=></span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">      // Smart retry logic based on Django view error handling</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">      if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> (error?.response?.status </span><span style="color:#D73A49;--shiki-dark:#F97583">===</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 401</span><span style="color:#D73A49;--shiki-dark:#F97583"> ||</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> error?.response?.status </span><span style="color:#D73A49;--shiki-dark:#F97583">===</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 403</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        return</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> false</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">      }</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">      return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> failureCount </span><span style="color:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 3</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  });</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">};</span></span></code></pre>
<p><strong>This is just a fraction of the output.</strong> <a href="https://gist.github.com/ashwch/65dc35b651c989355bb924b5bfb09bd8">See the complete generated documentation</a> which includes:</p>
<ul>
<li>Complete API documentation with request/response examples</li>
<li>Comprehensive error handling for all status codes</li>
<li>Authentication and permission requirements</li>
<li>TypeScript interfaces for all data structures</li>
<li>React Query hooks with infinite scrolling support</li>
<li>Testing examples and integration patterns</li>
<li>Business logic notes and performance considerations</li>
<li>Implementation details and database optimization notes</li>
</ul>
<p><strong>The key insight:</strong> Claude reads the actual implementation, so the documentation is always accurate. <em>It can still make mistakes, so it’s always critical to review the files and nudge it in right direction</em>.</p>
<h3 id="adapting-for-your-framework">Adapting for Your Framework</h3>
<p>This approach works for any framework:</p>
<ul>
<li><strong>Django</strong>: Find views, serializers, permissions</li>
<li><strong>Express</strong>: Parse routes, middleware, validators</li>
<li><strong>Rails</strong>: Analyze controllers, strong params</li>
<li><strong>FastAPI</strong>: Extract Pydantic models, dependencies</li>
</ul>
<h2 id="the-unexpected-benefits">The Unexpected Benefits</h2>
<h3 id="1-code-reviews-for-api-changes">1. Code Reviews for API Changes</h3>
<p>When someone changes an API, reviewers can see it in the PR:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="diff"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">git diff api/users/create.bru</span></span>
<span class="line"></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"><span style="user-select: none;">+</span> body:json {</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"><span style="user-select: none;">+</span>   {</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"><span style="user-select: none;">+</span>     "email": "{{email}}",</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"><span style="user-select: none;">+</span>     "role": "{{role}}",</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"><span style="user-select: none;">+</span>     "department": "{{department}}"  // New field added</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"><span style="user-select: none;">+</span>   }</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"><span style="user-select: none;">+</span> }</span></span></code></pre>
<p>Breaking changes are caught before deployment, not after.</p>
<h3 id="2-ai-powered-api-discovery">2. AI-Powered API Discovery</h3>
<p>New and existing team members can ask:</p>
<ul>
<li>“Show me all endpoints that return user data”</li>
<li>“How do I paginate through results?”</li>
<li>“Generate TypeScript types for the profile endpoint”</li>
</ul>
<p>Claude reads your Bruno collections and provides accurate answers and vice-versa.</p>
<h3 id="3-documentation-that-stays-fresh">3. Documentation That Stays Fresh</h3>
<p>Since docs live with code, they’re more likely to stay updated. We are working on a pre-commit hook that is going to remind developers to update Bruno files when an API related change is made.</p>
<h2 id="getting-your-team-started">Getting Your Team Started</h2>
<h3 id="start-small-move-fast">Start Small, Move Fast</h3>
<p>We did our entire migration over a weekend, and you can too. Here’s what worked for us:</p>
<ol>
<li><strong>Pick your proof of concept</strong> - Choose one well-used Postman collection</li>
<li><strong>Run the migration scripts</strong> - Use the Python converters linked above</li>
<li><strong>Set up your first AI command</strong> - Start with our <code>/bruno-api</code> template</li>
<li><strong>Show, don’t tell</strong> - Generate docs for one endpoint and share with the team</li>
</ol>
<h3 id="the-aha-moment">The Aha Moment</h3>
<p>The real buy-in happens when developers see:</p>
<ul>
<li>Their API changes appearing in PR diffs</li>
<li>Claude Code generating accurate TypeScript interfaces</li>
<li>Documentation that actually matches the code</li>
<li>No more “update Postman” tickets in the backlog</li>
</ul>
<h3 id="practical-next-steps">Practical Next Steps</h3>
<ul>
<li><strong>Today</strong>: <a href="https://learning.postman.com/docs/getting-started/importing-and-exporting/exporting-data/">Export one Postman collection</a>, import to Bruno</li>
<li><strong>Tomorrow</strong>: Create your first custom Claude command</li>
<li><strong>This Week</strong>: Add <code>.bru</code> files to your repo and update PR templates</li>
<li><strong>Next Sprint</strong>: Deprecate Postman licenses and celebrate the cost savings</li>
</ul>
<h3 id="common-questions-we-heard">Common Questions We Heard</h3>
<ul>
<li>“Can QA still use it?” → Yes, Bruno has a UI too (and it’s free)</li>
<li>“What if we need to go back?” → Keep Postman exports for 30 days, but we never looked back</li>
</ul>
<h3 id="creating-your-own-ai-commands">Creating Your Own AI Commands</h3>
<p>Here’s a complete example you can adapt:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="markdown"><code><span class="line"><span style="color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#79B8FF;--shiki-dark-font-weight:bold"># .claude/commands/bruno-api.md</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">You are an API documentation expert for our [</span><span style="color:#032F62;--shiki-light-text-decoration:underline;--shiki-dark:#DBEDFF;--shiki-dark-text-decoration:underline">Framework</span><span style="color:#24292E;--shiki-dark:#E1E4E8">] application.</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">When user types /bruno-api [</span><span style="color:#032F62;--shiki-light-text-decoration:underline;--shiki-dark:#DBEDFF;--shiki-dark-text-decoration:underline">file-path</span><span style="color:#24292E;--shiki-dark:#E1E4E8">]:</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">1.</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> Read the Bruno file at the specified path</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">2.</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> Extract: method, URL, headers, body structure</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">3.</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> Find the implementation:</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">   -</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> For Express: Find app.get/post/put in routes/</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">   -</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> For Django: Find path() in urls.py, then view</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">   -</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> For Rails: Find route in config/routes.rb</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">4.</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> Analyze the handler/controller to determine:</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">   -</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> Required parameters and validation</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">   -</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> Authentication/authorization </span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">   -</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> Response structure</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">   -</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> Error cases</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">5.</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> Generate documentation including:</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">   -</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> Clear description of what the endpoint does</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">   -</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> Request/response examples with real data</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">   -</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> [Your frontend framework] integration code</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">   -</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> Common errors and how to handle them</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Use our conventions:</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> TypeScript for all interfaces</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> Include data validation rules</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> Show rate limits if applicable</span></span>
<span class="line"><span style="color:#E36209;--shiki-dark:#FFAB70">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> Note any side effects (emails, webhooks, etc.)</span></span></code></pre>
<p><strong>Implementation tip</strong>: Start with one endpoint type (e.g., CRUD operations) and expand from there.</p>
<h2 id="what-this-means-for-engineering-teams">What This Means for Engineering Teams</h2>
<p>The shift from Postman to Bruno in 2025 is really a shift in how we think about API documentation. It’s no longer a separate artifact that gets out of sync. It’s part of your codebase, reviewed like code, and enhanced by AI.</p>
<h3 id="immediate-benefits-we-measured">Immediate Benefits We Measured</h3>
<ul>
<li><strong>API documentation time</strong>: Reduced from days to hours</li>
<li><strong>Documentation quality</strong>: Rich Markdown docs with examples, tables, and diagrams (vs. limited formatting options and inconsistent AI-generated docs in Postman)</li>
<li><strong>Breaking changes caught</strong>: Significant improvement during code review (from rarely caught to consistently visible in PRs)</li>
<li><strong>Team participation</strong>: Entire team can now contribute without needing a Postman seat</li>
<li><strong>Onboarding time</strong>: New engineers integrate APIs faster with self-documenting collections and AI-generated examples</li>
</ul>]]></content:encoded>
            <author>Ashwini Chaudhary</author>
            <category>postman</category>
            <category>bruno</category>
            <category>api</category>
            <category>ai</category>
            <category>claude-code</category>
            <category>productivity</category>
            <category>diversio</category>
            <category>engineering</category>
            <category>tooling</category>
            <category>developer-experience</category>
        </item>
        <item>
            <title><![CDATA[Managing Context Metadata in Django-PGHistory: Solving the Persistence Problem]]></title>
            <link>https://ashwch.com/managing-context-metadata-django-pghistory-solving-persistence-problem.html</link>
            <guid isPermaLink="false">tag:ashwch.com,2025-04-02:/managing-context-metadata-django-pghistory-solving-persistence-problem.html</guid>
            <pubDate>Wed, 02 Apr 2025 04:00:00 GMT</pubDate>
            <description><![CDATA[Learn how to prevent metadata leakage between contexts in django-pghistory for cleaner, more accurate audit trails in your Django applications.]]></description>
            <content:encoded><![CDATA[<p>In this post you would learn how <code>pghistory.context</code> works and some of its gotchas that we faced at Diversio(thanks to <a href="https://github.com/amalrajdiversio">Amal Raj B R</a> who identified this) and how we adjusted our approach to avoid saving context metadata that is not relevant to the current business logic but is being carried over from a parent context.</p>
<h3 id="what-is-pghistorycontext">What is <code>pghistory.context</code>?</h3>
<p><code>pghistory.context</code> is a decorator and a function that is part of the fantastic <a href="https://django-pghistory.readthedocs.io/en/stable/"><code>django-pghistory</code></a> project. I would cover how we use <code>django-pghistory</code> in a separate post.</p>
<p>When <code>django-pghistory</code> is keeping track of changes to Django models, there are several instances where we want to include additional information with the tracked changes. This is where <code>pghistory.context</code> comes into picture to easily capture such metadata.</p>
<p>We are making use of <a href="https://django-pghistory.readthedocs.io/en/3.5.5/context/?h=historymiddleware#middleware"><code>pghistory.middleware.HistoryMiddleware</code></a> to collect basic fields that we want to store with each change. This includes IP address, URL and the User who made the request. Some of this is done by the <a href="https://github.com/AmbitionEng/django-pghistory/blob/e991e610ddfc393aa7e3f39945627485e0d7bc60/pghistory/middleware.py#L42">library itself</a> (comments removed for brevity) as seen in the code below.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">class</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> HistoryMiddleware</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    def</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> __init__</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(self, get_response):</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">        self</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.get_response </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> get_response</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    def</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> get_context</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(self, request) -> Dict[</span><span style="color:#005CC5;--shiki-dark:#79B8FF">str</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, Any]:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">        user </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">            request.user._meta.pk.get_db_prep_value(request.user.pk, connection)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">            if</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> hasattr</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(request, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"user"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) </span><span style="color:#D73A49;--shiki-dark:#F97583">and</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> hasattr</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(request.user, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"_meta"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">            else</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> None</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">        )</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"user"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: user, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"url"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: request.path}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    def</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> __call__</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(self, request):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> request.method </span><span style="color:#D73A49;--shiki-dark:#F97583">in</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> config.middleware_methods():</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">            with</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> pghistory.context(</span><span style="color:#D73A49;--shiki-dark:#F97583">**</span><span style="color:#005CC5;--shiki-dark:#79B8FF">self</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.get_context(request)):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">                if</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> isinstance</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(request, DjangoWSGIRequest):</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">                    request.</span><span style="color:#005CC5;--shiki-dark:#79B8FF">__class__</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> WSGIRequest</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">                elif</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> isinstance</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(request, DjangoASGIRequest):</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">                    request.</span><span style="color:#005CC5;--shiki-dark:#79B8FF">__class__</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ASGIRequest</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">                return</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> self</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.get_response(request)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        else</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">            return</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> self</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.get_response(request)</span></span></code></pre>
<p>An interesting thing happening here is that the request-response is wrapped in the <code>pghistory.context</code> context manager. That means all call to <code>pghistory.context</code> anywhere in views would keep on accumulating the metadata that <code>pghistory.context</code> is getting. This is a powerful pattern for audit tracking without having to manually pass context through your application code. Every database change automatically gets tagged with “who” (user) and “where” (URL) the change happened.</p>
<p>But this also has an unintended effect, let’s use an example to explain it(<a href="https://gist.github.com/ashwch/1fc98f3f76860b95936bf278ba38dbba">gist</a>)</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">In [</span><span style="color:#005CC5;--shiki-dark:#79B8FF">1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">]: </span><span style="color:#D73A49;--shiki-dark:#F97583">import</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> pghistory</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">   ...</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">   ...</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#D73A49;--shiki-dark:#F97583">with</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> pghistory.context(</span><span style="color:#E36209;--shiki-dark:#FFAB70">url</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"/foo/bar"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">user</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"bugs-bunny"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) </span><span style="color:#D73A49;--shiki-dark:#F97583">as</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> pg:</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">   ...</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:     </span><span style="color:#005CC5;--shiki-dark:#79B8FF">print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">f</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Level 1-1: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pg.metadata</span><span style="color:#005CC5;--shiki-dark:#79B8FF">}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">   ...</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">   ...</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:     </span><span style="color:#D73A49;--shiki-dark:#F97583">with</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> pghistory.context(</span><span style="color:#E36209;--shiki-dark:#FFAB70">foo</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"bar"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">):</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">   ...</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:         </span><span style="color:#005CC5;--shiki-dark:#79B8FF">print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">f</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Level 2-1: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pg.metadata</span><span style="color:#005CC5;--shiki-dark:#79B8FF">}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">   ...</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:         </span><span style="color:#D73A49;--shiki-dark:#F97583">with</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> pghistory.context(</span><span style="color:#E36209;--shiki-dark:#FFAB70">spam</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"eggs"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">):</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">   ...</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:             </span><span style="color:#005CC5;--shiki-dark:#79B8FF">print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">f</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Level 3-1: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pg.metadata</span><span style="color:#005CC5;--shiki-dark:#79B8FF">}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">   ...</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">   ...</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:         </span><span style="color:#D73A49;--shiki-dark:#F97583">with</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> pghistory.context(</span><span style="color:#E36209;--shiki-dark:#FFAB70">hannah</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"montana"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">):</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">   ...</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:             </span><span style="color:#005CC5;--shiki-dark:#79B8FF">print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">f</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Level 3-2: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pg.metadata</span><span style="color:#005CC5;--shiki-dark:#79B8FF">}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">   ...</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:             </span><span style="color:#D73A49;--shiki-dark:#F97583">with</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> pghistory.context(</span><span style="color:#E36209;--shiki-dark:#FFAB70">timon</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"pumba"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">):</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">   ...</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:                 </span><span style="color:#005CC5;--shiki-dark:#79B8FF">print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">f</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Level 4-1: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pg.metadata</span><span style="color:#005CC5;--shiki-dark:#79B8FF">}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">   ...</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">   ...</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:     </span><span style="color:#005CC5;--shiki-dark:#79B8FF">print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">f</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Level 1-2: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pg.metadata</span><span style="color:#005CC5;--shiki-dark:#79B8FF">}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">   ...</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:     </span><span style="color:#D73A49;--shiki-dark:#F97583">with</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> pghistory.context(</span><span style="color:#E36209;--shiki-dark:#FFAB70">monty</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"python"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">):</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">   ...</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:         </span><span style="color:#005CC5;--shiki-dark:#79B8FF">print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">f</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Level 2-2: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pg.metadata</span><span style="color:#005CC5;--shiki-dark:#79B8FF">}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">   ...</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">   ...</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">   ...</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#D73A49;--shiki-dark:#F97583">with</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> pghistory.context(</span><span style="color:#E36209;--shiki-dark:#FFAB70">guido</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"bdfl"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) </span><span style="color:#D73A49;--shiki-dark:#F97583">as</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> pg_2:</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">   ...</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:     </span><span style="color:#005CC5;--shiki-dark:#79B8FF">print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">f</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Level 1-4: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pg_2.metadata</span><span style="color:#005CC5;--shiki-dark:#79B8FF">}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">   ...</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">   ...</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">f</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Level 1-5: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pg.metadata</span><span style="color:#005CC5;--shiki-dark:#79B8FF">}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">   ...</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">f</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Level 1-6: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pg_2.metadata</span><span style="color:#005CC5;--shiki-dark:#79B8FF">}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">   ...</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Level </span><span style="color:#005CC5;--shiki-dark:#79B8FF">1</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#005CC5;--shiki-dark:#79B8FF">1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: {</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'url'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'/foo/bar'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'user'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'bugs-bunny'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Level </span><span style="color:#005CC5;--shiki-dark:#79B8FF">2</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#005CC5;--shiki-dark:#79B8FF">1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: {</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'url'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'/foo/bar'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'user'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'bugs-bunny'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'foo'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'bar'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Level </span><span style="color:#005CC5;--shiki-dark:#79B8FF">3</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#005CC5;--shiki-dark:#79B8FF">1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: {</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'url'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'/foo/bar'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'user'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'bugs-bunny'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'foo'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'bar'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'spam'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'eggs'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Level </span><span style="color:#005CC5;--shiki-dark:#79B8FF">3</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#005CC5;--shiki-dark:#79B8FF">2</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: {</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'url'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'/foo/bar'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'user'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'bugs-bunny'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'foo'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'bar'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'spam'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'eggs'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'hannah'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'montana'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Level </span><span style="color:#005CC5;--shiki-dark:#79B8FF">4</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#005CC5;--shiki-dark:#79B8FF">1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: {</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'url'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'/foo/bar'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'user'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'bugs-bunny'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'foo'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'bar'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'spam'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'eggs'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'hannah'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'montana'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'timon'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'pumba'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Level </span><span style="color:#005CC5;--shiki-dark:#79B8FF">1</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#005CC5;--shiki-dark:#79B8FF">2</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: {</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'url'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'/foo/bar'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'user'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'bugs-bunny'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'foo'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'bar'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'spam'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'eggs'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'hannah'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'montana'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'timon'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'pumba'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Level </span><span style="color:#005CC5;--shiki-dark:#79B8FF">2</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#005CC5;--shiki-dark:#79B8FF">2</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: {</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'url'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'/foo/bar'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'user'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'bugs-bunny'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'foo'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'bar'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'spam'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'eggs'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'hannah'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'montana'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'timon'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'pumba'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'monty'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'python'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Level </span><span style="color:#005CC5;--shiki-dark:#79B8FF">1</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#005CC5;--shiki-dark:#79B8FF">4</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: {</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'url'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'/foo/bar'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'user'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'bugs-bunny'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'foo'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'bar'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'spam'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'eggs'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'hannah'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'montana'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'timon'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'pumba'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'monty'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'python'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'guido'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'bdfl'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Level </span><span style="color:#005CC5;--shiki-dark:#79B8FF">1</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#005CC5;--shiki-dark:#79B8FF">5</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: {</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'url'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'/foo/bar'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'user'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'bugs-bunny'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'foo'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'bar'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'spam'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'eggs'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'hannah'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'montana'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'timon'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'pumba'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'monty'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'python'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'guido'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'bdfl'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Level </span><span style="color:#005CC5;--shiki-dark:#79B8FF">1</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#005CC5;--shiki-dark:#79B8FF">6</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: {</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'url'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'/foo/bar'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'user'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'bugs-bunny'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'foo'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'bar'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'spam'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'eggs'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'hannah'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'montana'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'timon'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'pumba'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'monty'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'python'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'guido'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'bdfl'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<p>Here as we can see, regardless of which level of context manager we are in, or even if it’s a completely new context manager, the keys keep on accumulating.</p>
<p>This is not something we want because there are multiple places in our business logic where we want to track different changes with different metadata instead of using the metadata from previous calls to <code>pghistory.context</code>.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">def</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> my_view</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(request):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> request.method </span><span style="color:#D73A49;--shiki-dark:#F97583">==</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "POST"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        with</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> pghistory.context(</span><span style="color:#E36209;--shiki-dark:#FFAB70">type</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"POST"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">):</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">            # Perform some action</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">            # save audit log</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">            pass</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    elif</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> request.method </span><span style="color:#D73A49;--shiki-dark:#F97583">==</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "GET"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        with</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> pghistory.context(</span><span style="color:#E36209;--shiki-dark:#FFAB70">type</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"GET"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">):</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">            # Perform some action</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">            # save audit log</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">            pass</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">    # External API call</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">    # Update an internal state</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    with</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> pghistory.context(</span><span style="color:#E36209;--shiki-dark:#FFAB70">type</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"EXTERNAL"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">):</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">        # Perform some action</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">        # save audit log</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        pass</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">    # Another action that updates the internal state</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    with</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> pghistory.context(</span><span style="color:#E36209;--shiki-dark:#FFAB70">type</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"INTERNAL"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">):</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">        # Perform some action</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">        # save audit log</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        pass</span></span>
<span class="line"></span></code></pre>
<p>Now for the above, the ideal behaviour we want is that every audit log should get the keys that were included in its context, but not the ones added by other contexts, apart from the common keys coming from the middleware.</p>
<h3 id="solution">Solution</h3>
<p>We want a solution where we want the keys added by the middleware to persist, but we do not want the keys added inside the views to persist, and every time we exit from a context the keys should be removed from the metadata.</p>
<p>For this the solution was to override the default implementation of <a href="https://github.com/AmbitionEng/django-pghistory/blob/e991e610ddfc393aa7e3f39945627485e0d7bc60/pghistory/runtime.py#L115"><code>pghistory.context</code></a>. Let’s say the custom implementation is called <code>custom_pg_context</code>, then we want the output of the program we had earlier on to be:</p>
<p>The key change we made was introducing a new parameter called <code>persist</code>. When set to <code>True</code> (the default), metadata keys will persist across nested contexts and remain available to other contexts. When set to <code>False</code>, metadata keys added in the current context will automatically be removed upon exiting the context, ensuring they don’t leak into subsequent operations.</p>
<p>Here only want the <code>user</code> and <code>url</code> to persist (later on <code>timon</code> as well)</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="bash"><code><span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">Level</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 1-1:</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> {'url':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '/foo/bar',</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'user':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'bugs-bunny'}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">Level</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 2-1:</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> {'url':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '/foo/bar',</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'user':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'bugs-bunny',</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'foo':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'bar'}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">Level</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 3-1:</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> {'url':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '/foo/bar',</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'user':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'bugs-bunny',</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'foo':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'bar',</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'spam':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'eggs'}</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Notice the lack of 'spam': 'eggs' in 3-2</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">Level</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 3-2:</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> {'url':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '/foo/bar',</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'user':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'bugs-bunny',</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'foo':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'bar',</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'hannah':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'montana'}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">Level</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 4-1:</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> {'url':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '/foo/bar',</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'user':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'bugs-bunny',</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'foo':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'bar',</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'hannah':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'montana',</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'timon':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'pumba'}</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Everything has been removed when we hit 1-2 except for url and user</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># and timon, because these we want to persist.</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">Level</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 1-2:</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> {'url':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '/foo/bar',</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'user':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'bugs-bunny',</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'timon':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'pumba'}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">Level</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 2-2:</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> {'url':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '/foo/bar',</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'user':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'bugs-bunny',</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'timon':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'pumba',</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'monty':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'python'}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">Level</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 1-4:</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> {'url':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '/foo/bar',</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'user':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'bugs-bunny',</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'timon':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'pumba',</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'guido':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'bdfl'}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">Level</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 1-5:</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> {'url':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '/foo/bar',</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'user':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'bugs-bunny',</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'timon':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'pumba',</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'guido':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'bdfl'}</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">Level</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 1-6:</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> {'url':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '/foo/bar',</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'user':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'bugs-bunny',</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'timon':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'pumba',</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'guido':</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'bdfl'}</span></span></code></pre>
<p>Now this is what the code looks like now(<a href="https://gist.github.com/ashwch/87a32121b80e41446260be0b3a89bf7e">gist</a>):</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">from</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> __future__</span><span style="color:#D73A49;--shiki-dark:#F97583"> import</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> annotations</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">from</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> contextvars </span><span style="color:#D73A49;--shiki-dark:#F97583">import</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ContextVar</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">from</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> copy </span><span style="color:#D73A49;--shiki-dark:#F97583">import</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> deepcopy</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">import</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> pghistory</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">context_stack: ContextVar[list[custom_pg_context]] </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ContextVar(</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">    "context_stack"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">default</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8">[]</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">class</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> custom_pg_context</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">pghistory</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">context</span><span style="color:#24292E;--shiki-dark:#E1E4E8">):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    def</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> __init__</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(self, </span><span style="color:#D73A49;--shiki-dark:#F97583">*</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, persist: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">bool</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> True</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#D73A49;--shiki-dark:#F97583">**</span><span style="color:#24292E;--shiki-dark:#E1E4E8">metadata):</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">        self</span><span style="color:#24292E;--shiki-dark:#E1E4E8">._persist </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> persist</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">        self</span><span style="color:#24292E;--shiki-dark:#E1E4E8">._local_metadata </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> deepcopy(metadata)</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">        self</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.token </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> context_stack.set(context_stack.get() </span><span style="color:#D73A49;--shiki-dark:#F97583">+</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> [</span><span style="color:#005CC5;--shiki-dark:#79B8FF">self</span><span style="color:#24292E;--shiki-dark:#E1E4E8">])</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">        super</span><span style="color:#24292E;--shiki-dark:#E1E4E8">().</span><span style="color:#005CC5;--shiki-dark:#79B8FF">__init__</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">**</span><span style="color:#24292E;--shiki-dark:#E1E4E8">metadata)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    def</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> __enter__</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(self):</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">        return_value </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> super</span><span style="color:#24292E;--shiki-dark:#E1E4E8">().</span><span style="color:#005CC5;--shiki-dark:#79B8FF">__enter__</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">        self</span><span style="color:#24292E;--shiki-dark:#E1E4E8">._tracker_value </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> return_value</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> return_value</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    def</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> __exit__</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(self, exc_type, exc_val, exc_tb):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        try</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">            if</span><span style="color:#D73A49;--shiki-dark:#F97583"> not</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> self</span><span style="color:#24292E;--shiki-dark:#E1E4E8">._persist:</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">                # Removing keys added in this context from parent contexts</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">                stack </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> context_stack.get()[:</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#005CC5;--shiki-dark:#79B8FF">1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">]</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">                for</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> parent </span><span style="color:#D73A49;--shiki-dark:#F97583">in</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> stack:</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">                    if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> parent._tracker_value:</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">                        for</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> key </span><span style="color:#D73A49;--shiki-dark:#F97583">in</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> self</span><span style="color:#24292E;--shiki-dark:#E1E4E8">._local_metadata:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">                            parent._tracker_value.metadata.pop(key, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">None</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">            return</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> super</span><span style="color:#24292E;--shiki-dark:#E1E4E8">().</span><span style="color:#005CC5;--shiki-dark:#79B8FF">__exit__</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(exc_type, exc_val, exc_tb)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        finally</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">            current_stack </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> context_stack.get()</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">            if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> current_stack </span><span style="color:#D73A49;--shiki-dark:#F97583">and</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> current_stack[</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#005CC5;--shiki-dark:#79B8FF">1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">] </span><span style="color:#D73A49;--shiki-dark:#F97583">is</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> self</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">                context_stack.set(current_stack[:</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#005CC5;--shiki-dark:#79B8FF">1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">])</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    @</span><span style="color:#005CC5;--shiki-dark:#79B8FF">staticmethod</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    def</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> get_parent_context</span><span style="color:#24292E;--shiki-dark:#E1E4E8">() -> custom_pg_context </span><span style="color:#D73A49;--shiki-dark:#F97583">|</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> None</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">        stack </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> context_stack.get()</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> stack[</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#005CC5;--shiki-dark:#79B8FF">2</span><span style="color:#24292E;--shiki-dark:#E1E4E8">] </span><span style="color:#D73A49;--shiki-dark:#F97583">if</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> len</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(stack) </span><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="color:#D73A49;--shiki-dark:#F97583"> else</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> None</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">with</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> custom_pg_context(</span><span style="color:#E36209;--shiki-dark:#FFAB70">url</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"/foo/bar"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">user</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"bugs-bunny"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">persist</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF">True</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) </span><span style="color:#D73A49;--shiki-dark:#F97583">as</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> pg:</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">    print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">f</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Level 1-1: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pg.metadata</span><span style="color:#005CC5;--shiki-dark:#79B8FF">}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    with</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> custom_pg_context(</span><span style="color:#E36209;--shiki-dark:#FFAB70">foo</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"bar"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">persist</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF">False</span><span style="color:#24292E;--shiki-dark:#E1E4E8">):</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">        print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">f</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Level 2-1: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pg.metadata</span><span style="color:#005CC5;--shiki-dark:#79B8FF">}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        with</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> custom_pg_context(</span><span style="color:#E36209;--shiki-dark:#FFAB70">spam</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"eggs"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">persist</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF">False</span><span style="color:#24292E;--shiki-dark:#E1E4E8">):</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">            print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">f</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Level 3-1: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pg.metadata</span><span style="color:#005CC5;--shiki-dark:#79B8FF">}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        with</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> custom_pg_context(</span><span style="color:#E36209;--shiki-dark:#FFAB70">hannah</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"montana"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">persist</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF">False</span><span style="color:#24292E;--shiki-dark:#E1E4E8">):</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">            print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">f</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Level 3-2: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pg.metadata</span><span style="color:#005CC5;--shiki-dark:#79B8FF">}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">            with</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> custom_pg_context(</span><span style="color:#E36209;--shiki-dark:#FFAB70">timon</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"pumba"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">persist</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF">True</span><span style="color:#24292E;--shiki-dark:#E1E4E8">):</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">                print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">f</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Level 4-1: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pg.metadata</span><span style="color:#005CC5;--shiki-dark:#79B8FF">}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">    print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">f</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Level 1-2: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pg.metadata</span><span style="color:#005CC5;--shiki-dark:#79B8FF">}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    with</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> custom_pg_context(</span><span style="color:#E36209;--shiki-dark:#FFAB70">monty</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"python"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">persist</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF">False</span><span style="color:#24292E;--shiki-dark:#E1E4E8">):</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">        print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">f</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Level 2-2: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pg.metadata</span><span style="color:#005CC5;--shiki-dark:#79B8FF">}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">with</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> custom_pg_context(</span><span style="color:#E36209;--shiki-dark:#FFAB70">guido</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"bdfl"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) </span><span style="color:#D73A49;--shiki-dark:#F97583">as</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> pg_2:</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">    print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">f</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Level 1-4: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pg_2.metadata</span><span style="color:#005CC5;--shiki-dark:#79B8FF">}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">f</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Level 1-5: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pg.metadata</span><span style="color:#005CC5;--shiki-dark:#79B8FF">}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">f</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Level 1-6: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pg_2.metadata</span><span style="color:#005CC5;--shiki-dark:#79B8FF">}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span></code></pre>
<h4 id="whats-happening-here">What’s happening here?</h4>
<ol>
<li>
<p>We are making use of <code>ContextVar</code> from the <a href="https://docs.python.org/3/library/contextvars.html"><code>contextvars</code></a> module to maintain a stack of all contexts and identify parents and children of any particular context we are in.</p>
</li>
<li>
<p>We create a custom class <code>custom_pg_context</code> that inherits from <code>pghistory.context</code>, with an additional <code>persist</code> parameter that defaults to <code>True</code>.</p>
</li>
<li>
<p>In the constructor, we keep a copy of the local metadata and add ourselves to the context stack.</p>
</li>
<li>
<p>During <code>__enter__</code>, we store the tracker object returned by the parent class for later use.</p>
</li>
<li>
<p>The magic happens in <code>__exit__</code>:</p>
<ul>
<li>If <code>persist=False</code>, we look through all parent contexts in the stack and remove any keys from our local metadata.</li>
<li>This ensures that temporary metadata isn’t carried forward to other contexts.</li>
<li>Keys with <code>persist=True</code> (like our <code>user</code>, <code>url</code>, and <code>timon</code>) remain in the metadata across all contexts.</li>
</ul>
</li>
<li>
<p>We also maintain proper stack management by removing our context from the stack when exiting.</p>
</li>
<li>
<p>The <code>get_parent_context()</code> static method provides a way to access the parent context if needed.</p>
</li>
</ol>
<p>This implementation solves our problem by allowing us to control which metadata persists across different parts of our application. We can use <code>persist=True</code> for global tracking (like user and URL from middleware) and <code>persist=False</code> for local, context-specific metadata that shouldn’t leak into other operations.</p>
<h3 id="using-the-custom-context-in-django">Using the Custom Context in Django</h3>
<p>To implement this in a Django project, you would:</p>
<ol>
<li>Create a new module (e.g., <code>core/context.py</code>) with the <code>custom_pg_context</code> implementation.</li>
<li>Update your middleware to use this custom context manager instead of the default one:</li>
</ol>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">from</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> core.context </span><span style="color:#D73A49;--shiki-dark:#F97583">import</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> custom_pg_context</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">class</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> CustomHistoryMiddleware</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">HistoryMiddleware</span><span style="color:#24292E;--shiki-dark:#E1E4E8">):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    def</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> __call__</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(self, request):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> request.method </span><span style="color:#D73A49;--shiki-dark:#F97583">in</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> config.middleware_methods():</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">            # Use our custom context manager with persist=True</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">            with</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> custom_pg_context(</span><span style="color:#E36209;--shiki-dark:#FFAB70">persist</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF">True</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#D73A49;--shiki-dark:#F97583">**</span><span style="color:#005CC5;--shiki-dark:#79B8FF">self</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.get_context(request)):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">                if</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> isinstance</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(request, DjangoWSGIRequest):</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">                    request.</span><span style="color:#005CC5;--shiki-dark:#79B8FF">__class__</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> WSGIRequest</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">                elif</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> isinstance</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(request, DjangoASGIRequest):</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">                    request.</span><span style="color:#005CC5;--shiki-dark:#79B8FF">__class__</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ASGIRequest</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">                return</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> self</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.get_response(request)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        else</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">            return</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> self</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.get_response(request)</span></span></code></pre>
<ol start="3">
<li>In your views and services, use the custom context manager with <code>persist=False</code> for operation-specific metadata:</li>
</ol>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">from</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> core.context </span><span style="color:#D73A49;--shiki-dark:#F97583">import</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> custom_pg_context</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">def</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> my_view</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(request):</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">    # Use persist=False for view-specific metadata</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    with</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> custom_pg_context(</span><span style="color:#E36209;--shiki-dark:#FFAB70">operation</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"view_details"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">persist</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF">False</span><span style="color:#24292E;--shiki-dark:#E1E4E8">):</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">        # Perform database operations</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">        item.save()  </span><span style="color:#6A737D;--shiki-dark:#6A737D"># This will include the operation metadata</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">        </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">    # Start a new context without leaking previous metadata</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    with</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> custom_pg_context(</span><span style="color:#E36209;--shiki-dark:#FFAB70">operation</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"update_related"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">persist</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF">False</span><span style="color:#24292E;--shiki-dark:#E1E4E8">):</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">        # Perform more operations</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">        related_item.save()  </span><span style="color:#6A737D;--shiki-dark:#6A737D"># Only includes this operation's metadata</span></span></code></pre>
<h3 id="performance-considerations">Performance Considerations</h3>
<p>One thing to note is that this approach does add some overhead, especially with deeply nested contexts. For most applications, this overhead is negligible compared to database operations, but it’s something to keep in mind for performance-critical code paths.</p>
<p>If you’re concerned about performance, you could optimize the implementation further:</p>
<ol>
<li>Use a more efficient data structure for the context stack</li>
<li>Limit the depth of context nesting</li>
<li>Consider using a profiler to identify bottlenecks in your specific use case</li>
</ol>
<h3 id="conclusion">Conclusion</h3>
<p>By extending <code>pghistory.context</code> with our custom implementation, we’ve solved the metadata leakage problem while maintaining the convenience of the context-based tracking system. This approach gives us fine-grained control over which metadata persists across different operations, making our audit logs more accurate and meaningful.</p>
<p>In a real-world Django application, this means we can add specific tracking metadata to different operations without polluting the audit trail of unrelated operations, while still maintaining the global context from middleware.</p>]]></content:encoded>
            <author>Ashwini Chaudhary</author>
            <category>django</category>
            <category>programming</category>
            <category>python</category>
            <category>django-pghistory</category>
            <category>diversio</category>
            <category>audit-trails</category>
            <category>context-management</category>
            <category>django-middleware</category>
            <category>python-contextvars</category>
            <category>database-tracking</category>
            <category>django-models</category>
            <category>code-patterns</category>
            <category>postgresql</category>
            <category>django-orm</category>
            <category>debugging</category>
        </item>
        <item>
            <title><![CDATA[Designing a Life: Lessons from Benjamin Franklin]]></title>
            <link>https://ashwch.com/life-lessons-benjamin-franklin.html</link>
            <guid isPermaLink="false">tag:ashwch.com,2025-03-26:/life-lessons-benjamin-franklin.html</guid>
            <pubDate>Wed, 26 Mar 2025 04:00:00 GMT</pubDate>
            <description><![CDATA[How Benjamin Franklin designed his life, built habits, and used humor, curiosity, and community to continuously reinvent himself.]]></description>
            <content:encoded><![CDATA[<p>Before listening to <a href="https://www.takeoverpod.com/episodes/benjamin-franklin"><em>How to Take Over the World</em></a>’s episode on Benjamin Franklin, my impression of him was limited to “kite, key, electricity.” Afterward, I realized he didn’t just discover electricity — he basically invented the blueprint for self-improvement, then actually lived it.</p>
<p>He wasn’t just an inventor; he created institutions and actively built himself. And he did it in a way that it feels surprisingly relevant today, without losing his sense of humour.</p>
<p>Some things that stuck with me.</p>
<h3 id="benjamin-franklin-the-original-self-made-person">Benjamin Franklin: The Original Self-Made Person</h3>
<p>He’s the most literal example of self-made people. He didn’t just bootstrap a career, he designed who he wanted to be and iterated toward it.</p>
<p>At one point in his life, he wrote out a list of 13 virtues. Things like temperance, resolution, industry, humility. And then created a tracking system to improve one per week. Like a habit tracker, but from the 1700s. He had a grid and he reviewed it nightly. He built an internal feedback loop before we had that language.</p>
<p>Did it make him perfect? Not even close. But <a href="https://www.goodreads.com/quotes/426224-but-on-the-whole-though-i-never-arrived-at-the">he said something</a> that stuck with me:</p>
<blockquote>
<p>“But on the whole, though I never arrived at the perfection I had been so ambitious of obtaining, but fell far short of it, yet I was, by the endeavour, a better and happier man than I otherwise should have been had I not attempted it; as those who aim at perfect writing by imitating the engraved copies, their hand is mended by the endeavour, and is tolerable while it continues fair and legible”</p>
</blockquote>
<p>That’s the point.</p>
<h3 id="learning-by-reverse-engineering">Learning by reverse-engineering</h3>
<p>Franklin taught himself how to write by reading essays in <em>The Spectator</em>. He used to summarize the key arguments, and then he would try to rewrite the entire piece from scratch a few days later. Then he’d compare his version with the original and note where he went wrong.</p>
<p>This is a simple yet brilliant way to learn. He built muscle memory towards it with this approach.</p>
<h3 id="curious-playful-and-unafraid-to-experiment">Curious, Playful, and Unafraid to Experiment</h3>
<p>This is my favorite part from the episode. Franklin did what interested him, even if it didn’t have any obvious ROI.</p>
<p>Someone once asked him why he was experimenting with hot air balloons, and he responded:</p>
<blockquote>
<p>“What is the use of a newborn baby?”</p>
</blockquote>
<p>He just liked it and trusted that something good would come out of following that instinct. Which is actually how most breakthroughs happen.</p>
<p>Most productive people aren’t just focussed on productivity, they are the ones who still let them chase just because it’s fun or weird or fascinating.</p>
<h3 id="peer-driven-growth">Peer-driven growth</h3>
<p>Franklin started something called the Junto. It was a group of ambitious, curious people who met weekly to debate ideas, share essays, and challenge each other to improve.</p>
<p>This wasn’t about ego or point-scoring. The rule was: no arguing, no personal attacks, no pretending you’re the smartest person in the room. Just honest enquiry and curiosity.</p>
<p>Junto lasted for 40 years.</p>
<p>And it wasn’t just talk — real things came out of it. The idea for America’s first subscription library was born in the Junto and became the Library Company of Philadelphia.</p>
<h3 id="serious-about-humour">Serious About Humour</h3>
<p>Franklin was funny, like actually funny. But he didn’t use it just to entertain — it was how he disarmed people, made ideas stick, and built a public persona people trusted.</p>
<p>When he was 15, he wrote letters under the name <em>Silence Dogood</em> and dropped them anonymously on the doorstep of his brother’s print shop. They were supposedly written by a widowed woman who was “handsome and sometimes witty.” That “sometimes” kills me. It’s the perfect character detail, funny, self-aware, instantly charming.</p>
<p>Later, as the fake author of <em>Poor Richard’s Almanack</em>, he’d write snarky fake feuds with imaginary critics. One of my favorites: he predicted another writer’s death, then when the guy wrote in angry that he was very much alive, Franklin insisted the letter must be a forgery — because the real man would never write so poorly.</p>
<p>Franklin didn’t take himself too seriously.  And that <em>was</em> part of his power.</p>
<p>His charm made people want to read him. It made political enemies underestimate him. And it made allies feel like they were in on the joke.</p>
<p>In a world full of Very Serious Men trying to win arguments, Franklin laughed — and then won anyway.</p>
<h3 id="lessons">Lessons</h3>
<p>Franklin wasn’t just a scientist, or a writer, or a politician, or a businessman. He was all of those things. But more than anything, he was a systems thinker for human life.</p>
<p>He designed how he lived.<br>
He chased what made him curious.<br>
He got better in public.<br>
He didn’t pretend to be perfect.<br>
And he never let ambition overshadow joy.</p>
<p>It’s a good model:</p>
<ul>
<li><strong>Create structures that support your growth.</strong><br>
Build your own systems—track habits, design feedback loops, give your goals shape.</li>
<li><strong>Form communities that push you forward.</strong><br>
Don’t just network. Find people who challenge, teach, and grow with you.</li>
<li><strong>Chase ideas without obsessing over immediate ROI.</strong><br>
Some of the most useful things start as playful experiments.</li>
<li><strong>Keep improving, even if you’ll never be perfect.</strong><br>
Progress matters more than perfection. The act of trying changes you.</li>
<li><strong>Don’t wait for permission to reinvent yourself.</strong><br>
Franklin didn’t. You don’t need anyone’s blessing to evolve.</li>
<li><strong>Use humor as a strategic advantage.</strong><br>
Be sharp, but approachable. Laugh. It makes people listen — and keeps you human.</li>
</ul>
<blockquote>
<p>Benjamin Franklin didn’t just build a remarkable life — he left behind a blueprint we can all still follow.</p>
</blockquote>
<h3 id="the-13-virtues">The 13 virtues</h3>
<ul>
<li><strong>Temperance</strong> – Eat not to dullness; drink not to elevation.</li>
<li><strong>Silence</strong> – Speak not but what may benefit others or yourself; avoid trifling conversation.</li>
<li><strong>Order</strong> – Let all your things have their places; let each part of your business have its time.</li>
<li><strong>Resolution</strong> – Resolve to perform what you ought; perform without fail what you resolve.</li>
<li><strong>Frugality</strong> – Make no expense but to do good to others or yourself; waste nothing.</li>
<li><strong>Industry</strong> – Lose no time; be always employed in something useful; cut off all unnecessary actions.</li>
<li><strong>Sincerity</strong> – Use no hurtful deceit; think innocently and justly, and, if you speak, speak accordingly.</li>
<li><strong>Justice</strong> – Wrong none by doing injuries or omitting the benefits that are your duty.</li>
<li><strong>Moderation</strong> – Avoid extremes; forbear resenting injuries so much as you think they deserve.</li>
<li><strong>Cleanliness</strong> – Tolerate no uncleanliness in body, clothes, or habitation.</li>
<li><strong>Tranquillity</strong> – Be not disturbed at trifles or at accidents common or unavoidable.</li>
<li><strong>Chastity</strong> – Rarely use venery (sexual indulgence) but for health or offspring, never to dullness, weakness, or the injury of your own or another’s peace or reputation.</li>
<li><strong>Humility</strong> – Imitate Jesus and Socrates.</li>
</ul>]]></content:encoded>
            <author>Ashwini Chaudhary</author>
            <category>benjamin-franklin</category>
            <category>self-improvement</category>
            <category>productivity</category>
            <category>habits</category>
            <category>personal-growth</category>
            <category>humor</category>
            <category>life-lessons</category>
            <category>systems-thinking</category>
            <category>history</category>
            <category>learning</category>
            <category>community</category>
            <category>curiosity</category>
        </item>
        <item>
            <title><![CDATA[Actionable Insights from Jeff Bezos' Interview at the DealBook Summit]]></title>
            <link>https://ashwch.com/actionable-insights-jeff-bezos-dealbook-summit.html</link>
            <guid isPermaLink="false">tag:ashwch.com,2025-03-10:/actionable-insights-jeff-bezos-dealbook-summit.html</guid>
            <pubDate>Mon, 10 Mar 2025 04:00:00 GMT</pubDate>
            <description><![CDATA[Actionable Insights from Jeff Bezos' Interview at the DealBook Summit.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Risk is often overestimated; opportunity is often underestimated.</p>
</blockquote>
<p>Podcast: <a href="https://www.founderspodcast.com/episodes/374-rare-jeff-bezos-interview">https://www.founderspodcast.com/episodes/374-rare-jeff-bezos-interview</a></p>
<p>YouTube: <a href="https://youtu.be/s71nJQqzYRQ">https://youtu.be/s71nJQqzYRQ</a></p>
<h4 id="build-something-that-outlasts-you">Build something that outlasts you</h4>
<p>Since early days of Amazon Bezos was thinking long-term and was quite clear that he wanted to build a company that would outlast him.</p>
<p>Spotify’s Daniel Ek shares a similar sentiment: Companies grow like children — initially they are a reflection of their creator, but over time they develops its own traits and characteristics.</p>
<p>Steve Jobs said something similar: You build a company that will stand for something a generation or two from now.</p>
<h4 id="follow-your-curiosity">Follow your curiosity</h4>
<p>One of his driving principles is “I wake up and follow my curiosity”.  Paul Graham calls curiosity the best guide for doing <em>great</em> work.</p>
<p>Instead of over-analyzing your next move, follow your curiosity and let that be your compass.</p>
<h4 id="ai-is-the-new-electricity">AI is the new electricity</h4>
<p>He describes AI as just an horizontal enabling layer, just like electricity. When electricity came out, people only wanted light bulbs; everything else came after. People weren’t putting electricity in their homes; they were putting light bulbs in their homes. At that point they weren’t even thinking of appliances, switchboard or anything, and there wasn’t even a concept of off switch.</p>
<p>Similarly, AI, like electricity before, will be embedded into everything, transforming industries in unpredictable ways.</p>
<blockquote>
<p>If AI is an enabling layer, ask: How can AI supercharge my business, my workflows, my products?_ The most valuable companies of the next decade will answer this question well.</p>
</blockquote>
<p>Link to his 2003 TED talk: <a href="https://www.ted.com/talks/jeff_bezos_the_electricity_metaphor_for_the_web_s_future">link</a></p>
<h4 id="the-aws-insight-computing-as-a-utility">The AWS Insight: Computing as a Utility</h4>
<p>His inspiration for AWS came from a 300 year old Luxembourg brewery that had its own power generator — because at that time, there were no power grids. Similarly, before AWS companies had their own data center and he realized that it’s not going to last.</p>
<p>That meant companies had to focus on something that wasn’t their core business, true for anything in the past that had to worry about generating their own electricity.</p>
<h4 id="the-power-of-being-misunderstood">The Power of Being Misunderstood</h4>
<p>He has long accepted that he will be misunderstood. He prioritizes doing what he believes is right over chasing a good PR. He said, if even your closest loved ones do not fully know you, how can you expect others to understand you?</p>
<p>Warren Buffett and Charlie Munger have similar philosophy. They keep an inner scorecard — measuring success by their own principles, not external validation.</p>
<blockquote>
<p>If no one skeptical about what you’re doing, you’re probably not pushing hard enough.</p>
</blockquote>
<h4 id="meetings-should-be-messy">Meetings Should Be Messy</h4>
<p>He insists that meetings should wander, and people should not come into meetings with rehearsed responses. He also always speaks last and is always asking if anyone has any dissenting opinions.</p>
<blockquote>
<p>Meetings should be raw, unfiltered and people should feel comfortable challenging ideas.</p>
</blockquote>
<h4 id="optimism-is-a-moral-duty">Optimism is a Moral Duty</h4>
<p>Edwin Land, Steve Jobs’ hero, believed that “optimism is a moral duty”. Bezos embodies this mindset, he truly believes there has never been a better time to be alive.</p>
<p><em>As a leader, you need to exude optimism. Ambitious projects require an unshakeable belief in a better future.</em></p>
<h4 id="every-decision-is-an-opportunity-cost">Every Decision is an Opportunity Cost</h4>
<p>He explains why he does so few interview. <em>“Every minute I’m doing this, I’m not spending that time doing something else”</em>.</p>
<blockquote>
<p>Time is your most valuable resource — guard it fiercely. Choose only what moves the needle for you.</p>
</blockquote>
<h4 id="ferocious-intelligence">Ferocious Intelligence</h4>
<p>Charlie Munger once said,  <em>“He is ferociously intelligent</em>.”</p>
<p>The relentless curiosity, optimism, and long-term thinking are what makes Bezos worth studying.</p>]]></content:encoded>
            <author>Ashwini Chaudhary</author>
            <category>jeff-bezos</category>
            <category>entrepreneurship</category>
            <category>long-term-thinking</category>
            <category>company-building</category>
            <category>ai-revolution</category>
            <category>innovation</category>
            <category>curiosity</category>
            <category>optimism</category>
            <category>opportunity-cost</category>
            <category>blue-origin</category>
            <category>amazon</category>
            <category>leadership</category>
            <category>decision-making</category>
            <category>business-strategy</category>
            <category>meetings</category>
            <category>bezos-quotes</category>
            <category>charlie-munger</category>
            <category>warren-buffett</category>
            <category>technology</category>
            <category>invention</category>
            <category>future-thinking</category>
            <category>risk-taking</category>
            <category>wealth-creation</category>
            <category>podcast</category>
            <category>founders-podcast</category>
        </item>
        <item>
            <title><![CDATA[30-60-90 — Set Up Your Engineering Hires for Success]]></title>
            <link>https://ashwch.com/30-60-90-set-up-your-engineering-hires-for-success.html</link>
            <guid isPermaLink="false">tag:ashwch.com,2025-03-08:/30-60-90-set-up-your-engineering-hires-for-success.html</guid>
            <pubDate>Sat, 08 Mar 2025 05:00:00 GMT</pubDate>
            <description><![CDATA[A 90-day roadmap that empowers new engineering hires through structured mentorship, engaging tasks, and continuous feedback.]]></description>
            <content:encoded><![CDATA[<h2 id="week-1">Week 1</h2>
<ul>
<li>
<p>Assign a buddy or mentor but avoid using a direct manager for this, as the person won’t be that open and comfortable with their manager.</p>
</li>
<li>
<p>Less emphasis on paperwork, focus more on mission, impact, values and culture.</p>
</li>
<li>
<p>Set up an interactive onboarding task to keep them engaged from day 1.</p>
</li>
<li>
<p>Encourage them to ask questions, voice concerns, and share ideas openly. It’s very important to teach them to not remain limited to DMs and become comfortable in public channels with the whole team.</p>
</li>
<li>
<p>Help them set up 1:1s with different members of the team</p>
</li>
</ul>
<h2 id="first-month">First month</h2>
<ul>
<li>
<p>Make sure their local set up is up and running.</p>
</li>
<li>
<p>Assign small tasks to them. This can be small bugfixes, features and any documentation update. Documentation update is a must, especially the docs they are going through during onboarding.</p>
<p>Always have tasks in your backlog that are tagged as <code>good-first-task</code>.</p>
</li>
<li>
<p>Very important for them to have something live on prod as soon as possible, as that builds up confidence. That means the initial tasks should not involve a lot of back and forth. The goal is to move fast and deploy on prod and get a quick understanding of the system.</p>
</li>
<li>
<p>Ask the senior folks with whom they will be directly working to set up regular pair-programming sessions with them. Very important in early days to ensure they feel comfortable and have someone who actually shows them how things are being done.</p>
</li>
<li>
<p>Encourage them to dogfood the product. We want their own fresh perspective the way a new user interacts with the product.</p>
</li>
<li>
<p>Set up weekly 1:1 with them as manager.</p>
</li>
<li>
<p>Have them identify gaps in documentation or processes for a fresh perspective.</p>
</li>
</ul>
<h2 id="second-month">Second month</h2>
<ul>
<li>
<p>Set up slightly bigger tasks for them. Something that would require critical thinking and creativity from their end. Do not spoon feed the task, instead let them go through it themselves and come back to you with their own understanding.</p>
<p>The goal is to help build confidence and see if they are able to fill in the gaps for the things that are not mentioned explicitly.</p>
</li>
<li>
<p>Keep an eye on whether they are asking for help or struggling silently.</p>
</li>
<li>
<p>Check if they are actively participating in Slack discussions and in meetings.</p>
</li>
<li>
<p>Ensure they are learning from mistakes and are avoiding them in the future. It’s also important to see if they pass on the same knowledge to others in the team.</p>
</li>
</ul>
<h2 id="third-month">Third month</h2>
<ul>
<li>
<p>By the end of this month they should be actively contributing to technical or non-technical discussions.</p>
</li>
<li>
<p>Delivering at least one meaningful project really helps overcome imposter syndrome.</p>
</li>
<li>
<p>They should be able to work on their own to an extent.</p>
</li>
<li>
<p>They should have strong opinions on things. They should challenge, ask questions, and propose solutions. If they are too passive, they are likely not a good fit.</p>
</li>
<li>
<p>If they are repeating the same mistakes, either coding or process related, then try to understand what’s causing it.</p>
</li>
<li>
<p>Check if they appear busy but are not taking any ownership. Do they help others but aren’t finishing their own work? These are some clear signs of procrastination.</p>
</li>
<li>
<p>Keep an eye on their code reviews. Are they mostly just accepting everything and not really providing much feedback? How are they interacting in code reviews?</p>
</li>
</ul>]]></content:encoded>
            <author>Ashwini Chaudhary</author>
            <category>engineering</category>
            <category>leadership</category>
            <category>hiring</category>
            <category>management</category>
            <category>new-hire</category>
            <category>engineering-hires</category>
            <category>onboarding</category>
            <category>mentorship</category>
            <category>90dayplan</category>
            <category>hiring-success</category>
        </item>
        <item>
            <title><![CDATA[uv and PEP 723]]></title>
            <link>https://ashwch.com/uv-and-pep-723.html</link>
            <guid isPermaLink="false">tag:ashwch.com,2025-03-05:/uv-and-pep-723.html</guid>
            <pubDate>Wed, 05 Mar 2025 05:00:00 GMT</pubDate>
            <description><![CDATA[Using uv with PEP 723 simplifies dependency management for standalone Python scripts, making quick scripting with LLM-generated code effortless.]]></description>
            <content:encoded><![CDATA[<h1 id="uv-and-pep-723">uv and PEP 723</h1>
<p>I have recently started using <code>uv</code> a lot, specially for quick standalone scripts, most of these scripts are generated by various LLMs for different tasks. The biggest pain point I had with these scripts was to setup a virtual env, installing the packages and asking ChatGPT and other LLMs to give me requirements file/list.</p>
<p><a href="https://docs.astral.sh/uv/guides/scripts/#declaring-script-dependencies"><code>uv</code> changed that by utilizing PEP 723</a> and embedding these requirement inline to the script.</p>
<p>At Diversio, I  frequently use <a href="https://github.com/copilot">GitHub Copilot</a> a lot, and there I’ve added a small instruction to make sure it’s embedding the requirements in the script itself.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="plaintext"><code><span class="line"><span>When asked to generate a uv based Python script, at the very top of the file, insert an inline metadata block listing all external packages in this format:</span></span>
<span class="line"><span></span></span>
<span class="line"><span># /// script</span></span>
<span class="line"><span># requires-python = ">=3.11"</span></span>
<span class="line"><span># dependencies = [</span></span>
<span class="line"><span>#   "requests&#x3C;3",</span></span>
<span class="line"><span>#   "rich",</span></span>
<span class="line"><span># ]</span></span>
<span class="line"><span># ///</span></span></code></pre>
<p>Now, I gave it the prompt:</p>
<blockquote>
<p>Give me a uv based Python script that pings google.com and prints the output in colorful tabular format.</p>
</blockquote>
<p>You can see its output <a href="https://gist.github.com/ashwch/7076b68543498851dabee5050a1c4ec5">here</a></p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">➜</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> uv</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> run</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> uv_pep_723_example.py</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">Installed</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 9</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> packages</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> in</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 207ms</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">Pinging</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> google.com</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 5</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> times...</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">Ping</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 1:</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Success</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> -</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 221.01</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ms</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">Ping</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 2:</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Success</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> -</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 461.52</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ms</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">Ping</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 3:</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Success</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> -</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 288.88</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ms</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">Ping</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 4:</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Success</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> -</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 216.29</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ms</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">Ping</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 5:</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Success</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> -</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 205.57</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ms</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">                 HTTP</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Ping</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Results</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> for</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> google.com</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">┏━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━┓</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">┃</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # ┃ Timestamp           ┃ Host       ┃ Status  ┃ Response Time ┃</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">┡━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━┩</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">│</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> │</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 2025-03-05</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 00:25:16</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> │</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> google.com</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> │</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Success</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> │</span><span style="color:#005CC5;--shiki-dark:#79B8FF">     221.01</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ms</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> │</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">│</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 2</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> │</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 2025-03-05</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 00:25:18</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> │</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> google.com</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> │</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Success</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> │</span><span style="color:#005CC5;--shiki-dark:#79B8FF">     461.52</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ms</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> │</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">│</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 3</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> │</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 2025-03-05</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 00:25:19</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> │</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> google.com</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> │</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Success</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> │</span><span style="color:#005CC5;--shiki-dark:#79B8FF">     288.88</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ms</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> │</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">│</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 4</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> │</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 2025-03-05</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 00:25:20</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> │</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> google.com</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> │</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Success</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> │</span><span style="color:#005CC5;--shiki-dark:#79B8FF">     216.29</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ms</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> │</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">│</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 5</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> │</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 2025-03-05</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 00:25:22</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> │</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> google.com</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> │</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Success</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> │</span><span style="color:#005CC5;--shiki-dark:#79B8FF">     205.57</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ms</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> │</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">└───┴─────────────────────┴────────────┴─────────┴───────────────┘</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">Summary</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Statistics:</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  Successful</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> requests:</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 5/5</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  Min/Avg/Max:</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 205.57/278.65/461.52</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ms</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  Standard</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Deviation:</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 107.35</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ms</span></span></code></pre>
<h2 id="where-is-uv-storing-these-packages">Where is <code>uv</code> storing these packages?</h2>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">➜</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ls</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -l</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ~/.cache/uv/environments-v2/uv-pep-723-example-af4300630d2aced4/lib/python3.12/site-packages</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">total</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 12</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">drwxr-xr-x</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  3</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> monty</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> staff</span><span style="color:#005CC5;--shiki-dark:#79B8FF">   96</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Mar</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  5</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 00:25</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> __pycache__</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">drwxr-xr-x</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  8</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> monty</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> staff</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  256</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Mar</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  5</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 00:25</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> certifi</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">drwxr-xr-x</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  9</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> monty</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> staff</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  288</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Mar</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  5</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 00:25</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> certifi-2025.1.31.dist-info</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">drwxr-xr-x</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 17</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> monty</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> staff</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  544</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Mar</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  5</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 00:25</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> charset_normalizer</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">drwxr-xr-x</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 10</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> monty</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> staff</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  320</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Mar</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  5</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 00:25</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> charset_normalizer-3.4.1.dist-info</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">drwxr-xr-x</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 12</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> monty</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> staff</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  384</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Mar</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  5</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 00:25</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> idna</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">drwxr-xr-x</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  8</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> monty</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> staff</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  256</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Mar</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  5</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 00:25</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> idna-3.10.dist-info</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">drwxr-xr-x</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 23</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> monty</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> staff</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  736</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Mar</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  5</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 00:25</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> markdown_it</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">drwxr-xr-x</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 10</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> monty</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> staff</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  320</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Mar</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  5</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 00:25</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> markdown_it_py-3.0.0.dist-info</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">drwxr-xr-x</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  9</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> monty</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> staff</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  288</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Mar</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  5</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 00:25</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> mdurl</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">drwxr-xr-x</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  8</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> monty</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> staff</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  256</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Mar</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  5</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 00:25</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> mdurl-0.1.2.dist-info</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">drwxr-xr-x</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 22</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> monty</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> staff</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  704</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Mar</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  5</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 00:25</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> pygments</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">drwxr-xr-x</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  9</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> monty</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> staff</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  288</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Mar</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  5</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 00:25</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> pygments-2.19.1.dist-info</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">drwxr-xr-x</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 21</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> monty</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> staff</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  672</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Mar</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  5</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 00:25</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> requests</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">drwxr-xr-x</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  9</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> monty</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> staff</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  288</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Mar</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  5</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 00:25</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> requests-2.32.3.dist-info</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">drwxr-xr-x</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 82</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> monty</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> staff</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 2624</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Mar</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  5</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 00:25</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> rich</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">drwxr-xr-x</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  8</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> monty</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> staff</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  256</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Mar</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  5</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 00:25</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> rich-13.9.4.dist-info</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">drwxr-xr-x</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 19</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> monty</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> staff</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  608</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Mar</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  5</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 00:25</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> urllib3</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">drwxr-xr-x</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  8</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> monty</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> staff</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  256</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Mar</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  5</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 00:25</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> urllib3-2.3.0.dist-info</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">-rw-r--r--</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  1</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> monty</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> staff</span><span style="color:#005CC5;--shiki-dark:#79B8FF">   18</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Mar</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  5</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 00:25</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> _virtualenv.pth</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">-rw-r--r--</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  1</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> monty</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> staff</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 4342</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Mar</span><span style="color:#005CC5;--shiki-dark:#79B8FF">  5</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 00:25</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> _virtualenv.py</span></span></code></pre>
<p>The cache can be cleaned using the <code>uv cache clean</code> command as specified <a href="https://docs.astral.sh/uv/concepts/cache/#clearing-the-cache">here</a>.</p>
<h2 id="references">References</h2>
<ul>
<li><a href="https://peps.python.org/pep-0723/">https://peps.python.org/pep-0723/</a></li>
<li><a href="https://docs.astral.sh/uv/guides/scripts/#declaring-script-dependencies">https://docs.astral.sh/uv/guides/scripts/#declaring-script-dependencies</a></li>
<li><a href="https://docs.astral.sh/uv/concepts/cache/#clearing-the-cache">https://docs.astral.sh/uv/concepts/cache/#clearing-the-cache</a></li>
</ul>]]></content:encoded>
            <author>Ashwini Chaudhary</author>
            <category>python</category>
            <category>uv</category>
            <category>packaging</category>
            <category>uv-internals</category>
            <category>til</category>
            <category>python-pep</category>
            <category>pep-723</category>
            <category>diversio</category>
            <category>github</category>
            <category>github-copilot</category>
        </item>
        <item>
            <title><![CDATA[Evolving Engineering Recruitment at Diversio in the Age of AI]]></title>
            <link>https://ashwch.com/evolving-engineering-recruitment-at-diversio-in-the-age-of-ai.html</link>
            <guid isPermaLink="false">tag:ashwch.com,2025-02-23:/evolving-engineering-recruitment-at-diversio-in-the-age-of-ai.html</guid>
            <pubDate>Sun, 23 Feb 2025 05:00:00 GMT</pubDate>
            <description><![CDATA[What we have learned hiring at Diversio]]></description>
            <content:encoded><![CDATA[<h2 id="table-of-contents">Table of Contents</h2>
<ol>
<li><a href="#introduction-evolution-at-diversio">Introduction &#x26; Evolution at Diversio</a></li>
<li><a href="#how-we-used-to-do-it-pre-chatgpt">How We Used to Do It (Pre-ChatGPT)</a>
<ul>
<li><a href="#resume-screening">Resume Screening</a></li>
<li><a href="#take-home-exercises">Take Home Exercises</a></li>
<li><a href="#zoom-interview">Zoom Interview</a></li>
</ul>
</li>
<li><a href="#interviewing-in-2025">Interviewing in 2025</a>
<ul>
<li><a href="#posting-the-job-ad">Posting the Job Ad</a></li>
<li><a href="#filtering-resumes">Filtering Resumes</a></li>
<li><a href="#screening-round">Screening Round</a></li>
<li><a href="#coding-round">Coding Round</a></li>
<li><a href="#final-round">Final Round</a></li>
</ul>
</li>
</ol>
<hr>
<h1 id="introduction--evolution-at-diversio">Introduction &#x26; Evolution at Diversio</h1>
<p>At Diversio, our hiring process has evolved significantly over the years. In our early days, we relied heavily on detailed resume screenings and take-home exercises. Today, we’ve adapted to a new landscape where remote hiring, AI-assisted filtering, and a streamlined interview process help us manage the increased applicant volume while ensuring we find the best long-term fits.</p>
<p><strong>Key Changes at a Glance:</strong></p>
<ul>
<li>Transition from take-home exercises to real-time, remote interviews.</li>
<li>Adoption of AI tools (like our custom GPT) to efficiently filter resumes.</li>
<li>Increased focus on cultural fit and long-term alignment.</li>
<li>Emphasis on transparent, remote hiring practices.</li>
</ul>
<h1 id="how-we-used-to-do-it-pre-chatgpt">How We Used to Do It (Pre-ChatGPT)</h1>
<p>We were very lucky in terms of hiring at Diversio until 2023—we had an excellent attrition rate and were fortunate to hire very talented interns from the University of Toronto, University of Waterloo, and Queen’s University. The full-time hires were mostly referrals.</p>
<h3 id="1-resume-screening">1. Resume Screening</h3>
<p>A resume screening was conducted by myself and the PM, including checking candidates’ past co-op remarks from other companies.</p>
<h3 id="2-take-home-exercises">2. Take Home Exercises</h3>
<p>We have a strong belief in asking questions that are directly relevant to what a candidate will do on a day-to-day basis at Diversio.</p>
<p>Depending on the position, we provided a different set of questions. Our exercises included:</p>
<ul>
<li>
<p><strong>Programming Puzzle:</strong><br>
Based on an internal algorithm and a problem we had solved in the past, this puzzle was reworded to sound engaging. The code was expected to be well documented and pass the test cases mentioned in the Python script.</p>
<p>Goals here were:</p>
<ul>
<li>Are they able to follow a complex problem statement (including the provided examples)?</li>
<li>Can they fill in the gaps? (i.e., how would they approach tasks at Diversio when some information is not mentioned explicitly, requiring them to figure it out on their own or use their best judgment?)</li>
<li>Can they identify edge cases on their own?</li>
<li>Can they write readable, well-documented code?</li>
</ul>
</li>
<li>
<p><strong>Command Line Script:</strong><br>
A task to parse our status page and provide details on how our apps have been performing over the past six months, including identifying the minimum and maximum response times of a service.</p>
<p>Goals here were:</p>
<ul>
<li>Are they able to parse HTML source code?</li>
<li>Are they inspecting elements to see if there are any internal APIs they can utilize?</li>
<li>Do they know how to build command-line utilities? (This is a skill we use frequently at Diversio.)</li>
<li>How is their code structure, documentation, and testing?</li>
</ul>
</li>
</ul>
<p>Candidates were expected to submit their solutions on GitHub, which we then reviewed (by myself and other engineers) and provided feedback on.</p>
<h3 id="3-zoom-interview">3. Zoom Interview</h3>
<h4 id="walkthrough-and-discussing-their-solutions">Walkthrough and Discussing Their Solutions</h4>
<p>Candidates whose solutions passed our internal review were invited to a Zoom interview. During this round, we discussed their take-home exercises in depth—asking them to explain their solutions and thought processes, and throwing in some edge cases or tweaking the requirements to see if they could adapt their solution on the fly.</p>
<h4 id="real-time-code-review">Real-Time Code Review</h4>
<p>At Diversio, we invest a significant amount of time in code reviews because we believe that the effort not only benefits the company but also provides an opportunity to teach and learn from each other.</p>
<p>For this, we used a Python script deliberately filled with poorly written code—ignoring best practices, using inefficient data structures, and exhibiting problematic file I/O, networking, and error handling. The candidate was asked to explain what each section of the script did and to propose better solutions for each section or function, along with justifications.</p>
<p>Goals here were:</p>
<ul>
<li>To determine if they focus on solving the underlying problem rather than just critiquing the code.</li>
<li>To assess their ability to read and understand someone else’s code.</li>
<li>To gauge their knowledge of best practices and their ability to identify poor coding practices.</li>
</ul>
<p>We allowed candidates to use Google during the interviews to look up anything they didn’t know. Since their screen was shared with us, we could observe how they searched for information and selected among the different results.</p>
<h1 id="interviewing-in-2025">Interviewing in 2025</h1>
<p>A lot has changed since 2023:</p>
<ul>
<li>We are a much bigger company now, so when we post a job, we receive many more applicants.</li>
<li>Many layoffs in big tech companies and AI’s impact on dev jobs are resulting in significantly more applicants.</li>
<li>Take-home interviews have been phased out in favor of real-time evaluations.</li>
<li>Candidates are using resume tools to trick ATS auto-screening into thinking their resumes are the best.</li>
<li>Remote hiring is the norm.</li>
</ul>
<h3 id="posting-the-job-ad">Posting the Job Ad</h3>
<p>We use Collage internally, which automatically posts the job on Indeed. In addition, we use Instahyre and <a href="https://hasjob.co/">Hasjob</a>. I also post about it on my LinkedIn to increase its reach.</p>
<h4 id="when-to-post">When to Post?</h4>
<p>We have found that posting on Friday works best for us. Within the first two days, we received around 400 applicants, and since it was the weekend, I had enough time to review them and filter out those who appeared promising. This built a pipeline for the next 1–2 weeks that was within our team’s capacity.</p>
<h3 id="filtering-resumes">Filtering Resumes</h3>
<ul>
<li>
<p><strong>Size of Resume:</strong>
Resumes longer than 1–2 pages are a direct reject, including those where candidates list every 10–20 bullet points under every role.</p>
</li>
<li>
<p><strong>Tenures:</strong><br>
Short tenures at multiple places are a red flag, as they increase the likelihood of job-hopping. At Diversio, we want to invest in people for the long term.</p>
</li>
<li>
<p><strong>Work History:</strong>
We prefer candidates with experience at product startups over service-based companies. We also assess how they explain the features they have worked on—whether they are concise and to the point or merely rely on buzzwords.<br>
Resumes that boast metrics like “x% improvement in signups/deployment” or “y% reduction in bugs” without proper context are directly rejected. Such figures often result from attempts to game ATS auto-screening and, frankly, are often meaningless—even after 12 years as a software engineer I don’t have such stats myself.</p>
</li>
<li>
<p><strong>GitHub and Portfolio (if any):</strong><br>
We look at candidates’ pinned repositories. Boilerplate projects, past interview tasks, or college projects aren’t meaningful to us—we value significant contributions, a robust commit history, and open-source work.</p>
</li>
<li>
<p><strong>LinkedIn:</strong><br>
We verify that the candidate’s LinkedIn profile matches their resume, that the listed companies are credible, and we review their post history.</p>
</li>
</ul>
<h4 id="chatgpt">ChatGPT</h4>
<p>To speed up the filtering process, we use a custom GPT tool that evaluates most of the above criteria (except for the last two points), assigning points to each resume so that we can sort them efficiently.</p>
<h3 id="screening-round">Screening Round</h3>
<p>Candidates selected after resume screening go through a screening round with a senior engineer at Diversio. We require screen sharing and that the camera be turned on to ensure there is no cheating. Candidates are allowed to use Google—but only on the shared screen.</p>
<p>The goal of this 30–45 minute call is to:</p>
<ul>
<li>Check their communication skills.</li>
<li>Explain Diversio, the job, and its requirements.</li>
<li>Walk through their resume, asking counter-questions to verify their project experience.</li>
<li>Check basic coding skills that shouldn’t require any preparation.</li>
<li>Discuss their notice period and salary expectations.</li>
</ul>
<p>Candidates are then ranked based on feedback, and top performers move forward.</p>
<h3 id="coding-round">Coding Round</h3>
<p>This round is conducted with the senior engineer leading the respective department (backend or frontend). It is a 1h 15min to 1h 30min call during which the candidate is required to share their screen and have their camera turned on.</p>
<p>This call includes:</p>
<ul>
<li>In-depth technical discussions around backend or frontend topics.</li>
<li>Solving two coding questions and explaining their approach.</li>
</ul>
<h3 id="final-round">Final Round</h3>
<p>The final round is a non-technical interview with me, reserved for candidates who performed exceptionally in the screening and coding rounds and met our criteria.</p>
<p>This interview is designed to provide a holistic view of the candidate by:</p>
<ul>
<li><strong>Creating a Comfortable Environment:</strong> Beginning with a relaxed introduction that encourages open conversation.</li>
<li><strong>Assessing Collaboration &#x26; Adaptability:</strong> Exploring how candidates navigate teamwork challenges and manage unexpected changes within dynamic environments.</li>
<li><strong>Evaluating Cultural &#x26; Career Fit:</strong> Discussing work styles, long-term aspirations, and feedback experiences to ensure alignment with Diversio’s team values.</li>
<li><strong>Setting Clear Role Expectations:</strong> Clarifying work dynamics such as flexible hours and communication preferences, while highlighting Diversio’s vision and engineering culture.</li>
</ul>]]></content:encoded>
            <author>Ashwini Chaudhary</author>
            <category>hiring</category>
            <category>interviewing</category>
            <category>ai</category>
            <category>diversio</category>
            <category>lessons-learned</category>
            <category>best-practices</category>
        </item>
        <item>
            <title><![CDATA[Terminal setup using ZSH, Prezto and Starship on MacOS]]></title>
            <link>https://ashwch.com/terminal-setup-using-zsh-prezto-starship.md.html</link>
            <guid isPermaLink="false">tag:ashwch.com,2023-12-23:/terminal-setup-using-zsh-prezto-starship.md.html</guid>
            <pubDate>Sat, 23 Dec 2023 05:00:00 GMT</pubDate>
            <description><![CDATA[Terminal setup using ZSH, Prezto and Starship on MacOS]]></description>
            <content:encoded><![CDATA[<h1 id="zsh-installation">ZSH installation</h1>
<p>Starting with Catalina the default shell was changed to zsh. You can verify your current shell using:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">➜</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> echo</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> $SHELL</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">/bin/zsh</span></span></code></pre>
<p>If it’s not <code>zsh</code> for you, you can install it using <a href="https://brew.sh/">Homebrew</a></p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">brew</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> zsh</span></span></code></pre>
<p>Once installed we need to make <code>zsh</code> the default shell using:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">chsh</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> /bin/zsh</span></span></code></pre>
<h1 id="prezto-installation">Prezto installation</h1>
<p><a href="https://github.com/sorin-ionescu/prezto">Prezto</a> is a configuration framework for <code>zsh</code>. Another well known alternative is <a href="https://ohmyz.sh/">Oh My Zsh</a>.</p>
<p>I was using Oh My Zsh previously but it had performance issues and switching to Prezto has helped with the performance issues. Oh My Zsh has a much bigger community and receives regular updates and is bit more beginner friendly.</p>
<p>Before installing make sure to make a back of your <code>.zshrc</code> file if you had one already as Prezto can ve problematic with existing <code>.zshrc</code> files.</p>
<h2 id="clone-the-prezto-repo">Clone the Prezto repo</h2>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">git</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> clone</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --recursive</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> https://github.com/sorin-ionescu/prezto.git</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "${</span><span style="color:#24292E;--shiki-dark:#E1E4E8">ZDOTDIR</span><span style="color:#D73A49;--shiki-dark:#F97583">:-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">$HOME</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}/.zprezto"</span></span></code></pre>
<h2 id="link-the-new-zsh-config-files-provided-by-prezto-to-your-home-directory">Link the new zsh config files provided by Prezto to your home directory</h2>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">setopt</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> EXTENDED_GLOB</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">for</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> rcfile </span><span style="color:#D73A49;--shiki-dark:#F97583">in</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "${</span><span style="color:#24292E;--shiki-dark:#E1E4E8">ZDOTDIR</span><span style="color:#D73A49;--shiki-dark:#F97583">:-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">$HOME</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}"/.zprezto/runcoms/^README.md</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">.N</span><span style="color:#24292E;--shiki-dark:#E1E4E8">); </span><span style="color:#D73A49;--shiki-dark:#F97583">do</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  ln</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="color:#24292E;--shiki-dark:#E1E4E8">$rcfile</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "${</span><span style="color:#24292E;--shiki-dark:#E1E4E8">ZDOTDIR</span><span style="color:#D73A49;--shiki-dark:#F97583">:-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">$HOME</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}/.${</span><span style="color:#24292E;--shiki-dark:#E1E4E8">rcfile</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#24292E;--shiki-dark:#E1E4E8">t</span><span style="color:#032F62;--shiki-dark:#9ECBFF">}"</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">done</span></span></code></pre>
<p>Once installed you’ll find the Prezto configuration in <code>.zpreztorc</code> file in your home directory. The default file looks like this: <a href="https://github.com/sorin-ionescu/prezto/blob/master/runcoms/zpreztorc">https://github.com/sorin-ionescu/prezto/blob/master/runcoms/zpreztorc</a></p>
<p>The list of modules are available here: <a href="https://github.com/sorin-ionescu/prezto/blob/master/runcoms/zpreztorc">https://github.com/sorin-ionescu/prezto/blob/master/runcoms/zpreztorc</a></p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="markdown"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">zstyle ':prezto:load' pmodule \</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> 'lazy-load' \</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> 'environment' \</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> 'terminal' \</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> 'editor' \</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> 'history' \</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> 'directory' \</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> 'spectrum' \</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> 'utility' \</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> 'completion' \</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> 'osx' \</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> 'ssh' \</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> 'git' \</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> 'python' \</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> 'node' \</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> 'syntax-highlighting' \</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> 'history-substring-search' \</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> 'prompt' \</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> 'autosuggestions'</span></span></code></pre>
<p>Here <code>lazy-load</code> is a 3rd party module to lazily load functions that are time consuming: <a href="https://github.com/xcv58/zsh-lazy-load">https://github.com/xcv58/zsh-lazy-load</a></p>
<p>To install this module if you want, do the following:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">cd</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> $HOME</span><span style="color:#032F62;--shiki-dark:#9ECBFF">/.zprezto</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">git</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> submodule</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> add</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> https://github.com/xcv58/zsh-lazy-load.git</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> modules/lazy-load</span></span></code></pre>
<p>Once that is done <code>'lazy-load'</code> should be the first item in your modules list as shown above.</p>
<p>In my current <code>.zshrc</code> file I’m using it to lazily load <code>nvm</code> and <code>virtualenvwrapper</code>:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># .zshrc</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">func</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> load_virtualenv</span><span style="color:#24292E;--shiki-dark:#E1E4E8">() </span><span style="color:#032F62;--shiki-dark:#9ECBFF">{</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">  source</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> path_to_virtualenvwrapper.sh</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">func</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> load_nvm</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> () {</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">    source</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> path_to_nvm.sh</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># At the end of .zshrc</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">lazy_load</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> load_virtualenv</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> load_nvm</span></span></code></pre>
<h2 id="selecting-theme">Selecting theme</h2>
<p>Prezto comes with a bunch of themes. To list the themes use <code>prompt -l</code> and to preview one use <code>prompt -p name</code>. Once you’ve picked a theme open your <code>.zpreztorc</code> file and add it  <code>zstyle ':prezto:module:prompt' theme 'pure'</code> (here <code>'pure'</code> is the prompt theme’s name).</p>
<h2 id="fonts">Fonts</h2>
<p>Currently, I’m using GitHub’s <a href="https://github.com/githubnext/monaspace#monaspace">Monaspace font</a>, its Krypton variation. This font supports ligatures.</p>
<p>Other fonts I highly recommend:</p>
<ul>
<li><a href="https://www.nerdfonts.com/font-downloads">Nerd font</a> — great for terminal prompt</li>
<li><a href="https://github.com/tonsky/FiraCode">Fira Code</a> (supports ligatures)</li>
<li>Monaco (ships with MacOS and great for code but lacks ligatures). <a href="https://github.com/GianCastle/FiraMonaco">A ligature version</a> is also available but I’ve not tried it personally. The Powerline mentioned here is a <a href="https://github.com/davidjrice/prezto_powerline">prompt theme</a>.</li>
</ul>
<p><strong>Font setup in iTerm 2:</strong></p>
<p><img src="https://i.imgur.com/E5t5mPe.png" alt="Font setup in iTerm 2"></p>
<p><strong>Font setup in VS Code:</strong></p>
<p><img src="https://i.imgur.com/bvNgNz0.png" alt="Font setup in VS Code"></p>
<p><img src="https://i.imgur.com/ausDfmG.png" alt="Ligatures setup in VS Code"></p>
<h1 id="prompt-customization">Prompt Customization</h1>
<p>I’m using <a href="https://starship.rs/">Starship</a> for customizing the prompt. Note that by default it requires <a href="https://www.nerdfonts.com/font-downloads">Nerd Font</a>.</p>
<p>To install it Homebrew can be used:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">brew</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> starship</span></span></code></pre>
<p>Then open your <code>.zshrc</code> file and add the following at the end:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">eval</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "$(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">starship</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> init zsh)"</span></span></code></pre>
<p>To configure Starship you need to create a config file first:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">mkdir</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -p</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ~/.config</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> &#x26;&#x26; </span><span style="color:#6F42C1;--shiki-dark:#B392F0">touch</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ~/.config/starship.toml</span></span></code></pre>
<p>Then define the items you’d want:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="toml"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">format = </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"""</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">$custom\</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">$username\</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">$hostname\</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">$shlvl\</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">$directory\</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">$git_branch\</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">$git_commit\</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">$git_state\</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">$git_status\</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">$package\</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">$elixir\</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">$golang\</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">$nodejs\</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">$python\</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">$ruby\</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">$rust\</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">$terraform\</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">$nix_shell\</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">$aws\</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">$env_var\</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">$line_break\</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">$status</span><span style="color:#005CC5;--shiki-dark:#79B8FF">\n</span><span style="color:#032F62;--shiki-dark:#9ECBFF">\</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">$cmd_duration\</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">$character"""</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">add_newline = </span><span style="color:#005CC5;--shiki-dark:#79B8FF">true</span></span></code></pre>
<p>Then for each item you can further make your own modification, for example here’s how username, directory and git status looks like for me:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="toml"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">[</span><span style="color:#6F42C1;--shiki-dark:#B392F0">username</span><span style="color:#24292E;--shiki-dark:#E1E4E8">]</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">disabled = </span><span style="color:#005CC5;--shiki-dark:#79B8FF">false</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">show_always = </span><span style="color:#005CC5;--shiki-dark:#79B8FF">true</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">style_user = </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"white bold"</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">style_root = </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"white bold"</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">format = </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"[xonix_$user]($style) "</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">[</span><span style="color:#6F42C1;--shiki-dark:#B392F0">directory</span><span style="color:#24292E;--shiki-dark:#E1E4E8">]</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">truncation_length = </span><span style="color:#005CC5;--shiki-dark:#79B8FF">100</span><span style="color:#6A737D;--shiki-dark:#6A737D">  # no truncation</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">truncate_to_repo = </span><span style="color:#005CC5;--shiki-dark:#79B8FF">true</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">format = </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"[$path]($style) "</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">## Git settings</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">[</span><span style="color:#6F42C1;--shiki-dark:#B392F0">git_branch</span><span style="color:#24292E;--shiki-dark:#E1E4E8">]</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">style = </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"bold purple"</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">truncation_length = </span><span style="color:#005CC5;--shiki-dark:#79B8FF">100</span><span style="color:#6A737D;--shiki-dark:#6A737D">  # no truncation</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">truncation_symbol = </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"..."</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">[</span><span style="color:#6F42C1;--shiki-dark:#B392F0">custom</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">xonix</span><span style="color:#24292E;--shiki-dark:#E1E4E8">]</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">command = </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"echo -n '🍺 '"</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">when = </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"true"</span></span></code></pre>
<p>The resulting prompt looks like:</p>
<p><img src="https://i.imgur.com/Y9VQ4SW.png" alt="Starship configured prompt"></p>
<p>The setting available under any such action is in the docs: <a href="https://starship.rs/config/#username">username</a>, <a href="https://starship.rs/config/#directory">directory</a>, <a href="https://starship.rs/config/#git_branch">git_branch</a> and <a href="https://starship.rs/config/#custom-commands">custom</a></p>
<p>Find detailed documentation here: <a href="https://starship.rs/config/#prompt">https://starship.rs/config/#prompt</a></p>
<h1 id="checking-performance">Checking performance</h1>
<p>If your shell is taking a while to load then it can be profiled by adding <code>zmodload zsh/zprof</code> at the start of <code>.zshrc</code> file and <code>zprof</code> at the end of the file. In addition individual modules can be timed using <code>time (pmodload '&#x3C;module_name>')</code>.</p>]]></content:encoded>
            <author>Ashwini Chaudhary</author>
            <category>terminal</category>
            <category>setup</category>
            <category>prezto</category>
            <category>starship</category>
            <category>zsh</category>
        </item>
        <item>
            <title><![CDATA[Become a pdb power-user]]></title>
            <link>https://ashwch.com/become-a-pdb-power-user.html</link>
            <guid isPermaLink="false">tag:ashwch.com,2016-11-08:/become-a-pdb-power-user.html</guid>
            <pubDate>Tue, 08 Nov 2016 05:00:00 GMT</pubDate>
            <description><![CDATA[Become a pdb power-user]]></description>
            <content:encoded><![CDATA[<p>This is an explanatory article related to my talk at <a href="https://www.pypals.org/mupy">MUPy</a>  — <a href="http://slides.com/ashwch/pdb-mupy#/"> Become a pdb power-user</a>.</p>
<p>This article was originally published on <a href="https://medium.com/instamojo-matters/become-a-pdb-power-user-e3fc4e2774b2">Medium</a></p>
<h2 id="table-of-contents">Table of Contents</h2>
<ol>
<li><a href="#whats-pdb">What’s pdb?</a></li>
<li><a href="#why-pdb">Why pdb?</a></li>
<li><a href="#how-to-start-pdb">How to start pdb?</a>
<ul>
<li><a href="#starting-program-under-debugger-control">Starting program under debugger control</a></li>
<li><a href="#running-code-under-debugger-control">Running code under debugger control</a></li>
<li><a href="#set-a-hardcoded-breakpoint">Set a hardcoded breakpoint</a></li>
<li><a href="#post-mortem-debugging">Post-mortem debugging</a></li>
</ul>
</li>
<li><a href="#basic-pdb-commands">Basic pdb commands</a>
<ul>
<li><a href="#stepping-through-code">Stepping through code</a></li>
<li><a href="#jumping-between-stacks">Jumping between stacks</a></li>
<li><a href="#breakpoints">Breakpoints</a></li>
<li><a href="#tips-and-tricks">Tips and tricks</a></li>
<li><a href="#whats-new-in-python-3">What’s new in Python 3</a></li>
</ul>
</li>
</ol>
<hr>
<h2 id="whats-pdb">What’s pdb?</h2>
<p><code>pdb</code> is a module from Python’s standard library that allows us to do things like:</p>
<ul>
<li>Stepping through source code</li>
<li>Setting conditional breakpoints</li>
<li>Inspecting stack trace</li>
<li>Viewing source code</li>
<li>Running Python code in a context</li>
<li>Post-mortem debugging</li>
</ul>
<h2 id="why-pdb">Why pdb?</h2>
<p>It is not necessary to use <code>pdb</code> all the time, sometimes we can get away with simple <code>print</code> statements or logging.</p>
<p>But these other approaches are most of the time not good enough and don’t give us enough control while debugging. Plus after debugging we also have to take care of removing the `print“ statements that we had added to our program just for debugging purpose, this isn’t true for logging though as we can filter out logs easily. But at the end both of these approaches clutter our code and don’t give us enough debugging power either.</p>
<h2 id="how-to-startpdb">How to start pdb?</h2>
<p>There are multiple ways to start pdb depending on your use case.</p>
<h3 id="1-starting-program-under-debugger-control">1. Starting program under debugger control</h3>
<p>We can start a script itself under debugger’s control by executing the script using <code>-m pdb</code> argument. Let’s run script.py:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">$</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> python</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -m</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> pdb</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> script.py</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> ></span><span style="color:#6F42C1;--shiki-dark:#B392F0"> /pdb-mupy/script.py(1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#6F42C1;--shiki-dark:#B392F0">&#x3C;module></span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0"> -</span><span style="color:#24292E;--shiki-dark:#E1E4E8">> </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"""I am the first script in this demo"""</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Pdb</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span></code></pre>
<p>In this mode Python will stop the program on first line and you are going to be inside debugger(<code>(Pdb)</code> is pdb’s prompt). At this point you can either set breakpoints or continue executing your program.</p>
<p>Another special thing about it is that after the program completion if no exception occurred then your program will restart in same mode otherwise it will start in post-mortem mode. After post-mortem mode you can restart the program again.</p>
<h3 id="2-running-code-under-debugger-control">2. Running code under debugger control</h3>
<p>Instead of running the whole code under debugger control we can run particular code under using <code>pdb.run</code>, <code>pdb.runeval</code> and <code>pdb.runcall</code>.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">>>></span><span style="color:#D73A49;--shiki-dark:#F97583"> import</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> pdb</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">>>></span><span style="color:#D73A49;--shiki-dark:#F97583"> import</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> script</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">>>></span><span style="color:#24292E;--shiki-dark:#E1E4E8"> pdb.run(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'script.divide(10, 5)'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">string</span><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">module</span><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">-></span><span style="color:#005CC5;--shiki-dark:#79B8FF">None</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) s   </span><span style="color:#6A737D;--shiki-dark:#6A737D"># we can run any pdb command here</span></span>
<span class="line"><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">--</span><span style="color:#24292E;--shiki-dark:#E1E4E8">Call</span><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">--</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#D73A49;--shiki-dark:#F97583"> /</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pdb</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">mupy</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">script.py(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">6</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)divide()</span></span>
<span class="line"><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">-></span><span style="color:#D73A49;--shiki-dark:#F97583"> def</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> divide</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(numerator, denominator):</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) n</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#D73A49;--shiki-dark:#F97583"> /</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pdb</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">mupy</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">script.py(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">7</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)divide()</span></span>
<span class="line"><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">-></span><span style="color:#D73A49;--shiki-dark:#F97583"> return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> numerator </span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> denominator</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) p numerator, denominator</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">10</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">5</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) c</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">>>></span></span></code></pre>
<p>Here <code>&#x3C;string>(1)&#x3C;module>()</code> means that we are at the start of string passed to <code>run()</code> and no code has executed yet. In the above example we stepped into the divide function using <code>s</code>(don’t worry about <code>s</code>, <code>n</code>, <code>c</code> etc, we will be covering them in detail).</p>
<p><code>runeval()</code> does the same thing as <code>run()</code> except that it also returns the value of executed code.</p>
<p><code>runcall()</code> allows us to pass a Python callable itself instead of a string.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">>>></span><span style="color:#24292E;--shiki-dark:#E1E4E8"> pdb.runcall(script.divide, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">10</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">5</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#D73A49;--shiki-dark:#F97583"> /</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pdb</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">mupy</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">script.py(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">7</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)divide()</span></span>
<span class="line"><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">-></span><span style="color:#D73A49;--shiki-dark:#F97583"> return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> numerator </span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> denominator</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb)</span></span></code></pre>
<h3 id="3-set-a-hardcoded-breakpoint">3. Set a hardcoded breakpoint</h3>
<p>This is the most common way to debug programs, it basically involves adding the line pdb.set_trace() in the source code wherever we want our program to stop.</p>
<h3 id="4-post-mortem-debugging">4. Post-mortem debugging</h3>
<p>Post-mortem debugging allows us to debug a dead program using its <code>traceback</code> object. In post-mortem debugging we can inspect the state of the program at the time it died. But apart from inspecting the state we can’t do much here(like stepping through the code) because like the name suggests we are performing post-mortem of a dead program.</p>
<p>By default the <code>-m pdb</code> we had discussed earlier puts us in post-mortem mode if an exception occurs. Other ways are using: <code>pdb.pm()</code> and <code>pdm.post_mortem()</code>.</p>
<p><code>pdb.pm()</code> will take us to the post-mortem mode for the exception found in <code>sys.last_traceback</code>.</p>
<p>On the other hand <code>pdb.post_mortem()</code> excepts an optional <code>traceback</code> object otherwise will try to handle the exception currently being handled.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">>>></span><span style="color:#D73A49;--shiki-dark:#F97583"> import</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> pdb</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">>>></span><span style="color:#D73A49;--shiki-dark:#F97583"> import</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> script</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">>>></span><span style="color:#24292E;--shiki-dark:#E1E4E8"> script.divide(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">10</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">0</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Traceback (most recent call last):</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  File </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"&#x3C;ipython-input-8-fe270324adad>"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, line </span><span style="color:#005CC5;--shiki-dark:#79B8FF">1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#D73A49;--shiki-dark:#F97583">in</span><span style="color:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">module</span><span style="color:#D73A49;--shiki-dark:#F97583">></span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    script.divide(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">10</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">0</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  File </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"script.py"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, line </span><span style="color:#005CC5;--shiki-dark:#79B8FF">7</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#D73A49;--shiki-dark:#F97583">in</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> divide</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> numerator </span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> denominator</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">ZeroDivisionError</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: integer division </span><span style="color:#D73A49;--shiki-dark:#F97583">or</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> modulo by zero</span></span></code></pre>
<p>Now to inspect the state at the time this above exception occurred using <code>pdb.pm()</code>.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">>>></span><span style="color:#24292E;--shiki-dark:#E1E4E8"> pdb.pm()</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#D73A49;--shiki-dark:#F97583"> /</span><span style="color:#24292E;--shiki-dark:#E1E4E8">Users</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">ashwini</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">work</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">instamojo</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pdb</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">mupy</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">script.py(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">7</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)divide()</span></span>
<span class="line"><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">-></span><span style="color:#D73A49;--shiki-dark:#F97583"> return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> numerator </span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> denominator</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) args  </span><span style="color:#6A737D;--shiki-dark:#6A737D"># Arguments passed to the function at that time</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">numerator </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 10</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">denominator </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 0</span></span></code></pre>
<p>We could have done something similar using <code>pdb.post_mortem()</code> with the <code>traceback</code> object:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">>>></span><span style="color:#D73A49;--shiki-dark:#F97583"> import</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> sys</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">>>></span><span style="color:#24292E;--shiki-dark:#E1E4E8"> pdb.post_mortem(sys.last_traceback)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#D73A49;--shiki-dark:#F97583"> /</span><span style="color:#24292E;--shiki-dark:#E1E4E8">Users</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">ashwini</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">work</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">instamojo</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pdb</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">mupy</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">script.py(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">7</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)divide()</span></span>
<span class="line"><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">-></span><span style="color:#D73A49;--shiki-dark:#F97583"> return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> numerator </span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> denominator</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb)</span></span></code></pre>
<p>Similarly we can handle the current exception being handled using <code>pdb.post_mortem()</code> without any argument:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">>>></span><span style="color:#D73A49;--shiki-dark:#F97583"> try</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">...</span><span style="color:#24292E;--shiki-dark:#E1E4E8">     script.divide(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">10</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">0</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">...</span><span style="color:#D73A49;--shiki-dark:#F97583"> except</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> Exception</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">...</span><span style="color:#24292E;--shiki-dark:#E1E4E8">     pdb.post_mortem()</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">...</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#D73A49;--shiki-dark:#F97583"> /</span><span style="color:#24292E;--shiki-dark:#E1E4E8">Users</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">ashwini</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">work</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">instamojo</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pdb</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">mupy</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">script.py(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">7</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)divide()</span></span>
<span class="line"><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">-></span><span style="color:#D73A49;--shiki-dark:#F97583"> return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> numerator </span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> denominator</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb)</span></span></code></pre>
<h2 id="basic-pdbcommands">Basic pdb commands</h2>
<p><code>(Pdb)</code> prompt we have seen so far is pdb’s own shell and it has its own set of commands that makes debugging even easier. In this section we will go through some of the basic commands.</p>
<p>Before starting with the commands it is important to understand the notation we use for commands, for example a command like <code>c</code>(<code>ont</code>(<code>inue</code>)) means we can either use <code>c</code>, <code>cont</code> or <code>continue</code> for this command. The square brackets(<code>[]</code>) followed by a command are its optional arguments, without square brackets it is a compulsory argument.</p>
<ul>
<li>
<p><code>h(elp) [command]</code></p>
</li>
<li>
<p><code>help</code> or simply <code>h</code> provides help related to a <code>pdb</code> command. Without arguments it lists all of the pdb commands available.</p>
</li>
<li>
<p>(Pdb) help</p>
</li>
</ul>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="plaintext"><code><span class="line"><span>Documented commands (type help &#x3C;topic>):</span></span>
<span class="line"><span>========================================</span></span>
<span class="line"><span>EOF    c          d        h         list      q        rv       undisplay</span></span>
<span class="line"><span>a      cl         debug    help      ll        quit     s        unt</span></span>
<span class="line"><span>alias  clear      disable  ignore    longlist  r        source   until</span></span>
<span class="line"><span>args   commands   display  interact  n         restart  step     up</span></span>
<span class="line"><span>b      condition  down     j         next      return   tbreak   w</span></span>
<span class="line"><span>break  cont       enable   jump      p         retval   u        whatis</span></span>
<span class="line"><span>bt     continue   exit     l         pp        run      unalias  where</span></span></code></pre>
<p>Get help related to args command:</p>
<ul>
<li><code>(Pdb) help args</code></li>
</ul>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="plaintext"><code><span class="line"><span>(Pdb) help args</span></span>
<span class="line"><span>a(rgs)</span></span>
<span class="line"><span>        Print the argument list of the current function.</span></span></code></pre>
<p>This command can save your time related to visiting Python documentation in case you forgot about a command.</p>
<p>Note: <code>!</code> command is the only exception here as help only works with valid Python identifiers. Alternative is to use <code>help exec</code>.</p>
<ul>
<li><code>p</code> or <code>pp</code></li>
</ul>
<p>To print variable inside debugger we can use <code>p</code> for normal printing and pp for pretty-printing. We can use simple Python print as well but it is not a pdb command.</p>
<ul>
<li><code>a(rgs)</code></li>
</ul>
<p><code>args</code> prints the arguments with their values of the current function.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">>>></span><span style="color:#24292E;--shiki-dark:#E1E4E8"> pdb.runcall(script.divide, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">10</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> , </span><span style="color:#005CC5;--shiki-dark:#79B8FF">15</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#D73A49;--shiki-dark:#F97583"> /</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pdb</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">mupy</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">script.py(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">7</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)divide()</span></span>
<span class="line"><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">-></span><span style="color:#D73A49;--shiki-dark:#F97583"> return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> numerator </span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> denominator</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) args</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">numerator </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 10</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">denominator </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 15</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">q(uit)</span></span></code></pre>
<p>To exit the debugger we can use <code>q</code> or <code>quit</code>.</p>
<ul>
<li><code>! statement</code></li>
</ul>
<p>To run Python code in debugger we can use <code>!</code> followed by the code we want to run. Without <code>!</code> the code can fail if it collides with any <code>pdb</code> command, hence it is recommended to always use <code>!</code> to run Python code.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">$</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> python</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -m</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> pdb</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> script.py</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#24292E;--shiki-dark:#E1E4E8"> /pdb-mupy/script.py(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#6F42C1;--shiki-dark:#B392F0">&#x3C;module></span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">> </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"""I am the first script in this demo"""</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Pdb</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) </span><span style="color:#D73A49;--shiki-dark:#F97583">!</span><span style="color:#6F42C1;--shiki-dark:#B392F0">c</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> =</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 2</span><span style="color:#6A737D;--shiki-dark:#6A737D">  # Define a variable named c</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Pdb</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) </span><span style="color:#6F42C1;--shiki-dark:#B392F0">p</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> c</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">2</span></span></code></pre>
<p>Without <code>!</code> it fails because pdb thinks we are trying to run pdb’s <code>c</code> command.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) c </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 2</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">2</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">The program finished </span><span style="color:#D73A49;--shiki-dark:#F97583">and</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> will be restarted:</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#D73A49;--shiki-dark:#F97583"> /</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pdb</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">mupy</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">script.py(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">module</span><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">-></span><span style="color:#032F62;--shiki-dark:#9ECBFF"> """I am the first script in this demo"""</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb)</span></span></code></pre>
<ul>
<li><code>run [args ...]</code></li>
</ul>
<p><code>run</code> allows us to restart a program. This is helpful if we want to restart the programs with different argument without exiting the debugger.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">$</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> python</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -m</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> pdb</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> script.py</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 10</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 5</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#24292E;--shiki-dark:#E1E4E8"> /pdb-mupy/script.py(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#6F42C1;--shiki-dark:#B392F0">&#x3C;module></span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">> </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"""I am the first script in this demo"""</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Pdb</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) </span><span style="color:#D73A49;--shiki-dark:#F97583">!</span><span style="color:#6F42C1;--shiki-dark:#B392F0">import</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> sys</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Pdb</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) </span><span style="color:#6F42C1;--shiki-dark:#B392F0">p</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> sys.argv</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">[</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'script.py'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'10'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'5'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">]</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">Let</span><span style="color:#6F42C1;--shiki-dark:#B392F0">'s restart this program with different arguments:</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">(Pdb) run 30 40</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">Restarting script.py with arguments:</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    30 40</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">> /pdb-mupy/script.py(1)&#x3C;module>()</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">-> """I am the first script in this demo"""</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">(Pdb) !import sys</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">(Pdb) p sys.argv</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">['</span><span style="color:#6F42C1;--shiki-dark:#B392F0">script.py</span><span style="color:#6F42C1;--shiki-dark:#B392F0">', '</span><span style="color:#6F42C1;--shiki-dark:#B392F0">30</span><span style="color:#6F42C1;--shiki-dark:#B392F0">', '</span><span style="color:#6F42C1;--shiki-dark:#B392F0">40</span><span style="color:#6F42C1;--shiki-dark:#B392F0">']</span></span></code></pre>
<ul>
<li><code>l(ist) [first[, last]]</code></li>
</ul>
<p><code>l</code> or <code>list</code> command can be used to list the source code.</p>
<p>Without any argument it lists the <em>11</em> lines around the current line. With one argument 11 lines around the specified line number. With two argument it lists the lines in that range, if second argument is less that first then it is taken as count.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">$</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> python</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -m</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> pdb</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> script.py</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#24292E;--shiki-dark:#E1E4E8"> /pdb-mupy/script.py(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#6F42C1;--shiki-dark:#B392F0">&#x3C;module></span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">> </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"""I am the first script in this demo"""</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">List</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 11</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> lines</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> around</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> the</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> current</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> line:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Pdb</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) list</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">  -</span><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#032F62;--shiki-dark:#9ECBFF"> """I am the first script in this demo"""</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  2</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  3</span><span style="color:#032F62;--shiki-dark:#9ECBFF">     import</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> sys</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  4</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  5</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  6</span><span style="color:#032F62;--shiki-dark:#9ECBFF">     def</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> divide</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">numerator,</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> denominator</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#032F62;--shiki-dark:#9ECBFF">:</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  7</span><span style="color:#032F62;--shiki-dark:#9ECBFF">         return</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> numerator</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> /</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> denominator</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  8</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  9</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0"> 10</span><span style="color:#032F62;--shiki-dark:#9ECBFF">     if</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> __name__</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ==</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '__main__':</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0"> 11</span><span style="color:#032F62;--shiki-dark:#9ECBFF">         numerator</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> =</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> int</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">sys.argv[1]</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">List</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> lines</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 5</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> to</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 8:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Pdb</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) </span><span style="color:#6F42C1;--shiki-dark:#B392F0">list</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 5,</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 8</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  5</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  6</span><span style="color:#032F62;--shiki-dark:#9ECBFF">     def</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> divide</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">numerator,</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> denominator</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#032F62;--shiki-dark:#9ECBFF">:</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  7</span><span style="color:#032F62;--shiki-dark:#9ECBFF">         return</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> numerator</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> /</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> denominator</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  8</span></span></code></pre>
<ul>
<li><code>alias [name [command]]</code> or <code>unalias</code></li>
</ul>
<p><code>alias</code> command can be used to set aliases for commands in debugger, similarly <code>unalias</code> can be used to unset an already existing alias.</p>
<p>Let’s say we want to create an alias that returns a list of squares.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) alias squares [i</span><span style="color:#D73A49;--shiki-dark:#F97583">**</span><span style="color:#005CC5;--shiki-dark:#79B8FF">2</span><span style="color:#D73A49;--shiki-dark:#F97583"> for</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> i </span><span style="color:#D73A49;--shiki-dark:#F97583">in</span><span style="color:#E36209;--shiki-dark:#FFAB70"> xrange</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">%</span><span style="color:#005CC5;--shiki-dark:#79B8FF">1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)]</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) squares </span><span style="color:#005CC5;--shiki-dark:#79B8FF">5</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">[</span><span style="color:#005CC5;--shiki-dark:#79B8FF">0</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">4</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">9</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">16</span><span style="color:#24292E;--shiki-dark:#E1E4E8">]</span></span></code></pre>
<p>Here <code>%1</code> is the argument that our alias expects(5 in the above example), if it expects more then we can use <code>%2</code>, <code>%3</code> etc</p>
<p>We can also create aliases using existing aliases:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) alias squares_7 squares </span><span style="color:#005CC5;--shiki-dark:#79B8FF">7</span></span></code></pre>
<p>Now <code>squares_7</code> is equivalent to running <code>squares 7</code></p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) squares_7</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">[</span><span style="color:#005CC5;--shiki-dark:#79B8FF">0</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">4</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">9</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">16</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">25</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">36</span><span style="color:#24292E;--shiki-dark:#E1E4E8">]</span></span></code></pre>
<p>To remove an alias use <code>unalias</code> command followed by the command name.</p>
<h3 id="stepping-throughcode">Stepping through code</h3>
<p>One of the strongest feature of pdb is that we can move through our code in various ways: </p>
<ul>
<li>Line by line </li>
<li>Jumping inside a function </li>
<li>Skip a loop </li>
<li>Skip function</li>
</ul>
<p>In this section we will learn about the commands that allow us to step through the code. The code that we will be use in this section is <a href="https://github.com/ashwch/pdb-mupy/blob/master/next_and_step_until.py."><code>next_and_step_until.py</code></a>. These are the commands that you will be using the most, hence it is important to have a clear understanding here.</p>
<ul>
<li><code>n(ext)</code></li>
</ul>
<p><code>n</code> or simply <code>next</code> command runs the code on current line at full-speed and takes us to the next line in the current function.</p>
<ul>
<li><code>s(tep)</code></li>
</ul>
<p><code>s</code> or <code>step</code> is similar to next but they vary when a callable(function etc) is involved. If a callable is there then it will step us inside that callable instead of taking us to next line in the current function. If no callable is involved then it is same as next.</p>
<ul>
<li><code>unt(il)</code></li>
</ul>
<p><code>until</code> command tells the debugger to continue executing until we have reached a line number greater than the current line number. This command is helpful in exiting a loop.</p>
<ul>
<li><code>r(eturn)</code></li>
</ul>
<p><code>r</code> or <code>return</code> takes us to the end of the current function. At global level it takes us to the last line in the module. This command is helpful you want to step through the whole function body at once.</p>
<ul>
<li><code>c(ont(inue))</code></li>
</ul>
<p><code>c</code> or <code>cont</code> or <code>continue</code> command lets us run the whole code at full-speed when we are done with our debugging. If there’s another breakpoint in your program then it will stop at that next breakpoint.</p>
<p>Let’s debug through our script <a href="https://github.com/ashwch/pdb-mupy/blob/master/next_and_step_until.py"><code>next_and_step_until.py</code></a> while making use of the above commands. We have set a breakpoint on line #19 in that script and debugger will stop on next valid line: #21.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">$</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> python</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> next_and_step_until.py</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#24292E;--shiki-dark:#E1E4E8"> /Users/ashwini/work/instamojo/pdb-mupy/next_and_step_until.py(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">21</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#6F42C1;--shiki-dark:#B392F0">&#x3C;module></span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">> </span><span style="color:#032F62;--shiki-dark:#9ECBFF">knights</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()  </span><span style="color:#6A737D;--shiki-dark:#6A737D"># Just want to run this function and move on to next line? Use n(ext).</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Pdb</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span></code></pre>
<p>Now to run the <code>knights()</code> function at full-speed use <code>n(ext)</code>. As you can see it printed the statements we have inside the <code>knights()</code> function and stopped on the next line in the current function.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) n</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">We are the Knights who say ni!</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Get us a shrubbery.</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#D73A49;--shiki-dark:#F97583"> /</span><span style="color:#24292E;--shiki-dark:#E1E4E8">Users</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">ashwini</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">work</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">instamojo</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pdb</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">mupy</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">next_and_step_until.py(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">22</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">module</span><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">-></span><span style="color:#005CC5;--shiki-dark:#79B8FF"> credits</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()  </span><span style="color:#6A737D;--shiki-dark:#6A737D"># Want to step inside this function? Use s(tep).</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb)</span></span></code></pre>
<p>Now let’s say we want to debug something inside <code>credits()</code> call, for that we can use <code>s(tep)</code>.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) s</span></span>
<span class="line"><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">--</span><span style="color:#24292E;--shiki-dark:#E1E4E8">Call</span><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">--</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#D73A49;--shiki-dark:#F97583"> /</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pdb</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">mupy</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">next_and_step_until.py(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">11</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#005CC5;--shiki-dark:#79B8FF">credits</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">-></span><span style="color:#D73A49;--shiki-dark:#F97583"> def</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> credits</span><span style="color:#24292E;--shiki-dark:#E1E4E8">():</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) n</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#D73A49;--shiki-dark:#F97583"> /</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pdb</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">mupy</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">next_and_step_until.py(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">12</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#005CC5;--shiki-dark:#79B8FF">credits</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">-></span><span style="color:#005CC5;--shiki-dark:#79B8FF"> print</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "A Møøse once bit my sister... No realli!"</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) n</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">A Møøse once bit my sister</span><span style="color:#005CC5;--shiki-dark:#79B8FF">...</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> No realli!</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#D73A49;--shiki-dark:#F97583"> /</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pdb</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">mupy</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">next_and_step_until.py(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">13</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#005CC5;--shiki-dark:#79B8FF">credits</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">-></span><span style="color:#005CC5;--shiki-dark:#79B8FF"> print</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "We apologise for the fault in the print statements."</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb)</span></span></code></pre>
<p>Now that we are done with this function we can use <code>r(eturn)</code> to go to its end and then use <code>n</code> to exit:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) r</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">We apologise </span><span style="color:#D73A49;--shiki-dark:#F97583">for</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> the fault </span><span style="color:#D73A49;--shiki-dark:#F97583">in</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> the </span><span style="color:#005CC5;--shiki-dark:#79B8FF">print</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> statements.</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Those responsible have been sacked.</span></span>
<span class="line"><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">--</span><span style="color:#24292E;--shiki-dark:#E1E4E8">Return</span><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">--</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#D73A49;--shiki-dark:#F97583"> /</span><span style="color:#24292E;--shiki-dark:#E1E4E8">Users</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">ashwini</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">work</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">instamojo</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pdb</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">mupy</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">next_and_step_until.py(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">15</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#005CC5;--shiki-dark:#79B8FF">credits</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">-></span><span style="color:#032F62;--shiki-dark:#9ECBFF">'M</span><span style="color:#005CC5;--shiki-dark:#79B8FF">\xc3\xb8</span><span style="color:#032F62;--shiki-dark:#9ECBFF">\xc...pretti nasti.'</span></span>
<span class="line"><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">-></span><span style="color:#D73A49;--shiki-dark:#F97583"> return</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "Møøse bites Kan be pretti nasti."</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) n</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#D73A49;--shiki-dark:#F97583"> /</span><span style="color:#24292E;--shiki-dark:#E1E4E8">Users</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">ashwini</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">work</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">instamojo</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pdb</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">mupy</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">next_and_step_until.py(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">24</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">module</span><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">-></span><span style="color:#D73A49;--shiki-dark:#F97583"> for</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> i </span><span style="color:#D73A49;--shiki-dark:#F97583">in</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> range</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">5</span><span style="color:#24292E;--shiki-dark:#E1E4E8">):</span></span></code></pre>
<p>Now we are in a loop and both <code>n(ext)</code> and <code>s(tep)</code> can’t be used to complete it in a single step. To skip the loop we can go its last line and use <code>unt(il)</code>.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">-></span><span style="color:#D73A49;--shiki-dark:#F97583"> for</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> i </span><span style="color:#D73A49;--shiki-dark:#F97583">in</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> range</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">5</span><span style="color:#24292E;--shiki-dark:#E1E4E8">):</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) n</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#D73A49;--shiki-dark:#F97583"> /</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pdb</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">mupy</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">next_and_step_until.py(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">26</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">module</span><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">-></span><span style="color:#005CC5;--shiki-dark:#79B8FF"> print</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "Shrubbery #</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format(i)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) until</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Shrubbery </span><span style="color:#6A737D;--shiki-dark:#6A737D">#1</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Shrubbery </span><span style="color:#6A737D;--shiki-dark:#6A737D">#2</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Shrubbery </span><span style="color:#6A737D;--shiki-dark:#6A737D">#3</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Shrubbery </span><span style="color:#6A737D;--shiki-dark:#6A737D">#4</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#D73A49;--shiki-dark:#F97583"> /</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pdb</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">mupy</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">next_and_step_until.py(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">28</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">module</span><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">-></span><span style="color:#005CC5;--shiki-dark:#79B8FF"> print</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "We have found the Holy Grail."</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb)</span></span></code></pre>
<p>Now we can continue executing the program using <code>c(ont(inue))</code>.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) cont</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">We have found the Holy Grail.</span></span></code></pre>
<h3 id="jumping-betweenstacks">Jumping between stacks</h3>
<p>So far we have only seen how to move forward in code by moving line by line and jumping inside a function call. But <code>pdb</code> also provides us functionality to jump up and down in the current stack.</p>
<p>Best way to demonstrate this is to use a recursive function as an example, its code can be found at <a href="https://github.com/ashwch/pdb-mupy/blob/master/recursive.py"><code>recursive.py</code></a>.</p>
<p>The three commands we will be going through in this section are <code>u(p)</code>, <code>d(own)</code> and <code>w(here)</code>.</p>
<ul>
<li><code>w(here)</code></li>
</ul>
<p><code>w</code> or <code>where</code> prints the whole trace till the most recent frame and current frame is represented using an arrow.</p>
<p>Let’s run our program and when it stops at the breakpoint we will use <code>w(here)</code> to view the whole stack trace</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">$</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> python</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> recursive.py</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#24292E;--shiki-dark:#E1E4E8"> /pdb-mupy/recursive.py(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">10</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#6F42C1;--shiki-dark:#B392F0">func</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">> </span><span style="color:#032F62;--shiki-dark:#9ECBFF">return</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 0</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Pdb</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) where</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  /pdb-mupy/recursive.py(14</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#6F42C1;--shiki-dark:#B392F0">&#x3C;module></span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">> </span><span style="color:#032F62;--shiki-dark:#9ECBFF">print</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> (func(4))</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  /pdb-mupy/recursive.py(7</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#6F42C1;--shiki-dark:#B392F0">func</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">> </span><span style="color:#032F62;--shiki-dark:#9ECBFF">return</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> func</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">n</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> -</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  /pdb-mupy/recursive.py(7</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#6F42C1;--shiki-dark:#B392F0">func</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">> </span><span style="color:#032F62;--shiki-dark:#9ECBFF">return</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> func</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">n</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> -</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  /pdb-mupy/recursive.py(7</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#6F42C1;--shiki-dark:#B392F0">func</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">> </span><span style="color:#032F62;--shiki-dark:#9ECBFF">return</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> func</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">n</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> -</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">  /pdb-mupy/recursive.py(7</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#6F42C1;--shiki-dark:#B392F0">func</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">> </span><span style="color:#032F62;--shiki-dark:#9ECBFF">return</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> func</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">n</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> -</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#24292E;--shiki-dark:#E1E4E8"> /pdb-mupy/recursive.py(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">10</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#6F42C1;--shiki-dark:#B392F0">func</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()  </span><span style="color:#6A737D;--shiki-dark:#6A737D"># Current frame</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">> </span><span style="color:#032F62;--shiki-dark:#9ECBFF">return</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 0</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Pdb</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span></code></pre>
<p>Here <code>></code> represents the current frame.</p>
<p>Now we can go up and down in the stack using <code>u(p)</code> and <code>d(own)</code>. Let’s move up twice and then check the argument value using <code>a(rgs)</code>. The breakpoint was set at <code>n = 0</code>, now when we moved up twice we have <code>n = 2</code>.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) u</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#D73A49;--shiki-dark:#F97583"> /</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pdb</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">mupy</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">recursive.py(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">7</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)func()</span></span>
<span class="line"><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">-></span><span style="color:#D73A49;--shiki-dark:#F97583"> return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> func(n </span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) u</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#D73A49;--shiki-dark:#F97583"> /</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pdb</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">mupy</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">recursive.py(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">7</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)func()</span></span>
<span class="line"><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">-></span><span style="color:#D73A49;--shiki-dark:#F97583"> return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> func(n </span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) args</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">n </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 2</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb)</span></span></code></pre>
<p>Similarly we can go back down using <code>d(own)</code>:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) d</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#D73A49;--shiki-dark:#F97583"> /</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pdb</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">mupy</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">recursive.py(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">7</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)func()</span></span>
<span class="line"><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">-></span><span style="color:#D73A49;--shiki-dark:#F97583"> return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> func(n </span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) args</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">n </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 1</span></span></code></pre>
<p><code>u(p)</code> is also useful when you stepped(<code>s(tep)</code>)inside a function accidentally and want to go back.</p>
<h3 id="breakpoints">Breakpoints</h3>
<p>So far we have only seen how to set a break-point by updating the code and adding <code>pdb.set_trace()</code> wherever we want to stop our program. But, <code>pdb</code> also provides us a way to set dynamic conditional breakpoints without updating the source code. In this section we will be using code from <a href="https://github.com/ashwch/pdb-mupy/blob/master/breakpoints.py"><code>breakpoints.py</code></a> file.</p>
<ul>
<li><code>b(reak) [[filename:]lineno | function[, condition]]</code>:</li>
</ul>
<p>We can set a breakpoint in the current file by specifying the line number or function name in the current file. Or we can also set breakpoint in some other file(this file should be present in module search path) by specifying the file name followed by a line number.
Each number is assigned a number and this number can be used later with other commands to access the breakpoint.</p>
<p><code>condition</code> is a Python statement that should be True to stop at the breakpoint. This condition is executed in the scope at which we have set the breakpoint.</p>
<p>Few examples of setting breakpoints.</p>
<ul>
<li><code>break 13</code> # Set breakpoint on line number 13</li>
<li><code>break divide</code> # Set breakpoint on <code>divide</code> function</li>
<li><code>break divide, denominator == 0</code> # Set breakpoint in divide function only if <code>denominator</code> is 0</li>
</ul>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">$</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> python</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -m</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> pdb</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> breakpoints.py</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 10</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 0</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#24292E;--shiki-dark:#E1E4E8"> /pdb-mupy/breakpoints.py(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">2</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#6F42C1;--shiki-dark:#B392F0">&#x3C;module></span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">> </span><span style="color:#032F62;--shiki-dark:#9ECBFF">import</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> sys</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Pdb</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) break divide, denominator == 0</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">Breakpoint</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> at</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> /pdb-mupy/breakpoints.py:5</span></span></code></pre>
<p>Get list of breakpoints using <code>b(break)</code>. Here <code>Num = 1</code> is the number assigned to the breakpoint. <code>Disp == keep</code> means it is a permanent breakpoint and <code>End = yes</code> means this breakpoint is right now enabled.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) </span><span style="color:#D73A49;--shiki-dark:#F97583">break</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    Num Type         Disp Enb   Where</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">    1</span><span style="color:#005CC5;--shiki-dark:#79B8FF">   breakpoint</span><span style="color:#24292E;--shiki-dark:#E1E4E8">   keep yes   at </span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pdb</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">mupy</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">breakpoints.py:</span><span style="color:#005CC5;--shiki-dark:#79B8FF">5</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">        stop only </span><span style="color:#D73A49;--shiki-dark:#F97583">if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> denominator </span><span style="color:#D73A49;--shiki-dark:#F97583">==</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 0</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    (Pdb) c</span></span></code></pre>
<p>As denominator is program our program stopped under debugger control:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#D73A49;--shiki-dark:#F97583"> /</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pdb</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">mupy</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">breakpoints.py(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">8</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)divide()</span></span>
<span class="line"><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">    -></span><span style="color:#005CC5;--shiki-dark:#79B8FF"> print</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "Calculating </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">/</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format(numerator, denominator)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    (Pdb) args</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    numerator </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 10.0</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    denominator </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 0.0</span></span></code></pre>
<p>Let’s restart the program using different arguments:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) run </span><span style="color:#005CC5;--shiki-dark:#79B8FF">10</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 5</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Restarting breakpoints.py </span><span style="color:#D73A49;--shiki-dark:#F97583">with</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> arguments:</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">    10</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 5</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#D73A49;--shiki-dark:#F97583"> /</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pdb</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">mupy</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">breakpoints.py(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">2</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">module</span><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">-></span><span style="color:#D73A49;--shiki-dark:#F97583"> import</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> sys</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) c</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Calculating </span><span style="color:#005CC5;--shiki-dark:#79B8FF">10.0</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#005CC5;--shiki-dark:#79B8FF">5.0</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">2.0</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">The program finished </span><span style="color:#D73A49;--shiki-dark:#F97583">and</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> will be restarted</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#D73A49;--shiki-dark:#F97583"> /</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pdb</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">mupy</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">breakpoints.py(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">2</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">module</span><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">-></span><span style="color:#D73A49;--shiki-dark:#F97583"> import</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> sys</span></span></code></pre>
<p>As the denominator wasn’t zero this time the program didn’t stop at the breakpoint.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) </span><span style="color:#D73A49;--shiki-dark:#F97583">break</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Num Type         Disp Enb   Where</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">1</span><span style="color:#005CC5;--shiki-dark:#79B8FF">   breakpoint</span><span style="color:#24292E;--shiki-dark:#E1E4E8">   keep yes   at </span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pdb</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">mupy</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">breakpoints.py:</span><span style="color:#005CC5;--shiki-dark:#79B8FF">5</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    stop only </span><span style="color:#D73A49;--shiki-dark:#F97583">if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> denominator </span><span style="color:#D73A49;--shiki-dark:#F97583">==</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 0</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">    breakpoint</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> already hit </span><span style="color:#005CC5;--shiki-dark:#79B8FF">2</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> times</span></span></code></pre>
<p><strong>Note</strong>: Breakpoints persist even after the auto restart or forced restart(using <code>run</code>) in <code>-m pdb</code> mode.</p>
<ul>
<li><code>tbreak [[filename:]lineno | function[, condition]]</code>:</li>
</ul>
<p><code>tbreak</code> allows us to set a temporary breakpoint. This breakpoint goes away as soon as it is hit once. Can be pretty useful if you want to set a breakpoint only once, say inside a loop or for the first time a function is invoked.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">$</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> python</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -m</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> pdb</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> breakpoints.py</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 10</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 5</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#24292E;--shiki-dark:#E1E4E8"> /pdb-mupy/breakpoints.py(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">2</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#6F42C1;--shiki-dark:#B392F0">&#x3C;module></span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">> </span><span style="color:#032F62;--shiki-dark:#9ECBFF">import</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> sys</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Pdb</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) tbreak divide, denominator == 0</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">Breakpoint</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> at/pdb-mupy/breakpoints.py:5</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Pdb</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) </span><span style="color:#D73A49;--shiki-dark:#F97583">break</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">Num</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Type</span><span style="color:#032F62;--shiki-dark:#9ECBFF">         Disp</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Enb</span><span style="color:#032F62;--shiki-dark:#9ECBFF">   Where</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">1</span><span style="color:#032F62;--shiki-dark:#9ECBFF">   breakpoint</span><span style="color:#032F62;--shiki-dark:#9ECBFF">   del</span><span style="color:#032F62;--shiki-dark:#9ECBFF">  yes</span><span style="color:#032F62;--shiki-dark:#9ECBFF">   at</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> /pdb-mupy/breakpoints.py:5</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">    stop</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> only</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> if</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> denominator</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ==</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 0</span></span></code></pre>
<p>Notice the value of <code>Disp</code> this time. It is <code>del</code> instead of <code>keep</code>, means it is a temporary breakpoint.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) c</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Calculating </span><span style="color:#005CC5;--shiki-dark:#79B8FF">10.0</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#005CC5;--shiki-dark:#79B8FF">5.0</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">2.0</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">The program finished </span><span style="color:#D73A49;--shiki-dark:#F97583">and</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> will be restarted</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#D73A49;--shiki-dark:#F97583"> /</span><span style="color:#24292E;--shiki-dark:#E1E4E8">Users</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">ashwini</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">work</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">instamojo</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pdb</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">mupy</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">breakpoints.py(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">2</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">module</span><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">-></span><span style="color:#D73A49;--shiki-dark:#F97583"> import</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> sys</span></span></code></pre>
<p>Programs simply restarted because the breakpoint condition wasn’t true, now let’s make it true by restarting it with different arguments.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) run </span><span style="color:#005CC5;--shiki-dark:#79B8FF">10</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 0</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Restarting breakpoints.py </span><span style="color:#D73A49;--shiki-dark:#F97583">with</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> arguments:</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">    10</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 0</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#D73A49;--shiki-dark:#F97583"> /</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pdb</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">mupy</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">breakpoints.py(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">2</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">module</span><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">-></span><span style="color:#D73A49;--shiki-dark:#F97583"> import</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> sys</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) c</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Deleted </span><span style="color:#005CC5;--shiki-dark:#79B8FF">breakpoint</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 1</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#D73A49;--shiki-dark:#F97583"> /</span><span style="color:#24292E;--shiki-dark:#E1E4E8">Users</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">ashwini</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">work</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">instamojo</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pdb</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">mupy</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">breakpoints.py(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">8</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)divide()</span></span>
<span class="line"><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">-></span><span style="color:#005CC5;--shiki-dark:#79B8FF"> print</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "Calculating </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">/</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format(numerator, denominator)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) </span><span style="color:#D73A49;--shiki-dark:#F97583">break</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb)</span></span></code></pre>
<p>This time it did hit the breakpoint and was deleted as well.</p>
<ul>
<li><code>cl(ear) [filename:lineno | bpnumber [bpnumber ...]]</code>:</li>
</ul>
<p>We can permanently remove breakpoints using <code>cl(ear)</code> command.</p>
<ul>
<li><code>disable [bpnumber [bpnumber ...]]</code> or <code>enable [bpnumber [bpnumber ...]]</code>:</li>
</ul>
<p>To temporary disable a breakpoint use <code>disable</code> and to re-enable a breakpoint use <code>enable</code>. Unlike <code>clear</code> breakpoints are not removed permanently in this case.</p>
<ul>
<li><code>ignore bpnumber [count]</code>:</li>
</ul>
<p>We can also ignore a breakpoint count number of times using <code>ignore</code> command. Breakpoint is re-activated when the count becomes 0.</p>
<ul>
<li><code>condition bpnumber [condition]</code>:</li>
</ul>
<p>To update or add condition to a breakpoint we can use the condition command.</p>
<p><strong>Note</strong>: If the condition is an invalid Python code then it will be evaluated as True but in case the breakpoint was a temporary one then it won’t be deleted and if the breakpoint had an ignore count then it won’t be decremented. This is done to notify the user that something’s wrong.</p>
<ul>
<li><code>commands [bpnumber]</code>:</li>
</ul>
<p><code>commands</code> is a pretty useful command related to breakpoints. If used in a certain way then it can be equivalent to adding print statements in our code.</p>
<p>This command allows us to run multiple commands when a breakpoint is hit.</p>
<p>Let’s add a breakpoint on <code>divide</code> function and now we will print some stuff as well using <code>commands</code>.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">$</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> python</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -m</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> pdb</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> breakpoints.py</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 10</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 5</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#24292E;--shiki-dark:#E1E4E8"> /pdb-mupy/breakpoints.py(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">2</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#6F42C1;--shiki-dark:#B392F0">&#x3C;module></span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">> </span><span style="color:#032F62;--shiki-dark:#9ECBFF">import</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> sys</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Pdb</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) break divide</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">Breakpoint</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> at</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> /Users/ashwini/work/instamojo/pdb-mupy/breakpoints.py:5</span></span></code></pre>
<p>In commands mode the prompt is (com). To end the commands use end.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) commands </span><span style="color:#005CC5;--shiki-dark:#79B8FF">1</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(com) args</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(com) p </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Inside divide()"</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(com) end</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(Pdb) c</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">numerator </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 10.0</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">denominator </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 5.0</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">'Inside divide()'</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#D73A49;--shiki-dark:#F97583"> /</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pdb</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">mupy</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">breakpoints.py(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">8</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)divide()</span></span>
<span class="line"><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">-></span><span style="color:#005CC5;--shiki-dark:#79B8FF"> print</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "Calculating </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">/</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format(numerator, denominator)</span></span></code></pre>
<p>Now as you can see our program printed few things on hitting the breakpoint.</p>
<p>We can also use commands like <code>cont</code>, <code>next</code> etc. But these commands will also act as <code>end</code> because these commands can lead us to next breakpoint which may have its own set of commands and then debugger will be confused about whose commands to run next.</p>
<p>Another commands is silent, when this command is part of commands list then you won’t see the message we get at a breakpoint.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">$</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> python</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -m</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> pdb</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> breakpoints.py</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 10</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 5</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#24292E;--shiki-dark:#E1E4E8"> /pdb-mupy/breakpoints.py(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">2</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#6F42C1;--shiki-dark:#B392F0">&#x3C;module></span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">> </span><span style="color:#032F62;--shiki-dark:#9ECBFF">import</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> sys</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Pdb</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) break divide</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">Breakpoint</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> at</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> /pdb-mupy/breakpoints.py:5</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Pdb</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) </span><span style="color:#6F42C1;--shiki-dark:#B392F0">commands</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 1</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">com</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) </span><span style="color:#6F42C1;--shiki-dark:#B392F0">args</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">com</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) </span><span style="color:#6F42C1;--shiki-dark:#B392F0">p</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "Inside divide()"</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">com</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) </span><span style="color:#6F42C1;--shiki-dark:#B392F0">silent</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">com</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) </span><span style="color:#6F42C1;--shiki-dark:#B392F0">cont</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Pdb</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) </span><span style="color:#6F42C1;--shiki-dark:#B392F0">c</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">numerator</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> =</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 10.0</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">denominator</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> =</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 5.0</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">'Inside divide()'</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">Calculating</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 10.0/5.0</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">2.0</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">The</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> program</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> finished</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> and</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> will</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> be</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> restarted</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#24292E;--shiki-dark:#E1E4E8"> /pdb-mupy/breakpoints.py(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">2</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span><span style="color:#6F42C1;--shiki-dark:#B392F0">&#x3C;module></span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">> </span><span style="color:#032F62;--shiki-dark:#9ECBFF">import</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> sys</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Pdb</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span></code></pre>
<p>As you can see above the program didn’t stop at the breakpoint this time due to <code>cont</code> command and we didn’t see the lines(shown below) related to breakpoint either due to <code>silent</code> command:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#D73A49;--shiki-dark:#F97583"> /</span><span style="color:#24292E;--shiki-dark:#E1E4E8">pdb</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">mupy</span><span style="color:#D73A49;--shiki-dark:#F97583">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">breakpoints.py(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">8</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)divide()</span></span>
<span class="line"><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">-></span><span style="color:#005CC5;--shiki-dark:#79B8FF"> print</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "Calculating </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">/</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format(numerator, denominator)</span></span></code></pre>
<h3 id="tips-andtricks">Tips and tricks</h3>
<ul>
<li>
<p><code>.pdbrc</code> file: If present the commands present in this file are ran at the start of debugger session. This file can be added to your home directory and/or current directory.</p>
</li>
<li>
<p>Plain enter repeats the last command(list command is an exception).</p>
</li>
<li>
<p>To enter multiple commands on a single line use <code>;;</code> as separator.</p>
</li>
</ul>
<h3 id="whats-new-in-python3">What’s new in Python 3</h3>
<h4 id="python-33">Python 3.3+</h4>
<ul>
<li>Tab-completion via the <code>readline</code> module is available for commands and command arguments.</li>
</ul>
<h4 id="python-32">Python 3.2+</h4>
<ul>
<li>
<p><code>pdb.py</code> now accepts a <code>-c</code> option that executes commands as if given in a “.pdbrc file`</p>
</li>
<li>
<p>A new command <code>ll</code> can be used to see the source related to the current function or frame.</p>
</li>
<li>
<p>A new command <code>source</code> can be used to see the source code of an expression: <code>source expression</code>.</p>
</li>
<li>
<p>A new command <code>interact</code> can be used to start interactive shell in debugger using the <code>globals()</code> and <code>locals()</code> in the current frame. This can be done in Python 2 using <code>!import code; code.interact(local=vars())</code>.</p>
</li>
</ul>]]></content:encoded>
            <author>Ashwini Chaudhary</author>
            <category>python</category>
            <category>programming</category>
            <category>debugging</category>
            <category>pdb</category>
            <category>ipdb</category>
        </item>
        <item>
            <title><![CDATA[Handling missing keys in str.format_map properly]]></title>
            <link>https://ashwch.com/handling-missing-keys-in-str-format-map.html</link>
            <guid isPermaLink="false">tag:ashwch.com,2010-12-03:/handling-missing-keys-in-str-format-map.html</guid>
            <pubDate>Fri, 03 Dec 2010 05:00:00 GMT</pubDate>
            <description><![CDATA[Handling missing keys in `str.format_map` properly]]></description>
            <content:encoded><![CDATA[<p><code>str.format_map</code> was introduced in Python 3.2, it allows users to a pass a dictionary instead of individual keyword arguments. This can be very useful in case some of the format arguments are missing from the dictionary, take this example from docs:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">class</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Default</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">dict</span><span style="color:#24292E;--shiki-dark:#E1E4E8">):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    def</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> __missing__</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(self, key):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> key</span></span>
<span class="line"></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">print</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{name}</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> was born in </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{country}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format_map(Default(</span><span style="color:#E36209;--shiki-dark:#FFAB70">name</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'Guido'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">))) </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Guido was born in country</span></span></code></pre>
<p>But this fails:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">>>></span><span style="color:#005CC5;--shiki-dark:#79B8FF"> print</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{name}</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> was born in </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{country.state}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format_map(Default(</span><span style="color:#E36209;--shiki-dark:#FFAB70">name</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'Guido'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)))</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Traceback (most recent call last):</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  File </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"&#x3C;ipython-input-324-1012aa68ba8d>"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, line </span><span style="color:#005CC5;--shiki-dark:#79B8FF">1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#D73A49;--shiki-dark:#F97583">in</span><span style="color:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">module</span><span style="color:#D73A49;--shiki-dark:#F97583">></span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">    print</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{name}</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> was born in </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{country.state}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format_map(Default(</span><span style="color:#E36209;--shiki-dark:#FFAB70">name</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'Guido'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)))</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">AttributeError</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'str'</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> object</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> has no attribute </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'state'</span></span></code></pre>
<p>That is obvious because we are returning a string from <code>__missing__</code> and that string doesn’t have any attribute of the name state.</p>
<p>Note that the above way is also possible in Python 2 and Python 3.0-3.1 using the <a href="https://docs.python.org/2/library/string.html#string.Formatter"><code>Formatter</code></a> class’s <code>vformat</code> method.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">from</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> string </span><span style="color:#D73A49;--shiki-dark:#F97583">import</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> Formatter</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">f </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> Formatter()</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">print</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> f.vformat(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{name}</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> was born in </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{country}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, (), Default(</span><span style="color:#E36209;--shiki-dark:#FFAB70">name</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'Guido'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">))</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Guido was born in country</span></span></code></pre>
<p>Dealing with dot notations, conversions(<code>!s</code> or <code>!r</code>) and format specs(<code>^</code>, <code>></code> etc)</p>
<p>The solution is, for missing keys instead of returning simple string, return an instance of a class that can handle these attribute calls along with creating full string back:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">class</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> MissingAttrHandler</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">object</span><span style="color:#24292E;--shiki-dark:#E1E4E8">):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    def</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> __init__</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(self, format):</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">        self</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> format</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    def</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> __getattr__</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(self, attr):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        return</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> type</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">self</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">.</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">self</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format, attr))</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    def</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> __repr__</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(self):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        return</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> self</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format </span><span style="color:#D73A49;--shiki-dark:#F97583">+</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '}'</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">class</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Default</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">dict</span><span style="color:#24292E;--shiki-dark:#E1E4E8">):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    def</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> __missing__</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(self, key):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> MissingAttrHandler(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{{{}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format(key))</span></span></code></pre>
<p>Now let’s test this:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">>>></span><span style="color:#005CC5;--shiki-dark:#79B8FF"> print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{name}</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> was born in </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{country.state}</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> and his last '</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">          'name is </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{Person.full_name.last_name}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format_map(Default(</span><span style="color:#E36209;--shiki-dark:#FFAB70">name</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'Guido'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)))</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Guido was born </span><span style="color:#D73A49;--shiki-dark:#F97583">in</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {country.state} </span><span style="color:#D73A49;--shiki-dark:#F97583">and</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> his last name </span><span style="color:#D73A49;--shiki-dark:#F97583">is</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {Person.full_name.last_name}</span></span></code></pre>
<p>Some of you may have already noticed, this solution has one issue though, it will fail if other formatting details like ^, 10d etc are present:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">>>></span><span style="color:#005CC5;--shiki-dark:#79B8FF"> print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{name}</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> was born in </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{country.state}</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> and his last '</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">      'name is </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{Person.full_name.last_name</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#005CC5;--shiki-dark:#79B8FF">*^30}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format_map(Default(</span><span style="color:#E36209;--shiki-dark:#FFAB70">name</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'Guido'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)))</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Traceback (most recent call last):</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  File </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"&#x3C;ipython-input-94-b375bfa3e06c>"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, line </span><span style="color:#005CC5;--shiki-dark:#79B8FF">2</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#D73A49;--shiki-dark:#F97583">in</span><span style="color:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">module</span><span style="color:#D73A49;--shiki-dark:#F97583">></span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">    'name is {Person.full_name.last_name:*^30}'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format_map(Default(</span><span style="color:#E36209;--shiki-dark:#FFAB70">name</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'Guido'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)))</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">TypeError</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: non</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">empty </span><span style="color:#005CC5;--shiki-dark:#79B8FF">format</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> string passed to </span><span style="color:#005CC5;--shiki-dark:#79B8FF">object</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#005CC5;--shiki-dark:#79B8FF">__format__</span></span></code></pre>
<p>This is because MissingAttrHandler has no <code>__format__`` method of its own, hence the </code><strong>format</strong><code> lookup goes to its base class object(</code>object.<strong>format</strong>`)</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">>>></span><span style="color:#24292E;--shiki-dark:#E1E4E8"> MissingAttrHandler.</span><span style="color:#005CC5;--shiki-dark:#79B8FF">__format__</span><span style="color:#D73A49;--shiki-dark:#F97583"> is</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> object</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#005CC5;--shiki-dark:#79B8FF">__format__</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">True</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">>>></span><span style="color:#005CC5;--shiki-dark:#79B8FF"> object</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#005CC5;--shiki-dark:#79B8FF">__format__</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(MissingAttrHandler(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">''</span><span style="color:#24292E;--shiki-dark:#E1E4E8">), </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'^*30s'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Traceback (most recent call last):</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">  File </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"&#x3C;ipython-input-129-c4e00a46bd28>"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, line </span><span style="color:#005CC5;--shiki-dark:#79B8FF">1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#D73A49;--shiki-dark:#F97583">in</span><span style="color:#D73A49;--shiki-dark:#F97583"> &#x3C;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">module</span><span style="color:#D73A49;--shiki-dark:#F97583">></span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">    object</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#005CC5;--shiki-dark:#79B8FF">__format__</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(MissingAttrHandler(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">''</span><span style="color:#24292E;--shiki-dark:#E1E4E8">), </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'^*30s'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">TypeError</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: non</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#24292E;--shiki-dark:#E1E4E8">empty </span><span style="color:#005CC5;--shiki-dark:#79B8FF">format</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> string passed to </span><span style="color:#005CC5;--shiki-dark:#79B8FF">object</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#005CC5;--shiki-dark:#79B8FF">__format__</span></span></code></pre>
<p>So, let’s define a `<strong>format</strong>“ method in our class that takes care of this:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">def</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> __format__</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(self, format):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        return</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">:</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{}}}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">self</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">format</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span></code></pre>
<p>Let’s test it:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">>>></span><span style="color:#005CC5;--shiki-dark:#79B8FF"> print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{name}</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> was born in </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{country.state}</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> and dict has '</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">      '</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{dict.get</span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#005CC5;--shiki-dark:#79B8FF">*^30}</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> method.'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format_map(Default(</span><span style="color:#E36209;--shiki-dark:#FFAB70">name</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'Guido'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)))</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Guido was born </span><span style="color:#D73A49;--shiki-dark:#F97583">in</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {country.state:} </span><span style="color:#D73A49;--shiki-dark:#F97583">and</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> dict</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> has {</span><span style="color:#005CC5;--shiki-dark:#79B8FF">dict</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.get:</span><span style="color:#D73A49;--shiki-dark:#F97583">*^</span><span style="color:#005CC5;--shiki-dark:#79B8FF">30</span><span style="color:#24292E;--shiki-dark:#E1E4E8">} method.</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">>>></span><span style="color:#005CC5;--shiki-dark:#79B8FF"> print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{name}</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> was born in </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{country.state}</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> and dict has '</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">      '</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{dict.get</span><span style="color:#D73A49;--shiki-dark:#F97583">:>30d</span><span style="color:#005CC5;--shiki-dark:#79B8FF">}</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> method.'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format_map(Default(</span><span style="color:#E36209;--shiki-dark:#FFAB70">name</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'Guido'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)))</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Guido was born </span><span style="color:#D73A49;--shiki-dark:#F97583">in</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {country.state:} </span><span style="color:#D73A49;--shiki-dark:#F97583">and</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> dict</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> has {</span><span style="color:#005CC5;--shiki-dark:#79B8FF">dict</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.get:</span><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">30d</span><span style="color:#24292E;--shiki-dark:#E1E4E8">} method.</span></span></code></pre>
<p>Seems to be working fine, let’s try one more thing:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">>>></span><span style="color:#005CC5;--shiki-dark:#79B8FF"> print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{name}</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> was born in </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{country.state}</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> and dict has '</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">...</span><span style="color:#032F62;--shiki-dark:#9ECBFF">       '</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{dict.get</span><span style="color:#D73A49;--shiki-dark:#F97583">!s:</span><span style="color:#005CC5;--shiki-dark:#79B8FF">*^30}</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> method.'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format_map(Default(</span><span style="color:#E36209;--shiki-dark:#FFAB70">name</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'Guido'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)))</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Guido was born </span><span style="color:#D73A49;--shiki-dark:#F97583">in</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {country.state:} </span><span style="color:#D73A49;--shiki-dark:#F97583">and</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> dict</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> has </span><span style="color:#D73A49;--shiki-dark:#F97583">**********</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span><span style="color:#005CC5;--shiki-dark:#79B8FF">dict</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.get}</span><span style="color:#D73A49;--shiki-dark:#F97583">**********</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> method.</span></span></code></pre>
<p>Well this was quite unexpected, what exactly happened there?</p>
<p>Well due to the <code>!s</code> present in the format string after getting the value of these fields using either <code>str()</code> or <code>repr()</code>(which is a string object), Python will now call <code>__format__</code> on it with <code>*^30</code> as an argument. But as we returned a string object and not a <code>MissingAttrHandler</code> object the format call goes to that str.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">>>></span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{dict.get}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#005CC5;--shiki-dark:#79B8FF">__format__</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'*^30'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">'**********{dict.get}**********'</span></span></code></pre>
<p>We can try to return an instance of MissingAttrHandler rather than a string from its <code>__repr__</code> method. But to return <code>MissingAttrHandle</code> instance from <code>__str__</code> or <code>__repr__</code> we will have to inherit from str as well because Python expects us to return an instance of type str. Now <code>__repr__</code> will look like:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">def</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> __repr__</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(self):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> MissingAttrHandler(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">self</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format </span><span style="color:#D73A49;--shiki-dark:#F97583">+</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '!r}'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span></code></pre>
<p>Note that now we need to define <code>__str__</code> as well because our class does not inherit from str which provides a <code>__str__</code> method, hence calling <code>__str__</code> on it won’t fallback to <code>__repr__</code> anymore.</p>
<p>And one cool thing about <code>__format__</code> is that once defined, it is the function that is by default called during string formatting unless we provide <code>!r</code> or !s explicitly. If <code>!r</code> or <code>!s</code> are present on the format string then <code>__repr__</code> and <code>__str__</code> are called respectively and then <code>__format__</code> is called on the resulting object.</p>
<p>Ah! ha that’s exactly what we needed right? Using this we can also add !r or !s in our format strings and later complete it with the <code>__format__</code> method.</p>
<p>So, in the end our class will look like:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">class</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> MissingAttrHandler</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">str</span><span style="color:#24292E;--shiki-dark:#E1E4E8">):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    def</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> __init__</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(self, format):</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">        self</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> format</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    def</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> __getattr__</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(self, attr):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        return</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> type</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">self</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">.</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">self</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format, attr))</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    def</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> __repr__</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(self):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> MissingAttrHandler(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">self</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format </span><span style="color:#D73A49;--shiki-dark:#F97583">+</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '!r}'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    def</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> __str__</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(self):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> MissingAttrHandler(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">self</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format </span><span style="color:#D73A49;--shiki-dark:#F97583">+</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '!s}'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    def</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> __format__</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(self, format):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        if</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> self</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format.endswith(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'}'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">):</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">            self</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> self</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format[:</span><span style="color:#D73A49;--shiki-dark:#F97583">-</span><span style="color:#005CC5;--shiki-dark:#79B8FF">1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">]</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        return</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">:</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{}}}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">self</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">format</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">class</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Default</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">dict</span><span style="color:#24292E;--shiki-dark:#E1E4E8">):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    def</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> __missing__</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(self, key):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> MissingAttrHandler(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{{{}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format(key))</span></span></code></pre>
<p>Let’s try it:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">>>></span><span style="color:#005CC5;--shiki-dark:#79B8FF"> print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{name}</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> was born in </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{country.state}</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> and dict has '</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">      '</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{dict.get</span><span style="color:#D73A49;--shiki-dark:#F97583">!r:</span><span style="color:#005CC5;--shiki-dark:#79B8FF">*^30}</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> method.'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format_map(Default(</span><span style="color:#E36209;--shiki-dark:#FFAB70">name</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'Guido'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">dct</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF">dict</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)))</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Guido was born </span><span style="color:#D73A49;--shiki-dark:#F97583">in</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {country.state:} </span><span style="color:#D73A49;--shiki-dark:#F97583">and</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> dict</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> has {</span><span style="color:#005CC5;--shiki-dark:#79B8FF">dict</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.get</span><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">!</span><span style="color:#24292E;--shiki-dark:#E1E4E8">r:</span><span style="color:#D73A49;--shiki-dark:#F97583">*^</span><span style="color:#005CC5;--shiki-dark:#79B8FF">30</span><span style="color:#24292E;--shiki-dark:#E1E4E8">} method.</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">>>></span><span style="color:#005CC5;--shiki-dark:#79B8FF"> print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{name}</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> was born in </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{country.state</span><span style="color:#D73A49;--shiki-dark:#F97583">!r:=20s</span><span style="color:#005CC5;--shiki-dark:#79B8FF">}</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> and dict has '</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">      '</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{dict.get</span><span style="color:#D73A49;--shiki-dark:#F97583">!s:</span><span style="color:#005CC5;--shiki-dark:#79B8FF">*^30}</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> method.'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format_map(Default(</span><span style="color:#E36209;--shiki-dark:#FFAB70">name</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'Guido'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">dct</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF">dict</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)))</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Guido was born </span><span style="color:#D73A49;--shiki-dark:#F97583">in</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {country.state</span><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">!</span><span style="color:#24292E;--shiki-dark:#E1E4E8">r:=</span><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">20s</span><span style="color:#24292E;--shiki-dark:#E1E4E8">} </span><span style="color:#D73A49;--shiki-dark:#F97583">and</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> dict</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> has {</span><span style="color:#005CC5;--shiki-dark:#79B8FF">dict</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.get</span><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">!</span><span style="color:#24292E;--shiki-dark:#E1E4E8">s:</span><span style="color:#D73A49;--shiki-dark:#F97583">*^</span><span style="color:#005CC5;--shiki-dark:#79B8FF">30</span><span style="color:#24292E;--shiki-dark:#E1E4E8">} method.</span></span></code></pre>
<p>Works! ;-)</p>
<p>I hope you must have learned something about string formatting in Python with the aforementioned method.</p>
<p>But is there any other way to do this?</p>
<h3 id="yes">Yes!</h3>
<h2 id="second-way">Second way:</h2>
<p>We can achieve the same thing as above using <a href="https://docs.python.org/2/library/string.html#string.Formatter"><code>Formatter</code></a> class from string module, the <a href="https://docs.python.org/2/library/string.html#string.Formatter.parse"><code>parse()</code></a> method of this class can be used to parse the format string. It returns an iterable that yields a tuple containing (literal_text, field_name, format_spec, conversion). We can use these fields to re-create our string.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">from</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> functools </span><span style="color:#D73A49;--shiki-dark:#F97583">import</span><span style="color:#E36209;--shiki-dark:#FFAB70"> reduce</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">from</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> operator </span><span style="color:#D73A49;--shiki-dark:#F97583">import</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> attrgetter</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">from</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> string </span><span style="color:#D73A49;--shiki-dark:#F97583">import</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> Formatter</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">def</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> get_field_value</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(field_name, mapping):</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    try</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        if</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '.'</span><span style="color:#D73A49;--shiki-dark:#F97583"> not</span><span style="color:#D73A49;--shiki-dark:#F97583"> in</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> field_name:</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">            return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> mapping[field_name], </span><span style="color:#005CC5;--shiki-dark:#79B8FF">True</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        else</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">            obj, attrs </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> field_name.split(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'.'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">            return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> attrgetter(attrs)(mapping[obj]), </span><span style="color:#005CC5;--shiki-dark:#79B8FF">True</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    except</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> Exception</span><span style="color:#D73A49;--shiki-dark:#F97583"> as</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> e:</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> field_name, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">False</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">def</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> str_format_map</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(format_string, mapping):</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    f </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> Formatter()</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    parsed </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> f.parse(format_string)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    output </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> []</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    for</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> literal_text, field_name, format_spec, conversion </span><span style="color:#D73A49;--shiki-dark:#F97583">in</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> parsed:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">        conversion </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '!'</span><span style="color:#D73A49;--shiki-dark:#F97583"> +</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> conversion </span><span style="color:#D73A49;--shiki-dark:#F97583">if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> conversion </span><span style="color:#D73A49;--shiki-dark:#F97583">is</span><span style="color:#D73A49;--shiki-dark:#F97583"> not</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> None</span><span style="color:#D73A49;--shiki-dark:#F97583"> else</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ''</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">        format_spec </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ':'</span><span style="color:#D73A49;--shiki-dark:#F97583"> +</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> format_spec </span><span style="color:#D73A49;--shiki-dark:#F97583">if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> format_spec </span><span style="color:#D73A49;--shiki-dark:#F97583">else</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ''</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">        if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> field_name </span><span style="color:#D73A49;--shiki-dark:#F97583">is</span><span style="color:#D73A49;--shiki-dark:#F97583"> not</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> None</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">            field_value, found </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> get_field_value(field_name, mapping)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">            if</span><span style="color:#D73A49;--shiki-dark:#F97583"> not</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> found:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">                text </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{{{}{}{}}}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format(field_value,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">                                           conversion,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">                                           format_spec)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">            else</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">                format_string </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{{{}{}}}</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.format(conversion, format_spec)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">                text </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> format_string.format(field_value)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">        output.append(literal_text </span><span style="color:#D73A49;--shiki-dark:#F97583">+</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> text)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">        text </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ''</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">    return</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ''</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.join(output)</span></span></code></pre>
<p>Demo:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">>>></span><span style="color:#24292E;--shiki-dark:#E1E4E8"> s </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{name}</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> was born in </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{country.state}</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> and dict has </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{dict.get</span><span style="color:#D73A49;--shiki-dark:#F97583">!r:</span><span style="color:#005CC5;--shiki-dark:#79B8FF">*^30}</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> method.'</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">>>></span><span style="color:#005CC5;--shiki-dark:#79B8FF"> print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(str_format_map(s, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">dict</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">dict</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF">dict</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">name</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"guido"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)))</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">guido was born </span><span style="color:#D73A49;--shiki-dark:#F97583">in</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {country.state} </span><span style="color:#D73A49;--shiki-dark:#F97583">and</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> dict</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> has </span><span style="color:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="color:#24292E;--shiki-dark:#E1E4E8">method </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'get'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> of </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'dict'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> objects</span><span style="color:#D73A49;--shiki-dark:#F97583">></span><span style="color:#24292E;--shiki-dark:#E1E4E8"> method.</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">>>></span><span style="color:#24292E;--shiki-dark:#E1E4E8"> s </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '</span><span style="color:#005CC5;--shiki-dark:#79B8FF">{name}</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> was born in </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{country.state</span><span style="color:#D73A49;--shiki-dark:#F97583">!r:=20s</span><span style="color:#005CC5;--shiki-dark:#79B8FF">}</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> and dict has </span><span style="color:#005CC5;--shiki-dark:#79B8FF">{dict.get</span><span style="color:#D73A49;--shiki-dark:#F97583">!s:</span><span style="color:#005CC5;--shiki-dark:#79B8FF">*^30}</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> method.'</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">>>></span><span style="color:#005CC5;--shiki-dark:#79B8FF"> print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(str_format_map(s, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">dict</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">dct</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#005CC5;--shiki-dark:#79B8FF">dict</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">name</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"guido"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)))</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">guido was born </span><span style="color:#D73A49;--shiki-dark:#F97583">in</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {country.state</span><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">!</span><span style="color:#24292E;--shiki-dark:#E1E4E8">r:=</span><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">20s</span><span style="color:#24292E;--shiki-dark:#E1E4E8">} </span><span style="color:#D73A49;--shiki-dark:#F97583">and</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> dict</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> has {</span><span style="color:#005CC5;--shiki-dark:#79B8FF">dict</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.get</span><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">!</span><span style="color:#24292E;--shiki-dark:#E1E4E8">s:</span><span style="color:#D73A49;--shiki-dark:#F97583">*^</span><span style="color:#005CC5;--shiki-dark:#79B8FF">30</span><span style="color:#24292E;--shiki-dark:#E1E4E8">} method.</span></span></code></pre>]]></content:encoded>
            <author>Ashwini Chaudhary</author>
            <category>python</category>
            <category>programming</category>
        </item>
    </channel>
</rss>