<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Python on PG Blog</title><link>https://pg-blogs.netlify.app/categories/python/</link><description>Recent content in Python on PG Blog</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Fri, 03 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://pg-blogs.netlify.app/categories/python/index.xml" rel="self" type="application/rss+xml"/><item><title>Avoiding ORM Traps and the N+1 Problem in Python</title><link>https://pg-blogs.netlify.app/posts/9-avoiding-orm-traps-and-n-plus-1-in-python/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate><guid>https://pg-blogs.netlify.app/posts/9-avoiding-orm-traps-and-n-plus-1-in-python/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Python&amp;rsquo;s ORMs — SQLAlchemy and the Django ORM — are a joy to write and dangerously easy to make slow. A loop over a queryset that reads one related field can silently fire hundreds of queries. It looks fine in development and falls over on real data.&lt;/p&gt;
&lt;p&gt;This post covers the ORM traps Python teams hit most, starting with the &lt;strong&gt;N+1 query problem&lt;/strong&gt;, in both SQLAlchemy and Django.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="the-n1-problem"&gt;The N+1 Problem&lt;/h2&gt;
&lt;p&gt;You load authors and print each author&amp;rsquo;s book count:&lt;/p&gt;</description></item><item><title>Building Agentic Workflows in Python</title><link>https://pg-blogs.netlify.app/posts/15-building-agentic-workflows-in-python/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate><guid>https://pg-blogs.netlify.app/posts/15-building-agentic-workflows-in-python/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;&amp;ldquo;Agent&amp;rdquo; has become the word for any program that calls an LLM more than once, which makes it a word worth being precise about. An agent, in the sense this post uses, is a loop: the model decides which tool to call next, your code executes it, and the result feeds back in — repeating until the model decides it&amp;rsquo;s done. That&amp;rsquo;s a genuinely different (and riskier) shape than a single request/response call.&lt;/p&gt;</description></item><item><title>Building Reliable LLM Applications in Python</title><link>https://pg-blogs.netlify.app/posts/10-building-reliable-llm-apps-in-python/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate><guid>https://pg-blogs.netlify.app/posts/10-building-reliable-llm-apps-in-python/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Calling an LLM API is easy. Building an application on top of one that is &lt;em&gt;reliable&lt;/em&gt; — that fails predictably, doesn&amp;rsquo;t hallucinate its way into wrong answers, and doesn&amp;rsquo;t surprise you with a bill — is a real engineering discipline.&lt;/p&gt;
&lt;p&gt;The core mindset shift: &lt;strong&gt;treat model output as a hypothesis to verify, not a fact to trust.&lt;/strong&gt; This post covers the practices that make Python LLM applications production-grade, using Anthropic&amp;rsquo;s Claude and the official &lt;code&gt;anthropic&lt;/code&gt; SDK.&lt;/p&gt;</description></item><item><title>Database Indexing and Query Optimization for Python Developers</title><link>https://pg-blogs.netlify.app/posts/19-database-indexing-and-query-optimization-for-python/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate><guid>https://pg-blogs.netlify.app/posts/19-database-indexing-and-query-optimization-for-python/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Fixing N+1 queries with &lt;code&gt;select_related&lt;/code&gt;/&lt;code&gt;prefetch_related&lt;/code&gt; or &lt;code&gt;selectinload&lt;/code&gt; (see the &lt;a href="https://pg-blogs.netlify.app/posts/9-avoiding-orm-traps-and-n-plus-1-in-python/"&gt;previous post&lt;/a&gt;) gets you down to a small, sane number of queries per request. The next bottleneck is what each query costs once the table has millions of rows — and that is almost always about indexing.&lt;/p&gt;
&lt;p&gt;An index turns &amp;ldquo;scan every row&amp;rdquo; into &amp;ldquo;look it up directly.&amp;rdquo; Skip it, and a query that&amp;rsquo;s instant in development takes seconds once real data volume shows up in production.&lt;/p&gt;</description></item><item><title>Designing for Change: Boundaries, Contracts, and Dependency Inversion in Python</title><link>https://pg-blogs.netlify.app/posts/13-designing-for-change-in-python/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate><guid>https://pg-blogs.netlify.app/posts/13-designing-for-change-in-python/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Python&amp;rsquo;s flexibility makes it easy to wire everything together directly — call the &lt;code&gt;requests&lt;/code&gt; library from inside your business logic, import the ORM model straight into your pricing rules, instantiate a third-party SDK client wherever it&amp;rsquo;s needed. It works, right up until the payment provider changes, or you want a fast unit test that doesn&amp;rsquo;t need a live database.&lt;/p&gt;
&lt;p&gt;Designing for change means drawing boundaries around the parts of the system likely to move — third-party services, storage, transport — so that a swap or a test double touches one small adapter instead of rippling through the codebase.&lt;/p&gt;</description></item><item><title>Error Handling Best Practices in Python</title><link>https://pg-blogs.netlify.app/posts/5-error-handling-best-practices-in-python/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate><guid>https://pg-blogs.netlify.app/posts/5-error-handling-best-practices-in-python/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Python&amp;rsquo;s error handling reads deceptively simply — &lt;code&gt;try&lt;/code&gt;, &lt;code&gt;except&lt;/code&gt;, done. But the difference between code that fails clearly and code that fails mysteriously comes down to a handful of habits: catching the &lt;em&gt;right&lt;/em&gt; exception, preserving the original cause, and knowing when &lt;em&gt;not&lt;/em&gt; to raise at all.&lt;/p&gt;
&lt;p&gt;This post covers the practices that keep Python failures debuggable in production.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="prefer-eafp-over-look-before-you-leap"&gt;Prefer EAFP over Look-Before-You-Leap&lt;/h2&gt;
&lt;p&gt;Python idiom favors &lt;strong&gt;EAFP&lt;/strong&gt; — &amp;ldquo;Easier to Ask Forgiveness than Permission.&amp;rdquo; Rather than checking whether an operation will succeed, attempt it and handle the failure:&lt;/p&gt;</description></item><item><title>Production-Grade Docker Images for Python</title><link>https://pg-blogs.netlify.app/posts/7-production-grade-docker-images-for-python/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate><guid>https://pg-blogs.netlify.app/posts/7-production-grade-docker-images-for-python/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;The typical first-draft Python &lt;code&gt;Dockerfile&lt;/code&gt; — &lt;code&gt;FROM python&lt;/code&gt;, &lt;code&gt;COPY . .&lt;/code&gt;, &lt;code&gt;pip install -r requirements.txt&lt;/code&gt; — produces an image that is close to a gigabyte, reinstalls every dependency whenever a single source file changes, and runs as root with a Python process that ignores &lt;code&gt;SIGTERM&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This post covers how to fix all of that: &lt;strong&gt;small images, cached dependency installs, a non-root runtime, and clean shutdowns.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="pick-the-right-base-image"&gt;Pick the Right Base Image&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;python:3.12-slim&lt;/code&gt; is the pragmatic default: a Debian-based image with Python but without the large build toolchain. Avoid the full &lt;code&gt;python:3.12&lt;/code&gt; (hundreds of MB of tools you don&amp;rsquo;t run) and be cautious with &lt;code&gt;alpine&lt;/code&gt; — its &lt;code&gt;musl&lt;/code&gt; libc frequently breaks or slows down packages with C extensions (NumPy, pandas, database drivers), forcing slow source builds.&lt;/p&gt;</description></item><item><title>Testing Best Practices in Python</title><link>https://pg-blogs.netlify.app/posts/17-testing-best-practices-in-python/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate><guid>https://pg-blogs.netlify.app/posts/17-testing-best-practices-in-python/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Python&amp;rsquo;s testing tools are lightweight enough that it&amp;rsquo;s easy to write a lot of tests without writing &lt;em&gt;good&lt;/em&gt; ones. A suite that mocks every collaborator, duplicates the same assertion ten times with different inputs pasted in by hand, or chases a coverage number will pass in CI and still miss real bugs.&lt;/p&gt;
&lt;p&gt;pytest gives you fixtures, &lt;code&gt;parametrize&lt;/code&gt;, and &lt;code&gt;monkeypatch&lt;/code&gt; — the tools that make it just as easy to write the &lt;em&gt;right&lt;/em&gt; tests as the wrong ones. This post covers how to use them well.&lt;/p&gt;</description></item></channel></rss>