<?xml version="1.0"?>
<rdf:RDF
	xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:foaf="http://xmlns.com/foaf/0.1/"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns="http://purl.org/rss/1.0/"
>
<channel rdf:about="http://planetpython.org/">
	<title>Planet Python</title>
	<link>http://planetpython.org/</link>
	<description>Planet Python - http://planetpython.org/</description>

	<items>
		<rdf:Seq>
			<rdf:li rdf:resource="https://realpython.com/python-news-june-2026/" />
			<rdf:li rdf:resource="https://death.andgravity.com/albumtitle" />
			<rdf:li rdf:resource="https://wingware.com/news/2026-06-08" />
			<rdf:li rdf:resource="tag:eli.thegreenplace.net,2025-12-17:/2025/plugins-case-study-mdbook-preprocessors/" />
			<rdf:li rdf:resource="https://lucumr.pocoo.org/2026/6/6/communities-of-not/" />
			<rdf:li rdf:resource="https://nuitka.net/posts/nuitka-release-41.html" />
			<rdf:li rdf:resource="https://bluesock.org/~willkg/blog/dev/bleach_6_4_0_final_release.html" />
			<rdf:li rdf:resource="https://realpython.com/podcasts/rpp/298/" />
			<rdf:li rdf:resource="https://www.europython-society.org/rss/6a1dd63d0dbef2000128f996" />
			<rdf:li rdf:resource="https://belderbos.dev/blog/htmx-hx-swap-oob-django/" />
			<rdf:li rdf:resource="https://sethmlarson.dev/is-the-donut-from-super-smash-bros-brawl-a-mister-donut?utm_campaign=rss" />
			<rdf:li rdf:resource="https://www.thepythoncodingstack.com/p/down-the-iterator-rabbit-hole-python" />
			<rdf:li rdf:resource="https://realpython.com/quizzes/python-keyboard-input/" />
			<rdf:li rdf:resource="tag:blogger.com,1999:blog-8520.post-7989552834750612525" />
			<rdf:li rdf:resource="https://blog.adarshd.dev/ai/posts/building-ai-agents-in-python/" />
			<rdf:li rdf:resource="https://coredispatch.xyz/editions/5" />
			<rdf:li rdf:resource="https://2026.pycon.ie/blog/cfp-deadline-moved-to-31-july/" />
			<rdf:li rdf:resource="https://belderbos.dev/blog/jochen-rust-cohort-beat-cpython/" />
			<rdf:li rdf:resource="https://realpython.com/github-copilot-code-review/" />
			<rdf:li rdf:resource="https://realpython.com/quizzes/github-copilot-code-review/" />
			<rdf:li rdf:resource="https://www.djangoproject.com/weblog/2026/jun/03/security-releases/" />
			<rdf:li rdf:resource="tag:www.pythonguis.com,2026-06-03:/faq/authentication-and-authorization-with-pyqt6-or-pyside6/" />
			<rdf:li rdf:resource="https://belderbos.dev/blog/python-mock-patch-verify-interception/" />
			<rdf:li rdf:resource="https://pycoders.com/issues/737" />
			<rdf:li rdf:resource="https://realpython.com/courses/structuring-your-python-script/" />
		</rdf:Seq>
	</items>
</channel>

<item rdf:about="https://realpython.com/python-news-june-2026/">
	<title>Real Python: Python 3.15 Hits Feature Freeze and Other News for June 2026</title>
	<link>https://realpython.com/python-news-june-2026/</link>
	<content:encoded>&lt;div&gt;&lt;p&gt;While the Northern Hemisphere warms up for summer, Python 3.15 went the other way with its &lt;strong&gt;beta 1 feature freeze&lt;/strong&gt; 🥶. Since May 7, the list of what will be included in the next release is final. That list includes a brand-new &lt;strong&gt;&lt;code&gt;sentinel&lt;/code&gt; built-in&lt;/strong&gt; that finally standardizes a pattern Python developers have been hand-rolling for decades.&lt;/p&gt;
&lt;p&gt;And while AI kept writing code, buggy or not, developers also directed it to &lt;em&gt;look&lt;/em&gt; for bugs in code that had been sitting untouched for years. The results were hundreds of bug fixes in Python’s C extensions and in Firefox. Meanwhile, in a quieter corner of the ecosystem, Pydantic forked &lt;code&gt;httpx&lt;/code&gt;, kicking off one of the more interesting governance stories of the year.&lt;/p&gt;
&lt;p&gt;Time to dig into the &lt;strong&gt;Python news&lt;/strong&gt; from the past month!&lt;/p&gt;
&lt;div class=&quot;alert alert-warning&quot;&gt;
&lt;p&gt;&lt;strong&gt;Join Now:&lt;/strong&gt; &lt;a href=&quot;https://realpython.com/bonus/newsletter/&quot; class=&quot;alert-link&quot;&gt;Click here to join the Real Python Newsletter&lt;/a&gt; and you’ll never miss another Python tutorial, course, or news update.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id=&quot;python-releases-and-pep-highlights&quot;&gt;Python Releases and PEP Highlights&lt;a class=&quot;headerlink&quot; href=&quot;https://realpython.com/atom.xml#python-releases-and-pep-highlights&quot; title=&quot;Permanent link&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The 3.15 release of &lt;a href=&quot;https://realpython.com/cpython-source-code-guide/&quot;&gt;CPython&lt;/a&gt; crossed from alpha into beta, which means its feature set is now frozen, and the &lt;a href=&quot;https://realpython.com/ref/glossary/python-steering-council/&quot; class=&quot;ref-link&quot;&gt;Steering Council&lt;/a&gt; cleared out a backlog of proposals before the gate closed. Two of those changes will touch the code you write every day.&lt;/p&gt;
&lt;h3 id=&quot;beta-1-marks-the-315-feature-freeze&quot;&gt;Beta 1 Marks the 3.15 Feature Freeze&lt;a class=&quot;headerlink&quot; href=&quot;https://realpython.com/atom.xml#beta-1-marks-the-315-feature-freeze&quot; title=&quot;Permanent link&quot;&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Last month, &lt;a href=&quot;https://realpython.com/python-news-may-2026/#python-3150-alpha-8-final-alpha-before-beta-freeze&quot;&gt;the eighth and final alpha&lt;/a&gt; rolled out as the runway to the beta phase. With &lt;a href=&quot;https://www.python.org/downloads/release/python-3150b1/&quot;&gt;Python 3.15.0b1&lt;/a&gt; on May 7 came the &lt;strong&gt;feature freeze&lt;/strong&gt;, which means that from here until the final release of 3.15, the core team works only on bug fixes and polishing.&lt;/p&gt;
&lt;p&gt;That makes the beta releases a good moment to step back and look at the headline features of 3.15, which are now locked:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Explicit lazy imports&lt;/strong&gt; (&lt;a href=&quot;https://realpython.com/python-news-december-2025/#pep-810-accepted-explicit-lazy-imports&quot;&gt;PEP 810&lt;/a&gt;) for faster startup&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;&lt;code&gt;frozendict&lt;/code&gt; built-in&lt;/strong&gt; (&lt;a href=&quot;https://realpython.com/python-news-march-2026/#pep-814-accepted-frozendict-joins-the-built-ins&quot;&gt;PEP 814&lt;/a&gt;) for immutable mappings&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;&lt;code&gt;sentinel&lt;/code&gt; built-in&lt;/strong&gt; (&lt;a href=&quot;https://peps.python.org/pep-0661/&quot;&gt;PEP 661&lt;/a&gt;), which you’ll dig into &lt;a href=&quot;https://realpython.com/atom.xml#a-built-in-sentinel-lands-in-python-315&quot;&gt;below&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unpacking in comprehensions&lt;/strong&gt; (&lt;a href=&quot;https://realpython.com/python-news-march-2026/#python-3150-alpha-6-comprehension-unpacking-and-more&quot;&gt;PEP 798&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UTF-8 as the default &lt;a href=&quot;https://realpython.com/python-encodings-guide/&quot;&gt;encoding&lt;/a&gt;&lt;/strong&gt; (&lt;a href=&quot;https://peps.python.org/pep-0686/&quot;&gt;PEP 686&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;A stable ABI for &lt;a href=&quot;https://realpython.com/python-news-may-2026/#pep-803-accepted-stable-abi-goes-free-threaded&quot;&gt;free-threaded builds&lt;/a&gt; (&lt;a href=&quot;https://peps.python.org/pep-0803/&quot;&gt;PEP 803&lt;/a&gt;), plus C-API modernization (PEPs &lt;a href=&quot;https://peps.python.org/pep-0820/&quot;&gt;820&lt;/a&gt; and &lt;a href=&quot;https://peps.python.org/pep-0793/&quot;&gt;793&lt;/a&gt;) that should make it easier to write C extensions that work across Python versions&lt;/li&gt;
&lt;li&gt;A new &lt;strong&gt;sampling profiler&lt;/strong&gt; in the &lt;a href=&quot;https://realpython.com/ref/glossary/standard-library/&quot; class=&quot;ref-link&quot;&gt;standard library&lt;/a&gt; (&lt;a href=&quot;https://peps.python.org/pep-0799/&quot;&gt;PEP 799&lt;/a&gt;) for low-overhead &lt;a href=&quot;https://realpython.com/python-profiling/&quot;&gt;profiling&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;a href=&quot;https://realpython.com/ref/glossary/jit-compiler/&quot; class=&quot;ref-link&quot;&gt;JIT compiler&lt;/a&gt; also gets faster, with the beta announcement citing an 8–9 percent geometric-mean improvement on x86-64 Linux. If you’ve been putting off testing your code against 3.15, then now is the time to get started! The &lt;a href=&quot;https://realpython.com/ref/glossary/api/&quot; class=&quot;ref-link&quot;&gt;API&lt;/a&gt; surface won’t shift under you anymore, and your feedback will help catch regressions before the release candidate phase.&lt;/p&gt;
&lt;div class=&quot;alert alert-primary&quot;&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Beta builds are for testing, not production. &lt;a href=&quot;https://realpython.com/python-pre-release/&quot;&gt;Install the pre-release version&lt;/a&gt;, run your test suite against 3.15, and report anything that breaks while there’s still time to fix it before the release candidate.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The first round of improvements already landed with &lt;a href=&quot;https://www.python.org/downloads/release/python-3150b2/&quot;&gt;beta 2&lt;/a&gt; on June 2, and the next big checkpoint is the &lt;a href=&quot;https://realpython.com/ref/glossary/release-candidate/&quot; class=&quot;ref-link&quot;&gt;release candidate&lt;/a&gt; phase on August 4, with the final release expected, as usual, this fall.&lt;/p&gt;
&lt;h3 id=&quot;a-built-in-sentinel-lands-in-python-315&quot;&gt;A Built-in &lt;code&gt;sentinel&lt;/code&gt; Lands in Python 3.15&lt;a class=&quot;headerlink&quot; href=&quot;https://realpython.com/atom.xml#a-built-in-sentinel-lands-in-python-315&quot; title=&quot;Permanent link&quot;&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Here’s the new feature that you’ll likely want to reach for. If you’ve ever needed to tell the difference between a caller passing &lt;code&gt;None&lt;/code&gt; and a caller passing nothing at all, then you’ve probably written something like this:&lt;/p&gt;

  &lt;div class=&quot;codeblock__header codeblock--blue&quot;&gt;
    &lt;span class=&quot;mr-2 noselect&quot;&gt;&lt;span class=&quot;sr-only&quot;&gt;Language: &lt;/span&gt;Python&lt;/span&gt;
    
    &lt;div class=&quot;noselect&quot;&gt;
      
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;codeblock__contents&quot;&gt;
    &lt;div class=&quot;highlight highlight--with-header&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;_MISSING&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_MISSING&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_MISSING&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# No value was provided&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;button class=&quot;codeblock__copy btn btn-outline-secondary border m-1 px-1 d-hover-only&quot; title=&quot;Copy to clipboard&quot;&gt;&lt;span class=&quot;icon baseline&quot;&gt;&lt;/span&gt;&lt;/button&gt;
    
  &lt;/div&gt;

&lt;p&gt;It works, but it has rough edges. The &lt;code&gt;repr()&lt;/code&gt; is an unhelpful &lt;code&gt;&amp;lt;object object at 0x7f...&amp;gt;&lt;/code&gt;, the marker can’t be used cleanly in type &lt;a href=&quot;https://realpython.com/ref/glossary/annotation/&quot; class=&quot;ref-link&quot;&gt;annotations&lt;/a&gt;, and its identity doesn’t survive copying or pickling. &lt;a href=&quot;https://peps.python.org/pep-0661/&quot;&gt;PEP 661&lt;/a&gt; replaces the idiom with a new &lt;a href=&quot;https://docs.python.org/3.15/library/functions.html#sentinel&quot;&gt;&lt;code&gt;sentinel&lt;/code&gt;&lt;/a&gt; built-in:&lt;/p&gt;

  &lt;div class=&quot;codeblock__header codeblock--blue&quot;&gt;
    &lt;span class=&quot;mr-2 noselect&quot;&gt;&lt;span class=&quot;sr-only&quot;&gt;Language: &lt;/span&gt;Python&lt;/span&gt;
    
    &lt;div class=&quot;noselect&quot;&gt;
      
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;codeblock__contents&quot;&gt;
    &lt;div class=&quot;highlight highlight--with-header&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;MISSING&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sentinel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;MISSING&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MISSING&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MISSING&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MISSING&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# No value was provided&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    
    &lt;button class=&quot;codeblock__copy btn btn-outline-secondary border m-1 px-1 d-hover-only&quot; title=&quot;Copy to clipboard&quot;&gt;&lt;span class=&quot;icon baseline&quot;&gt;&lt;/span&gt;&lt;/button&gt;
    
  &lt;/div&gt;

&lt;p&gt;The signature is &lt;code&gt;sentinel(name, /, *, repr=None)&lt;/code&gt;, and the result is a unique truthy object whose default &lt;code&gt;repr()&lt;/code&gt; is the name you gave it, so &lt;code&gt;MISSING&lt;/code&gt; shows up as &lt;code&gt;MISSING&lt;/code&gt; in &lt;a href=&quot;https://realpython.com/ref/glossary/traceback/&quot; class=&quot;ref-link&quot;&gt;tracebacks&lt;/a&gt; instead of a memory address.&lt;/p&gt;
&lt;div class=&quot;alert alert-primary&quot;&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Sentinels and &lt;code&gt;None&lt;/code&gt; solve related but different problems. If you’re still fuzzy on when &lt;code&gt;None&lt;/code&gt; is the right tool, then Real Python’s guide to &lt;a href=&quot;https://realpython.com/null-in-python/&quot;&gt;Python’s &lt;code&gt;None&lt;/code&gt;&lt;/a&gt; is worth revisiting.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Because the sentinel is its own type, you can drop it straight into annotations like &lt;code&gt;int | MISSING&lt;/code&gt; without reaching for &lt;code&gt;Literal&lt;/code&gt;. The &lt;a href=&quot;https://realpython.com/ref/glossary/pep/&quot; class=&quot;ref-link&quot;&gt;PEP&lt;/a&gt; was first submitted back in 2021, so it’s satisfying to see it cross the finish line.&lt;/p&gt;
&lt;h3 id=&quot;pep-829-graduates-from-draft-to-accepted&quot;&gt;PEP 829 Graduates From Draft to Accepted&lt;a class=&quot;headerlink&quot; href=&quot;https://realpython.com/atom.xml#pep-829-graduates-from-draft-to-accepted&quot; title=&quot;Permanent link&quot;&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://realpython.com/python-news-may-2026/#pep-829-draft-replacing-pth-files-for-package-startup&quot;&gt;Last month’s roundup&lt;/a&gt; featured PEP 829 while it was still a draft. It’s since been accepted for Python 3.15, so the change is now official.&lt;/p&gt;
&lt;p&gt;As a quick recap, &lt;code&gt;.pth&lt;/code&gt; files in your &lt;code&gt;site-packages&lt;/code&gt; directory can do two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Extend &lt;code&gt;sys.path&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Run arbitrary code through &lt;code&gt;import&lt;/code&gt; lines that Python feeds directly to &lt;a href=&quot;https://realpython.com/python-exec/&quot;&gt;&lt;code&gt;exec()&lt;/code&gt;&lt;/a&gt; at startup&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;&lt;h2&gt;&lt;a href=&quot;https://realpython.com/python-news-june-2026/?utm_source=realpython&amp;utm_medium=rss&quot;&gt;Read the full article at https://realpython.com/python-news-june-2026/ »&lt;/a&gt;&lt;/h2&gt;
        &lt;hr /&gt;
        &lt;p&gt;&lt;em&gt;[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short &amp;amp; sweet Python Trick delivered to your inbox every couple of days. &lt;a href=&quot;https://realpython.com/python-tricks/?utm_source=realpython&amp;utm_medium=rss&amp;utm_campaign=footer&quot;&gt;&amp;gt;&amp;gt; Click here to learn more and see examples&lt;/a&gt; ]&lt;/em&gt;&lt;/p&gt;</content:encoded>
	<dc:date>2026-06-08T14:00:00+00:00</dc:date>
</item>
<item rdf:about="https://death.andgravity.com/albumtitle">
	<title>death and gravity: Ordered key sharding in DynamoDB</title>
	<link>https://death.andgravity.com/albumtitle</link>
	<content:encoded>&lt;p&gt;So, you want to keep a &lt;strong&gt;sorted index&lt;/strong&gt; in DynamoDB,
but for whatever reason
– usually &lt;a class=&quot;internal&quot; href=&quot;https://death.andgravity.com/dynamodb-model#partition-throughput&quot;&gt;throughput-related&lt;/a&gt; –
it &lt;strong&gt;won't fit on a single partition&lt;/strong&gt;. What do you do?&lt;/p&gt;
&lt;p&gt;Today, we look at the available solutions,
do the math, and find out which is best.&lt;/p&gt;

&lt;p class=&quot;admonition-title&quot;&gt;Tip&lt;/p&gt;
&lt;p&gt;This worked example is part of my &lt;a class=&quot;internal&quot; href=&quot;https://death.andgravity.com/dynamodb&quot;&gt;DynamoDB crash course&lt;/a&gt; series.&lt;/p&gt;


Contents
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://death.andgravity.com/albumtitle#requirements&quot;&gt;Requirements&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://death.andgravity.com/albumtitle#a-sparse-index-is-almost-enough&quot;&gt;A sparse index is almost enough&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://death.andgravity.com/albumtitle#but-scan-results-are-not-ordered&quot;&gt;But scan results are not ordered&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://death.andgravity.com/albumtitle#but-a-single-partition-key-causes-throttling&quot;&gt;But a single partition key causes throttling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://death.andgravity.com/albumtitle#but-random-suffixes-are-random&quot;&gt;But random suffixes are random&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://death.andgravity.com/albumtitle#but-hash-suffixes-are-not-ordered&quot;&gt;But hash suffixes are not ordered&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://death.andgravity.com/albumtitle#but-there-are-a-lot-of-first-characters&quot;&gt;But there are a lot of first characters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://death.andgravity.com/albumtitle#but-some-first-bytes-need-multiple-shards&quot;&gt;But some first bytes need multiple shards&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://death.andgravity.com/albumtitle#but-tries-and-prefix-ranges-are-complicated&quot;&gt;But tries and prefix ranges are complicated&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://death.andgravity.com/albumtitle#but-the-prefix-distribution-can-change&quot;&gt;But the prefix distribution can change&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;requirements&quot;&gt;Requirements&lt;span class=&quot;headerlink&quot;&gt;&amp;nbsp;&lt;a href=&quot;https://death.andgravity.com/albumtitle#requirements&quot; title=&quot;permalink&quot;&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Say you're using &lt;a class=&quot;internal&quot; href=&quot;https://death.andgravity.com/dynamodb-patterns#single-table-design&quot;&gt;single table design&lt;/a&gt;
with a table of artists, albums, and songs.&lt;sup class=&quot;footnote-ref&quot; id=&quot;fnref-1&quot;&gt;&lt;a href=&quot;https://death.andgravity.com/albumtitle#fn-1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;You keep an artist's items in a single collection
(aka same partition key),
and use sort keys &lt;code&gt;artist&lt;/code&gt;, &lt;code&gt;album#{Album}&lt;/code&gt;, and &lt;code&gt;song#{Album}#{Song}&lt;/code&gt;,
depending on their type:&lt;/p&gt;
&lt;div class=&quot;highlight code-container&quot;&gt;&lt;pre class=&quot;code&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# table Music (partition key: Artist, sort key: sk)&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;Solar Fields&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;!btree&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'album#Leaving&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Home'&lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt; Album&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;Leaving Home&lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'artist'&lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'song#Leaving&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Home#Air&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Song'&lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'song#Leaving&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Home#Monogram'&lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To list albums without doing a full table scan,
you need a &lt;a class=&quot;internal&quot; href=&quot;https://death.andgravity.com/dynamodb-model#global-secondary-indexes&quot;&gt;global secondary index&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let's come up with some reasonable requirements; the &lt;abbr title=&quot;Global Secondary Index&quot;&gt;GSI&lt;/abbr&gt; should support:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;items up 500 bytes (we project additional attributes besides the keys)&lt;/li&gt;
&lt;li&gt;10,000 queries/second, max 100 items/query, &lt;strong&gt;sorted alphabetically&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;em&gt;list all albums&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;list albums by title&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;10,000 writes/second (to avoid &lt;a class=&quot;external&quot; href=&quot;https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/gsi-throttling.html&quot;&gt;write throttling&lt;/a&gt; during imports)&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;a-sparse-index-is-almost-enough&quot;&gt;A sparse index is almost enough&lt;span class=&quot;headerlink&quot;&gt;&amp;nbsp;&lt;a href=&quot;https://death.andgravity.com/albumtitle#a-sparse-index-is-almost-enough&quot; title=&quot;permalink&quot;&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;One way to do it is to use a &lt;em&gt;dedicated&lt;/em&gt;
&lt;a class=&quot;internal&quot; href=&quot;https://death.andgravity.com/dynamodb-patterns#sparse-indexes&quot;&gt;sparse index&lt;/a&gt;,
taking advantage of the fact that
items with missing index keys don't appear in the index.&lt;/p&gt;
&lt;p&gt;If only albums have an &lt;em&gt;Album&lt;/em&gt; attribute, we just create a new &lt;abbr title=&quot;Global Secondary Index&quot;&gt;GSI&lt;/abbr&gt;:&lt;/p&gt;
&lt;div class=&quot;highlight code-container&quot;&gt;&lt;pre class=&quot;code&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# GSI Albums (partition key: Album, sort key: Artist)&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;Leaving Home&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;!btree&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;International Pony&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt; sk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'album#Leaving&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Home'&lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;Solar Fields&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt; sk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'album#Leaving&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Home'&lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;Heavy Migration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;!btree&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;Dday One&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt; sk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'album#Heavy&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Migration'&lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If songs have an &lt;em&gt;Album&lt;/em&gt; too,
we add a dedicated &lt;em&gt;AlbumsPK&lt;/em&gt; attribute instead.&lt;/p&gt;
&lt;p&gt;In many ways, this is the ideal solution.
To &lt;em&gt;list all albums&lt;/em&gt;, we scan the index.
To &lt;em&gt;list albums by title&lt;/em&gt;, we query an index partition key.
We have lots of unique partition keys
with items spread pretty evenly across them,
which should prevent throttling.&lt;/p&gt;
&lt;h2 id=&quot;but-scan-results-are-not-ordered&quot;&gt;But scan results are not ordered&lt;span class=&quot;headerlink&quot;&gt;&amp;nbsp;&lt;a href=&quot;https://death.andgravity.com/albumtitle#but-scan-results-are-not-ordered&quot; title=&quot;permalink&quot;&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;...except scan results are not ordered,
so we're missing the &lt;em&gt;sorted alphabetically&lt;/em&gt; part.&lt;/p&gt;
&lt;p&gt;What &lt;em&gt;is&lt;/em&gt; ordered are sort keys,
so we can use a single index collection instead:&lt;/p&gt;
&lt;div class=&quot;highlight code-container&quot;&gt;&lt;pre class=&quot;code&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# GSI GSI1 (partition key: gsi1pk, sort key: gsi1sk)&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;'albums'&lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;!btree&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;Heavy Migration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt; Artist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;Dday One&lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt; sk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'album#Heavy&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Migration'&lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;Leaving Home&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt; Artist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;Solar Fields&lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt; sk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'album#Leaving&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Home'&lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;Leaving Home&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt; Artist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;International Pony&lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt; sk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'album#Leaving&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Home'&lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is also seemingly ideal.
To &lt;em&gt;list all albums&lt;/em&gt;, we query the entire index partition key.
To &lt;em&gt;list albums by title&lt;/em&gt;, we use a sort key.
The results are sorted as required,
and there's no limit on the number of items in a collection.&lt;/p&gt;
&lt;h2 id=&quot;but-a-single-partition-key-causes-throttling&quot;&gt;But a single partition key causes throttling&lt;span class=&quot;headerlink&quot;&gt;&amp;nbsp;&lt;a href=&quot;https://death.andgravity.com/albumtitle#but-a-single-partition-key-causes-throttling&quot; title=&quot;permalink&quot;&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;However, there are
&lt;a class=&quot;internal&quot; href=&quot;https://death.andgravity.com/dynamodb-model#partition-throughput&quot;&gt;per-partition limits&lt;/a&gt; of
24 MB/s for reads and
1 MB/s for writes.&lt;/p&gt;
&lt;p&gt;Let's see how they compare to our requirements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;reads: 500 bytes/item * 10k queries/s * 100 items/query = 500 MB/s (&lt;strong&gt;~21x&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;writes: 500 bytes/item * 10k items/s = 5 MB/s (&lt;strong&gt;5x&lt;/strong&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Uh-oh, turns out we need &lt;strong&gt;21 times&lt;/strong&gt; the throughput one partition can deliver.&lt;/p&gt;
&lt;p&gt;One way to spread the load is
&lt;a class=&quot;internal&quot; href=&quot;https://death.andgravity.com/dynamodb-patterns#partition-key-sharding&quot;&gt;sharding&lt;/a&gt;,
using multiple synthetic partition keys of the form &lt;code&gt;album#{shard_id}&lt;/code&gt;.
A common option for the shard id is a &lt;strong&gt;random number&lt;/strong&gt; from a known range,
e.g. &lt;code&gt;album#{randrange(21)}&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;highlight code-container&quot;&gt;&lt;pre class=&quot;code&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# GSI GSI1 (partition key: gsi1pk, sort key: gsi1sk)&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;'album#1'&lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;!btree&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;Leaving Home&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt; Artist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;Solar Fields&lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;'album#12'&lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;!btree&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;Heavy Migration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt; Artist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;Dday One&lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;'album#20'&lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;!btree&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;Leaving Home&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt; Artist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;International Pony&lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To &lt;em&gt;list all albums&lt;/em&gt;, query each shard in turn:&lt;/p&gt;
&lt;div class=&quot;highlight code-container&quot;&gt;&lt;pre class=&quot;code&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shard&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;21&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dynamodb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;album#&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shard&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;but-random-suffixes-are-random&quot;&gt;But random suffixes are random&lt;span class=&quot;headerlink&quot;&gt;&amp;nbsp;&lt;a href=&quot;https://death.andgravity.com/albumtitle#but-random-suffixes-are-random&quot; title=&quot;permalink&quot;&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;There's a problem, though –
with random shard ids we can't easily &lt;em&gt;list albums by title&lt;/em&gt;,
since albums with the same title may end up on any shard.&lt;/p&gt;
&lt;p&gt;A better option is to &lt;strong&gt;calculate&lt;/strong&gt; the shard id
from the album title using a hash function:&lt;/p&gt;
&lt;div class=&quot;highlight code-container&quot;&gt;&lt;pre class=&quot;code&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;from_bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sha256&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;digest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;album_shard_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;album_title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;album_title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;21&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;highlight code-container&quot;&gt;&lt;pre class=&quot;code&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# GSI GSI1 (partition key: gsi1pk, sort key: gsi1sk)&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;'album#6'&lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;!btree&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;Leaving Home&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt; Artist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;Solar Fields&lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;Leaving Home&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt; Artist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;International Pony&lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;'album#8'&lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;!btree&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;Heavy Migration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt; Artist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;Dday One&lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p p-Indicator&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To &lt;em&gt;list albums by title&lt;/em&gt;:&lt;/p&gt;
&lt;div class=&quot;highlight code-container&quot;&gt;&lt;pre class=&quot;code&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;dynamodb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;album#&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;album_shard_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;album_title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sk&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;album_title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'GSI1'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;but-hash-suffixes-are-not-ordered&quot;&gt;But hash suffixes are not ordered&lt;span class=&quot;headerlink&quot;&gt;&amp;nbsp;&lt;a href=&quot;https://death.andgravity.com/albumtitle#but-hash-suffixes-are-not-ordered&quot; title=&quot;permalink&quot;&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;That takes care of throughput,
but now results aren't &lt;em&gt;sorted alphabetically&lt;/em&gt; anymore.
We can sort items within each shard using the sort key,
but they are spread uniformly across shards,
and there's no order &lt;em&gt;between shards&lt;/em&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Maybe we could use the &lt;strong&gt;first letter&lt;/strong&gt; as shard id instead?&lt;/p&gt;
&lt;p&gt;Of course, we have to account for some first letters
being &lt;a class=&quot;external&quot; href=&quot;https://en.wikipedia.org/wiki/Letter_frequency#Relative_frequencies_of_the_first_letters_of_a_word_in_the_English_language&quot;&gt;more frequent&lt;/a&gt; than others.
In this case, we can approximate the actual distribution
by using &lt;a class=&quot;external&quot; href=&quot;https://musicbrainz.org/doc/MusicBrainz_Database#Download&quot;&gt;MusicBrainz data&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There are 5.5 million albums:&lt;/p&gt;
&lt;!-- (incidentally this gives us an idea of how many albums we can have, although we should figured this out already): --&gt;

&lt;div class=&quot;highlight code-container&quot;&gt;&lt;pre class=&quot;code&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class=&quot;gp&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;polars&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;pl&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;titles&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;read_csv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;... &lt;/span&gt;    &lt;span class=&quot;s1&quot;&gt;'mbdump/release'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;... &lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;has_header&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;... &lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;separator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\t&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;... &lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;quote_char&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;... &lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;columns&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;... &lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;new_columns&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'title'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;... &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[:,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;titles&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt;5535986&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;...but only 3.3 million unique titles,
partly due to different releases of the same album,
partly due to some titles being more popular
– a few of them, &lt;em&gt;really&lt;/em&gt; popular:&lt;/p&gt;
&lt;div class=&quot;highlight code-container&quot;&gt;&lt;pre class=&quot;code&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class=&quot;gp&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;titles&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value_counts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt;shape: (3_370_505, 2)&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; title            count&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; Greatest Hits    4638&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; Demo             3140&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; …                …&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; Salsa salsa      1&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; Glamour: Deluxe  1&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;titles&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unique_counts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;quantile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;.9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;.99&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;.999&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;.9999&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt;[2.0, 11.0, 58.0, 221.0, 4638.0]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let's look at first characters:&lt;/p&gt;
&lt;div class=&quot;highlight code-container&quot;&gt;&lt;pre class=&quot;code&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class=&quot;gp&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;normalized&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;titles&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to_lowercase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;normalize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'NFKD'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;normalized&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value_counts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt;shape: (5_402, 2)&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; title  count&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; t      584760&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; s      509065&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; a      317513&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; l      298757&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; …      …&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; 🫀     1&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; 🫂     1&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; 🫧     1&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; 󠀼       1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;but-there-are-a-lot-of-first-characters&quot;&gt;But there are a lot of first characters&lt;span class=&quot;headerlink&quot;&gt;&amp;nbsp;&lt;a href=&quot;https://death.andgravity.com/albumtitle#but-there-are-a-lot-of-first-characters&quot; title=&quot;permalink&quot;&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;5402?!
Indeed, there's more to Unicode than the Latin alphabet:&lt;/p&gt;
&lt;div class=&quot;highlight code-container&quot;&gt;&lt;pre class=&quot;code&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class=&quot;gp&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;normalized&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unique&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sample&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt;['學', '舒', 'і', '进', '੦', '潮', '向', '妳', '陳', '🍅']&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And it's actually worse than that –
there are five thousand characters &lt;em&gt;in our dataset&lt;/em&gt;,
but there are hundreds of thousands of &lt;em&gt;possible&lt;/em&gt; Unicode characters.&lt;/p&gt;
&lt;p&gt;This is not a problem when adding the albums,
but it is a problem when listing them,
since we need to enumerate all the shards in a reasonable amount of time
(and most shards being empty doesn't help, either).&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;As a very bad compromise,
we could use the &lt;strong&gt;first byte&lt;/strong&gt; of the UTF-8 encoding instead;
this caps the number of shard ids at 256,
and at least Latin titles would be sorted
(I did say it's a bad compromise).
There:&lt;/p&gt;
&lt;div class=&quot;highlight code-container&quot;&gt;&lt;pre class=&quot;code&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class=&quot;gp&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;firstbytes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;normalized&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;map_elements&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;firstbytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value_counts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt;shape: (136, 2)&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; title    count&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; b&amp;quot;t&amp;quot;     584760&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; b&amp;quot;s&amp;quot;     509065&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; b&amp;quot;a&amp;quot;     317513&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; b&amp;quot;l&amp;quot;     298757&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; …        …&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; b&amp;quot;\xd4&amp;quot;  2&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; b&amp;quot;U&amp;quot;     1&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; b&amp;quot;\xee&amp;quot;  1&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; b&amp;quot;\xf3&amp;quot;  1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;but-some-first-bytes-need-multiple-shards&quot;&gt;But some first bytes need multiple shards&lt;span class=&quot;headerlink&quot;&gt;&amp;nbsp;&lt;a href=&quot;https://death.andgravity.com/albumtitle#but-some-first-bytes-need-multiple-shards&quot; title=&quot;permalink&quot;&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;We knew the first byte distribution would be skewed,
but some of them don't even fit on a single shard
(and it gets worse the more shards we need):&lt;/p&gt;
&lt;div class=&quot;highlight code-container&quot;&gt;&lt;pre class=&quot;code&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class=&quot;gp&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shard_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;21&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;firstbytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value_counts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;with_columns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;... &lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;pl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;col&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'count'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;firstbytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shard_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;... &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;head&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt;shape: (5, 2)&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; title  count&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; b&amp;quot;t&amp;quot;   2.218206&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; b&amp;quot;s&amp;quot;   1.931068&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; b&amp;quot;a&amp;quot;   1.204442&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; b&amp;quot;l&amp;quot;   1.133294&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; b&amp;quot;b&amp;quot;   1.106949&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We're back to where we started:
how do we sort between shards with the same prefix?&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;We don't – we find &lt;strong&gt;longer prefixes&lt;/strong&gt; that fit in one shard.&lt;/p&gt;
&lt;p&gt;That sounds like the perfect job for a &lt;a class=&quot;external&quot; href=&quot;https://en.wikipedia.org/wiki/Trie&quot;&gt;trie&lt;/a&gt; (aka prefix tree).
This would also allow us to switch back to characters,
and merge small prefixes into ranges
until each range fits one shard.
But that's complicated,
and as often the case,
there must be a better way.&lt;sup class=&quot;footnote-ref&quot; id=&quot;fnref-2&quot;&gt;&lt;a href=&quot;https://death.andgravity.com/albumtitle#fn-2&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h2 id=&quot;but-tries-and-prefix-ranges-are-complicated&quot;&gt;But tries and prefix ranges are complicated&lt;span class=&quot;headerlink&quot;&gt;&amp;nbsp;&lt;a href=&quot;https://death.andgravity.com/albumtitle#but-tries-and-prefix-ranges-are-complicated&quot; title=&quot;permalink&quot;&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;We're looking for contiguous ranges,
each of a certain size.
Tries are good for finding the &lt;em&gt;shortest&lt;/em&gt; prefix,
but we don't really care about prefix length.&lt;/p&gt;
&lt;p&gt;Why not just split the sorted titles into N &lt;strong&gt;equal ranges&lt;/strong&gt; instead?
This takes care of the uneven distribution:&lt;/p&gt;
&lt;div class=&quot;highlight code-container&quot;&gt;&lt;pre class=&quot;code&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class=&quot;gp&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boundaries&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;normalized&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gather_every&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boundaries&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value_counts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt;shape: (2_103, 2)&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; title     count&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; the       161&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; live      23&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; …         …&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; 風吹けは  1&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; 魔法少女  1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;...provided a long enough prefix:&lt;/p&gt;
&lt;div class=&quot;highlight code-container&quot;&gt;&lt;pre class=&quot;code&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class=&quot;gp&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boundaries&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;normalized&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gather_every&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boundaries&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value_counts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;col&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'count'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt;shape: (3, 2)&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; title             count&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; greatest hits     3&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; demo              2&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; the very best of  2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;...almost there:&lt;/p&gt;
&lt;div class=&quot;highlight code-container&quot;&gt;&lt;pre class=&quot;code&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class=&quot;gp&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boundaries&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;normalized&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gather_every&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boundaries&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value_counts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;col&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'count'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt;shape: (2, 2)&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; title          count&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; greatest hits  3&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt; demo           2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This highlights another problem –
if the shard size is too small,
there may be more than a shard's worth of albums with identical titles;
we can fix this by using another, random suffix
(ordering doesn't matter anymore, since they have the same title).&lt;/p&gt;
&lt;p&gt;Thankfully, our shards are huge, so it's not an issue:&lt;/p&gt;
&lt;div class=&quot;highlight code-container&quot;&gt;&lt;pre class=&quot;code&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class=&quot;gp&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shard_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;math&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ceil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;normalized&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shard_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shard_size&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt;263619&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boundaries&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;normalized&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gather_every&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shard_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To use this,
save the list of boundaries in code,
and find the index of the biggest boundary smaller than a given album title:&lt;/p&gt;
&lt;div class=&quot;highlight code-container&quot;&gt;&lt;pre class=&quot;code&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;bisect&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;unicodedata&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;ALBUM_TITLE_BOUNDARIES&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# replaced with the smallest possible string&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;'agartha'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;'barstow / crazy'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;'can you feel it'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;'cyan rot'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;'dreams take over eve'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;'feud semiotics (rb. '&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;'grave poetry'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;'i live'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;'kannaval'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;'live in florence'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&amp;quot;mir ist's gleich / i&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;'notice'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;'platforms ep'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;'rituals'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;'skylten'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;'surtr / absorbed'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;'the human touch'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;'tonttujen jouluyö: '&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;'walking away'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;'голос'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;album_shard_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;album_title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;normalized&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;unicodedata&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;normalize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'NFKD'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;album_title&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lower&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bisect&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bisect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ALBUM_TITLE_BOUNDARIES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;normalized&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;highlight code-container&quot;&gt;&lt;pre class=&quot;code&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class=&quot;gp&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;album_shard_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'2 Pie Island'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt;0&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;album_shard_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'Heavy Migration'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt;7&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;album_shard_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'Leaving Home'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt;9&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;album_shard_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'Space Cadet'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt;15&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;but-the-prefix-distribution-can-change&quot;&gt;But the prefix distribution can change&lt;span class=&quot;headerlink&quot;&gt;&amp;nbsp;&lt;a href=&quot;https://death.andgravity.com/albumtitle#but-the-prefix-distribution-can-change&quot; title=&quot;permalink&quot;&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;We were lucky to have data on the prefix distribution,
but that's not always the case,
and even if it is, the distribution can change over time.&lt;/p&gt;
&lt;p&gt;For example, the last of the 21 shards above
starts in the Cyrillic &lt;a class=&quot;external&quot; href=&quot;https://en.wikipedia.org/wiki/Unicode_block#List_of_blocks&quot;&gt;Unicode block&lt;/a&gt;,
which means most existing scripts go into a single shard.
What if we import 1 million Chinese and Japanese albums at some point?&lt;/p&gt;
&lt;p&gt;One way to deal with this is to give more weight to known gaps in the data.
Another is to have more shards from the start to account for unknown gaps –
210 shards instead of 21 sounds pretty reasonable.&lt;/p&gt;
&lt;p&gt;If all else fails,
you can move to a new index with more shards,
but that comes with its own &lt;a class=&quot;internal&quot; href=&quot;https://death.andgravity.com/dynamodb-patterns#more-shards&quot;&gt;complications&lt;/a&gt;,
so it's best to get it roughly right from the start.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Anyway, that's it for now.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Learned something new today?&lt;/strong&gt; Share it with others, it really helps! &lt;span class=&quot;text-large&quot;&gt;
&lt;span class=&quot;share-icons&quot;&gt;
&lt;a class=&quot;share-icon pycoders color&quot; href=&quot;https://pycoders.com/submissions&quot; target=&quot;_blank&quot;&gt;PyCoder's Weekly&lt;/a&gt;
&lt;a class=&quot;share-icon hacker-news color&quot; href=&quot;https://news.ycombinator.%63%6f%6d/submitlink?u=https%3A//death.andgravity.com/albumtitle&amp;t=Ordered%20key%20sharding%20in%20DynamoDB&quot;&gt;HN&lt;/a&gt;
&lt;a class=&quot;share-icon bluesky color&quot; href=&quot;https://bsky.%61%70%70/intent/compose?text=Ordered%20key%20sharding%20in%20DynamoDB%20https%3A//death.andgravity.com/albumtitle&quot;&gt;Bluesky&lt;/a&gt;
&lt;!--
&lt;a
    class=&quot;share-icon reddit color&quot;
    href=&quot;https://www.reddit.%63%6f%6d/%73%75%62%6d%69%74?url=https%3A//death.andgravity.com/albumtitle&amp;amp;title=Ordered%20key%20sharding%20in%20DynamoDB&quot;
&gt;Reddit&lt;/a&gt;
--&gt;
&lt;a class=&quot;share-icon linkedin color&quot; href=&quot;https://www.linkedin.%63%6f%6d/sharing/share-offsite/?url=https%3A//death.andgravity.com/albumtitle&quot;&gt;linkedin&lt;/a&gt;
&lt;a class=&quot;share-icon twitter color&quot; href=&quot;https://twitter.%63%6f%6d/%73%68%61%72%65?text=Ordered%20key%20sharding%20in%20DynamoDB&amp;url=https%3A//death.andgravity.com/albumtitle&amp;via=_andgravity&quot;&gt;Twitter&lt;/a&gt;
&lt;/span&gt;
&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;b&gt;
Want to know when new articles come out?

&lt;a href=&quot;https://death.andgravity.com/albumtitle#embedded-subscribe-form&quot;&gt;Subscribe here&lt;/a&gt;
to get new stuff straight to your inbox!

&lt;/b&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li id=&quot;fn-1&quot;&gt;&lt;p&gt;This is a simplified example;
as the &lt;a class=&quot;external&quot; href=&quot;https://musicbrainz.org/doc/MusicBrainz_Database/Schema&quot;&gt;MusicBrainz database&lt;/a&gt; shows,
the schema for this kind of thing would be way more complicated in practice. &lt;a href=&quot;https://death.andgravity.com/albumtitle#fnref-1&quot; class=&quot;footnote&quot;&gt;&lt;sup&gt;[return]&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li id=&quot;fn-2&quot;&gt;&lt;p&gt;You're welcome to try, though,
especially if you're preparing for an interview. &lt;a href=&quot;https://death.andgravity.com/albumtitle#fnref-2&quot; class=&quot;footnote&quot;&gt;&lt;sup&gt;[return]&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;</content:encoded>
	<dc:date>2026-06-08T13:43:32+00:00</dc:date>
</item>
<item rdf:about="https://wingware.com/news/2026-06-08">
	<title>Wingware: Wing Python IDE 12 Early Access - June 8, 2026</title>
	<link>https://wingware.com/news/2026-06-08</link>
	<content:encoded>&lt;p&gt;Wing 12 is now available as an early access release that focuses on AI agent
driven development. Wing 12 introduces deep integration with Claude Code,
including a dedicated &lt;tt class=&quot;literal&quot;&gt;&lt;span class=&quot;pre&quot;&gt;Claude&lt;/span&gt; &lt;span class=&quot;pre&quot;&gt;Code&lt;/span&gt;&lt;/tt&gt; tool, a new &lt;tt class=&quot;literal&quot;&gt;&lt;span class=&quot;pre&quot;&gt;Tasks&lt;/span&gt;&lt;/tt&gt; tool for
planning, executing, and reviewing AI agent work, and a set of MCP servers
that allow agents to work more efficiently by giving them access to Wing's
source code analysis, unit testing, and debugger features.&lt;/p&gt;
&lt;img src=&quot;https://wingware.com/images/screenshots/wing12-screenshot.png&quot; alt=&quot;Wing 12 Screen Shot&quot; class=&quot;eye-candy-image unfloat-at-900&quot; width=&quot;650px&quot; /&gt;&lt;p&gt;For those using Claude Code, Wing 12 broadens the focus from code-centric
direct development to also include managing multiple concurrent AI agents. Of
course all of Wing's classic IDE features are still available -- the powerful
debugger, deep code analysis and warnings, full-featured editor, project &amp;amp;
package management, and much more.&lt;/p&gt;
&lt;p&gt;Wing 12 also adds true pseudo-terminal support to &lt;tt class=&quot;literal&quot;&gt;&lt;span class=&quot;pre&quot;&gt;OS&lt;/span&gt; &lt;span class=&quot;pre&quot;&gt;Commands&lt;/span&gt;&lt;/tt&gt; and &lt;tt class=&quot;literal&quot;&gt;&lt;span class=&quot;pre&quot;&gt;Debug&lt;/span&gt;
&lt;span class=&quot;pre&quot;&gt;I/O&lt;/span&gt;&lt;/tt&gt;, reenvisions the &lt;tt class=&quot;literal&quot;&gt;&lt;span class=&quot;pre&quot;&gt;OS&lt;/span&gt; &lt;span class=&quot;pre&quot;&gt;Commands&lt;/span&gt;&lt;/tt&gt; tool so that commands can be dragged
to tool or editor splits, reorganizes the &lt;tt class=&quot;literal&quot;&gt;&lt;span class=&quot;pre&quot;&gt;Tools&lt;/span&gt;&lt;/tt&gt; menu, adds search and
back/forward navigation to the &lt;tt class=&quot;literal&quot;&gt;&lt;span class=&quot;pre&quot;&gt;Preferences&lt;/span&gt;&lt;/tt&gt; dialog, supports automatic
test discovery, speeds up detection of externally modified files, and makes
many other improvements.&lt;/p&gt;
&lt;p&gt;For more information, see the &lt;a class=&quot;reference&quot; href=&quot;https://wingware.com/wingide/early&quot;&gt;Wingware Early Access Program&lt;/a&gt;. Anyone can participate just by
downloading the release.&lt;/p&gt;
&lt;p&gt;If you have questions, please don't hesitate to contact us at &lt;a class=&quot;reference&quot; href=&quot;mailto:support@wingware.com&quot;&gt;support&amp;#64;wingware.com&lt;/a&gt;.&lt;/p&gt;</content:encoded>
	<dc:date>2026-06-08T01:00:00+00:00</dc:date>
</item>
<item rdf:about="https://eli.thegreenplace.net/2025/plugins-case-study-mdbook-preprocessors/">
	<title>Eli Bendersky: Plugins case study: mdBook preprocessors</title>
	<link>https://eli.thegreenplace.net/2025/plugins-case-study-mdbook-preprocessors/</link>
	<content:encoded>&lt;p&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://rust-lang.github.io/mdBook/index.html&quot;&gt;mdBook&lt;/a&gt; is a tool for easily
creating books out of Markdown files. It's very popular in the Rust ecosystem,
where it's used (among other things) to publish &lt;a class=&quot;reference external&quot; href=&quot;https://doc.rust-lang.org/book/&quot;&gt;the official Rust book&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;mdBook has a simple yet effective plugin mechanism that can be used to modify
the book output in arbitrary ways, using any programming language or tool. This
post describes the mechanism and how it aligns with the
&lt;a class=&quot;reference external&quot; href=&quot;https://eli.thegreenplace.net/2012/08/07/fundamental-concepts-of-plugin-infrastructures&quot;&gt;fundamental concepts of plugin infrastructures&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;section&quot; id=&quot;mdbook-preprocessors&quot;&gt;
&lt;h2&gt;mdBook preprocessors&lt;/h2&gt;
&lt;p&gt;mdBook's architecture is pretty simple: your contents go into a directory tree
of Markdown files. mdBook then renders these into a book, with one file per
chapter. The book's output is HTML by default, but mdBook supports other
outputs like PDF.&lt;/p&gt;
&lt;p&gt;The &lt;a class=&quot;reference external&quot; href=&quot;https://rust-lang.github.io/mdBook/for_developers/preprocessors.html&quot;&gt;preprocessor mechanism&lt;/a&gt;
lets us register an arbitrary program that runs on the book's source after
it's loaded from Markdown files; this program can modify the book's contents in
any way it wishes before it all gets sent to the renderer for generating output.&lt;/p&gt;
&lt;img alt=&quot;Preprocessor flow for mdbook&quot; class=&quot;align-center&quot; src=&quot;https://eli.thegreenplace.net/images/2025/mdbook-preprocessor.png&quot; /&gt;
&lt;p&gt;The official documentation &lt;a class=&quot;reference external&quot; href=&quot;https://rust-lang.github.io/mdBook/for_developers/preprocessors.html#hooking-into-mdbook&quot;&gt;explains this process very well&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;sample-plugin&quot;&gt;
&lt;h2&gt;Sample plugin&lt;/h2&gt;
&lt;p&gt;I rewrote &lt;a class=&quot;reference external&quot; href=&quot;https://eli.thegreenplace.net/2012/08/07/fundamental-concepts-of-plugin-infrastructures&quot;&gt;my classical &amp;quot;nacrissist&amp;quot; plugin&lt;/a&gt;
for mdBook; the code is &lt;a class=&quot;reference external&quot; href=&quot;https://github.com/eliben/code-for-blog/tree/main/2025/plugin-mdbook&quot;&gt;available here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In fact, there are two renditions of the same plugin there:&lt;/p&gt;
&lt;ol class=&quot;arabic simple&quot;&gt;
&lt;li&gt;One in Python, to demonstrate how mdBook can invoke preprocessors written in
any programming language.&lt;/li&gt;
&lt;li&gt;One in Rust, to demonstrate how mdBook exposes an application API to plugins
written in Rust (since mdBook is itself written in Rust).&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;fundamental-plugin-concepts-in-this-case-study&quot;&gt;
&lt;h2&gt;Fundamental plugin concepts in this case study&lt;/h2&gt;
&lt;p&gt;Let's see how this case study of mdBook preprocessors measures against the
&lt;a class=&quot;reference external&quot; href=&quot;https://eli.thegreenplace.net/2012/08/07/fundamental-concepts-of-plugin-infrastructures&quot;&gt;Fundamental plugin concepts&lt;/a&gt;
that were covered &lt;a class=&quot;reference external&quot; href=&quot;https://eli.thegreenplace.net/tag/plugins&quot;&gt;several times on this blog&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;section&quot; id=&quot;discovery&quot;&gt;
&lt;h3&gt;Discovery&lt;/h3&gt;
&lt;p&gt;Discovery in mdBook is very explicit. For every plugin we want mdBook to use,
it has to be listed in the project's &lt;tt class=&quot;docutils literal&quot;&gt;book.toml&lt;/tt&gt; configuration file. For
example, in &lt;a class=&quot;reference external&quot; href=&quot;https://github.com/eliben/code-for-blog/tree/main/2025/plugin-mdbook&quot;&gt;the code sample for this post&lt;/a&gt;, the Python narcissist plugin
is noted in &lt;tt class=&quot;docutils literal&quot;&gt;book.toml&lt;/tt&gt; as follows:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;[preprocessor.narcissistpy]
command = &amp;quot;python3 ../preprocessor-python-narcissist/narcissist.py&amp;quot;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Each preprocessor is a command for &lt;tt class=&quot;docutils literal&quot;&gt;mdBook&lt;/tt&gt; to execute in a sub-process.
Here it uses Python, but it can be anything else that can be validly executed.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;registration&quot;&gt;
&lt;h3&gt;Registration&lt;/h3&gt;
&lt;p&gt;For the purpose of registration, &lt;tt class=&quot;docutils literal&quot;&gt;mdBook&lt;/tt&gt; actually invokes the plugin command
&lt;em&gt;twice&lt;/em&gt;. The first time, it passes the arguments &lt;tt class=&quot;docutils literal&quot;&gt;supports &amp;lt;renderer&amp;gt;&lt;/tt&gt; where
&lt;tt class=&quot;docutils literal&quot;&gt;&amp;lt;renderer&amp;gt;&lt;/tt&gt; is the name of the renderer (e.g. &lt;tt class=&quot;docutils literal&quot;&gt;html&lt;/tt&gt;). If the command
returns 0, it means the preprocessor supports this renderer; otherwise, it
doesn't.&lt;/p&gt;
&lt;p&gt;In the second invocation, &lt;tt class=&quot;docutils literal&quot;&gt;mdBook&lt;/tt&gt; passes some metadata plus the entire book
in JSON format to the preprocessor through stdin, and expects the preprocessor
to return the modified book as JSON to stdout (using the same schema).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;hooks&quot;&gt;
&lt;h3&gt;Hooks&lt;/h3&gt;
&lt;p&gt;In terms of hooks, &lt;tt class=&quot;docutils literal&quot;&gt;mdBook&lt;/tt&gt; takes a very coarse-grained approach. The
preprocessor gets the &lt;em&gt;entire book&lt;/em&gt; in a single JSON object (along with a
context object that contains metadata), and is expected to emit the entire
modified book in a single JSON object.
It's up to the preprocessor to figure out which parts of the book to read and
which parts to modify.&lt;/p&gt;
&lt;p&gt;Given that books and other documentation typically have limited sizes, this
is a reasonable design choice. Even tens of MiB of JSON-encoded data are very
quick to pass between sub-processes via stdout and marshal/unmarshal. But we
wouldn't be able to implement Wikipedia using this design.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;exposing-an-application-api-to-plugins&quot;&gt;
&lt;h3&gt;Exposing an application API to plugins&lt;/h3&gt;
&lt;p&gt;This is tricky, given that the preprocessor mechanism is language-agnostic.
Here, &lt;tt class=&quot;docutils literal&quot;&gt;mdBook&lt;/tt&gt; only offers additional utilities to preprocessors implemented
in Rust. These get access to &lt;tt class=&quot;docutils literal&quot;&gt;mdBook&lt;/tt&gt;'s API to unmarshal the JSON
representing the context metadata and book's contents. &lt;tt class=&quot;docutils literal&quot;&gt;mdBook&lt;/tt&gt; offers the
&lt;a class=&quot;reference external&quot; href=&quot;https://docs.rs/mdbook-preprocessor/latest/mdbook_preprocessor/trait.Preprocessor.html&quot;&gt;Preprocessor trait&lt;/a&gt;
Rust preprocessors can implement, which makes it easier to wrangle the book's
contents. See &lt;a class=&quot;reference external&quot; href=&quot;https://github.com/eliben/code-for-blog/tree/main/2025/plugin-mdbook/preprocessor-rust-narcissist&quot;&gt;my Rust version of the narcissist preprocessor&lt;/a&gt;
for a basic example of this.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;section&quot; id=&quot;renderers-backends&quot;&gt;
&lt;h2&gt;Renderers / backends&lt;/h2&gt;
&lt;p&gt;Actually, &lt;tt class=&quot;docutils literal&quot;&gt;mdBook&lt;/tt&gt; has &lt;em&gt;another&lt;/em&gt; plugin mechanism, but it's very similar
conceptually to preprocessors. A &lt;em&gt;renderer&lt;/em&gt; (also called a &lt;em&gt;backend&lt;/em&gt; in some
of &lt;tt class=&quot;docutils literal&quot;&gt;mdBook&lt;/tt&gt;'s own doc pages) takes the same input as a preprocessor, but is
free to do whatever it wants with it. The default renderer emits the HTML
for the book; &lt;a class=&quot;reference external&quot; href=&quot;https://github.com/rust-lang/mdBook/wiki/Third-party-plugins#backends&quot;&gt;other renderers&lt;/a&gt;
can do other things.&lt;/p&gt;
&lt;p&gt;The idea is that the book can go through multiple preprocessors, but at the
end a &lt;em&gt;single&lt;/em&gt; renderer.&lt;/p&gt;
&lt;p&gt;The data a renderer receives is exactly the same as a preprocessor - JSON
encoded book contents. Due to this similarity, there's no real point getting
deeper into renderers in this post.&lt;/p&gt;
&lt;/div&gt;</content:encoded>
	<dc:date>2026-06-07T07:38:58+00:00</dc:date>
</item>
<item rdf:about="https://lucumr.pocoo.org/2026/6/6/communities-of-not/">
	<title>Armin Ronacher: Communities of Not</title>
	<link>https://lucumr.pocoo.org/2026/6/6/communities-of-not/</link>
	<content:encoded>&lt;p&gt;There is a strange thing that happens in communities that gather around
abstinence from something: identity from opposition.  At their best these
communities are not &lt;em&gt;just&lt;/em&gt; negative: childfree spaces can be about autonomy,
choice and acceptance, anti-car spaces about safer streets and transit, and
LLM-skeptical developer spaces about the future of labor, code quality and
slop&lt;sup class=&quot;footnote-ref&quot; id=&quot;fnref-1&quot;&gt;&lt;a href=&quot;https://lucumr.pocoo.org/feed.atom#fn-1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;.  But the thing being refused often does not go away and instead
becomes the main subject of the community&amp;#8217;s identity.&lt;/p&gt;
&lt;p&gt;That would be fine if it stayed at criticism, maybe even angry criticism, but
more often than not it turns into policing and hatred towards others.  An
influencer without children becomes a parent, an urban bike commuter by choice
buys a Porsche, a respected developer tries LLMs, and the community feels
betrayed because it assumed they were members of the same tribe.  The expulsion
of that person (who never signed up to be a community member) is entirely
imaginary but the punishment that the community unleashes is not: people pile on
and shame them, quote them out of context and turn their weakest moments into
proof that the person was always unserious, a sharlatan or should not be
listened to.&lt;/p&gt;
&lt;p&gt;I do not think the answer is to tell people to stop paying attention.  Cars
shape cities even for people who cycle, children influence politics, workplaces
and taxes even for people who do not have them.  For us developers, LLMs show up
in editors, issue trackers, hiring conversations, management pressure and code
reviews whether we asked for them or not.  Resisting that can be legitimate but
that is no excuse for using one&amp;#8217;s rejection to justify shitty mob behavior.&lt;/p&gt;
&lt;p&gt;I understand the thinking all too well, because I have done versions of this
myself in the past.  It took me a while to become more accepting of other
people&amp;#8217;s worldviews that diverge from mine.  Whatever insecurities we have,
finding a group of others sharing them can be comforting.  The danger is that
being part of a crowd of negativity can easily make us part of collective
harassment.&lt;/p&gt;
&lt;p&gt;I can only encourage you to breathe, slow down, de-escalate when given the
chance, and resist the temptation to always assume the most catastrophic
reading.  Default to being &lt;a href=&quot;https://lucumr.pocoo.org/2026/4/11/the-center-has-a-bias/&quot;&gt;open to new things&lt;/a&gt;.
Being negative towards something, and making that ones identity, is an easy trap
to fall into.&lt;/p&gt;
&lt;div class=&quot;footnotes&quot;&gt;
&lt;ol&gt;
&lt;li id=&quot;fn-1&quot;&gt;
&lt;p&gt;These examples are not meant as equivalents.  The recent
&lt;a href=&quot;https://github.com/RsyncProject/rsync/issues/929&quot;&gt;mob&lt;/a&gt; &lt;a href=&quot;https://mastodon.gamedev.place/@JeremiahFieldhaven/116654345332213390&quot;&gt;against
rsync&lt;/a&gt;
is the LLM version that prompted this post.  I picked the others because I&amp;#8217;m
familiar with those communities and they all show similar cases of personal
choices being interpreted as betrayal.&lt;a href=&quot;https://lucumr.pocoo.org/feed.atom#fnref-1&quot; class=&quot;footnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content:encoded>
	<dc:date>2026-06-06T00:00:00+00:00</dc:date>
</item>
<item rdf:about="https://nuitka.net/posts/nuitka-release-41.html">
	<title>Kay Hayen: Nuitka Release 4.1</title>
	<link>https://nuitka.net/posts/nuitka-release-41.html</link>
	<content:encoded>&lt;p&gt;This is to inform you about the new stable release of &lt;a class=&quot;reference external&quot; href=&quot;https://nuitka.net&quot;&gt;Nuitka&lt;/a&gt;. It is the extremely compatible Python compiler,
&lt;a class=&quot;reference external&quot; href=&quot;https://nuitka.net/doc/download.html&quot;&gt;“download now”&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This release adds many new features and corrections with a focus on
async code compatibility, missing generics features, and Python 3.14
compatibility and Python compilation scalability yet again.&lt;/p&gt;

&lt;h2&gt;Bug Fixes&lt;/h2&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python 3.14:&lt;/strong&gt; Fix, decorators were breaking when disabling
deferred annotations. (Fixed in 4.0.1 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fix, nested loops could have wrong traces lead to mis-optimization.
(Fixed in 4.0.1 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Plugins:&lt;/strong&gt; Fix, run-time check of package configuration was
incorrect. (Fixed in 4.0.1 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Compatibility:&lt;/strong&gt; Fix, &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;__builtins__&lt;/span&gt;&lt;/code&gt; lacked necessary
compatibility in compiled functions. (Fixed in 4.0.1 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Distutils:&lt;/strong&gt; Fix, incorrect UTF-8 decoding was used for TOML input
file parsing. (Fixed in 4.0.1 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fix, multiple hard value assignments could cause compile time
crashes. (Fixed in 4.0.1 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fix, string concatenation was not properly annotating exception
exits. (Fixed in 4.0.2 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Windows:&lt;/strong&gt; Fix, &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;--verbose-output&lt;/span&gt;&lt;/code&gt; and &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;--show-modules-output&lt;/span&gt;&lt;/code&gt;
did not work with forward slashes. (Fixed in 4.0.2 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python 3.14:&lt;/strong&gt; Fix, there were various compatibility issues
including dictionary watchers and inline values. (Fixed in 4.0.2
already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python 3.14:&lt;/strong&gt; Fix, stack pointer initialization to &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;localsplus&lt;/span&gt;&lt;/code&gt;
was incorrect to avoid garbage collection issues. (Fixed in 4.0.2
already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python 3.12+:&lt;/strong&gt; Fix, generic type variable scoping in classes was
incorrect. (Fixed in 4.0.2 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python 3.12+:&lt;/strong&gt; Fix, there were various issues with function
generics. (Fixed in 4.0.2 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python 3.8+:&lt;/strong&gt; Fix, names in named expressions were not mangled.
(Fixed in 4.0.2 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Plugins:&lt;/strong&gt; Fix, module checksums were not robust against quoting
style of module-name entry in YAML configurations. (Fixed in 4.0.2
already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Plugins:&lt;/strong&gt; Fix, doing imports in queried expressions caused
corruption. (Fixed in 4.0.2 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;UI:&lt;/strong&gt; Fix, support for &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;uv_build&lt;/span&gt;&lt;/code&gt; in the &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;--project&lt;/span&gt;&lt;/code&gt; option was
broken. (Fixed in 4.0.2 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Compatibility:&lt;/strong&gt; Fix, names assigned in assignment expressions were
not mangled. (Fixed in 4.0.2 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python 3.12+:&lt;/strong&gt; Fix, there were still various issues with function
generics. (Fixed in 4.0.3 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Clang:&lt;/strong&gt; Fix, debug mode was disabled for clang generally, but only
ClangCL and macOS Clang didn’t want it. (Fixed in 4.0.3 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Zig:&lt;/strong&gt; Fix, &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;--windows-console-mode=attach|disable&lt;/span&gt;&lt;/code&gt; was not
working when using Zig. (Fixed in 4.0.3 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;macOS:&lt;/strong&gt; Fix, yet another way self dependencies can look like,
needed to have support added. (Fixed in 4.0.3 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python 3.12+:&lt;/strong&gt; Fix, generic types in classes had bugs with
multiple type variables. (Fixed in 4.0.3 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scons:&lt;/strong&gt; Fix, repeated builds were not producing binary identical
results. (Fixed in 4.0.3 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scons:&lt;/strong&gt; Fix, compiling with newer Python versions did not fall
back to Zig when the developer prompt MSVC was unusable, and error
reporting could crash. (Fixed in 4.0.4 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Zig:&lt;/strong&gt; Fix, the workaround for Windows console mode &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;attach&lt;/span&gt;&lt;/code&gt; or
&lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;disable&lt;/span&gt;&lt;/code&gt; was incorrectly applied on non-Windows platforms. (Fixed
in 4.0.4 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Standalone:&lt;/strong&gt; Fix, linking with Python Build Standalone failed
because &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;libHacl_Hash_SHA2&lt;/span&gt;&lt;/code&gt; was not filtered out unconditionally.
(Fixed in 4.0.4 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python 3.6+:&lt;/strong&gt; Fix, exceptions like &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;CancelledError&lt;/span&gt;&lt;/code&gt; thrown into
an async generator awaiting an inner awaitable could be swallowed,
causing crashes. (Fixed in 4.0.4 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fix, not all ordered set modules accepted generators for update.
(Fixed in 4.0.5 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Plugins:&lt;/strong&gt; Disabled warning about rebuilding the &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;pytokens&lt;/span&gt;&lt;/code&gt;
extension module. (Fixed in 4.0.5 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Standalone:&lt;/strong&gt; Filtered &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;libHacl_Hash_SHA2&lt;/span&gt;&lt;/code&gt; from link libs
unconditionally. (Fixed in 4.0.5 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Debugging:&lt;/strong&gt; Disabled unusable unicode consistency checks for
Python versions 3.4 to 3.6. (Fixed in 4.0.5 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python3.12+&lt;/strong&gt; Avoided cloning call nodes on class level which
caused issues with generic functions in combination with decorators.
(Added in 4.0.5 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python 3.12+:&lt;/strong&gt; Added support for generic type variables in &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;async&lt;/span&gt;
&lt;span class=&quot;pre&quot;&gt;def&lt;/span&gt;&lt;/code&gt; functions. (Added in 4.0.5 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;UI:&lt;/strong&gt; Fix, flushing outputs for prompts was not working in all
cases when progress bars were enabled. (Fixed in 4.0.6 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;UI:&lt;/strong&gt; Fix, unused variable warnings were missing at C compile time
when using &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;zig&lt;/span&gt;&lt;/code&gt; as a C compiler. (Fixed in 4.0.6 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scons:&lt;/strong&gt; Fix, forced stdout and stderr paths as a feature was
broken. (Fixed in 4.0.6 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fix, replacing a branch did not accurately track shared active
variables causing optimization crashes. (Fixed in 4.0.7 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;macOS:&lt;/strong&gt; Fix, failed to remove extended attributes because files
need to be made writable first. (Fixed in 4.0.7 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fix, dict &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;pop&lt;/span&gt;&lt;/code&gt; and &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;setdefault&lt;/span&gt;&lt;/code&gt; using with &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;:=&lt;/span&gt;&lt;/code&gt; rewrites
lacked exception-exit annotations for un-hashable keys. (Fixed in
4.0.8 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python 3.13:&lt;/strong&gt; Fix, the &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;__parameters__&lt;/span&gt;&lt;/code&gt; attribute of generic
classes was not working. (Fixed in 4.0.8 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python 3.11+:&lt;/strong&gt; Fix, starred arguments were not working as type
variables. (Fixed in 4.0.8 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python2:&lt;/strong&gt; Fix, &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;FileNotFoundError&lt;/span&gt;&lt;/code&gt; compatibility fallback
handling was not working properly. (Fixed in 4.0.8 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Compatibility:&lt;/strong&gt; Fix, loop ownership check in value traces was
missing, causing issues with nested loops.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Windows:&lt;/strong&gt; Improved &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;--windows-console-mode=attach&lt;/span&gt;&lt;/code&gt; to properly
handle console handles, enabling cases like &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;os.system&lt;/span&gt;&lt;/code&gt; to work
nicely.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python2:&lt;/strong&gt; Fix, there was a compatibility issue where providing
default values to the &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;mkdtemp&lt;/span&gt;&lt;/code&gt; function was failing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Windows:&lt;/strong&gt; Fix, there were spurious issues with C23 embedding in
32-bit MinGW64 by switching to &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;coff_obj&lt;/span&gt;&lt;/code&gt; resource mode for it as
well.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Plugins:&lt;/strong&gt; Fix, the &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;post-import-code&lt;/span&gt;&lt;/code&gt; execution could fail
because the triggering sub-package was not yet available in
&lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;sys.modules&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;UI:&lt;/strong&gt; Fix, listing package DLLs with &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;--list-package-dlls&lt;/span&gt;&lt;/code&gt; was
broken due to recent plugin lifecycle changes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;UI:&lt;/strong&gt; Fix, &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;--list-package-exe&lt;/span&gt;&lt;/code&gt; was not working properly on
non-Windows platforms failing to detect executable files correctly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;UI:&lt;/strong&gt; Handled paths starting with &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;{PROGRAM_DIR}&lt;/span&gt;&lt;/code&gt; the same as a
relative path when parsing the &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;--onefile-tempdir-spec&lt;/span&gt;&lt;/code&gt; option.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Plugins:&lt;/strong&gt; Followed multiprocessing &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;forkserver&lt;/span&gt;&lt;/code&gt; changes for
newer Python versions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python 3.12+:&lt;/strong&gt; Fix, generic class type parameters handling was
incorrect.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python 3.12:&lt;/strong&gt; Fix, deferred evaluation of type aliases was
failing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python 3.12+:&lt;/strong&gt; Aligned &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;sum&lt;/span&gt;&lt;/code&gt; built-in float summation with
CPython’s compensated sum for better accuracy.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python 3.10+:&lt;/strong&gt; Fix, uncompiled coroutine &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;throw()&lt;/span&gt;&lt;/code&gt; return
handling was incorrect, restoring completed coroutine results via
&lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;StopIteration.value&lt;/span&gt;&lt;/code&gt; rather than exposing them as ordinary return
values to the outer await chain.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python 3.13+:&lt;/strong&gt; Fix, uncompiled coroutine &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;cancel()/await&lt;/span&gt;&lt;/code&gt;
suspension handling was incorrect, improved to ensure integration
compatibility.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;macOS:&lt;/strong&gt; Made finding &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;create-dmg&lt;/span&gt;&lt;/code&gt; more robustly by also checking
the Homebrew path for Intel and from &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;PATH&lt;/span&gt;&lt;/code&gt; properly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Compatibility:&lt;/strong&gt; Fix, class frames were not exposing frame locals.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;UI:&lt;/strong&gt; Detected &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;static-libpython&lt;/span&gt;&lt;/code&gt; problems, which affected some
forms of Anaconda.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Distutils:&lt;/strong&gt; Rejected &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;--project&lt;/span&gt;&lt;/code&gt; mixed with &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;--main&lt;/span&gt;&lt;/code&gt; arguments
as it is not useful.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;macOS:&lt;/strong&gt; Fix, &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;zig&lt;/span&gt;&lt;/code&gt; from &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;PATH&lt;/span&gt;&lt;/code&gt; or from &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;ziglang&lt;/span&gt;&lt;/code&gt; was not
being used.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Distutils:&lt;/strong&gt; Fix, the wrong &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;module-root&lt;/span&gt;&lt;/code&gt; config value was being
checked for &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;uv&lt;/span&gt;&lt;/code&gt; build backend.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;macOS:&lt;/strong&gt; Fix, was attempting to change removed (rejected) DLLs,
which of course failed and errored out.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python 3.14:&lt;/strong&gt; Fix, tuple reuse was not fully compatible,
potentially causing crashes due to outdated hash caches.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fix, fake modules were still being attempted to located when imported
by other code, which could conflict with existing modules.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python 3.5+:&lt;/strong&gt; Fix, failed to send uncompiled coroutines the sent
in value in &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;pre&quot;&gt;from&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fix, older &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;gcc&lt;/span&gt;&lt;/code&gt; compilers lacking newer intrinsic methods had
compilation issues that needed to be addressed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Standalone:&lt;/strong&gt; Fix, multiphase module extension modules with
post-load code were not working properly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fix, Avoid using the non-inline copy of &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;pkg_resources&lt;/span&gt;&lt;/code&gt; with the
inline copy of Jinja2. These could mismatch and cause errors.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fix, loops could make releasing of previous values very unclear,
causing optimization errors.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fix, &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;incbin&lt;/span&gt;&lt;/code&gt; resource mode was not working with old &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;gcc&lt;/span&gt;&lt;/code&gt; C++
fallback.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python 3.4 to 3.6:&lt;/strong&gt; Fix, bytecode demotion was not working
properly for these versions, also bytecode only files not working.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Plugins:&lt;/strong&gt; Added a check for the broken &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;patchelf&lt;/span&gt;&lt;/code&gt; versions 0.10
and 0.11 to prevent breaking Qt plugins.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Android:&lt;/strong&gt; Allowed &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;patchelf&lt;/span&gt;&lt;/code&gt; version 0.18 on Android.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Windows:&lt;/strong&gt; Fix, the header path for self uninstalled Python was not
detected correctly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; Fix, inclusion of the &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;pkg_resources&lt;/span&gt;&lt;/code&gt; inline copy for
Python 2 to source distributions was missing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;UI:&lt;/strong&gt; Detected the OBS versions of SUSE Linux better.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Suse:&lt;/strong&gt; Allowed using &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;patchelf&lt;/span&gt;&lt;/code&gt; 0.18.0 there too.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python 3.11:&lt;/strong&gt; Fix, package and module dicts were not aligned close
enough to avoid a CPython bug.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fix, unbound compiled methods could crash when called without an
object passed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Standalone:&lt;/strong&gt; Fix, multiphase module extension modules with
postload. (Fixed in 4.0.8 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Onefile:&lt;/strong&gt; Fix, while waiting for the child, it may already be
terminated.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;macOS:&lt;/strong&gt; Removed existing absolute rpaths for Homebrew and
MacPorts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python 3.14:&lt;/strong&gt; Avoided warning in CPython headers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python 3.14:&lt;/strong&gt; Followed allocator changes more closely.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Compatibility:&lt;/strong&gt; Avoided using &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;pkg_resources&lt;/span&gt;&lt;/code&gt; for Jinja2
template location for loading.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No-GIL:&lt;/strong&gt; Applied some bug fixes to get basic things to work.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;Package Support&lt;/h2&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Standalone:&lt;/strong&gt; Add support for newer &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;paddle&lt;/span&gt;&lt;/code&gt; version. (Added in
4.0.1 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Standalone:&lt;/strong&gt; Add workaround for refcount checks of &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;pandas&lt;/span&gt;&lt;/code&gt;.
(Fixed in 4.0.1 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Standalone:&lt;/strong&gt; Add support for newer &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;h5py&lt;/span&gt;&lt;/code&gt; version. (Added in
4.0.2 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Standalone:&lt;/strong&gt; Add support for newer &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;scipy&lt;/span&gt;&lt;/code&gt; package. (Added in
4.0.2 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Plugins:&lt;/strong&gt; Revert accidental &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;os.getenv&lt;/span&gt;&lt;/code&gt; over &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;os.environ.get&lt;/span&gt;&lt;/code&gt;
changes in anti-bloat configurations that stopped them from working.
Affected packages are &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;networkx&lt;/span&gt;&lt;/code&gt;, &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;persistent&lt;/span&gt;&lt;/code&gt;, and
&lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;tensorflow&lt;/span&gt;&lt;/code&gt;. (Fixed in 4.0.5 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Standalone:&lt;/strong&gt; Added missing DLLs for &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;openvino&lt;/span&gt;&lt;/code&gt;. (Added in 4.0.7
already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enhanced the package configuration YAML schema by adding the
&lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;relative_to&lt;/span&gt;&lt;/code&gt; parameter for &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;from_filenames&lt;/span&gt;&lt;/code&gt; DLL specification,
avoiding error-prone purely relative paths.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Standalone:&lt;/strong&gt; Fix, &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;flet_desktop&lt;/span&gt;&lt;/code&gt; app assets were missing, now
preserving the packaged runtime and sidecar DLLs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Standalone:&lt;/strong&gt; Added support for the &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;tyro&lt;/span&gt;&lt;/code&gt; package.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Standalone:&lt;/strong&gt; Added data files for the &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;perfetto&lt;/span&gt;&lt;/code&gt; package.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Standalone:&lt;/strong&gt; Added support for &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;anyio&lt;/span&gt;&lt;/code&gt; process forking.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Standalone:&lt;/strong&gt; Added support for the &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;plotly.graph&lt;/span&gt;&lt;/code&gt; package.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Anaconda:&lt;/strong&gt; Fix, dependencies for the &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;numpy&lt;/span&gt;&lt;/code&gt; conda package on
Windows were incorrect.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Plugins:&lt;/strong&gt; Enhanced the auto-icon hack in PySide6 to use compatible
class names.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Standalone:&lt;/strong&gt; Fix, Qt libraries were duplicated with &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;PySide6&lt;/span&gt;&lt;/code&gt;
WebEngine framework support on macOS.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Plugins:&lt;/strong&gt; Fix, automatic detection of &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;mypyc&lt;/span&gt;&lt;/code&gt; runtime
dependencies was including all top level modules of the containing
package by accident. (Fixed in 4.0.5 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Anaconda:&lt;/strong&gt; Fix, &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;delvewheel&lt;/span&gt;&lt;/code&gt; plugin was not working with Python
3.8+. This enhances compatibility with installed PyPI packages that
use it for their DLLs. (Fixed in 4.0.6 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Plugins:&lt;/strong&gt; Fix, our protection workaround could confuse methods
used with &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;PySide6&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;New Features&lt;/h2&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;UI:&lt;/strong&gt; Added the &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;--recommended-python-version&lt;/span&gt;&lt;/code&gt; option to display
recommended Python versions for supported, working, or commercial
usage.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;UI:&lt;/strong&gt; Add message to inform users about &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;Nuitka[onefile]&lt;/span&gt;&lt;/code&gt; if
compression is not installed. (Added in 4.0.1 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;UI:&lt;/strong&gt; Add support for &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;uv_build&lt;/span&gt;&lt;/code&gt; in the &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;--project&lt;/span&gt;&lt;/code&gt; option.
(Added in 4.0.1 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Onefile:&lt;/strong&gt; Allow extra includes as well. (Added in 4.0.2 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;UI:&lt;/strong&gt; Add &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;nuitka-project-set&lt;/span&gt;&lt;/code&gt; feature to define project
variables, checking for collisions with reserved runtime variables.
(Added in 4.0.2 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scons:&lt;/strong&gt; Added new option to select &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;--reproducible&lt;/span&gt;&lt;/code&gt; builds or
not. (Added in 4.0.6 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python 3.10+:&lt;/strong&gt; Added support for
&lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;importlib.metadata.package_distributions()&lt;/span&gt;&lt;/code&gt;. (Added in 4.0.8
already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Plugins:&lt;/strong&gt; Added support for the multiprocessing &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;forkserver&lt;/span&gt;&lt;/code&gt;
context. (Added in 4.0.8 already, for 4.1 Python 3.6 and earlier, as
well as 3.14 support were added too.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reports:&lt;/strong&gt; Added structured resource usage (&lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;rusage&lt;/span&gt;&lt;/code&gt;) performance
information to compilation reports.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reports:&lt;/strong&gt; Included individual module-level C compiler caching
(&lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;ccache&lt;/span&gt;&lt;/code&gt;/&lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;clcache&lt;/span&gt;&lt;/code&gt;) statistics in compilation reports.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Added support for detecting and correctly resolving the Python prefix
for the &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;PyEnv&lt;/span&gt; &lt;span class=&quot;pre&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;pre&quot;&gt;Homebrew&lt;/span&gt;&lt;/code&gt; Python flavor.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;macOS:&lt;/strong&gt; Added support for &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;rusage&lt;/span&gt;&lt;/code&gt; information for Scons.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;UI:&lt;/strong&gt; Added the &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;__compiled__.extension_filename&lt;/span&gt;&lt;/code&gt; attribute to
give the real filename of the containing extension module.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Windows:&lt;/strong&gt; Added support for &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;--clang&lt;/span&gt;&lt;/code&gt; or ARM. (Added in 4.0.8
already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Windows:&lt;/strong&gt; Added support for resources names as not just integers,
important when we copy them from template files.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;MacPorts:&lt;/strong&gt; Added basic support for this Python flavor. More work
will be needed to get it to work fully though.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;Optimization&lt;/h2&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;&lt;p&gt;Avoid including &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;importlib._bootstrap&lt;/span&gt;&lt;/code&gt; and
&lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;importlib._bootstrap_external&lt;/span&gt;&lt;/code&gt;. (Added in 4.0.1 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Linux:&lt;/strong&gt; Cached the &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;syscall&lt;/span&gt;&lt;/code&gt; used for time keeping during
compilation to avoid loading &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;libc&lt;/span&gt;&lt;/code&gt; for each trace. (Added in 4.0.8
already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;UI:&lt;/strong&gt; Output a warning for modules that remain unfinished after the
third optimization pass.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Added an extra micro pass trigger when new variables are introduced
or variable usage changes severely, ensuring optimizations are fully
propagated, avoiding unnecessary extra full passes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Provided scripts to compile Python statically with PGO tailored for
Nuitka on Linux, Windows, and macOS.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Added support for running the Data Composer tool from a compiled
Nuitka binary without spawning an uncompiled Python process.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enhanced the usage of &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;vectorcall&lt;/span&gt;&lt;/code&gt; for &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;PyCFunction&lt;/span&gt;&lt;/code&gt; objects by
directly checking for its presence instead of relying purely on
flags, allowing more frequent use of this faster execution path.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cached frequently used declarations for top-level variables to speed
up C code generation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sped up trace collection merging by avoiding unnecessary set creation
and using a set instead of a list for escaped traces.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Optimized plugin hook execution by tracking overloaded methods and
added an option to show plugin usage statistics.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Improved performance of module location by avoiding unnecessary
module name reconstruction and redundant filesystem checks for
pre-loaded packages.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Improved the caching of distribution name lookups to effectively
avoid repeated IO operations across all package types.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Plugins:&lt;/strong&gt; Cached callback plugin dispatch for
&lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;onFunctionBodyParsing&lt;/span&gt;&lt;/code&gt; and &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;onClassBodyParsing&lt;/span&gt;&lt;/code&gt; to skip argument
computation when no plugin overrides them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python 3.13:&lt;/strong&gt; Handled sub-packages of &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;pathlib&lt;/span&gt;&lt;/code&gt; as hard modules.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Handled hard attributes through merge traces as well.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Made constant blobs more compact by avoiding repeated identifiers and
unnecessary fields.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enhanced Python compilation scripts further. (Fixed in 4.0.8
already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Recognized late incomplete variables better. (Fixed in 4.0.8
already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Made constant blobs more compact. (Fixed in 4.0.8 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Optimized calls with only constant keywords and variable posargs too.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;Anti-Bloat&lt;/h2&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;&lt;p&gt;Fix, memory bloat occurred when C compiling &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;sqlalchemy&lt;/span&gt;&lt;/code&gt;. (Fixed in
4.0.2 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Avoid using &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;pydoc&lt;/span&gt;&lt;/code&gt; in &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;PySimpleGUI&lt;/span&gt;&lt;/code&gt;. (Added in 4.0.2 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Avoided using &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;doctest&lt;/span&gt;&lt;/code&gt; from &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;zodbpickle&lt;/span&gt;&lt;/code&gt;. (Added in 4.0.5
already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Avoided inclusion of &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;cython&lt;/span&gt;&lt;/code&gt; when using &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;pyav&lt;/span&gt;&lt;/code&gt;. (Added in 4.0.7
already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Avoided including &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;typing_extensions&lt;/span&gt;&lt;/code&gt; when using &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;numpy&lt;/span&gt;&lt;/code&gt;. (Added
in 4.0.7 already.)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;Organizational&lt;/h2&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;UI:&lt;/strong&gt; Relocated the warning about the available source code of
extension modules to be evaluated at a more appropriate time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Debian:&lt;/strong&gt; Remove recommendation for &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;libfuse2&lt;/span&gt;&lt;/code&gt; package as it is
no longer useful.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Debian:&lt;/strong&gt; Used &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;platformdirs&lt;/span&gt;&lt;/code&gt; instead of &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;appdirs&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Debugging:&lt;/strong&gt; Removed Python 3.11+ restriction for &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;clang-format&lt;/span&gt;&lt;/code&gt;
as it is available everywhere, even Python 2.7, and we still want
nicely formatted code when we read things. (Added in 4.0.6 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Removed no longer useful inline copy of &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;wax_off&lt;/span&gt;&lt;/code&gt;. We have our own
stubs generator project.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; Added missing package to the CI container for building
Nuitka Debian packages.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Developer:&lt;/strong&gt; Updated AI instructions for creating Minimal
Reproducible Examples (MRE) to skip unneeded C compilation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Debugging:&lt;/strong&gt; Added an internal function for checking if a string is
a valid Python identifier.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AI:&lt;/strong&gt; Added a task in Visual Studio Code to export the currently
selected Python interpreter path to a file, making it available as
“python” and “pip” matching the selected interpreter. This makes it
easier to use a specific version with no instructions needed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AI:&lt;/strong&gt; Updated the rules to instruct AI to only generate useful
comments that add context not present in the code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Containers:&lt;/strong&gt; Added template rendering support for Jinja2 (&lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;.j2&lt;/span&gt;&lt;/code&gt;)
container files in our internal Podman tools.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Projects:&lt;/strong&gt; Clarified the current status and rationale of Python
2.6 support in the developer manual.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Debugging:&lt;/strong&gt; Added experimental flag
&lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;--experimental=ignore-extra-micro-pass&lt;/span&gt;&lt;/code&gt; to allow ignoring extra
micro pass detection.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Visual Code:&lt;/strong&gt; Added integration scripts for &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;bash&lt;/span&gt;&lt;/code&gt; and &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;zsh&lt;/span&gt;&lt;/code&gt;
autocompletion of Nuitka CLI options. These are now also integrated
into Visual Studio Code terminal profiles and the Debian package.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;RPM:&lt;/strong&gt; Included the Python compile script for Linux.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;RPM:&lt;/strong&gt; Removed the requirement for &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;distutils&lt;/span&gt;&lt;/code&gt; in the spec.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;Tests&lt;/h2&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;&lt;p&gt;Install only necessary build tools for test cases.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Avoided spurious failures in reference counting tests due to Python
internal caching differences. (Fixed in 4.0.3 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fix, the parsing of the compilation report for reflected tests was
incorrect.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python 3.14:&lt;/strong&gt; Ignored a syntax error message change.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python 3.14:&lt;/strong&gt; Added test execution support options to the main
test runner to use this version as well.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fix, the runner binary path was mishandled for the third pass of
reflected compilations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Removed the usage of obsolete plugins in reflected compilation tests.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Debugging:&lt;/strong&gt; Prevented boolean testing of &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;namedtuples&lt;/span&gt;&lt;/code&gt; to avoid
unexpected bugs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Added the &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;Test&lt;/span&gt;&lt;/code&gt; suffix to syntax test files and disabled “python”
mode and spell checking for them to resolve issues reported in IDEs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fix, newline handling in diff outputs from the output comparison tool
was incorrect.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Covered &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;post-import-code&lt;/span&gt;&lt;/code&gt; functionality with a new subpackage test
case.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Prevented the program test suite from running an unnecessary variant
to save execution time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;macOS:&lt;/strong&gt; Ignored differences from GUI framework error traces in
headless runs in output comparisons.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reflected test for Nuitka, where it compiles itself and compares its
operation has been restored to functional state.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Used the new method to clear internal caches if available for
reference counts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Disabled running nested loops test with Python 2.6.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Containers:&lt;/strong&gt; Detected Python 2 defaulting containers in Podman
tooling.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;Cleanups&lt;/h2&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;UI:&lt;/strong&gt; Fix, there was a double space in the Windows Runtime DLLs
inclusion message. (Fixed in 4.0.1 already.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Onefile:&lt;/strong&gt; Separated files and defines for extra includes for
onefile boot and Python build.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scons:&lt;/strong&gt; Provided nicer errors in case of “unset” variables being
used, so we can tell it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Refactored the process execution results to correctly utilize our
&lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;namedtuples&lt;/span&gt;&lt;/code&gt; variant, that makes it easier to understand what code
does with the results.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Quality:&lt;/strong&gt; Enabled automatic conversion of em-dashes and en-dashes
in code comments to the autoformat tool. AI won’t stop producing them
and they can cause &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;SyntaxError&lt;/span&gt;&lt;/code&gt; for older Python versions, nor is
unnecessarily using UTF-8 welcome.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ensured that cloned outline nodes are assigned their correct names
immediately upon creation, that avoids inconsistencies during their
creation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Quality:&lt;/strong&gt; Updated to the latest versions of &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;black&lt;/span&gt;&lt;/code&gt; and adopted
a faster &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;isort&lt;/span&gt;&lt;/code&gt; execution by caching results.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Quality:&lt;/strong&gt; Modified the PyLint wrapper to exit gracefully instead
of raising an error when no matching files require checking.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Quality:&lt;/strong&gt; Avoided checking YAML package configuration files twice,
since autoformat already handles them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Quality:&lt;/strong&gt; Ensured that YAML package configuration checks output
the original filename instead of the temporary one when a failure
occurs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Quality:&lt;/strong&gt; Prevented pushing of tags from triggering git pre-push
quality checks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Quality:&lt;/strong&gt; Silenced the output of &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;optipng&lt;/span&gt;&lt;/code&gt; and &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;jpegoptim&lt;/span&gt;&lt;/code&gt;
during image optimization auto-formatting.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Visual Code:&lt;/strong&gt; Added the generated Python alias path file to the
ignore list.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Quality:&lt;/strong&gt; Enabled auto-formatting for the Nuitka devcontainer
configuration file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Watch:&lt;/strong&gt; Avoided absolute paths in compilation to make reports more
comparable across machines.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Quality:&lt;/strong&gt; Changed &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;mdformat&lt;/span&gt;&lt;/code&gt; checks to run only once and
silently.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scons:&lt;/strong&gt; Disabled format security errors in debug mode and moved
Python-related warning disables into common build setup code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Quality:&lt;/strong&gt; Updated to the latest &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;deepdiff&lt;/span&gt;&lt;/code&gt; version.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scons:&lt;/strong&gt; Avoided MSVC telemetry since it can produce outputs that
break CI.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Debugging:&lt;/strong&gt; Enhanced non-deployment handler for importing excluded
modules.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Split import module finding functionality into more pieces for
enhanced readability.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Debugging:&lt;/strong&gt; Added more assertions for constants loading and
checking.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;macOS:&lt;/strong&gt; Dropped the &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;universal&lt;/span&gt;&lt;/code&gt; target arch.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Debugging:&lt;/strong&gt; Added more traces for deep hash verification.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;This release builds on the scalability improvements established in 4.0,
with enhanced Python 3.14 support, expanded package compatibility, and
significant optimization work.&lt;/p&gt;
&lt;p&gt;The &lt;code class=&quot;docutils literal notranslate&quot;&gt;&lt;span class=&quot;pre&quot;&gt;--project&lt;/span&gt;&lt;/code&gt; option seems usable now.&lt;/p&gt;
&lt;p&gt;Python 3.14 support remains experimental, but only barely made the cut,
and probably will get there in hotfixes. Some of the corrections came in
so late before the release, that it was just not possible to feel good
about declaring it fully supported just yet.&lt;/p&gt;</content:encoded>
	<dc:date>2026-06-05T22:00:00+00:00</dc:date>
</item>
<item rdf:about="https://bluesock.org/~willkg/blog/dev/bleach_6_4_0_final_release.html">
	<title>Will Kahn-Greene: Bleach 6.4.0 releases -- final release</title>
	<link>https://bluesock.org/~willkg/blog/dev/bleach_6_4_0_final_release.html</link>
	<content:encoded>&lt;h2&gt;What is it?&lt;/h2&gt;
&lt;p&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://bleach.readthedocs.io/&quot;&gt;Bleach&lt;/a&gt; is a Python library for sanitizing
and linkifying text from untrusted sources for safe usage in HTML.&lt;/p&gt;


&lt;h2&gt;Bleach v6.4.0 released!&lt;/h2&gt;
&lt;p&gt;Bleach 6.4.0 includes two security fixes, a fix to tinycss2 dependency
requirements, and some other things.&lt;/p&gt;
&lt;p&gt;See the changes here:&lt;/p&gt;
&lt;p&gt;&lt;a class=&quot;reference external&quot; href=&quot;https://bleach.readthedocs.io/en/latest/changes.html#version-6-4-0-june-5th-2026&quot;&gt;https://bleach.readthedocs.io/en/latest/changes.html#version-6-4-0-june-5th-2026&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;Bleach v6.4.0 is the final release&lt;/h2&gt;
&lt;p&gt;I haven't used Bleach on a project in years, but I still had some time to
maintain it. That changed about a year ago when I got re-orged into a new role
and I haven't had time to do any Bleach work since then.&lt;/p&gt;
&lt;p&gt;To recap, Bleach sits on top of
&lt;a class=&quot;reference external&quot; href=&quot;https://github.com/html5lib/html5lib-python&quot;&gt;html5lib&lt;/a&gt; which hasn't
been actively maintained in years. It is dangerous to maintain Bleach in that
context.&lt;/p&gt;
&lt;p&gt;We vendored html5lib so we could make adjustments to the library to keep Bleach
going. This is not a sustainable approach, but it was ok for the short term.&lt;/p&gt;
&lt;p&gt;Over the years, we've talked about other options:&lt;/p&gt;
&lt;ol class=&quot;arabic simple&quot;&gt;
&lt;li&gt;&lt;p&gt;find another library to switch to&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;take over html5lib development&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;fork html5lib and vendor and maintain our fork&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;write a new HTML parser&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;etc&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;None of those are feasible for me.&lt;/p&gt;
&lt;p&gt;Bleach has been a solo-maintained project for a while now. The world is crazy
and it's much harder to build a team of trusted maintainers now than it was (or
at least, it sure feels that way). I don't see any possibility of increasing
the maintenance team or passing it to someone else responsibly.&lt;/p&gt;
&lt;p&gt;Switching contexts from my regular work to Bleach is really hard. Bleach is
complicated, the problem domain is complicated, and there's a lot of nuanced
context. I can't just switch gears, spend 15 minutes on Bleach to do something,
and then switch back to the rest of my day. I periodically get nag messages
about this which are entirely valid, but there's nothing I can do about it.
It doesn't feel great.&lt;/p&gt;
&lt;p&gt;Then in 2025, Emil, a long-time Bleach contributor, built
&lt;a class=&quot;reference external&quot; href=&quot;https://emilstenstrom.github.io/justhtml/&quot;&gt;justhtml&lt;/a&gt; which gives us an easy
migration path off of Bleach. He even took the time to write a
&lt;a class=&quot;reference external&quot; href=&quot;https://emilstenstrom.github.io/justhtml/bleach-migration.html&quot;&gt;migration guide&lt;/a&gt;.&lt;/p&gt;


&lt;h2&gt;Thoughts and statistics&lt;/h2&gt;
&lt;p&gt;In 2019, when I stepped down the first time, I wrote
&lt;a class=&quot;reference external&quot; href=&quot;https://bluesock.org/~willkg/blog/dev/bleach_stepping_down.html&quot;&gt;a post on stepping down&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In 2023, when I deprecated the project, I wrote
&lt;a class=&quot;reference external&quot; href=&quot;https://bluesock.org/~willkg/blog/dev/bleach_6_0_0_deprecation.html&quot;&gt;a post on Bleach 6.0.0 and deprecation&lt;/a&gt;.&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;&lt;p&gt;From the first commit on 2010-02-18 to today's final commit on 2026-06-05,
the Bleach project lasted 16 years, 3 months — 5,951 days, or about 16.29
years.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;There were 64 releases.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;There were roughly 960 commits.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;From 80 roughly contributors&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Top 3:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Will Kahn-Greene: 462&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;James Socol: 182&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Greg Guthe: 133&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Roughly 5,040 lines of Python code excluding the vendored html5lib.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I was maintainer from October 2015 to now--that's a little under 11 years.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It feels weird to end a project that's outlived many of the Mozilla sites and
Python web frameworks it was designed to protect.&lt;/p&gt;


&lt;h2&gt;What happens now?&lt;/h2&gt;
&lt;p&gt;This is the end of the project.&lt;/p&gt;

&lt;a class=&quot;reference external image-reference&quot; href=&quot;https://bluesock.org/~willkg/blog/images/bleach_deprecation.jpg&quot;&gt;
&lt;img alt=&quot;/images/bleach_deprecation.thumbnail.jpg&quot; src=&quot;https://bluesock.org/~willkg/blog/images/bleach_deprecation.thumbnail.jpg&quot; /&gt;
&lt;/a&gt;

&lt;p&gt;Bleach. Last release.&lt;/p&gt;


&lt;p&gt;If you're still using Bleach, I think you have three options:&lt;/p&gt;
&lt;ol class=&quot;arabic simple&quot;&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;End your project.&lt;/strong&gt; Maybe you don't need to be maintaining your thing
anymore? Use Bleach as your reason to exit and do something different with
your time on Earth.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Switch to the sanitizer API.&lt;/strong&gt; Rework your project to use the sanitizer API.&lt;/p&gt;
&lt;ul class=&quot;simple&quot;&gt;
&lt;li&gt;&lt;p&gt;Spec: &lt;a class=&quot;reference external&quot; href=&quot;https://wicg.github.io/sanitizer-api/&quot;&gt;https://wicg.github.io/sanitizer-api/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Docs: &lt;a class=&quot;reference external&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Element/setHTML&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/API/Element/setHTML&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Swap Bleach out for justhtml.&lt;/strong&gt; Emil provided a
&lt;a class=&quot;reference external&quot; href=&quot;https://emilstenstrom.github.io/justhtml/bleach-migration.html&quot;&gt;migration guide&lt;/a&gt;
for switching from Bleach to justhtml.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Good luck with whatever option you choose!&lt;/p&gt;


&lt;h2&gt;Thanks!&lt;/h2&gt;
&lt;p&gt;Many thanks to &lt;a class=&quot;reference external&quot; href=&quot;https://github.com/jsocol&quot;&gt;James&lt;/a&gt; who created Bleach and
gave it a set of first principles that guided our choices for 16 years.&lt;/p&gt;
&lt;p&gt;Many thanks to &lt;a class=&quot;reference external&quot; href=&quot;https://github.com/g-k&quot;&gt;Greg&lt;/a&gt; who I worked with on Bleach
for a long while and maintained Bleach for several years. Working with Greg was
always easy and his reviews were thoughtful and spot-on.&lt;/p&gt;
&lt;p&gt;Many thanks to &lt;a class=&quot;reference external&quot; href=&quot;https://github.com/EmilStenstrom&quot;&gt;Emil&lt;/a&gt; who was
a contributor to Bleach for a long while and created
&lt;a class=&quot;reference external&quot; href=&quot;https://emilstenstrom.github.io/justhtml/&quot;&gt;justhtml&lt;/a&gt;
providing Bleach users a migration path.&lt;/p&gt;
&lt;p&gt;Many thanks to &lt;a class=&quot;reference external&quot; href=&quot;https://github.com/jvanasco&quot;&gt;Jonathan&lt;/a&gt; who, over the years,
provided a lot of insight into how best to solve some of Bleach's more
squirrely problems.&lt;/p&gt;
&lt;p&gt;Many thanks to &lt;a class=&quot;reference external&quot; href=&quot;https://github.com/gsnedders&quot;&gt;Sam&lt;/a&gt; who was an indispensible
resource on HTML parsing and sanitizing text in the context of HTML.&lt;/p&gt;
&lt;p&gt;Many thanks to all the users and contributors of Bleach!&lt;/p&gt;


&lt;h2&gt;Where to go for more&lt;/h2&gt;
&lt;p&gt;For more specifics on this release, see here:
&lt;a class=&quot;reference external&quot; href=&quot;https://bleach.readthedocs.io/en/latest/changes.html#version-6-4-0-june-5th-2026&quot;&gt;https://bleach.readthedocs.io/en/latest/changes.html#version-6-4-0-june-5th-2026&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Documentation and quickstart here:
&lt;a class=&quot;reference external&quot; href=&quot;https://bleach.readthedocs.io/en/latest/&quot;&gt;https://bleach.readthedocs.io/en/latest/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Source code and issue tracker here:
&lt;a class=&quot;reference external&quot; href=&quot;https://github.com/mozilla/bleach/&quot;&gt;https://github.com/mozilla/bleach/&lt;/a&gt;&lt;/p&gt;</content:encoded>
	<dc:date>2026-06-05T13:00:00+00:00</dc:date>
</item>
<item rdf:about="https://realpython.com/podcasts/rpp/298/">
	<title>Real Python: The Real Python Podcast – Episode #298: Reducing the Size of Python Docker Containers</title>
	<link>https://realpython.com/podcasts/rpp/298/</link>
	<content:encoded>&lt;p&gt;How can you easily reduce the size of a Python Docker container? What are the exceptions you should catch in your code? Christopher Trudeau is back on the show this week with another batch of PyCoder's Weekly articles and projects.&lt;/p&gt;
        &lt;hr /&gt;
        &lt;p&gt;&lt;em&gt;[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short &amp;amp; sweet Python Trick delivered to your inbox every couple of days. &lt;a href=&quot;https://realpython.com/python-tricks/?utm_source=realpython&amp;utm_medium=rss&amp;utm_campaign=footer&quot;&gt;&amp;gt;&amp;gt; Click here to learn more and see examples&lt;/a&gt; ]&lt;/em&gt;&lt;/p&gt;</content:encoded>
	<dc:date>2026-06-05T12:00:00+00:00</dc:date>
</item>
<item rdf:about="https://www.europython-society.org/europython-society-at-pycon-us-2026/">
	<title>EuroPython Society: EuroPython Society at PyCon US 2026</title>
	<link>https://www.europython-society.org/europython-society-at-pycon-us-2026/</link>
	<content:encoded>&lt;p&gt;This year we were back at PyCon US, and this time in sunny Long Beach, California.&lt;/p&gt;&lt;p&gt;We had a booth again, which has quickly become one of our favourite parts of the trip. It&amp;amp;aposs such a great chance to meet folks from other Python communities, catch up with old friends, and put faces to names we&amp;amp;aposve only seen online. People stopped by to chat about EuroPython, pick up stickers, ask about our grants programme, and share what their own local communities are up to. We loved every minute of it.&lt;/p&gt;&lt;img src=&quot;https://www.europython-society.org/content/images/2026/06/us1.jpg&quot; class=&quot;kg-image&quot; alt=&quot;alt&quot; width=&quot;1069&quot; height=&quot;802&quot; /&gt;&lt;img src=&quot;https://www.europython-society.org/content/images/2026/06/us2.jpg&quot; class=&quot;kg-image&quot; alt=&quot;alt&quot; width=&quot;803&quot; height=&quot;802&quot; /&gt;&lt;p&gt;We also filmed some shorts at the booth, which will be up on our YouTube channel soon! Keep an eye out, there are some lovely conversations in there.&lt;/p&gt;&lt;p&gt;Since EuroPython is celebrating its 25th anniversary this year, we took the chance to talk to community members who have been to many, many EuroPythons over the years. Hearing their stories, the editions they remember most, the friendships that started at one of our conferences, was genuinely moving. It&amp;amp;aposs a good reminder of how much history this community carries with it, and how much of it has been built by people simply showing up year after year.&lt;/p&gt;&lt;p&gt;PyCon US was also where some wonderful people from our community received well-deserved recognition. A huge congratulations to Maria Jose Montreas-Colina, who received an Outstanding PyLady Award for her work with PyLadies and the wider community. Maria is part of our team and helps look after PyLadies and community matters at EuroPython. Congratulations also to Rodrigo Gir&amp;#xE3;o Serr&amp;#xE3;o for receiving the Community Service Award for his contributions to the community. Rodrigo works on our programme and sprints.&lt;/p&gt;&lt;p&gt;Thank you both for everything you do. &amp;#x1F49B;&lt;/p&gt;&lt;img src=&quot;https://www.europython-society.org/content/images/2026/06/us3.jpg&quot; class=&quot;kg-image&quot; alt=&quot;alt&quot; width=&quot;1069&quot; height=&quot;802&quot; /&gt;&lt;img src=&quot;https://www.europython-society.org/content/images/2026/06/us4.jpg&quot; class=&quot;kg-image&quot; alt=&quot;alt&quot; width=&quot;1069&quot; height=&quot;802&quot; /&gt;&lt;p&gt;Long Beach itself was a lovely city. Palm trees, warm weather, the ocean nearby. A very different vibe from the usual conference cities, and a really lovely backdrop for a week of Python.&lt;/p&gt;&lt;p&gt;A big thank you to the PyCon US organisers for having us, and for making space for the wider Python world to come together. And a thank you to everyone who stopped by the booth to say hello, it was a pleasure meeting you.&lt;/p&gt;&lt;p&gt;See you next year, and we hope to see many of you in Krak&amp;#xF3;w for EuroPython 2026!&lt;/p&gt;&lt;img src=&quot;https://www.europython-society.org/content/images/2026/06/us8.jpg&quot; class=&quot;kg-image&quot; alt=&quot;alt&quot; width=&quot;1069&quot; height=&quot;802&quot; /&gt;&lt;img src=&quot;https://www.europython-society.org/content/images/2026/06/us6.jpg&quot; class=&quot;kg-image&quot; alt=&quot;alt&quot; width=&quot;1426&quot; height=&quot;802&quot; /&gt;</content:encoded>
	<dc:date>2026-06-05T09:28:38+00:00</dc:date>
</item>
<item rdf:about="https://belderbos.dev/blog/htmx-hx-swap-oob-django/">
	<title>Bob Belderbos: How to Update Multiple Page Elements from One htmx Request</title>
	<link>https://belderbos.dev/blog/htmx-hx-swap-oob-django/</link>
	<content:encoded>&lt;p&gt;A button submits code, tests run, feedback appears. Standard htmx. But the submissions dropdown stays stale; the new submission is in the database, just not in the dropdown. One request, two elements to update.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;the-problem-one-request-two-things-to-update&quot;&gt;The problem: one request, two things to update&lt;/h2&gt;
&lt;p&gt;On our &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://rustplatform.com/&quot;&gt;Rust platform&lt;/a&gt;, each exercise page has a &quot;Run Tests&quot; button. It posts the editor code to a Django view, which compiles and runs the tests, then swaps a pass/fail panel into a &lt;code&gt;#feedback&lt;/code&gt; div.&lt;/p&gt;
&lt;p&gt;Next to the editor there is a dropdown of your past submissions. Run the tests, and a new submission gets saved server-side. But the dropdown stayed stale until you reloaded the page. The new submission was there in the database, just not in the &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So now I have one request that needs to update two unrelated parts of the page: the feedback panel (the htmx target) and the submissions dropdown (somewhere else entirely in the DOM).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://belderbos.dev/images/htmx-oob.png&quot; alt=&quot;Rust platform exercise page showing feedback panel and submissions dropdown&quot; /&gt;&lt;/p&gt;
&lt;p&gt;First instinct: write JavaScript to read the response, build a new &lt;code&gt;&amp;lt;option&amp;gt;&lt;/code&gt;, prepend it to the select. That works until you remember the dropdown also enforces a max number of submissions, drops duplicates, and orders newest-first. Replicate that logic in the browser and you now have two sources of truth that drift apart the first time you change the server rule.&lt;/p&gt;
&lt;h2 id=&quot;htmx-out-of-band-swaps&quot;&gt;Htmx out-of-band swaps&lt;/h2&gt;
&lt;p&gt;htmx has a feature for exactly this: &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://htmx.org/attributes/hx-swap-oob/&quot;&gt;out-of-band swaps&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The hx-swap-oob attribute allows you to specify that some content in a response should be swapped into the DOM somewhere other than the target, that is &quot;Out of Band&quot;. This allows you to piggyback updates to other element updates on a response.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;htmx swaps any element carrying &lt;code&gt;hx-swap-oob&lt;/code&gt; into its matching target on the page, separately from the main swap. One response, many updates.&lt;/p&gt;
&lt;p&gt;First I pulled the submissions dropdown options into a partial so the page and the view render them identically:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;&amp;lt;!-- _submission_options.html --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;option&lt;/span&gt;&lt;span class=&quot;z-entity&quot;&gt; disabled selected&lt;/span&gt;&lt;span&gt;&amp;gt;Submissions / Reset&amp;lt;/&lt;/span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;option&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{% for submission in submissions %}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;option&lt;/span&gt;&lt;span class=&quot;z-entity&quot;&gt; value&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;{{ submission.unique_hash }}&amp;quot;&lt;/span&gt;&lt;span&gt;&amp;gt;{{ submission.created_at|date:&amp;quot;Y-m-d H:i&amp;quot; }} {% if submission.ok %}(OK){% else %}(Failed){% endif %}&amp;lt;/&lt;/span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;option&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{% endfor %}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;option&lt;/span&gt;&lt;span class=&quot;z-entity&quot;&gt; value&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;reset&amp;quot;&lt;/span&gt;&lt;span&gt;&amp;gt;Reset&amp;lt;/&lt;/span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;option&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The page includes it inside the &lt;code&gt;&amp;lt;select id=&quot;submissions&quot;&amp;gt;&lt;/code&gt;. The view renders the same partial and tags it for an out-of-band swap:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;from&lt;/span&gt;&lt;span&gt; django.template.loader&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; import&lt;/span&gt;&lt;span&gt; render_to_string&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;from&lt;/span&gt;&lt;span&gt; django.utils.html&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; import&lt;/span&gt;&lt;span&gt; escape&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; validate&lt;/span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;(request):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;    # ... run the tests, save the submission ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    submissions&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;/span&gt;&lt;span&gt; Submission.objects.filter(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable&quot;&gt;        exercise&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;/span&gt;&lt;span&gt;exercise,&lt;/span&gt;&lt;span class=&quot;z-variable&quot;&gt; user&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ).order_by(&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;-created_at&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    options&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;/span&gt;&lt;span&gt; render_to_string(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;        &amp;quot;_submission_options.html&amp;quot;&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;submissions&amp;quot;&lt;/span&gt;&lt;span&gt;: submissions}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;/span&gt;&lt;span&gt; HttpResponse(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;        f&lt;/span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;        &amp;lt;div class=&amp;quot;...&amp;quot;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt;{&lt;/span&gt;&lt;span&gt;message&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;        &amp;lt;pre&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt;{&lt;/span&gt;&lt;span&gt;escape(output)&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;        &amp;lt;div hx-swap-oob=&amp;quot;innerHTML:#submissions&amp;quot;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt;{&lt;/span&gt;&lt;span&gt;options&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first part of the response swaps into &lt;code&gt;#feedback&lt;/code&gt; as usual. htmx spots the &lt;code&gt;hx-swap-oob&lt;/code&gt; element, pulls it out, and applies it to &lt;code&gt;#submissions&lt;/code&gt; instead. The button HTML only knows about &lt;code&gt;#feedback&lt;/code&gt;. The view decides what else to update. Whoever owns the data controls how it renders.&lt;/p&gt;
&lt;h2 id=&quot;a-second-example-progress-bars-that-update-themselves&quot;&gt;A second example: progress bars that update themselves&lt;/h2&gt;
&lt;p&gt;On the Python platform the same trick drives the learning-path progress widget. Passing an exercise recomputes your progress along every path it belongs to and swaps the bars into a sidebar, from the same request that renders the pass/fail panel:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;if&lt;/span&gt;&lt;span&gt; ok:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    paths_html&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    for&lt;/span&gt;&lt;span&gt; path&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; in&lt;/span&gt;&lt;span&gt; bite.bite_paths.prefetch_related(&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;bites&amp;quot;&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;        # ... compute completed / total / pct for this path ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        paths_html&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +=&lt;/span&gt;&lt;span&gt; render_progress_bar(path, completed, total, pct)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    extra_html&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;        f&lt;/span&gt;&lt;span class=&quot;z-string&quot;&gt;'&amp;lt;div id=&amp;quot;learning-paths-progress&amp;quot; hx-swap-oob=&amp;quot;innerHTML&amp;quot;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt;{&lt;/span&gt;&lt;span&gt;paths_html&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;lt;/div&amp;gt;'&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The two examples aim at their targets differently. The progress widget uses a bare &lt;code&gt;hx-swap-oob=&quot;innerHTML&quot;&lt;/code&gt;: htmx swaps the fragment into whatever element already shares its &lt;code&gt;id&lt;/code&gt;. The dropdown uses the selector form, &lt;code&gt;hx-swap-oob=&quot;innerHTML:#submissions&quot;&lt;/code&gt;, so the carrier &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; can target the &lt;code&gt;&amp;lt;select id=&quot;submissions&quot;&amp;gt;&lt;/code&gt; without needing to share its id.&lt;/p&gt;
&lt;p&gt;Use &lt;code&gt;innerHTML&lt;/code&gt; instead of the default &lt;code&gt;hx-swap-oob=&quot;true&quot;&lt;/code&gt;. &lt;code&gt;true&lt;/code&gt; replaces the whole element (its &lt;code&gt;outerHTML&lt;/code&gt;), which for the &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; throws away the htmx listener attached to it. &lt;code&gt;innerHTML&lt;/code&gt; keeps the element and swaps only its children, so the listener survives and the options refresh underneath it.&lt;/p&gt;
&lt;p&gt;Here's the progress bars before and after passing an exercise. The bars update via out-of-band swap from the same response that renders the pass/fail feedback:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://belderbos.dev/images/pybites-platform-before-pass.png&quot; alt=&quot;PyBites platform showing progress bars before passing an exercise&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://belderbos.dev/images/pybites-platform-after-pass.png&quot; alt=&quot;PyBites platform showing updated progress bars after passing an exercise&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You might spot the submissions dropdown in these shots and wonder why it is not OOB-swapped here too, like on the Rust platform above. It is the boundary of the technique: OOB swap is solid when you replace an element's whole inner content (&lt;code&gt;innerHTML:#submissions&lt;/code&gt;), but inserting a single new &lt;code&gt;&amp;lt;option&amp;gt;&lt;/code&gt; means wrapping it in a &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; so the browser does not strip it, and htmx 2.x handled that case unreliably. So here the one-option insert runs through a small &lt;code&gt;htmx:afterSwap&lt;/code&gt; listener instead. The view writes the new submission into a hidden div, and the listener reads it and prepends the &lt;code&gt;&amp;lt;option&amp;gt;&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;document.&lt;/span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;htmx:afterSwap&amp;quot;&lt;/span&gt;&lt;span&gt;, ()&lt;/span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;  const&lt;/span&gt;&lt;span class=&quot;z-variable z-other z-constant&quot;&gt; newSub&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; document.&lt;/span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;getElementById&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;new-submission&amp;quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;newSub)&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; return&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;  const&lt;/span&gt;&lt;span class=&quot;z-variable z-other z-constant&quot;&gt; select&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; document.&lt;/span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;getElementById&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;submissions&amp;quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;  const&lt;/span&gt;&lt;span class=&quot;z-variable z-other z-constant&quot;&gt; opt&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; document.&lt;/span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;createElement&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;option&amp;quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;  opt.value&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; newSub.dataset.hash;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;  opt.textContent&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; newSub.dataset.label;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;  select.&lt;/span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;insertBefore&lt;/span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;(opt, select.children[&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;/span&gt;&lt;span&gt;]);&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt; // after the placeholder&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;  select.value&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; opt.value;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One honest cost: this adds a query. After saving, the view re-fetches the submissions to render the partial. The alternative is re-implementing state changes in pure JavaScript, creating behavior in two places. With out-of-band swaps you drive the logic from the view, all in one place. One more query, but less code and a more maintainable solution.&lt;/p&gt;
&lt;p&gt;Whoever fetches the data should render it. Keep the query and the template together.&lt;/p&gt;
&lt;p&gt;For more on hypermedia-driven applications, see this great book: &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://hypermedia.systems&quot;&gt;Hypermedia Systems&lt;/a&gt;.&lt;/p&gt;</content:encoded>
	<dc:date>2026-06-05T00:00:00+00:00</dc:date>
</item>
<item rdf:about="https://sethmlarson.dev/is-the-donut-from-super-smash-bros-brawl-a-mister-donut?utm_campaign=rss">
	<title>Seth Michael Larson: Is the Super Smash Bros. Brawl donut from Mister Donut?</title>
	<link>https://sethmlarson.dev/is-the-donut-from-super-smash-bros-brawl-a-mister-donut?utm_campaign=rss</link>
	<content:encoded>&lt;p&gt;Happy &lt;a href=&quot;https://en.wikipedia.org/wiki/National_Donut_Day&quot;&gt;Donut Day&lt;/a&gt; (and &lt;a href=&quot;https://sethmlarson.dev/fedi-donut-friday&quot;&gt;#FediDonutFriday&lt;/a&gt;) to those who celebrate! 🍩 
&lt;a href=&quot;https://www.presentandcorrect.com/&quot;&gt;Present and Correct&lt;/a&gt; shared a link to the &lt;a href=&quot;https://www.misterdonut.jp/enjoy/zukan/donut/y1971.html&quot;&gt;Mister Donut
museum on Bluesky&lt;/a&gt; and upon clicking through
I was greeted with a familiar face: a chocolate ring donut.&lt;/p&gt;

&lt;p&gt;Strangely, I've seen this chocolate ring donut before: from the &lt;a href=&quot;https://sethmlarson.dev/food-jpegs-in-super-smash-bros-and-kirby-air-riders&quot;&gt;hours
staring at sprite-sheets&lt;/a&gt; from the Super Smash Bros.
and Kirby Air Riders franchises. That donut looked just
like the one from Super Smash Bros. Brawl.&lt;/p&gt;

&lt;p&gt;“But Seth”, I hear you say, “chocolate ring donuts all look the same
anyway!” Maybe... &lt;em&gt;and yet...&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;center&gt;
&lt;img width=&quot;140px&quot; src=&quot;https://storage.googleapis.com/sethmlarson-dev-static-assets/food-jpegs/ssbb-donut.png&quot; /&gt;
&lt;img width=&quot;128px&quot; src=&quot;https://www.misterdonut.jp/enjoy/zukan/donut/img/00053.jpg&quot; /&gt;
&lt;/center&gt;&lt;/p&gt;

&lt;p&gt;Funnily enough, the &lt;a href=&quot;https://github.com/Render96/Render96Wiki&quot;&gt;Render96 wiki&lt;/a&gt;, which collects origins for
artwork such as Super Smash Bros. Melee and Brawl, lists
&lt;a href=&quot;https://github.com/Render96/Render96Wiki/wiki/Super-Smash-Bros.-Melee#currently-missing&quot;&gt;the donut&lt;/a&gt; as one of the few foods where the origin is not known.
Could this donut &lt;em&gt;also&lt;/em&gt; be a Mister Donut? We'll probably never know!&lt;/p&gt;
&lt;br /&gt;&lt;hr /&gt;&lt;p&gt;Thanks for keeping RSS alive! ♥ What to do next? Share your thoughts with me on &lt;a href=&quot;https://mastodon.social/@sethmlarson&quot;&gt;Mastodon&lt;/a&gt;, &lt;a href=&quot;https://bsky.app/profile/sethmlarson.dev&quot;&gt;Bluesky&lt;/a&gt;, or &lt;a href=&quot;mailto:sethmichaellarson@gmail.com&quot;&gt;email&lt;/a&gt;. I try to reply to everyone!Browse the &lt;a href=&quot;https://sethmlarson.dev/&quot;&gt;blog archive&lt;/a&gt;. Check out my &lt;a href=&quot;https://sethmlarson.dev/blogroll&quot;&gt;blogroll&lt;/a&gt;. Or maybe go outside (best option)?&lt;/p&gt;&lt;hr /&gt;&lt;br /&gt;</content:encoded>
	<dc:date>2026-06-05T00:00:00+00:00</dc:date>
</item>
<item rdf:about="https://www.thepythoncodingstack.com/p/down-the-iterator-rabbit-hole-python">
	<title>The Python Coding Stack: Down The Iterator Rabbit Hole</title>
	<link>https://www.thepythoncodingstack.com/p/down-the-iterator-rabbit-hole-python</link>
	<content:encoded>&lt;p&gt;You know that street game where the performer (con artist?) has three opaque cups and a small ball. He places the cups upside down on the table, with the ball under one of the cups. He quickly shuffles the cups around and then asks the player to guess which cup has the ball. You&amp;#8217;ve seen the game on TV, even if you&amp;#8217;ve not seen it in real life.&lt;/p&gt;&lt;p&gt;Following what&amp;#8217;s happening when you have a chain of iterators in Python can feel like playing that game. But, unlike the street game, there are no scams when you&amp;#8217;re playing the iterator game. Let&amp;#8217;s make sure you&amp;#8217;ll always win.&lt;/p&gt;&lt;p&gt;I&amp;#8217;ll keep this article short. I wrote many articles about iterables and iterators. If you need to refresh your memory, have a look at &lt;a href=&quot;https://www.thepythoncodingstack.com/p/the-anatomy-of-a-for-loop&quot;&gt;The Anatomy of a for Loop&lt;/a&gt; and &lt;a href=&quot;https://www.thepythoncodingstack.com/p/iterators-in-python-data-structure-6&quot;&gt;A One-Way Stream of Data &amp;#8226; Iterators in Python (Data Structure Categories #6)&lt;/a&gt;.&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;Follow The Data in a Chain of Iterators&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;Let&amp;#8217;s keep the example simple. Start with this list in a REPL session:&lt;/p&gt;&lt;div class=&quot;captioned-image-container&quot;&gt;&lt;a class=&quot;image-link image2&quot; target=&quot;_blank&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!bqeu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F946268b4-b00d-4e66-82b0-cd9ad1e673dc_1200x168.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!bqeu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F946268b4-b00d-4e66-82b0-cd9ad1e673dc_1200x168.png&quot; width=&quot;1200&quot; height=&quot;168&quot; src=&quot;src&quot; /&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;All code blocks are available in text format at the end of this article &amp;#8226; #1&lt;/div&gt;&lt;p&gt;A list is iterable. You can create an iterator from any iterable. Let&amp;#8217;s create an iterator from this list:&lt;/p&gt;&lt;div class=&quot;captioned-image-container&quot;&gt;&lt;a class=&quot;image-link image2&quot; target=&quot;_blank&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!0vvp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a484c85-613d-458b-a515-0a18b8a22f7d_1200x210.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!0vvp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a484c85-613d-458b-a515-0a18b8a22f7d_1200x210.png&quot; width=&quot;1200&quot; height=&quot;210&quot; src=&quot;src&quot; /&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;#2&lt;/div&gt;&lt;p&gt;The built-in function &lt;code&gt;iter()&lt;/code&gt; creates an iterator from an iterable. Iterators don&amp;#8217;t contain data. They don&amp;#8217;t create copies of the data. They&amp;#8217;re lightweight objects that create a stream. They&amp;#8217;ll fetch data from the original source, which is the list &lt;code&gt;boring_numbers&lt;/code&gt; in this case, as and when needed.&lt;/p&gt;&lt;p&gt;Iterators can only fetch an item once. So, they&amp;#8217;re a one-way stream. Once you use an item, it&amp;#8217;s gone from the iterator &amp;#8211; but not from the original list, which remains unchanged.&lt;/p&gt;&lt;p&gt;Therefore, &lt;code&gt;first_iter&lt;/code&gt; is an iterator that relies on data from the list &lt;code&gt;boring_numbers&lt;/code&gt;. But let&amp;#8217;s not fetch any items from the &lt;code&gt;first_iter&lt;/code&gt; iterator. Not yet, anyway.&lt;/p&gt;&lt;p&gt;Create a second iterator. This time, you&amp;#8217;ll use a generator expression. Generators are iterators, so you create a second iterator with this code:&lt;/p&gt;&lt;div class=&quot;captioned-image-container&quot;&gt;&lt;a class=&quot;image-link image2&quot; target=&quot;_blank&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!gh5b!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc69303a5-8ecb-4676-b2ec-e8fe71412506_1200x210.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!gh5b!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc69303a5-8ecb-4676-b2ec-e8fe71412506_1200x210.png&quot; width=&quot;1200&quot; height=&quot;210&quot; src=&quot;src&quot; /&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;#3&lt;/div&gt;&lt;p&gt;Note that the expression on the right-hand side of the equals sign is enclosed in parentheses &amp;#8211; the round ones, to be clear. This is a generator expression, which creates a generator iterator. Read &lt;a href=&quot;https://www.thepythoncodingstack.com/p/generators-in-python-data-structure-7&quot;&gt;Pay As You Go &amp;#8226; Generate Data Using Generators (Data Structure Categories #7)&lt;/a&gt; for more on generators.&lt;/p&gt;&lt;p&gt;As we said, generators are iterators.&lt;/p&gt;&lt;p&gt;The &lt;code&gt;second_iter&lt;/code&gt; iterator generates data from &lt;code&gt;first_iter&lt;/code&gt;, which is itself an iterator. Iterators are also iterable, which is why you can use them directly in a &lt;code&gt;for&lt;/code&gt; clause or anywhere else you&amp;#8217;d generally use an iterable. The &lt;code&gt;second_iter&lt;/code&gt; iterator will yield the values as floats. But you&amp;#8217;ve not yielded any value from this iterator either. Not yet.&lt;/p&gt;&lt;p&gt;Let&amp;#8217;s go a step further and create a third iterator, which is also a generator in this case. You build this third iterator from the second one, &lt;code&gt;second_iter&lt;/code&gt;:&lt;/p&gt;&lt;div class=&quot;captioned-image-container&quot;&gt;&lt;a class=&quot;image-link image2&quot; target=&quot;_blank&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!MiBd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46aa982e-6786-4085-91d9-80ebcaf38bbd_1200x210.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!MiBd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46aa982e-6786-4085-91d9-80ebcaf38bbd_1200x210.png&quot; width=&quot;1200&quot; height=&quot;210&quot; src=&quot;src&quot; /&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;#4&lt;/div&gt;&lt;p&gt;The generator iterator &lt;code&gt;third_iter&lt;/code&gt; yields the sum of &lt;code&gt;0.5&lt;/code&gt; and the value yielded by &lt;code&gt;second_iter&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Incidentally, I used a &amp;#8220;standard&amp;#8221; iterator and two generator iterators in this example. However, for the journey we&amp;#8217;re following in this article, it doesn&amp;#8217;t matter whether we&amp;#8217;re using a basic iterator or a generator iterator. If you prefer, you can repeat this exercise with iterators you get from &lt;code&gt;iter()&lt;/code&gt; directly.&lt;/p&gt;&lt;p class=&quot;button-wrapper&quot;&gt;&lt;a class=&quot;button primary&quot; href=&quot;https://buy.stripe.com/00g3de2iGdgg4gg7su&quot;&gt;&lt;span&gt;Support The Python Coding Stack&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;Don&amp;#8217;t Blink &amp;#8226; Follow the Data&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;You started with a list called &lt;code&gt;boring_numbers&lt;/code&gt;. This data structure contains* the data. It&amp;#8217;s where the data lives. We&amp;#8217;ll be following the data in this section. So it&amp;#8217;s important to know where it&amp;#8217;s stored!&lt;/p&gt;&lt;div&gt;&lt;hr /&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;*Note: Lists, like all data structures, don&amp;#8217;t really contain data in the purest sense of the word. See &lt;a href=&quot;https://www.thepythoncodingstack.com/p/whats-really-in-a-python-list&quot;&gt;What&amp;#8217;s In A List&amp;#8212;Yes, But What&amp;#8217;s Really In A Python List&lt;/a&gt; for more on this. But in general, it&amp;#8217;s fine to talk about a list &amp;#8216;containing&amp;#8217; items of data.&lt;/em&gt;&lt;/p&gt;&lt;div&gt;&lt;hr /&gt;&lt;/div&gt;&lt;p&gt;You then create three iterators. The first uses data from &lt;code&gt;boring_numbers&lt;/code&gt;. The second iterator uses data from the first. And the third iterator uses data from the second.&lt;/p&gt;&lt;p&gt;But you haven&amp;#8217;t tried to fetch any value from any of the iterators yet.&lt;/p&gt;&lt;p&gt;Let&amp;#8217;s look at what each iterator is doing at the moment before you fetch any values. The first iterator, &lt;code&gt;first_iter&lt;/code&gt;, is pointing at the first item in &lt;code&gt;boring_numbers&lt;/code&gt;. It&amp;#8217;s ready to read this value and yield it.&lt;/p&gt;&lt;p&gt;The second iterator, &lt;code&gt;second_iter&lt;/code&gt;, is pointing at the first item in &lt;code&gt;first_iter&lt;/code&gt;. But &lt;code&gt;first_iter&lt;/code&gt; doesn&amp;#8217;t have any data. Iterators don&amp;#8217;t have their own data. But that&amp;#8217;s OK. Whenever &lt;code&gt;second_iter&lt;/code&gt; needs to fetch the value, it will ask &lt;code&gt;first_iter&lt;/code&gt; to fetch and yield its &amp;#8220;first&amp;#8221; value. I put &amp;#8220;first&amp;#8221; in quotation marks because you&amp;#8217;ll see later that this may or may not be the first value.&lt;/p&gt;&lt;p&gt;Finally, &lt;code&gt;third_iter&lt;/code&gt; is pointing at the first item in &lt;code&gt;second_iter&lt;/code&gt;. The same logic applies. When &lt;code&gt;third_iter&lt;/code&gt; needs the first item, it will ask &lt;code&gt;second_iter&lt;/code&gt; for its &amp;#8220;first&amp;#8221; item, and &lt;code&gt;second_iter&lt;/code&gt; will need to ask &lt;code&gt;first_iter&lt;/code&gt; for its &amp;#8220;first&amp;#8221; item. And &lt;code&gt;first_iter&lt;/code&gt; is pointing at the first item in the list &lt;code&gt;boring_numbers&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Are you with me? Let&amp;#8217;s complicate things a bit&amp;#8230;&lt;/p&gt;&lt;p&gt;Note how your code so far includes the following lines:&lt;/p&gt;&lt;div class=&quot;captioned-image-container&quot;&gt;&lt;a class=&quot;image-link image2&quot; target=&quot;_blank&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!QrUQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6a8d744-1eb6-4ed5-aeff-7ce7b7a291c0_1200x294.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!QrUQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6a8d744-1eb6-4ed5-aeff-7ce7b7a291c0_1200x294.png&quot; width=&quot;1200&quot; height=&quot;294&quot; src=&quot;src&quot; /&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;#5&lt;/div&gt;&lt;p&gt;None of the iterators has yielded any value. For now.&lt;/p&gt;&lt;p&gt;Let&amp;#8217;s jumble things up and start by fetching the first value from &lt;code&gt;second_iter&lt;/code&gt;:&lt;/p&gt;&lt;div class=&quot;captioned-image-container&quot;&gt;&lt;a class=&quot;image-link image2&quot; target=&quot;_blank&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!cSUf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F603a95ea-8bc3-4566-ac9c-66e579caf0cc_1200x252.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!cSUf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F603a95ea-8bc3-4566-ac9c-66e579caf0cc_1200x252.png&quot; width=&quot;1200&quot; height=&quot;252&quot; src=&quot;src&quot; /&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;#6&lt;/div&gt;&lt;p&gt;You ask for the next value in &lt;code&gt;second_iter&lt;/code&gt;, which is the first one since you haven&amp;#8217;t yielded any values yet.&lt;/p&gt;&lt;p&gt;As you&amp;#8217;ve seen earlier, &lt;code&gt;second_iter&lt;/code&gt; needs the first value from &lt;code&gt;first_iter&lt;/code&gt;. So, behind the scenes, Python calls &lt;code&gt;next(first_iter)&lt;/code&gt;, which yields the first item from &lt;code&gt;boring_numbers&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;So, &lt;code&gt;first_iter&lt;/code&gt; reads the first value from &lt;code&gt;boring_numbers&lt;/code&gt;, which is the integer &lt;code&gt;1&lt;/code&gt;, and it yields it to &lt;code&gt;second_iter&lt;/code&gt;, which then yields the transformed version to the REPL as the return value of &lt;code&gt;next(second_iter)&lt;/code&gt;. That&amp;#8217;s why the output is the float &lt;code&gt;1.0&lt;/code&gt;. The first iterator, &lt;code&gt;first_iter&lt;/code&gt;, now moves to point at the second item in &lt;code&gt;boring_numbers&lt;/code&gt;, ready for when it&amp;#8217;s needed.&lt;/p&gt;&lt;p&gt;Note that &lt;code&gt;boring_numbers&lt;/code&gt; doesn&amp;#8217;t change in this process. The first item in &lt;code&gt;boring_numbers&lt;/code&gt; remains there. It doesn&amp;#8217;t disappear.&lt;/p&gt;&lt;p&gt;So far, so good?&lt;/p&gt;&lt;p&gt;Continue in the same REPL session and try the following:&lt;/p&gt;&lt;div class=&quot;captioned-image-container&quot;&gt;&lt;a class=&quot;image-link image2&quot; target=&quot;_blank&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!ROlh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bb21a58-a80e-404e-9d5d-5921f2b2e278_1200x252.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!ROlh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bb21a58-a80e-404e-9d5d-5921f2b2e278_1200x252.png&quot; width=&quot;1200&quot; height=&quot;252&quot; src=&quot;src&quot; /&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;#7&lt;/div&gt;&lt;p&gt;You ask &lt;code&gt;third_iter&lt;/code&gt; to give you its &amp;#8220;next&amp;#8221; value. You haven&amp;#8217;t used &lt;code&gt;third_iter&lt;/code&gt; anywhere so far. So, you might expect it to yield the &amp;#8220;first&amp;#8221; value.&lt;/p&gt;&lt;p&gt;And it does.&lt;/p&gt;&lt;p&gt;But its interpretation of what&amp;#8217;s the &amp;#8220;first&amp;#8221; item may be different to what you expect.&lt;/p&gt;&lt;p&gt;Let&amp;#8217;s follow the data. When you call &lt;code&gt;next(third_iter)&lt;/code&gt;, the third iterator asks &lt;code&gt;second_iter&lt;/code&gt; for its &lt;em&gt;next&lt;/em&gt; item. The second iterator, &lt;code&gt;second_iter&lt;/code&gt;, relies on &lt;code&gt;first_iter&lt;/code&gt;, so it asks &lt;code&gt;first_iter&lt;/code&gt; for its &lt;em&gt;next&lt;/em&gt; item. And &lt;code&gt;first_iter&lt;/code&gt;, as you may recall, is currently pointing at the second item in &lt;code&gt;boring_numbers&lt;/code&gt;, which is the integer &lt;code&gt;2&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;So:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;The first iterator &lt;code&gt;first_iter&lt;/code&gt; gets the integer &lt;code&gt;2&lt;/code&gt; from &lt;code&gt;boring_numbers&lt;/code&gt; and yields it to &lt;code&gt;second_iter&lt;/code&gt;. And &lt;code&gt;first_iter&lt;/code&gt; now points at the third item in &lt;code&gt;boring_numbers&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Then, &lt;code&gt;second_iter&lt;/code&gt; transforms this value into a float and yields &lt;code&gt;2.0&lt;/code&gt; to &lt;code&gt;third_iter&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Finally, &lt;code&gt;third_iter&lt;/code&gt; adds &lt;code&gt;0.5&lt;/code&gt; to this value and yields &lt;code&gt;2.5&lt;/code&gt;, which is what you see displayed in the REPL.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;When you called &lt;code&gt;next(second_iter)&lt;/code&gt; earlier in the code, you used up the first item in &lt;code&gt;second_iter&lt;/code&gt;, which in turn used up the first item in &lt;code&gt;first_iter&lt;/code&gt;. Since this first value is gone and since &lt;code&gt;third_iter&lt;/code&gt; depends on the data yielded by &lt;code&gt;second_iter&lt;/code&gt; and &lt;code&gt;first_iter&lt;/code&gt;, the earlier call to &lt;code&gt;next(second_iter)&lt;/code&gt; also affected the iterator that&amp;#8217;s downstream, &lt;code&gt;third_iter&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;What will happen if you call &lt;code&gt;next(first_iter)&lt;/code&gt; now? Try to follow the data in your head before trying it out or reading on.&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;Have you worked it out?&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;Let&amp;#8217;s run the code:&lt;/p&gt;&lt;div class=&quot;captioned-image-container&quot;&gt;&lt;a class=&quot;image-link image2&quot; target=&quot;_blank&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!Bknm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57a33a2d-b878-40d8-85b7-86e89d89411e_1200x252.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!Bknm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57a33a2d-b878-40d8-85b7-86e89d89411e_1200x252.png&quot; width=&quot;1200&quot; height=&quot;252&quot; src=&quot;src&quot; /&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;#8&lt;/div&gt;&lt;p&gt;Although it&amp;#8217;s the first time you explicitly use &lt;code&gt;first_iter&lt;/code&gt; in your code, you already used two of its values when your code yielded values from iterators downstream. Therefore, the &lt;em&gt;next&lt;/em&gt; item in &lt;code&gt;first_iter&lt;/code&gt; is the third item in &lt;code&gt;boring_numbers&lt;/code&gt;, the integer &lt;code&gt;3&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Let&amp;#8217;s finish with one more expression, still running in the same REPL session:&lt;/p&gt;&lt;div class=&quot;captioned-image-container&quot;&gt;&lt;a class=&quot;image-link image2&quot; target=&quot;_blank&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!s8bc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fd3c45c-e3ff-4874-adcb-fa0ab2493900_1200x252.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!s8bc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6fd3c45c-e3ff-4874-adcb-fa0ab2493900_1200x252.png&quot; width=&quot;1200&quot; height=&quot;252&quot; src=&quot;src&quot; /&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;#9&lt;/div&gt;&lt;p&gt;You call &lt;code&gt;next(third_iter)&lt;/code&gt;, which asks &lt;code&gt;second_iter&lt;/code&gt; for its &lt;em&gt;next&lt;/em&gt; item. And &lt;code&gt;second_iter&lt;/code&gt; asks &lt;code&gt;first_iter&lt;/code&gt; for its &lt;em&gt;next&lt;/em&gt; item. At this stage in the process, &lt;code&gt;first_iter&lt;/code&gt; is pointing at the fourth item in the original source of data, which is the list &lt;code&gt;boring_numbers&lt;/code&gt;. That&amp;#8217;s why the output is &lt;code&gt;4.5&lt;/code&gt;.&lt;/p&gt;&lt;h3&gt;&lt;strong&gt;Independent Iterators&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Consider the following code, which is similar to the one you wrote above but has one extra line:&lt;/p&gt;&lt;div class=&quot;captioned-image-container&quot;&gt;&lt;a class=&quot;image-link image2 is-viewable-img&quot; target=&quot;_blank&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!SapE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4459cfa-8992-4175-8fa1-93794bcfac01_1200x756.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!SapE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4459cfa-8992-4175-8fa1-93794bcfac01_1200x756.png&quot; width=&quot;1200&quot; height=&quot;756&quot; src=&quot;src&quot; /&gt;&lt;div class=&quot;image-link-expand&quot;&gt;&lt;div class=&quot;pencraft pc-display-flex pc-gap-8 pc-reset&quot;&gt;&lt;button tabindex=&quot;0&quot; type=&quot;button&quot; class=&quot;pencraft pc-reset pencraft icon-container restack-image&quot;&gt;&lt;/button&gt;&lt;button tabindex=&quot;0&quot; type=&quot;button&quot; class=&quot;pencraft pc-reset pencraft icon-container view-image&quot;&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;#11&lt;/div&gt;&lt;p&gt;The iterators &lt;code&gt;first_iter&lt;/code&gt; and &lt;code&gt;another_first_iter&lt;/code&gt; both use the same source of data, &lt;code&gt;boring_numbers&lt;/code&gt;. However, they are independent iterators. Note that when you use up some of the elements in &lt;code&gt;first_iter&lt;/code&gt;, the independent &lt;code&gt;another_first_iter&lt;/code&gt; is not affected. The first time you ask for the first item in &lt;code&gt;another_first_iter&lt;/code&gt;, you get the integer &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;Final Words&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;Iterators don&amp;#8217;t contain data. They rely on data that&amp;#8217;s stored elsewhere. But you can have a chain of iterators, each asking the previous one to yield a value. Weird things can happen if you&amp;#8217;re not careful. But now you know how to follow the data when you have a chain of iterators.&lt;/p&gt;&lt;p&gt;As a rule of thumb, if you create an iterator that depends on another iterator, you should only use the final iterator to avoid these issues. So, in the example above, you should only yield values from &lt;code&gt;third_iter&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Have a play with this example and make your own chains of iterators, too. And once you&amp;#8217;re comfortable with this, get ready to be confused again with my next article, which will discuss &lt;code&gt;itertools.tee()&lt;/code&gt;!&lt;/p&gt;&lt;p&gt;And next time you pass by someone in the street offering to let you play the three-cups-and-ball game, don&amp;#8217;t feel overconfident because of your iterator knowledge &amp;#8211; it won&amp;#8217;t help you find the ball.&lt;/p&gt;&lt;div class=&quot;captioned-image-container&quot;&gt;&lt;a class=&quot;image-link image2 is-viewable-img&quot; target=&quot;_blank&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!vA2Z!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a0f16f6-ea62-451b-9915-7c01b45de7f8_2048x1152.webp&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!vA2Z!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a0f16f6-ea62-451b-9915-7c01b45de7f8_2048x1152.webp&quot; width=&quot;1456&quot; height=&quot;819&quot; src=&quot;src&quot; /&gt;&lt;div class=&quot;image-link-expand&quot;&gt;&lt;div class=&quot;pencraft pc-display-flex pc-gap-8 pc-reset&quot;&gt;&lt;button tabindex=&quot;0&quot; type=&quot;button&quot; class=&quot;pencraft pc-reset pencraft icon-container restack-image&quot;&gt;&lt;/button&gt;&lt;button tabindex=&quot;0&quot; type=&quot;button&quot; class=&quot;pencraft pc-reset pencraft icon-container view-image&quot;&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;Code in this article uses Python 3.14&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;The code images used in this article are created using &lt;a href=&quot;https://snappify.cello.so/f4AsFrwgwov&quot;&gt;Snappify&lt;/a&gt;.&lt;/em&gt; &lt;em&gt;[Affiliate link]&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;&lt;a href=&quot;https://www.thepythoncodingstack.com/subscribe&quot;&gt;Join&lt;/a&gt;&lt;/strong&gt;&lt;/em&gt;&lt;strong&gt;&lt;a href=&quot;https://www.thepythoncodingstack.com/subscribe&quot;&gt; The Club&lt;/a&gt;&lt;/strong&gt;&lt;em&gt;, the exclusive area for paid subscribers for &lt;a href=&quot;https://www.thepythoncodingstack.com/s/the-club&quot;&gt;more Python posts&lt;/a&gt;, videos, a members&amp;#8217; forum, and more.&lt;/em&gt;&lt;/p&gt;&lt;p class=&quot;button-wrapper&quot;&gt;&lt;a class=&quot;button primary&quot; href=&quot;https://www.thepythoncodingstack.com/subscribe&quot;&gt;&lt;span&gt;Subscribe now&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;div&gt;&lt;hr /&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;For more Python resources, you can also visit&lt;/em&gt; &lt;em&gt;&lt;a href=&quot;https://realpython.com?utm_source=the-python-coding-stack&quot;&gt;Real Python&lt;/a&gt;&amp;#8212;you may even stumble on one of my own articles or courses there!&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;Also, are you interested in technical writing? You&amp;#8217;d like to make your own writing more narrative, more engaging, more memorable? Have a look at&lt;/em&gt; &lt;em&gt;&lt;a href=&quot;http://stephengruppetta.com/breaking-the-rules&quot;&gt;Breaking the Rules&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;&lt;em&gt;And you can find out more about me at&lt;/em&gt; &lt;em&gt;&lt;a href=&quot;https://stephengruppetta.com?utm_source=the-python-coding-stack&quot;&gt;stephengruppetta.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Further reading related to this article&amp;#8217;s topic:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;a href=&quot;https://www.thepythoncodingstack.com/p/the-anatomy-of-a-for-loop&quot;&gt;The Anatomy of a for Loop&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href=&quot;https://www.thepythoncodingstack.com/p/iterators-in-python-data-structure-6&quot;&gt;A One-Way Stream of Data &amp;#8226; Iterators in Python (Data Structure Categories #6)&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href=&quot;https://www.thepythoncodingstack.com/p/generators-in-python-data-structure-7&quot;&gt;Pay As You Go &amp;#8226; Generate Data Using Generators (Data Structure Categories #7)&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href=&quot;https://www.thepythoncodingstack.com/p/whats-really-in-a-python-list&quot;&gt;What&amp;#8217;s In A List&amp;#8212;Yes, But What&amp;#8217;s &lt;/a&gt;&lt;em&gt;&lt;a href=&quot;https://www.thepythoncodingstack.com/p/whats-really-in-a-python-list&quot;&gt;Really&lt;/a&gt;&lt;/em&gt;&lt;a href=&quot;https://www.thepythoncodingstack.com/p/whats-really-in-a-python-list&quot;&gt; In A Python List&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;hr /&gt;&lt;/div&gt;&lt;h2&gt;&lt;strong&gt;Appendix: Code Blocks&lt;/strong&gt;&lt;/h2&gt;&lt;h5&gt;&lt;strong&gt;Code Block #1&lt;/strong&gt;&lt;/h5&gt;&lt;pre&gt;&lt;code&gt;boring_numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]&lt;/code&gt;&lt;/pre&gt;&lt;h5&gt;&lt;strong&gt;Code Block #2&lt;/strong&gt;&lt;/h5&gt;&lt;pre&gt;&lt;code&gt;# ...
first_iter = iter(boring_numbers)&lt;/code&gt;&lt;/pre&gt;&lt;h5&gt;&lt;strong&gt;Code Block #3&lt;/strong&gt;&lt;/h5&gt;&lt;pre&gt;&lt;code&gt;# ...
second_iter = (float(number) for number in first_iter)&lt;/code&gt;&lt;/pre&gt;&lt;h5&gt;&lt;strong&gt;Code Block #4&lt;/strong&gt;&lt;/h5&gt;&lt;pre&gt;&lt;code&gt;# ...
third_iter = (num + 0.5 for num in second_iter)&lt;/code&gt;&lt;/pre&gt;&lt;h5&gt;&lt;strong&gt;Code Block #5&lt;/strong&gt;&lt;/h5&gt;&lt;pre&gt;&lt;code&gt;boring_numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
first_iter = iter(boring_numbers)
second_iter = (float(number) for number in first_iter)
third_iter = (num + 0.5 for num in second_iter)&lt;/code&gt;&lt;/pre&gt;&lt;h5&gt;&lt;strong&gt;Code Block #6&lt;/strong&gt;&lt;/h5&gt;&lt;pre&gt;&lt;code&gt;# ...
next(second_iter)
# 1.0&lt;/code&gt;&lt;/pre&gt;&lt;h5&gt;&lt;strong&gt;Code Block #7&lt;/strong&gt;&lt;/h5&gt;&lt;pre&gt;&lt;code&gt;# ...
next(third_iter)
# 2.5&lt;/code&gt;&lt;/pre&gt;&lt;h5&gt;&lt;strong&gt;Code Block #8&lt;/strong&gt;&lt;/h5&gt;&lt;pre&gt;&lt;code&gt;# ...
next(first_iter)
# 3&lt;/code&gt;&lt;/pre&gt;&lt;h5&gt;&lt;strong&gt;Code Block #9&lt;/strong&gt;&lt;/h5&gt;&lt;pre&gt;&lt;code&gt;# ...
next(third_iter)
# 4.5&lt;/code&gt;&lt;/pre&gt;&lt;h5&gt;&lt;strong&gt;Code Block #10&lt;/strong&gt;&lt;/h5&gt;&lt;pre&gt;&lt;code&gt;boring_numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
first_iter = iter(boring_numbers)
another_first_iter = iter(boring_numbers)
second_iter = (float(number) for number in first_iter)
third_iter = (num + 0.5 for num in second_iter)
next(second_iter)
# 1.0
next(third_iter)
# 2.5
next(first_iter)
# 3
next(third_iter)
# 4.5
next(another_first_iter)
# 1&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;hr /&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;For more Python resources, you can also visit&lt;/em&gt; &lt;em&gt;&lt;a href=&quot;https://realpython.com?utm_source=the-python-coding-stack&quot;&gt;Real Python&lt;/a&gt;&amp;#8212;you may even stumble on one of my own articles or courses there!&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;Also, are you interested in technical writing? You&amp;#8217;d like to make your own writing more narrative, more engaging, more memorable? Have a look at&lt;/em&gt; &lt;em&gt;&lt;a href=&quot;http://stephengruppetta.com/breaking-the-rules&quot;&gt;Breaking the Rules&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;&lt;em&gt;And you can find out more about me at&lt;/em&gt; &lt;em&gt;&lt;a href=&quot;https://stephengruppetta.com?utm_source=the-python-coding-stack&quot;&gt;stephengruppetta.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content:encoded>
	<dc:date>2026-06-04T12:50:32+00:00</dc:date>
</item>
<item rdf:about="https://realpython.com/quizzes/python-keyboard-input/">
	<title>Real Python: Quiz: How to Read User Input From the Keyboard in Python</title>
	<link>https://realpython.com/quizzes/python-keyboard-input/</link>
	<content:encoded>&lt;p&gt;In this quiz, you&amp;rsquo;ll test your understanding of &lt;a href=&quot;https://realpython.com/python-keyboard-input/&quot;&gt;How to Read User Input From the Keyboard in Python&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;By working through this quiz, you&amp;rsquo;ll revisit the &lt;a href=&quot;https://realpython.com/ref/builtin-functions/input/&quot; class=&quot;ref-link&quot;&gt;&lt;code&gt;input()&lt;/code&gt;&lt;/a&gt; function, type conversion, error handling with &lt;code&gt;try&lt;/code&gt; and &lt;code&gt;except&lt;/code&gt;, the &lt;a href=&quot;https://realpython.com/ref/stdlib/getpass/&quot; class=&quot;ref-link&quot;&gt;&lt;code&gt;getpass&lt;/code&gt;&lt;/a&gt; module for hidden input, and the PyInputPlus library for automatic validation.&lt;/p&gt;
        &lt;hr /&gt;
        &lt;p&gt;&lt;em&gt;[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short &amp;amp; sweet Python Trick delivered to your inbox every couple of days. &lt;a href=&quot;https://realpython.com/python-tricks/?utm_source=realpython&amp;utm_medium=rss&amp;utm_campaign=footer&quot;&gt;&amp;gt;&amp;gt; Click here to learn more and see examples&lt;/a&gt; ]&lt;/em&gt;&lt;/p&gt;</content:encoded>
	<dc:date>2026-06-04T12:00:00+00:00</dc:date>
</item>
<item rdf:about="https://pyfound.blogspot.com/2026/06/psf-strategic-plan-2026-draft-open-for.html">
	<title>Python Software Foundation: PSF Strategic Plan 2026 Draft: Open for Community Feedback</title>
	<link>https://pyfound.blogspot.com/2026/06/psf-strategic-plan-2026-draft-open-for.html</link>
	<content:encoded>&lt;p&gt;In May, &lt;a href=&quot;https://pyfound.blogspot.com/2026/05/strategic-planning-at-psf.html&quot; target=&quot;_blank&quot;&gt;we shared the high-level goals&lt;/a&gt; of the Python Software Foundation's (PSF) strategic plan and asked for your commentary. Today we are publishing the full draft and opening a three-week community feedback window.&lt;/p&gt;&lt;p&gt;We welcome you to review the full &lt;a href=&quot;https://drive.google.com/file/d/1jK-09qeXmZ0pPWW9Ux89dxdgE2BSNZDF/view?usp=sharing&quot; target=&quot;_blank&quot;&gt;PSF Strategic Plan Community Draft 2026 document&lt;/a&gt;, also embedded below.&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;b&gt;The feedback window closes on &lt;a href=&quot;https://www.timeanddate.com/worldclock/fixedtime.html?msg=PSF+Strategic+Plan+Feedback+Deadline&amp;iso=20260626T1159&amp;p1=1440&quot; target=&quot;_blank&quot;&gt;June 25, 2026, End Of Day, Anywhere on Earth&lt;/a&gt;.&lt;/b&gt; The PSF Board will carefully review all input, use it to refine the final version of the strategic plan, and aims to hold a vote to adopt it in a future board meeting.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;h2&gt;What's in the full draft&lt;/h2&gt;&lt;p&gt;The &lt;a href=&quot;https://pyfound.blogspot.com/2026/05/strategic-planning-at-psf.html&quot; target=&quot;_blank&quot;&gt;earlier blog post&lt;/a&gt; covered the six organizational goals and four program goals at a high level. The full draft goes deeper: each program goal includes specific strategic objectives, and the organizational goals include tactical ideas the board developed during the planning process. These tactical ideas are starting points for strategic discussion, not commitments.&lt;/p&gt;&lt;p&gt;This is the first post in a short series. Individual board members will share posts that go into specific parts of the plan in more depth. We want the plan to speak for itself, so these posts will draw directly from the document rather than rewriting it.&lt;br /&gt;&lt;/p&gt;&lt;h2&gt;What we heard at PyCon US&lt;/h2&gt;&lt;p&gt;At PyCon US 2026, the PSF Board held its on-site board meeting, with a portion of that time dedicated to strategy. We also discussed the strategic plan at the Members Lunch, a dedicated Open Space session, and in conversations throughout the conference.&lt;/p&gt;&lt;p&gt;The topic of financial sustainability came up repeatedly, and we hear you. The community is waiting for updated financial information, and typically the Members Lunch at PyCon US is where those details are shared. Staffing changes in our accounting functions made that impossible this year. Publishing the full picture is a priority, and we will share an update as soon as we can. The high-level view is that the PSF is stable for now, but we cannot continue on the current path without making meaningful changes. The strategic plan and the PSF's financial outlook are connected, and we understand that context matters. We are committed to being transparent about both.&lt;/p&gt;&lt;p&gt;We also noticed that conversations naturally moved toward implementation (&quot;How will you do this?&quot;). &lt;b&gt;For this feedback round, we are asking you to focus on the direction itself. Are these the right goals? Are the objectives the right ones? Is anything important missing? &lt;/b&gt;Implementation will be shaped by PSF staff over time, and there will be opportunities to weigh in on that, too.&lt;br /&gt;&lt;/p&gt;&lt;h2&gt;How to give feedback&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Email strategy@python.org to share detailed or private feedback. &lt;/b&gt;This is the best way to reach us.&lt;/li&gt;&lt;li&gt;&lt;b&gt;&lt;a href=&quot;https://discuss.python.org/t/strategic-planning-at-the-psf/107314&quot; target=&quot;_blank&quot;&gt;Discuss thread&lt;/a&gt;&lt;/b&gt; for open conversation.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://pyfound.blogspot.com/2025/10/a-new-psf-board-another-year-of-psf.html&quot; target=&quot;_blank&quot;&gt;&lt;b&gt;PSF Board Office Hours&lt;/b&gt;&lt;/a&gt; on the &lt;a href=&quot;https://discord.gg/4Hm36PPgpG&quot; target=&quot;_blank&quot;&gt;PSF Discord&lt;/a&gt; on:&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://www.timeanddate.com/worldclock/fixedtime.html?msg=PSF+Board+Office+Hours&amp;iso=20260609T2100&amp;p1=1440&quot; target=&quot;_blank&quot;&gt;June 9, 2026, 9 PM UTC&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.timeanddate.com/worldclock/fixedtime.html?msg=PSF+Strategy+Office+Hours&amp;iso=20260623T1300&amp;p1=1440&quot; target=&quot;_blank&quot;&gt;June 23, 2026, 1 PM UTC&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;p&gt;The feedback window closes on June 25th. After that, the board will review all feedback received and decide what changes to make to the strategy document in response.&amp;nbsp;&lt;/p&gt;&lt;p&gt;Thank you for your time. We’re working on this strategic plan because the Python community deserves a PSF that's deliberate about where it's headed. Your input makes that possible, and we’re grateful for your help.&lt;/p&gt;&lt;p&gt;&lt;i&gt;Jannis Leidel, PSF Board Chair, on behalf of the PSF Board of Directors&lt;/i&gt;&lt;/p&gt;</content:encoded>
	<dc:date>2026-06-04T09:38:17+00:00</dc:date>
</item>
<item rdf:about="https://blog.adarshd.dev/ai/posts/building-ai-agents-in-python/">
	<title>Adrarsh Divakaran: Building AI Agents in Python</title>
	<link>https://blog.adarshd.dev/ai/posts/building-ai-agents-in-python/</link>
	<content:encoded>&lt;p&gt;2026 is shaping up to be a big year for AI agents. We are seeing more products where the AI not only answers a question but also does some work for the user.&lt;/p&gt;
&lt;p&gt;You have probably used ChatGPT or a similar AI tool to answer a question, help with writing, or explain some code. You type something, the AI responds, and the conversation goes back and forth. That is powerful, but it is also limited. The AI is essentially stuck in a chat box - it can only talk to you; it cannot do anything on your behalf.&lt;/p&gt;
&lt;p&gt;AI agents change that. An agent is an AI that can actually take actions - browse the web, read and write files, run code, call APIs, and more. It does not just answer your question; it works toward a goal, step by step, using whatever tools it needs. Tools like Lovable, Cursor, and Claude Code are examples of this in practice.&lt;/p&gt;
&lt;p&gt;In this article, we will explore the concepts behind building an AI agent in Python. We will use the &lt;a href=&quot;https://github.com/openai/openai-python&quot;&gt;OpenAI Python SDK&lt;/a&gt; (Responses API) for the examples, but the same ideas can be generalized to any other LLM SDK. We will use a low-level SDK with minimal abstractions so we can observe and implement most of the agent’s behavior on our end.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This tutorial explains how AI agents work by building a simple one in Python.&lt;/p&gt;
&lt;p&gt;We will cover the core pieces: LLMs, prompts, context, memory, the agent loop, tools, MCP, and skills:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LLM&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Acts as the reasoning engine that understands the user request and decides what to do next.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;System prompt&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Defines the agent’s role, behavior, boundaries, and response style.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Context window&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Controls how much information the model can see at once, including prompts, history, tool results, and files.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Memory&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Helps the agent remember useful information across steps or conversations.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Agent loop&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Repeats the process of thinking, acting, observing results, and deciding the next step.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tool calling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Lets the agent use external functions such as APIs, web search, file access, or code execution.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MCP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Provides a standard way to connect agents to reusable tools and data sources.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Skills&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Package reusable instructions, workflows, examples, and scripts for specific tasks.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;what-are-agents&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://blog.adarshd.dev/feed.xml#what-are-agents&quot;&gt;What are Agents?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;An AI agent is an AI system that can autonomously plan and execute multi-step actions toward a goal.&lt;/p&gt;
&lt;p&gt;To understand agents, it helps to first understand what is powering them under the hood - a large language model, or LLM. For example, ChatGPT is a product built on top of OpenAI GPT LLMs. When you type a message and get a response, an LLM is doing the heavy lifting. It takes text as input and generates text as output.&lt;/p&gt;
&lt;p&gt;On their own, LLMs are impressive but limited. They can only respond with text. They cannot open your browser, read a file on your computer, or send an email. They also do not know what happened yesterday, because their knowledge comes from training data with a cutoff date, not a live connection to the world.&lt;/p&gt;
&lt;p&gt;Agents fix this by giving LLMs access to tools. A tool is just a function your code exposes to the model - something like “search the web” or “read this file.” The model can decide to call a tool when it needs to, and your code actually runs it. This turns a passive text generator into something that can act.&lt;/p&gt;
&lt;p&gt;A good way to see the difference is to compare using ChatGPT with using Claude Code for a coding task. With ChatGPT, you describe the problem, copy the suggested code, paste it into your editor, run it, copy the error back, and repeat. The model has no idea what is actually in your project. Claude Code is different - it is powered by an LLM but also has access to tools like bash and file reading. You describe what you want, and it reads your files, writes code, runs tests, and fixes errors on its own. You just watch and steer.&lt;/p&gt;
&lt;p&gt;The simplest way to understand an agent is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The user gives a goal.&lt;/li&gt;
&lt;li&gt;The model decides what step to take.&lt;/li&gt;
&lt;li&gt;The agent runs that step using a tool.&lt;/li&gt;
&lt;li&gt;The model looks at the result.&lt;/li&gt;
&lt;li&gt;The process continues until the task is complete.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is different from a normal chatbot. A chatbot mainly responds. An agent can respond and act.&lt;/p&gt;
&lt;p&gt;In a simple agent, the model may only call one tool and return the result. In a more capable agent, the model may make a plan, call multiple tools, observe the results, adjust the plan, and continue until the task is complete.&lt;/p&gt;
&lt;p&gt;Before we build this kind of system, we need to choose the model that will drive it.&lt;/p&gt;
&lt;h2 id=&quot;llms&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://blog.adarshd.dev/feed.xml#llms&quot;&gt;LLMs&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;LLMs are trained on massive amounts of text data - entire open source repositories on GitHub, books, articles, websites, and more. Through training, the model learns patterns in language well enough to generate coherent, useful responses. The scale of this training is what makes them surprisingly capable across such a wide range of tasks.&lt;/p&gt;
&lt;p&gt;At their core, LLMs are text-in, text-out systems. You send them a block of text (called a prompt), and they generate a response. Everything that happens - reasoning, answering questions, writing code, making decisions - is expressed through that text interface. When an agent calls a tool, it is really the model writing out a structured text request, and your code intercepts that and actually runs the function.&lt;/p&gt;
&lt;p&gt;The key limitation to keep in mind: LLMs only know what they were trained on. They have no awareness of events after their training cutoff and no way to look things up in real time unless they are given a tool to do so. This is part of what makes tools so valuable - they extend the model’s reach into the real world.&lt;/p&gt;
&lt;h2 id=&quot;choosing-an-llm&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://blog.adarshd.dev/feed.xml#choosing-an-llm&quot;&gt;Choosing an LLM&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For an AI agent, the LLM is its brain. The quality of the model affects how well the agent understands instructions, chooses tools, handles errors, and completes multi-step tasks.&lt;/p&gt;
&lt;p&gt;At the same time, the most powerful model is not always the right choice. We also need to think about cost, speed, context window, reasoning ability, and where the model is hosted.&lt;/p&gt;
&lt;h3 id=&quot;benchmarks&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://blog.adarshd.dev/feed.xml#benchmarks&quot;&gt;Benchmarks&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Benchmarks are standardized tests used to compare the performance of different models. For coding tasks, there is &lt;a href=&quot;https://www.swebench.com/&quot;&gt;SWE-bench&lt;/a&gt;. For general reasoning, there is &lt;a href=&quot;https://www.kaggle.com/benchmarks/open-benchmarks/mmlu&quot;&gt;MMLU&lt;/a&gt;. Each benchmark tests the model on a specific type of problem and gives it a score. A higher score generally means the model will perform better on that type of task.&lt;/p&gt;
&lt;p&gt;Benchmarks are a useful starting point when choosing a model, but they are not the whole story. A model that scores well on a benchmark may still behave unexpectedly in your specific use case, so it is always worth testing with your actual workload.&lt;/p&gt;
&lt;h3 id=&quot;costs&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://blog.adarshd.dev/feed.xml#costs&quot;&gt;Costs&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Choosing the best-scoring model from a benchmark may not always be the most intelligent decision.&lt;/p&gt;
&lt;p&gt;Cost is a real factor, especially at scale. Most providers charge per token, which is the basic unit of text the model processes. A token is roughly four characters, or about three-quarters of a word on average. Both what you send to the model (input) and what it generates back (output) count toward your token usage.&lt;/p&gt;
&lt;p&gt;For an agent that runs multiple steps in a loop, token usage adds up quickly. A good approach is to start with a capable model and then see if a smaller or cheaper one can do the same job well enough. Sometimes a smaller model handles simple tasks just fine.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.ghost.io/c/a5/00/a5004977-0dd2-4bcd-9292-dd0e05d4c59e/content/images/2026/05/image-12.png&quot; alt=&quot;&quot; /&gt; &lt;em&gt;(Model costs table from &lt;a href=&quot;https://github.com/simonw/llm-prices&quot;&gt;https://github.com/simonw/llm-prices&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;reasoning-level&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://blog.adarshd.dev/feed.xml#reasoning-level&quot;&gt;Reasoning Level&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Some models are designed to think before they answer. These reasoning models break complex problems into smaller steps internally, often called reasoning traces. You can think of it as the model working through a scratchpad before writing its final response. This can improve performance for tasks that need planning, debugging, tool use, or careful decision-making.&lt;/p&gt;
&lt;p&gt;More reasoning effort usually means higher cost, higher response time, and better accuracy for complex tasks.&lt;/p&gt;
&lt;p&gt;Not every request needs high reasoning. If the task is simple, we can use a lower reasoning level or a cheaper model. If the task involves multiple steps, unknown errors, or important decisions, more reasoning can be useful.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.ghost.io/c/a5/00/a5004977-0dd2-4bcd-9292-dd0e05d4c59e/content/images/2026/05/image-10.png&quot; alt=&quot;&quot; /&gt; &lt;em&gt;(Conversation with GPT-OSS LLM showing reasoning/thought traces)&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;hosted-vs-local&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://blog.adarshd.dev/feed.xml#hosted-vs-local&quot;&gt;Hosted vs Local&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Most people start with a hosted model - one that runs on a provider’s servers and is accessed via an API. These are easy to set up, well-maintained, and generally the most capable options available. The trade-off is that you pay per token, and your data is processed by a third party.&lt;/p&gt;
&lt;p&gt;There are also open models that can run entirely on your own machine/server. They can avoid per-token API costs and give you more control over data. The downside is that they require capable hardware and are generally less powerful than the best hosted models today. That said, local models are getting better quickly. Previous generation frontier capabilities are being replicated in the next generation of local models, and this gap will continue to close. Examples of open-weight models that can be self-hosted, depending on hardware and quantization, include &lt;a href=&quot;https://deepmind.google/models/gemma/gemma-4/&quot;&gt;Gemma 4 series&lt;/a&gt; and &lt;a href=&quot;https://www.kimi.com/ai-models/kimi-k2-6&quot;&gt;Kimi K2.6&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There are already decent &lt;a href=&quot;https://medium.com/google-cloud/i-ran-gemma-4-as-a-local-model-in-codex-cli-7fda754dc0d4&quot;&gt;local coding models that people use&lt;/a&gt; for simple code generation and verification. In the coming years, this will improve, and stronger models will become available on consumer devices.&lt;/p&gt;
&lt;p&gt;Hosted models are still easier to use for many applications. They usually provide better quality, higher reliability, larger context windows, and managed infrastructure.&lt;/p&gt;
&lt;p&gt;Local models give more control over data, cost, and deployment. But they also require more setup, hardware, monitoring, and optimization.&lt;/p&gt;
&lt;h2 id=&quot;configuring-the-llm&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://blog.adarshd.dev/feed.xml#configuring-the-llm&quot;&gt;Configuring the LLM&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Once you have picked a model, there are two things you set up before the agent starts running: the system prompt and the context window.&lt;/p&gt;
&lt;h3 id=&quot;system-prompt&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://blog.adarshd.dev/feed.xml#system-prompt&quot;&gt;System Prompt&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A system prompt is the model’s top-level instruction that guides its behavior during a conversation.&lt;/p&gt;
&lt;p&gt;It can set rules such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;what role the AI should play&lt;/li&gt;
&lt;li&gt;what tone it should use&lt;/li&gt;
&lt;li&gt;what it should or should not do&lt;/li&gt;
&lt;li&gt;how it should handle tools&lt;/li&gt;
&lt;li&gt;how it should handle safety and user requests&lt;/li&gt;
&lt;li&gt;how it should format the final answer&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For an agent, the system prompt is very important. It tells the model how to behave while using tools. It can also define boundaries, such as asking for permission before destructive actions or avoiding actions outside the user’s request.&lt;/p&gt;
&lt;p&gt;Let’s see an example of this in practice:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; os

&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; __name__ &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;'__main__'&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; openai &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; OpenAI

    client &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; OpenAI&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;api_key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;environ&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;OPENAI_API_KEY&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;responses&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;create&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        model&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;gpt-5.4-mini&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token builtin&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;system&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;You are a friendly Python tutor. Refuse all requests unrelated to Python coding&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Enter your Python question: &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;output_text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the above script, we initialize an &lt;code&gt;OpenAI&lt;/code&gt; client and use &lt;code&gt;client.responses.create&lt;/code&gt; to send a message to &lt;code&gt;gpt-5.4-mini&lt;/code&gt; model. The system prompt is specified in the &lt;code&gt;input&lt;/code&gt; list as the first entry. &lt;code&gt;&amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;&lt;/code&gt; designates the entry as the system prompt. In the above example, the model is instructed to act as a Python tutor and refuse requests unrelated to Python. As the next entry, we accept the user prompt via &lt;code&gt;input()&lt;/code&gt; and pass it to the LLM for answering.&lt;/p&gt;
&lt;p&gt;If the script is run and any unrelated queries are passed to the LLM, we get a refusal response similar to the below one:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Enter your Python question: How many states are there in the US?

Model response: I’m here to help with Python coding questions only. If you have a Python-related question, feel free to ask!

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Even though the underlying large language model knows the answer to the user’s query, it refuses to answer as per direction in the system prompt.&lt;/p&gt;
&lt;h3 id=&quot;context-window&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://blog.adarshd.dev/feed.xml#context-window&quot;&gt;Context Window&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The context window is the model’s working memory. It is the amount of information the model can see in one request.&lt;/p&gt;
&lt;p&gt;The context can include the user message, conversation history, system prompt, tool results, files, documentation, and any other information we provide.&lt;/p&gt;
&lt;p&gt;Most of the latest flagship models support up to 1M tokens, which is roughly 750,000 words or about 15 books. Older models like GPT-4 series models had a 128K token window, around 2 books’ worth. For agents that run long tasks or work with large documents, context window size matters a lot. When the context fills up, older information gets dropped, which can cause the agent to lose track of earlier steps in a long task.&lt;/p&gt;
&lt;p&gt;A larger context window is useful, but it is not free. More context usually means more cost and slower responses. Also, just because a model can accept a lot of context does not mean every token is equally important.&lt;/p&gt;
&lt;p&gt;Good agents manage context carefully. They include what is needed, summarize old information, and avoid filling the context with unnecessary data.&lt;/p&gt;
&lt;p&gt;Once we understand the model and its context window, the next question is what the agent should remember across steps and conversations.&lt;/p&gt;
&lt;h2 id=&quot;memory&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://blog.adarshd.dev/feed.xml#memory&quot;&gt;Memory&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Memory helps an agent remember useful information.&lt;/p&gt;
&lt;p&gt;Short-term memory helps the agent remember what the user said earlier in the same conversation. This usually lives inside the context window.&lt;/p&gt;
&lt;p&gt;Let’s consider an example. The snippet below accepts a user query inside a loop and sends it to a model to get the response:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; os

&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; __name__ &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;'__main__'&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; openai &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; OpenAI

    client &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; OpenAI&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;api_key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;environ&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;OPENAI_API_KEY&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        user_query &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;You: &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; user_query&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lower&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;exit&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;quit&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;

        response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;responses&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;create&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            model&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;gpt-5.4-mini&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token builtin&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;user_query&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        assistant_reply &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;output_text
        &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;Model: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;assistant_reply&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code works, but there are issues:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;You: Tell me about Taj Mahal in 1 sentence
Model: The Taj Mahal is a magnificent white marble mausoleum in Agra, India, built by Emperor Shah Jahan in memory of his wife Mumtaz Mahal, and is one of the world’s most famous symbols of love.

You: When was it built?
Model: I can help, but I need to know **what “it” refers to**.  
Please share the name, photo, or location of the building/structure/object, and I’ll tell you when it was built.

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As seen from the transcript, the model fails to answer the user’s follow-up prompt. This is because, we did not implement short term memory. For the model to be able to respond to follow-ups properly, we need to store and pass the conversation history to LLM calls. The snippet improves on the above script with short term memory implementation:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; os
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; __name__ &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;'__main__'&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; openai &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; OpenAI

    client &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; OpenAI&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;api_key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;environ&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;OPENAI_API_KEY&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    conversation_history &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        user_query &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;You: &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; user_query&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lower&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;exit&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;quit&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;

        conversation_history&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;append&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; user_query&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;responses&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;create&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            model&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;gpt-5.4-mini&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token builtin&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;conversation_history&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        assistant_reply &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;output_text
        &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;Model: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;assistant_reply&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        conversation_history&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;append&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;assistant&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; assistant_reply&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We introduced a &lt;code&gt;conversation_history&lt;/code&gt; list that stores previous messages. User messages are appended to this list with &lt;code&gt;&amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;&lt;/code&gt; and model responses are appended with &lt;code&gt;&amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;&lt;/code&gt;. This way, whenever a request is sent to the model, it gets the entire message history through the &lt;code&gt;input&lt;/code&gt; argument and will be able to respond to follow-up prompts correctly.&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;You: Tell me about Taj Mahal in 1 sentence
Model: The Taj Mahal is a stunning white marble mausoleum in Agra, India, built by Emperor Shah Jahan in memory of his wife Mumtaz Mahal.

You: When was it built?
Model: It was built between 1632 and 1653.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Long-term memory stores information beyond one conversation and persists even after the current chat or task ends. This is useful when you want the agent to remember user preferences, past decisions, or domain-specific facts across sessions. Common approaches include RAG (retrieval-augmented generation), where relevant information is fetched from a database and added to the context as needed, and built-in memory systems like ChatGPT Memories, where key facts are stored and automatically recalled in future conversations.&lt;/p&gt;
&lt;h2 id=&quot;agent-loop&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://blog.adarshd.dev/feed.xml#agent-loop&quot;&gt;Agent Loop&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The agent loop is the core flow of an agent.&lt;/p&gt;
&lt;p&gt;A simple loop looks like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;User sends a message.&lt;/li&gt;
&lt;li&gt;Agent adds the message to the conversation context.&lt;/li&gt;
&lt;li&gt;Agent sends the context and system prompt to the LLM.&lt;/li&gt;
&lt;li&gt;LLM decides what to do next.&lt;/li&gt;
&lt;li&gt;If needed, the LLM calls a tool.&lt;/li&gt;
&lt;li&gt;Agent runs the tool and sends the result back to the LLM.&lt;/li&gt;
&lt;li&gt;LLM decides whether more steps are needed.&lt;/li&gt;
&lt;li&gt;When done, the LLM generates the final response.&lt;/li&gt;
&lt;li&gt;Agent sends the response to the user.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This loop is what makes agents feel different from normal chatbots. A chatbot usually gives one response. An agent can act, observe, and continue.&lt;/p&gt;
&lt;p&gt;In practice, the intermediate steps are where the interesting work happens. The model may call a tool, wait for the result, process that result, decide to call another tool, and keep going before it gives a final answer. The loop runs as many times as needed until the model decides the task is complete or the user stops it. This brings us to tools - what they are and how they actually work.&lt;/p&gt;
&lt;h2 id=&quot;tool-calling&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://blog.adarshd.dev/feed.xml#tool-calling&quot;&gt;Tool Calling&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Tools are external capabilities that the agent can use.&lt;/p&gt;
&lt;p&gt;Tools (also called functions) let an AI agent do things beyond generating text. They can be used to take actions or get information.&lt;/p&gt;
&lt;p&gt;Examples of tools:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;search the web&lt;/li&gt;
&lt;li&gt;call an API&lt;/li&gt;
&lt;li&gt;read files&lt;/li&gt;
&lt;li&gt;edit files&lt;/li&gt;
&lt;li&gt;run code&lt;/li&gt;
&lt;li&gt;send emails&lt;/li&gt;
&lt;li&gt;query a database&lt;/li&gt;
&lt;li&gt;create calendar events&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The agent chooses a tool when needed. The tool has a name, a description, and input parameters. The model decides which tool to call and what arguments to pass.&lt;/p&gt;
&lt;p&gt;Tool descriptions are important. If a tool description is unclear, the model may call it at the wrong time or pass the wrong input. We should describe tools in simple language and make their inputs strict.&lt;/p&gt;
&lt;p&gt;Here is an important detail: the model does not run the tool itself. When it decides to use a tool, it outputs a structured request with the tool name and the arguments it wants to pass. Your code intercepts this, runs the actual function, and passes the result back to the model. The model then reads the result and decides what to do next. This back-and-forth between the model and your code is what makes the agent loop so powerful.&lt;/p&gt;
&lt;p&gt;Let’s see an example of tool calling in action:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; json
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; os
&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; dotenv &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; load_dotenv

load_dotenv&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;get_weather&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;location&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; location&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;temperature&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;24 C&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;condition&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Sunny&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;humidity&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;52%&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;wind&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;11 km/h&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;


&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; __name__ &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;'__main__'&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; openai &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; OpenAI

    client &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; OpenAI&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;api_key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;environ&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;OPENAI_API_KEY&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    tools &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;function&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;get_weather&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Get the current weather for a destination.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;parameters&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;object&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;properties&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token string&quot;&gt;&quot;location&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;token string&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;string&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                        &lt;span class=&quot;token string&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;The city or destination, e.g. Paris or Tokyo&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;required&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;location&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;additionalProperties&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;strict&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

    input_list &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;system&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;You are Safar, a travel planning AI agent&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Ask you travel questions: &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

    response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;responses&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;create&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        model&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;gpt-5.4-mini&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token builtin&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;input_list&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        tools&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;tools&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        tool_choice&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;required&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;The model responded with:&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    input_list &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;output

    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; item &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;function_call&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;continue&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;get_weather&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            args &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; json&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;loads&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;arguments&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;The model wants to call get_weather with: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

            weather &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; get_weather&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;location&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;The local Python function returned: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;weather&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

            input_list&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;append&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;function_call_output&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;call_id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;call_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;output&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; json&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dumps&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;weather&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Sending the tool result back to the model&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    final_response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;responses&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;create&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        model&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;gpt-5.4-mini&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token builtin&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;input_list&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        tools&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;tools&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Final answer:&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;Model response: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;final_response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;output_text&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the &lt;code&gt;tools&lt;/code&gt; list, we have defined a function named &lt;code&gt;get_weather&lt;/code&gt; according to &lt;a href=&quot;https://developers.openai.com/api/docs/guides/function-calling&quot;&gt;OpenAI function calling&lt;/a&gt; guidelines and have specified the parameters that the model accepts using the &lt;code&gt;parameters&lt;/code&gt; key. This definition follows &lt;a href=&quot;https://json-schema.org/&quot;&gt;JSON Schema specification.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Since, we add this &lt;code&gt;tools&lt;/code&gt; list when making calls to OpenAI, the model will know that it has access to a weather tool and will be able to request a function call when needed.&lt;/p&gt;
&lt;p&gt;In the script, you can see that when we receive a response from the model, we always check if the response type is a function call or not (&lt;code&gt;item.type != &amp;quot;function_call&amp;quot;&lt;/code&gt;) and if the response is a request to call &lt;code&gt;get_weather&lt;/code&gt; tool, we call the &lt;code&gt;get_weather()&lt;/code&gt; Python function and send it back to the model:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;weather &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; get_weather&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;location&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
input_list&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;append&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;function_call_output&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;call_id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;call_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;output&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; json&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dumps&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;weather&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s run the script and ask the agent a question that would require a weather tool call:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Ask you travel questions: Sunscreen needed in Goa?

The model responded with:
[ResponseFunctionToolCall(arguments='{&amp;quot;location&amp;quot;:&amp;quot;Goa&amp;quot;}', call_id='call_X9OBZhGwT3yhfmTAOclefWE8', name='get_weather', type='function_call', id='fc_05ba95ec38f46f7f006a17ce9e3bb0819a9a0b430001f7bd91', namespace=None, status='completed')]

The model wants to call get_weather with: {'location': 'Goa'}
The local Python function returned: {'location': 'Goa', 'temperature': '24 C', 'condition': 'Sunny', 'humidity': '52%', 'wind': '11 km/h'}

Sending the tool result back to the model

Final answer:
Model response: Yes — sunscreen is a good idea in Goa. It’s sunny there right now, so UV exposure can be strong even if it feels pleasant.

Quick tips:
- Use broad-spectrum SPF 30+ (SPF 50 if you’ll be at the beach a lot)
- Reapply every 2 hours, and after swimming/sweating
- Don’t forget ears, neck, hands, and feet
- A hat and sunglasses help too

If you want, I can also suggest a Goa beach-day packing list.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For our query, the model initially responds with a &lt;code&gt;ResponseFunctionToolCall&lt;/code&gt; item. This requests our &lt;code&gt;get_weather&lt;/code&gt; function to be called with location argument set as Goa.&lt;/p&gt;
&lt;p&gt;Responding to this request, our script executes the function call and sends the function call response back to the model for getting the final response. The function call always returns temperature as 24 degree Celsius with condition as sunny. Trusting this data, the model produces its final response, suggesting the user to use a sunscreen.&lt;/p&gt;
&lt;p&gt;The weather function defined in the above script is not a very useful one, it returns a hardcoded weather data for all requests. In a practical scenario, the function should make an actual call to a real Weather API to fetch data.&lt;/p&gt;
&lt;p&gt;The above script illustrates the concept of an agent loop. Even though the example involves just one user request and model response, the agent takes intermediary steps (tool calls) before returning the final response.&lt;/p&gt;
&lt;p&gt;Now let’s move to a real world example involving tools. We will provide web search capability to our agent by defining a custom SerpApi web search tool.&lt;/p&gt;
&lt;p&gt;Providers usually have their own built-in tools for web search. However, &lt;a href=&quot;https://serpapi.com/blog/build-an-ai-agent-with-claude-agent-sdk/#websearch-tool&quot;&gt;these tools can be slow or unreliable&lt;/a&gt; at times. To get live search data from search engines reliably, we can write a custom tool/function using &lt;a href=&quot;https://pypi.org/project/serpapi/0.1.4/&quot;&gt;SerpApi Python SDK&lt;/a&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; json
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; os


&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;google_search&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;query&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; serpapi

    client &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; serpapi&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Client&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;api_key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;environ&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;SERPAPI_KEY&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    results &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;search&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;engine&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;google&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;q&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; query&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;link&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;link&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;snippet&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;snippet&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; result &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; results&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;organic_results&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;


&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; __name__ &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;'__main__'&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; openai &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; OpenAI

    client &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; OpenAI&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;api_key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;environ&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;OPENAI_API_KEY&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    tools &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;function&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;google_search&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Search Google with SerpApi and return web search results.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;parameters&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;object&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;properties&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token string&quot;&gt;&quot;query&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;token string&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;string&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                        &lt;span class=&quot;token string&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;The Google search query to run&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;required&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;query&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;additionalProperties&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;strict&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

    input_list &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;system&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;You are Safar, a travel planner. Use Google search when current destination information would improve your answer.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;What travel question should I research? &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

    response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;responses&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;create&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        model&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;gpt-5.4-mini&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token builtin&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;input_list&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        tools&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;tools&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        tool_choice&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;required&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;The model responded with:&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    input_list &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;output

    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; item &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;function_call&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;continue&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;google_search&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            args &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; json&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;loads&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;arguments&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;The model wants to call google_search with: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

            search_results &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; google_search&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;query&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;Step 7: SerpApi returned &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;search_results&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; search results&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

            input_list&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;append&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;function_call_output&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;call_id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;call_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;output&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; json&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dumps&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;search_results&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    final_response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;responses&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;create&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        model&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;gpt-5.4-mini&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token builtin&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;input_list&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        tools&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;tools&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;Model response: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;final_response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;output_text&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, we define a &lt;code&gt;google_search()&lt;/code&gt; that accepts a query and performs a Google search with the query using SerpApi Python SDK. The function returns the first five search results obtained from Google.&lt;/p&gt;
&lt;p&gt;Let’s see the results in action:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;What travel question should I research? When is the Tomato festival - La Tomatina happening this year?

The model responded with:
[ResponseFunctionToolCall(arguments='{&amp;quot;query&amp;quot;:&amp;quot;La Tomatina 2026 date official&amp;quot;}', call_id='call_mk2KL4xnvR0mexyt2lXFTHgE', name='google_search', type='function_call', id='fc_01af4c5fc07e8479006a192316ab20819bb10273439c89fb9a', namespace=None, status='completed')]

The model wants to call google_search with: {'query': 'La Tomatina 2026 date official'}
Step 7: SerpApi returned 5 search results

Model response: La Tomatina is happening on **Wednesday, August 26, 2026** in **Buñol, Spain**.

If you want, I can also help with:
- tickets
- how to get there from Valencia
- where to stay nearby
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the core idea behind tool calling. The model does not directly browse the web or fetch data by itself. Instead, it identifies when a tool is needed, asks for that tool to be called, and then uses the returned result to continue the conversation. This separation is useful because the model can focus on reasoning, while tools provide access to external systems and real-time information.&lt;/p&gt;
&lt;p&gt;Without the &lt;code&gt;google_search&lt;/code&gt; tool, the model would not be able to answer questions that require live data. It should respond with something like: “I don’t have access to real-time information.” By defining the tool, we give the model a safe and structured way to request the information it needs.&lt;/p&gt;
&lt;h2 id=&quot;mcp&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://blog.adarshd.dev/feed.xml#mcp&quot;&gt;MCP&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As you build more agents with more tools, a new problem emerges: every tool integration is custom-built and cannot easily be reused elsewhere. If you build a GitHub integration for one agent, you would have to rebuild it from scratch for another. That is where MCP comes in.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://serpapi.com/blog/model-context-protocol-mcp-a-unified-standard-for-ai-agents-and-tools/&quot;&gt;Model Context Protocol (MCP)&lt;/a&gt; is like USB-C for AI integrations. It is a standard protocol that lets models connect to external tools and data sources in a consistent, reusable way. Instead of building a custom integration for every tool, you write an MCP server once, and any model that supports MCP can use it.&lt;/p&gt;
&lt;p&gt;Examples include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/github/github-mcp-server&quot;&gt;GitHub MCP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.figma.com/mcp-catalog/&quot;&gt;Figma MCP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/serpapi/serpapi-mcp&quot;&gt;SerpApi MCP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;database MCP servers&lt;/li&gt;
&lt;li&gt;browser MCP servers&lt;/li&gt;
&lt;li&gt;file system MCP servers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With MCP, the model can discover supported functionality and call tools when needed. This makes integrations reusable across different models, clients, and applications. For a small agent, normal tool calling may be enough. For larger systems with many integrations, MCP can make the architecture cleaner.&lt;/p&gt;
&lt;p&gt;Let’s see an example of MCP usage in practice. The script below uses the &lt;a href=&quot;https://serpapi.com/blog/introducing-serpapis-mcp-server/&quot;&gt;SerpApi MCP server&lt;/a&gt; - using this, the agent will be able to call all the SerpApi supported engines like &lt;code&gt;google&lt;/code&gt;, &lt;code&gt;google_shopping&lt;/code&gt;, &lt;code&gt;amazon&lt;/code&gt;, etc.&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; os

&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; __name__ &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;'__main__'&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; openai &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; OpenAI

    client &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; OpenAI&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;api_key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;environ&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;OPENAI_API_KEY&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    serpapi_mcp_url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;https://mcp.serpapi.com/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;environ&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;'SERPAPI_KEY'&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/mcp&quot;&lt;/span&gt;&lt;/span&gt;

    response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;responses&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;create&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        model&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;gpt-5.4&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        tools&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;mcp&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;server_label&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;serpapi&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;server_description&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;SerpApi MCP server&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;server_url&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; serpapi_mcp_url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;require_approval&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;never&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token builtin&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;system&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;You are Cartwise, a shopping assistant. Help users compare products, prices, reviews, and buying options.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;What do you want to shop for? &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Full model response (includes MCP operations): &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;Model response: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;output_text&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;SerpApi exposes the MCP server via the URL &lt;a href=&quot;https://mcp.serpapi.com/&quot;&gt;https://mcp.serpapi.com/&lt;/a&gt;. Users can supply the API Key via the URL path as seen in the example: &lt;a href=&quot;https://mcp.serpapi.com/%7Bos.environ%5B'SERPAPI_KEY'%5D%7D/mcp&quot;&gt;&lt;code&gt;https://mcp.serpapi.com/{os.environ['SERPAPI_KEY']}/mcp&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The code here is relatively simpler compared to the tool calling example. We just need to provide the MCP server info via the &lt;code&gt;tools&lt;/code&gt; argument:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;tools&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;mcp&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;server_label&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;serpapi&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;server_description&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;SerpApi MCP server&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;server_url&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; serpapi_mcp_url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;require_approval&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;never&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From this definition alone, the model can discover supported MCP functionalities and it will be able to autonomously call the MCP server tools based on the user request.&lt;/p&gt;
&lt;p&gt;Let’s ask the agent a shopping query. Here, I am asking it to find the price of a mobile device:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;What do you want to shop for? Find best price for Moto Razr Ultra phone

Full model response (includes MCP operations): 
[ 
  McpListTools(id='mcpl_06176a7178fb9f5c006a17f6c23578819ab2c977e7bc2b0bc7', server_label='serpapi', ..., 

McpCall(id='mcp_06176a7178fb9f5c006a17f6c40930819aac6136e6a0f0ced8', arguments='{&amp;quot;params&amp;quot;:{&amp;quot;q&amp;quot;:&amp;quot;Moto Razr Ultra phone price&amp;quot;,&amp;quot;engine&amp;quot;:&amp;quot;google_shopping&amp;quot;,&amp;quot;num&amp;quot;:10},&amp;quot;mode&amp;quot;:&amp;quot;compact&amp;quot;}', name='search', server_label='serpapi', type='mcp_call', approval_request_id=None, error=None, output='{&amp;quot;shopping_results&amp;quot;: [{&amp;quot;position&amp;quot;: 1, &amp;quot;title&amp;quot;: &amp;quot;Motorola Razr Ultra 2025&amp;quot;, &amp;quot;product_id&amp;quot;: &amp;quot;14521999409488109662&amp;quot;, &amp;quot;product_link&amp;quot;: ...]}]}', status='completed'), 
  ResponseOutputMessage(id='msg_06176a7178fb9f5c006a17f6cc3e68819aa688012defa9cf78', content=[ResponseOutputText(annotations=[], text='Best price I found for a **new Moto Razr Ultra** is:...'
]

Model response: Best price I found for a **new Moto Razr Ultra** is:

- **$699.99 at Best Buy** — Motorola Razr Ultra 2025  
  - was **$1,300**
  - rating: **4.0/5** from **520 reviews**
  - free delivery by Sat

Also matching:
- **$699.99 at Motorola US** — Motorola Razr 2025
- **$764.00 at Etoren** — Motorola Razr 50 Ultra
- **$1,049.99+** for some Razr 60 Ultra / 2026 variants
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The model response includes a series of operations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;McpListTools&lt;/strong&gt;: This is the request from the model sent to the MCP server to list available operations. From this call, the model will know that SerpApi has a &lt;code&gt;google_shopping&lt;/code&gt; engine for shopping searches.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;McpCall:&lt;/strong&gt; Based on the above response, the model calls MCP servers &lt;code&gt;google_shopping&lt;/code&gt; engine with the query &lt;code&gt;Moto Razr Ultra phone price&lt;/code&gt;. This call will fetch the shopping results via SerpApi MCP.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ResponseOutputMessage&lt;/strong&gt;: Once the above response is obtained, the model has enough information regarding the prices to formulate its response to the user. The model responds by listing the device price across a number of retailers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If we omitted the SerpApi MCP definition in the above script, the model should have responded with something like: “I cannot access real-time prices.” This is because the model itself does not have live data access unless we explicitly connect it to external tools or systems. MCP is one way to provide that connection in a standard way.&lt;/p&gt;
&lt;p&gt;Now that we have seen how MCP connects agents to external capabilities, let’s look at another way to extend agent behavior: skills.&lt;/p&gt;
&lt;h2 id=&quot;skills&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://blog.adarshd.dev/feed.xml#skills&quot;&gt;Skills&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While tools handle actions, skills handle behavior. A &lt;a href=&quot;https://agentskills.io/&quot;&gt;skill&lt;/a&gt; is a reusable set of instructions or a workflow that tells an agent how to perform a specific type of task well.&lt;/p&gt;
&lt;p&gt;We have seen tools and MCP which are code-heavy. Tools are code that gets called by the model whereas MCP requires a server implementation according to the &lt;a href=&quot;https://modelcontextprotocol.io/specification&quot;&gt;Model Context Protocol spec&lt;/a&gt;. Skills are relatively simple and can just be a plain text markdown file.&lt;/p&gt;
&lt;p&gt;A skill can include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;steps to follow&lt;/li&gt;
&lt;li&gt;rules&lt;/li&gt;
&lt;li&gt;examples&lt;/li&gt;
&lt;li&gt;output formats&lt;/li&gt;
&lt;li&gt;scripts&lt;/li&gt;
&lt;li&gt;templates&lt;/li&gt;
&lt;li&gt;best practices&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Skills are useful for repeated tasks. Examples include writing reports, analyzing PDFs, creating slides, debugging code, or handling customer support. Skills make agents more specialized.&lt;/p&gt;
&lt;p&gt;Instead of putting every instruction into the system prompt, we can use skills where the model receives just the skill metadata in the context and will be able to load and use the full skill when the current task needs it.&lt;/p&gt;
&lt;p&gt;A skill file is just a markdown file with the below format:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;---
name: skill-name
description: A description of what this skill does and when to use it.
---
Skill contents in markdown
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s see a real-world example: The &lt;a href=&quot;https://github.com/serpapi/skills&quot;&gt;SerpApi Search Skill&lt;/a&gt; provides instructions for the agent to interact with SerpApi realtime search APIs. You can see the &lt;a href=&quot;https://github.com/serpapi/skills/blob/master/skills/serpapi-web-search/SKILL.md&quot;&gt;skill.md file&lt;/a&gt;, which provides instruction to the model to invoke various SerpApi API calls.&lt;/p&gt;
&lt;p&gt;You can see a usage example below, where we use SerpApi skill to build a travel planning agent.&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; os
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; subprocess
&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; pathlib &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Path

&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; openai &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; OpenAI


MODEL &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;getenv&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;OPENAI_MODEL&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gpt-5.4-mini&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
SKILL_PATH &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Path&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;__file__&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;resolve&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;parent &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;skills&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;serpapi-web-search&quot;&lt;/span&gt;


&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;run_shell_call&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shell_call&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;\nModel requested shell call: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;shell_call&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;call_id&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;Commands: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;shell_call&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;action&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;commands&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    command_outputs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; command &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; shell_call&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;action&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;commands&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;\n[script] Running command: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;command&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; subprocess&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;run&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            command&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            shell&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            executable&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/bin/zsh&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            capture_output&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            text&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            check&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;[script] Exit code: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;returncode&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stdout&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;[script] stdout:\n&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stdout&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token format-spec&quot;&gt;1500]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stderr&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;[script] stderr:\n&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stderr&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token format-spec&quot;&gt;1500]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        command_outputs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;append&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;stdout&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stdout&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;stderr&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stderr&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;outcome&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;exit&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;exit_code&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;returncode&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    output_item &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;shell_call_output&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;call_id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; shell_call&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;call_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;output&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; command_outputs&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; shell_call&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;action&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;max_output_length &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        output_item&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;max_output_length&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; shell_call&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;action&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;max_output_length

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; output_item


&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; __name__ &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;__main__&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    client &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; OpenAI&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;api_key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;environ&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;OPENAI_API_KEY&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    input_list &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;system&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;You are Safar, a travel planning assistant.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

    tools &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;shell&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;environment&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;local&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;skills&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;token string&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;serpapi-web-search&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                        &lt;span class=&quot;token string&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Search current travel information with the SerpApi CLI.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                        &lt;span class=&quot;token string&quot;&gt;&quot;path&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;SKILL_PATH&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Type 'exit' or 'quit' to stop.\n&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    waiting_for_user &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; waiting_for_user&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            user_query &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;You: &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; user_query&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lower&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;exit&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;quit&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;

            input_list&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;append&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; user_query&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            waiting_for_user &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;False&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;\n[script] Sending request to the model.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;responses&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;create&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            model&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;MODEL&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token builtin&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;input_list&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            tools&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;tools&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        input_list &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;output

        shell_calls &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;item &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; item &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;output &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;shell_call&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;[script] Shell calls requested: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shell_calls&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;not&lt;/span&gt; shell_calls&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-interpolation&quot;&gt;&lt;span class=&quot;token string&quot;&gt;f&quot;Model response: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;output_text&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;\n&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            waiting_for_user &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;continue&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; shell_call &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; shell_calls&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            input_list&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;append&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;run_shell_call&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shell_call&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;\n[script] Sending shell output back to the model.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The script uses local skills capability of OpenAI SDK - we have the skill files added in &lt;code&gt;skills/serpapi-web-search&lt;/code&gt; folder relative to the scripts parent directory.&lt;/p&gt;
&lt;p&gt;Skills can be specified using the below format:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;tools &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;shell&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;environment&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;local&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&quot;skills&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;token string&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;serpapi-web-search&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                        &lt;span class=&quot;token string&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Search current travel information with the SerpApi CLI.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                        &lt;span class=&quot;token string&quot;&gt;&quot;path&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;SKILL_PATH&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We provide the skill name, description and path to the agent. When using skills, OpenAI SDK will emit shell calls that must be run in the terminal. This is needed so that the agent can list and view the full skill file contents that are present locally. We have a &lt;code&gt;run_shell_call()&lt;/code&gt; function defined for this. Whenever the model requests for a shell call, we will run this function and pass back the shell results to the model.&lt;/p&gt;
&lt;blockquote class=&quot;prompt-tip&quot;&gt;
Since this example lets the model request shell commands, only run it in a trusted, sandboxed environment. Do not give shell access to untrusted prompts, repositories, or skill files without review.
&lt;/blockquote&gt;
&lt;p&gt;Now let’s run the agent and ask it a travel planning question. We will ask the model about hotel prices in Goa, India:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Type 'exit' or 'quit' to stop.
You: Find Goa hotel prices for a vacation: two nights from 10 June 26

[script] Sending request to the model.
[script] Shell calls requested: 1

Model requested shell call: call_Cgs0D4tNOZFPZ30GNVJzJNHZ
Commands: ['cd .../skills/serpapi-web-search &amp;amp;&amp;amp; cat SKILL.md']

[script] Running command: cd .../skills/serpapi-web-search &amp;amp;&amp;amp; cat SKILL.md
[script] Exit code: 0
[script] stdout:
---
name: serpapi-web-search
description: &amp;gt;-
  Search the web using SerpApi's 100+ search engines. Use this skill whenever
  the user needs current or web-sourced information: ...

[script] Sending shell output back to the model.

[script] Sending request to the model.
[script] Shell calls requested: 1

Model requested shell call: call_Y2PGcBkpSEkzIikqu1H30uRW
Commands: [&amp;quot;cd .../skills/serpapi-web-search &amp;amp;&amp;amp; sed -n '1,220p' rules/ENGINES.md&amp;quot;]

[script] Running command: cd .../skills/serpapi-web-search &amp;amp;&amp;amp; sed -n '1,220p' rules/ENGINES.md
[script] Exit code: 0
[script] stdout:
# SerpApi Search Engines Catalog

Complete list of 107 SerpApi search engines. Use the `engine` parameter to select the desired search engine.
...

[script] Sending shell output back to the model.

[script] Sending request to the model.
[script] Shell calls requested: 1

Model requested shell call: call_oncTqSWJWQoDbqJS06aJP1OD
Commands: ['cd .../skills/serpapi-web-search &amp;amp;&amp;amp; serpapi search engine=google_hotels q=&amp;quot;Goa&amp;quot; check_in_date=2026-06-10 check_out_date=2026-06-12 --fields \'properties[0:20]\'']

[script] Running command: cd .../skills/serpapi-web-search &amp;amp;&amp;amp; serpapi search engine=google_hotels q=&amp;quot;Goa&amp;quot; check_in_date=2026-06-10 check_out_date=2026-06-12 --fields 'properties[0:20]'
[script] Exit code: 0
[script] stdout:
{
  &amp;quot;properties&amp;quot;: [
    {
      &amp;quot;type&amp;quot;: &amp;quot;hotel&amp;quot;,
      &amp;quot;name&amp;quot;: &amp;quot;Vivanta Goa, Miramar&amp;quot;,
      &amp;quot;description&amp;quot;: &amp;quot;Swanky hotel across from the beach featuring a rooftop pool, a stylish restaurant &amp;amp; a 24-hour gym.&amp;quot;,
      ...


[script] Sending shell output back to the model.

[script] Sending request to the model.
[script] Shell calls requested: 0

Model response: I found live Goa hotel prices for **2 nights: 10 Jun 2026 to 12 Jun 2026**.

Sample prices from Google Hotels:
- **Vivanta Goa, Miramar** — **$82/night**, **$164 total**
- **Aloha Holiday Resort** — **$17/night**, **$33 total**
- Other properties in the Goa search were showing a wide range, from budget stays to luxury resorts.

A few notes:
- These are **current live rates** and can change quickly.
- The prices shown are from hotel search results and may be **before taxes/fees**.
- I searched broadly for **Goa**; if you want, I can narrow it down by:
  - **North Goa / South Goa**
  - **Budget / mid-range / luxury**
  - **Beachfront**
  - **2 adults vs family**

If you want, I can make a short list of the **best 10 Goa hotels under a budget you choose**.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As seen from the output, the model initially requested a shell call that runs &lt;code&gt;cat SKILL.md&lt;/code&gt; which is to read the skill contents.&lt;/p&gt;
&lt;p&gt;With the skill contents obtained, the model proceeds with another shell call &lt;code&gt;sed -n '1,220p' rules/ENGINES.md&lt;/code&gt; which lists all SerpApi supported engines. With this data, the model will be able to get all supported SerpApi search engines and choose the best one for the task.&lt;/p&gt;
&lt;p&gt;Next, model requests running the command &lt;code&gt;serpapi search engine=google_hotels q=&amp;quot;Goa&amp;quot; check_in_date=2026-06-10 check_out_date=2026-06-12 --fields 'properties[0:20]'&lt;/code&gt; which uses &lt;a href=&quot;https://github.com/serpapi/serpapi-cli&quot;&gt;SerpApi CLI&lt;/a&gt; to get results from Google Hotels. We run this shell command on our end and pass the results to the model that includes JSON results from Google Hotels API.&lt;/p&gt;
&lt;p&gt;With this data obtained, the model was able to generate its final response and give us suggestions for Hotels to book in Goa along with the prices.&lt;/p&gt;
&lt;p&gt;Now that we have seen prompts, memory, tools, MCP, and skills, we can put these pieces into one simple stack.&lt;/p&gt;
&lt;h2 id=&quot;agent-capability-stack&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://blog.adarshd.dev/feed.xml#agent-capability-stack&quot;&gt;Agent Capability Stack&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;An agent can be understood as a stack of capabilities. We have seen the core building blocks of an agent: system prompts, tools, MCP, and skills. Now, let’s compare how they fit together in the agent capability stack.&lt;/p&gt;
&lt;p&gt;At the bottom, we have the system prompt. This defines global behavior and constraints.&lt;/p&gt;
&lt;p&gt;Then we have skills. Skills provide packaged procedures for specific task types.&lt;/p&gt;
&lt;p&gt;Then we have tools. Tools let the agent do things in the world.&lt;/p&gt;
&lt;p&gt;Then we have MCP. MCP gives us a standard way to connect models to tools, files, APIs, databases, IDEs, browsers, and other systems.&lt;/p&gt;
&lt;p&gt;We can think about the stack like this:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Use when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;System prompt&lt;/td&gt;
&lt;td&gt;Global behavior and constraints&lt;/td&gt;
&lt;td&gt;You want rules that apply every turn&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skills&lt;/td&gt;
&lt;td&gt;Reusable workflows&lt;/td&gt;
&lt;td&gt;You want the model to follow a repeatable process&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tools&lt;/td&gt;
&lt;td&gt;External actions and information&lt;/td&gt;
&lt;td&gt;You want the model to call APIs, read files, run code, or fetch live state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MCP&lt;/td&gt;
&lt;td&gt;Standard integration layer&lt;/td&gt;
&lt;td&gt;You want reusable integrations across models and clients&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Use a system prompt for safety boundaries, tone, refusal style, and stable rules.&lt;/p&gt;
&lt;p&gt;Use a skill when you want the model to follow a repeatable workflow or use scripts and templates.&lt;/p&gt;
&lt;p&gt;Use tools when the model must call external services, fetch live state, create side effects, or interact with the environment.&lt;/p&gt;
&lt;p&gt;Use MCP when you want to expose tools and resources through a standard protocol.&lt;/p&gt;
&lt;h2 id=&quot;summary-and-next-steps&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://blog.adarshd.dev/feed.xml#summary-and-next-steps&quot;&gt;Summary and Next Steps&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In this tutorial, we started out with the components of an AI agent and built a few simple agents for use cases such as shopping and travel. We provided capabilities to agents using tool calling, MCP, and Skill files.&lt;/p&gt;
&lt;p&gt;To explore on your own, you can find the code snippets used in the tutorial in this &lt;a href=&quot;https://github.com/serpapi/tutorials/tree/master/python_projects/ai_agents_in_python&quot;&gt;GitHub repo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you are looking for a different SDK or tool to start with like the Claude agent SDK or n8n, we have you covered:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://serpapi.com/blog/build-an-ai-agent-with-claude-agent-sdk/&quot;&gt;Build an AI Agent with the Claude Agent SDK (Tutorial 2026)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://serpapi.com/blog/how-to-build-an-ai-agent-with-n8n-and-live-google-search-data/&quot;&gt;How to Build an AI Agent with n8n and Live Google Search Data&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;Even though we covered the basics for building simple agents, some important next steps to learn more about are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;multi-agent systems&lt;/li&gt;
&lt;li&gt;observability&lt;/li&gt;
&lt;li&gt;error handling&lt;/li&gt;
&lt;li&gt;permissions&lt;/li&gt;
&lt;li&gt;context compaction&lt;/li&gt;
&lt;li&gt;evaluation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A multi-agent system has multiple agents, where each agent can be specialized for a specific goal. These agents can communicate with each other. We can also have verifier models that check the output from other models.&lt;/p&gt;
&lt;p&gt;Similar to building a backend application, we need &lt;strong&gt;observability&lt;/strong&gt; and error handling for agents. The model can hallucinate, choose the wrong tool, pass bad arguments, or get stuck in a loop. We need a way to monitor this behavior and improve the system over time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Permissions&lt;/strong&gt; are also important. An agent that can read files is useful. An agent that can delete files or send emails should be more carefully controlled. We should decide which actions require user approval.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Context compaction&lt;/strong&gt; is another important idea. As the conversation grows, the agent cannot keep everything forever. It needs to summarize old information and keep only what is useful for the next step.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Evaluation&lt;/strong&gt; helps us understand whether the agent is actually doing a good job. We can test the agent on sample tasks, check if it used the right tools, verify whether the final answer is correct, and compare outputs across different prompts or models. Without evaluation, it is hard to know if the agent is improving or just producing confident-looking answers.&lt;/p&gt;
&lt;p&gt;The best way to understand agents is to build small ones, give them real tasks, inspect their tool calls, and evaluate their outputs. Start with a simple loop, add tools carefully, introduce memory only when needed, and add observability before trusting the agent with important actions. And if your agent needs real-time data access, you can explore &lt;a href=&quot;https://serpapi.com/search-engine-apis&quot;&gt;SerpApi APIs&lt;/a&gt; to extend its capabilities.&lt;/p&gt;</content:encoded>
	<dc:date>2026-06-04T06:50:00+00:00</dc:date>
</item>
<item rdf:about="https://coredispatch.xyz/editions/5">
	<title>Core Dispatch: Core Dispatch #5</title>
	<link>https://coredispatch.xyz/editions/5</link>
	<content:encoded>&lt;p&gt;Welcome back to Core Dispatch! This edition covers May 18 through June 4, 2026.
As promised, Python 3.15.0 beta 2 landed on June 2. Two more
milestones are close behind: 3.13.14 and 3.14.6 on June 9, followed by
3.15.0 beta 3 on June 23.&lt;/p&gt;
&lt;p&gt;There&amp;#x27;s also a healthy batch of changes landing for 3.15: an O(n^2) blowup in
&lt;code&gt;unicodedata.normalize()&lt;/code&gt; was fixed, the XML parser gained support for
multi-byte encodings, and a round of deprecation warnings went in for the
&lt;code&gt;ast&lt;/code&gt; module and &lt;code&gt;abc&lt;/code&gt;&amp;#x27;s &lt;code&gt;abstractclassmethod&lt;/code&gt;/&lt;code&gt;abstractstaticmethod&lt;/code&gt;/&lt;code&gt;abstractproperty&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;On the project side, the Python Security Response Team (PSRT) landed an
&lt;a href=&quot;https://github.com/python/devguide/pull/1804&quot;&gt;initial Python security policy&lt;/a&gt;
in the &lt;a href=&quot;https://devguide.python.org/&quot;&gt;Devguide&lt;/a&gt;, giving the vulnerability reporting and response process a
documented home. And dev builds of 3.15+ now report a version like
&lt;code&gt;3.15.0b2+dev&lt;/code&gt; instead of the old bare-plus &lt;code&gt;3.15.0b2+&lt;/code&gt;, which
&lt;a href=&quot;https://github.com/python/release-tools/pull/394&quot;&gt;wasn&amp;#x27;t PEP 440-compliant&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Looking ahead, the &lt;a href=&quot;https://ep2026.europython.eu/language-summit/&quot;&gt;EuroPython 2026 Language Summit&lt;/a&gt;
topics are out, with a lineup spanning a Rust-for-CPython roadmap, the future
of free-threading, garbage collection, and the buffer protocol.&lt;/p&gt;
&lt;p&gt;If you&amp;#x27;re interested in CPython internals, Victor Stinner has a great writeup on
&lt;a href=&quot;https://vstinner.github.io/free-threading-reference-counting.html&quot;&gt;free threading internals and reference counting&lt;/a&gt;
that&amp;#x27;s well worth your time.&lt;/p&gt;
&lt;p&gt;As always, if you maintain a package or just like living on the edge, give the
latest 3.15 beta a spin and &lt;a href=&quot;https://github.com/python/cpython/issues&quot;&gt;file any issues&lt;/a&gt;
you find.&lt;/p&gt;
&lt;h3&gt;Upcoming Releases&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://peps.python.org/pep-0719/&quot;&gt;Python 3.13.14&lt;/a&gt; — Jun 09&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://peps.python.org/pep-0745/&quot;&gt;Python 3.14.6&lt;/a&gt; — Jun 09&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://peps.python.org/pep-0790/&quot;&gt;Python 3.15.0 beta 3&lt;/a&gt; — Jun 23&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Official News&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.python.org/2026/06/python-3150-beta-2/&quot;&gt;Python 3.15.0 beta 2 is here!&lt;/a&gt; — By Hugo van Kemenade&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;PEP Updates&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://peps.python.org/pep-0808/&quot;&gt;PEP 808: Including static values in dynamic metadata&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://peps.python.org/pep-0798/&quot;&gt;PEP 798: Unpacking in Comprehensions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Merged PRs&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/python/cpython/pull/150011&quot;&gt;Restore &lt;code&gt;os.makedirs()&lt;/code&gt; ability to apply &lt;code&gt;mode&lt;/code&gt; recursively&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/python/cpython/pull/115989&quot;&gt;Add missing ARM64 and RISCV filter in the &lt;code&gt;lzma&lt;/code&gt; module&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/python/cpython/pull/149080&quot;&gt;Fix O(n^2) canonical ordering in &lt;code&gt;unicodedata.normalize()&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/python/cpython/pull/140345&quot;&gt;&lt;code&gt;ast&lt;/code&gt;: Add deprecation warnings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/python/cpython/pull/149636&quot;&gt;Raise deprecation warnings for &lt;code&gt;abc.{abstractclassmethod,abstractstaticmethod,abstractproperty}&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/python/cpython/pull/149860&quot;&gt;Add support of multi-byte encodings in the XML parser&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/python/cpython/pull/150542&quot;&gt;Improve the PEP 829 batch processing APIs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/python/cpython/pull/149389&quot;&gt;Remove &lt;code&gt;lazy_imports=none&lt;/code&gt; startup mode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/python/cpython/pull/149535&quot;&gt;Remove deprecated &lt;code&gt;&amp;#x27;u&amp;#x27;&lt;/code&gt; type code from the &lt;code&gt;array&lt;/code&gt; module&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/python/cpython/pull/149649&quot;&gt;Fix excessive overhead in the Tachyon profiler regarding the cache behavior&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/python/devguide/pull/1804&quot;&gt;&lt;code&gt;devguide&lt;/code&gt;: Add an initial Python security policy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/python/release-tools/pull/394&quot;&gt;&lt;code&gt;release-tools&lt;/code&gt;: Make untagged versions PEP 440-compliant&lt;/a&gt; — Untagged/dev builds of CPython now produce PEP 440-compliant version strings.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Discussion&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://discuss.python.org/t/understanding-pep-discussions/107452&quot;&gt;Understanding PEP discussions&lt;/a&gt; — 🆕 🔥 58 new replies · 1.3k views&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://discuss.python.org/t/pep-802-display-syntax-for-the-empty-set/101676&quot;&gt;PEP 802: Display Syntax for the Empty Set&lt;/a&gt; — 🔥 28 new replies · 11.9k views&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://discuss.python.org/t/pep-797-shared-object-proxies/105709&quot;&gt;PEP 797: Shared Object Proxies&lt;/a&gt; — 🔥 14 new replies · 1.9k views&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://discuss.python.org/t/pep-828-supporting-yield-from-in-asynchronous-generators/106459&quot;&gt;PEP 828: Supporting &amp;#x27;yield from&amp;#x27; in asynchronous generators&lt;/a&gt; — 🔥 11 new replies · 4.0k views&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://discuss.python.org/t/pep-832-virtual-environment-discovery/106998&quot;&gt;PEP 832: virtual environment discovery&lt;/a&gt; — 8 new replies · 4.8k views&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://discuss.python.org/t/revisiting-pep-505-none-aware-operators/74568&quot;&gt;Revisiting PEP 505 – None-aware operators&lt;/a&gt; — 4 new replies · 19.9k views&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Core Dev Musings&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://vstinner.github.io/free-threading-reference-counting.html&quot;&gt;Free Threading internals: reference counting&lt;/a&gt; — By Victor Stinner&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Upcoming CFPs &amp;amp; Conferences&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://gh.pycon.org/2026/&quot;&gt;📋 PyCon Ghana 2026 Deadline&lt;/a&gt; — Jun 06&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://2026.pybeach.org/&quot;&gt;📋 PyBeach 2026 Deadline&lt;/a&gt; — Jun 08&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://2026.geopython.net/&quot;&gt;GeoPython 2026&lt;/a&gt; — Jun 08&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.pycon.ke/&quot;&gt;📋 PyCon Kenya 2026 Deadline&lt;/a&gt; — Jun 09&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://2026.pycon.kr/&quot;&gt;📋 PyCon South Korea 2026 Deadline (extended)&lt;/a&gt; — Jun 14&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://2026.pythonho.com/&quot;&gt;📋 Python Ho 2026 Deadline&lt;/a&gt; — Jun 15&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://pycon.sg/&quot;&gt;PyCon Singapore 2026&lt;/a&gt; — Jun 19&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://2026.pythonnorte.org/&quot;&gt;Python Norte 2026&lt;/a&gt; — Jul 03&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ep2026.europython.eu/&quot;&gt;EuroPython 2026&lt;/a&gt; — Jul 13&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.scipy2026.scipy.org/&quot;&gt;SciPy 2026&lt;/a&gt; — Jul 13&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Community&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://kirigami.fastapicloud.dev/&quot;&gt;Kirigami — a guided reading workspace for discuss.python.org topics&lt;/a&gt; — Early development of a new frontend for DPO that turns a discuss.python.org topic into a guided reading workspace — summary, evidence, participant signals, and the original source posts stay connected.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ep2026.europython.eu/language-summit/&quot;&gt;EuroPython 2026 Language Summit topics are announced&lt;/a&gt; — The talk lineup is out — a Rust-for-CPython roadmap, the future of free-threading, garbage collection, the buffer protocol, the Developer in Residence update, and more.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eendebakpt.github.io/core_devs_activity/pyodide_web/&quot;&gt;An interactive look at CPython&amp;#x27;s core team over time&lt;/a&gt; — A Pyodide-powered chart of the CPython core team — listed members versus those active on python/cpython — with a slider to change the activity window.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;One More Thing&lt;/h3&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;&amp;quot;TBC&amp;quot; is &amp;quot;to be confirmed&amp;quot; for Pablo&amp;#x27;s [Language Summit talk]?&quot;&lt;/p&gt;— &lt;a href=&quot;https://github.com/gpshead&quot;&gt;Gregory Smith&lt;/a&gt;&lt;p&gt;&quot;The Banana Council 🍌&quot;&lt;/p&gt;— &lt;a href=&quot;https://github.com/corona10&quot;&gt;Donghee Na&lt;/a&gt;&lt;/blockquote&gt;
&lt;h3&gt;Credits&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/savannahostrowski&quot;&gt;Savannah Ostrowski&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded>
	<dc:date>2026-06-04T00:00:00+00:00</dc:date>
</item>
<item rdf:about="https://2026.pycon.ie/blog/cfp-deadline-moved-to-31-july/">
	<title>PyCon Ireland: CFP Deadline Moved to 31 July 2026</title>
	<link>https://2026.pycon.ie/blog/cfp-deadline-moved-to-31-july/</link>
	<content:encoded>&lt;p&gt;We&amp;rsquo;ve moved the deadline for the &lt;strong&gt;PyCon Ireland 2026 Call for Proposals&lt;/strong&gt; forward to &lt;strong&gt;31 July 2026&lt;/strong&gt;. It was previously set to 30 August. The submission page on &lt;a href=&quot;https://sessionize.com/pycon-ireland-2026/&quot;&gt;Sessionize&lt;/a&gt; already reflects the new date.&lt;/p&gt;
&lt;p&gt;If you were planning to submit, please get your proposal in by &lt;strong&gt;31 July 2026&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;why-we-brought-the-deadline-forward&quot;&gt;Why We Brought the Deadline Forward&lt;/h2&gt;
&lt;p&gt;There are two reasons behind this change, and both are about giving people the time they need to do things well.&lt;/p&gt;
&lt;h3 id=&quot;giving-the-programme-committee-room-to-review-properly&quot;&gt;Giving the programme committee room to review properly&lt;/h3&gt;
&lt;p&gt;Building a great schedule takes careful work. With dozens of proposals to read, discuss, and compare, the programme committee needs enough time to give every submission a fair and thorough review. Closing the CFP at the end of August left a tight window between the deadline and the point where we have to lock in the schedule. By moving to 31 July, we give the committee the breathing room to evaluate each proposal on its merits, balance the tracks, and make thoughtful decisions rather than rushed ones.&lt;/p&gt;
&lt;h3 id=&quot;giving-speakers-time-to-plan-their-trip-to-dublin&quot;&gt;Giving speakers time to plan their trip to Dublin&lt;/h3&gt;
&lt;p&gt;PyCon Ireland brings speakers from across Ireland and beyond. Travelling to Dublin means booking flights, arranging accommodation, sorting out time off, and sometimes applying for visas or financial aid. The sooner we can confirm accepted talks, the sooner speakers can start planning, and the less stressful and less expensive that planning tends to be. An earlier deadline means earlier notifications, which is better for everyone making the journey.&lt;/p&gt;
&lt;h2 id=&quot;what-this-means-for-you&quot;&gt;What This Means for You&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The new deadline is 31 July 2026.&lt;/strong&gt; Submit before then.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Decisions will follow sooner.&lt;/strong&gt; Bringing the deadline forward lets us notify accepted speakers earlier, so you can lock in travel and accommodation with more lead time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nothing else changes.&lt;/strong&gt; We still welcome proposals from first-time and experienced speakers alike, across the full range of topics, in both full-talk (30 minutes) and lightning-talk (5 minutes) formats.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;ready-to-submit&quot;&gt;Ready to Submit?&lt;/h2&gt;
&lt;p&gt;Head over to our &lt;a href=&quot;https://sessionize.com/pycon-ireland-2026/&quot;&gt;proposal submission page&lt;/a&gt; and tell us what you&amp;rsquo;d like to talk about. If you have any questions, reach out at &lt;a href=&quot;mailto:contact@python.ie&quot;&gt;contact@python.ie&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We can&amp;rsquo;t wait to read your proposals, and we&amp;rsquo;re looking forward to seeing you in Dublin on 17 October 2026.&lt;/p&gt;</content:encoded>
	<dc:date>2026-06-04T00:00:00+00:00</dc:date>
</item>
<item rdf:about="https://belderbos.dev/blog/jochen-rust-cohort-beat-cpython/">
	<title>Bob Belderbos: &quot;Rust Is for People Who Want to Be Punished.&quot; Now Jochen Trusts It More Than Python.</title>
	<link>https://belderbos.dev/blog/jochen-rust-cohort-beat-cpython/</link>
	<content:encoded>&lt;p&gt;Jochen Deister is a lawyer who codes for fun. He has years of Python behind him and no intention of ever being hired to program.&lt;/p&gt;
&lt;p&gt;Three months ago, Rust was just a name to him, the language for &quot;the big shots&quot; with a notoriously steep learning curve. Then he built a JSON parser from scratch in Rust, and it ran faster than the equivalent in Python on every dataset he tested, up to 3.5x faster on some. &quot;Holy F&quot; he reacted when he saw the results.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;
&lt;p&gt;Six weeks of work produced:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/Pattkopp/rust-cohort&quot;&gt;from-scratch JSON parser&lt;/a&gt;, no parsing libraries&lt;/li&gt;
&lt;li&gt;Benchmarks beating Python's standard &lt;code&gt;json&lt;/code&gt; module (C-accelerated in CPython), up to 3.5x faster&lt;/li&gt;
&lt;li&gt;Close to 30 commits in the final week alone, each one a single performance step&lt;/li&gt;
&lt;li&gt;A deliberate 78-error refactor, with the compiler as the guide to a faster implementation&lt;/li&gt;
&lt;li&gt;A new default language: Rust is now the one he reaches for first&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here's how it happened.&lt;/p&gt;
&lt;h2 id=&quot;the-gap&quot;&gt;The gap&lt;/h2&gt;
&lt;p&gt;Jochen learned to code on a Commodore VIC-20 with six kilobytes of RAM, then a C64, then a stint in assembly and Turbo Pascal when the bottleneck moved from memory to speed.&lt;/p&gt;
&lt;p&gt;Then life took him into law and academia, and he forgot all of it until he picked Python back up years ago.&lt;/p&gt;
&lt;p&gt;Python suited him, but it hid the machine. &quot;Python abstracts a lot of these concepts away&quot; he said. &quot;It hides the mechanics&quot;.&lt;/p&gt;
&lt;p&gt;He'd heard Rust had a notoriously steep learning curve, and he was doing this for fun. &quot;Rust is for people who want to be punished in their life&quot; he figured, and left it there.&lt;/p&gt;
&lt;p&gt;The trigger that changed it was small: &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://www.youtube.com/watch?v=-5uLLBvWK5Q&quot;&gt;the last Pybites podcast episode&lt;/a&gt;, a $49 lifetime offer &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://rustplatform.com/&quot;&gt;on our Rust practice platform&lt;/a&gt;, and a remote cabin on the Danish coast where his only job was to keep his kids fed during exam season.&lt;/p&gt;
&lt;p&gt;He finished all 61 platform exercises, third on the leaderboard, then shortly after signed up for the cohort for a deeper challenge.&lt;/p&gt;
&lt;p&gt;The platform taught him the vocabulary. What it couldn't give him was a real project with a coach reading his code in detail. That's what our cohort is about: six weeks building a JSON parser, one PR review a week with &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://www.refactorcoach.com&quot;&gt;Jim Hodapp&lt;/a&gt;, expert Rust coach.&lt;/p&gt;
&lt;h2 id=&quot;the-constraints-stopped-feeling-like-constraints&quot;&gt;The constraints stopped feeling like constraints&lt;/h2&gt;
&lt;p&gt;Most people describe their first weeks of Rust as a &lt;em&gt;fight with the borrow checker&lt;/em&gt;; the compiler rule that tracks who owns each value and won't let two parts of your code modify the same data at once. Jochen didn't feel it this way at all.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;I never had the feeling that I was fighting the borrow checker. The error messages were my friends right out of the gate. They had a good explanation of the error, but also a hint about what you could do differently.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What hooked him was aesthetics. Run the formatter on a chain of iterator steps and each transformation lands on its own line, readable top to bottom.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Rust is a beautiful language. It's an aesthetic language. It looks good, and working toward more beautiful code was really something I liked.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That pulled him toward idiomatic Rust on its own. He stopped wanting code that merely worked, the bar he'd accepted in Python, and started wanting code that was safe, performant and idiomatic.&lt;/p&gt;
&lt;h2 id=&quot;he-broke-his-own-code-on-purpose&quot;&gt;He broke his own code on purpose&lt;/h2&gt;
&lt;p&gt;Week five, PyO3, was the real step up. &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://pyo3.rs/v0.28.3/&quot;&gt;PyO3&lt;/a&gt; is the bridge that lets you call a Rust module straight from Python, the same layer Pydantic and Polars are built on. It was the first concept the practice platform hadn't prepared him for, so he leaned on the implementation steps and went slowly.&lt;/p&gt;
&lt;p&gt;The clearest sign of how his thinking changed came in the final week. Three of his four benchmark datasets were already beating Python; one wasn't. He suspected the parser was copying the entire input onto the heap instead of borrowing it. So he changed the entry point to take a borrowed string with an explicit lifetime (a lifetime is Rust's way of letting you reference data without copying it, while proving the reference can't outlive the data) and ran &lt;code&gt;cargo check&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It reported 78 errors.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Those 78 errors were my path of what I needed to fix to get to the results. You change something up the chain and 78 reduces to 50, and so on down the line. It is your implementation guide.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;He'd deliberately broken the code, then followed the compiler error by error back to a working, faster version. It's like having a 200% test suite for free; you feel confident making changes.&lt;/p&gt;
&lt;p&gt;The rewrite turned a parser that collected every token into a list up front into one that reads tokens on demand in a single pass. Jim's note on the PR: &quot;This is such a clean functional style API for your tokenizer, it's evolved and matured nicely&quot;.&lt;/p&gt;
&lt;h2 id=&quot;the-profiler-told-him-where-he-was-wrong&quot;&gt;The profiler told him where he was wrong&lt;/h2&gt;
&lt;p&gt;Speed in Rust isn't automatic, and Jochen learned that the hard way. He'd swapped a list for a double-ended queue, proud of it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Two days later, when I looked at the profiler, that very line that I was so proud of was now by far the biggest offender.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A profiler measures where a program actually spends its time, so you optimize the real bottleneck instead of a guessed one. His showed the standard-library hash map dominating. He read the docs, realized that map carries protection against denial-of-service attacks he'd never need in a local command-line tool, and replaced it with a stripped-down one. Data-driven, one commit at a time, until the last dataset crossed the line.&lt;/p&gt;
&lt;p&gt;Through all of it he kept AI out of the code on purpose. He used it to make himself learn faster, NotebookLM turning Rust docs into podcasts and flashcards, never to write a solution. &quot;Only I write the code&quot; is the rule he gives his AI mentor.&lt;/p&gt;
&lt;h2 id=&quot;what-changed&quot;&gt;What changed&lt;/h2&gt;
&lt;p&gt;Ask him how confident he feels starting a new Rust project and he says a 3 out of 10, and means it as a compliment to the language.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;I'm not a total noob anymore. I have a rough understanding of the key concepts, but I also know there's a heck of a lot to learn.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The transfer is in the habits. Rust is now his default for new projects, he caught himself skipping the Python newsletters to read about Rust instead, and the deliberate, idiomatic thinking followed him back into his Python. After years in the Python community, his loyalty quietly shifted:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;I've always liked Python. But it's changed in a way that I think I like Rust more, because of its honesty and because it forces you to think stricter.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;His favorite piece of the language is pattern matching, the construct that lets you branch on the shape of a value and pull data out of it in one move. He went deep enough that he used a binding trick his coach hadn't seen before, matching and naming a value in the same arm. Jim's reply on the PR:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;You taught me something I didn't realize Rust has. It's a nice match-and-bind pattern that saves boilerplate code.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The reason he loves it is the same one running through everything he said:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Computer languages need to be beautiful.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Next up for Jochen: porting a coding agent from Python to Rust, and a privacy tool that strips personal data out of text before it reaches an LLM.&lt;/p&gt;
&lt;p&gt;For someone who started three months ago thinking Rust was punishment, that's a real shift. (For more on how Rust rewires the way you write Python, see &lt;a href=&quot;https://belderbos.dev/blog/rust-made-me-a-better-python-developer/&quot;&gt;Learning Rust Made Me a Better Python Developer&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;Here is our full conversation with Jochen about his cohort experience, the parser he built, and the performance work he did:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=i5WJCthYhSE&quot;&gt;Watch on YouTube&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;If you're a Python developer wanting to reach a new level in your career, Rust is a strong contender. &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://calendly.com/bob-belderbos/chat&quot;&gt;Book me in for a call&lt;/a&gt; and we'll discuss this further.&lt;/p&gt;</content:encoded>
	<dc:date>2026-06-04T00:00:00+00:00</dc:date>
</item>
<item rdf:about="https://realpython.com/github-copilot-code-review/">
	<title>Real Python: How to Use GitHub Copilot Code Review in Pull Requests</title>
	<link>https://realpython.com/github-copilot-code-review/</link>
	<content:encoded>&lt;div&gt;&lt;p&gt;GitHub offers several AI tools under the Copilot umbrella that cover your entire development workflow. Copilot can provide an AI-powered code review shortly after you open a pull request on GitHub. Rather than waiting for a teammate, you can add Copilot as a reviewer to receive context-aware feedback. With access to your entire codebase, it delivers actionable suggestions that you can apply in just a few clicks:&lt;/p&gt;
&lt;div&gt;





&lt;div class=&quot;embed-responsive rounded mb-3 bg-light&quot;&gt;
  
    
  
&lt;/div&gt;


&lt;/div&gt;

&lt;p&gt;Pull requests are the standard collaborative workflow provided by GitHub and similar services like GitLab to facilitate code review for projects managed with &lt;a href=&quot;https://realpython.com/ref/tools/git/&quot; class=&quot;ref-link&quot;&gt;Git&lt;/a&gt;. A pull request, or a PR for short, is a formal request to merge code from one branch—or fork—into another, and it’s where code review typically happens.&lt;/p&gt;
&lt;p&gt;In practice, code review isn’t always timely or consistent. Some reviewers approve pull requests immediately without much scrutiny, while others leave long lists of minor nitpicks. It can also be difficult to find someone with the right level of experience or enough context about a specific part of the codebase. These issues are common in open-source projects as well, where reviews depend on the limited time of volunteer maintainers.&lt;/p&gt;
&lt;p&gt;In this tutorial, you’ll learn how to leverage GitHub Copilot for &lt;strong&gt;AI-assisted code review&lt;/strong&gt; in pull requests and how to integrate it into your workflow to get faster, more structured feedback. Whether you’re working on a commercial project or contributing to an open-source one, Copilot can help you catch issues early and improve your code before it’s merged.&lt;/p&gt;
&lt;p&gt;Think of Copilot’s review as a fast first pass. It can reliably flag correctness mistakes and regressions to documented behavior, often before a human reviewer has even opened the PR.&lt;/p&gt;
&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;a class=&quot;headerlink&quot; href=&quot;https://realpython.com/atom.xml#prerequisites&quot; title=&quot;Permanent link&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Before you get started with AI-assisted code reviews, make sure you have the following in place:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Git and GitHub Knowledge:&lt;/strong&gt; You should have a basic familiarity with &lt;a href=&quot;https://realpython.com/python-git-github-intro/&quot;&gt;Git and GitHub&lt;/a&gt;, including how to &lt;a href=&quot;https://realpython.com/python-git-github-intro/#branching-basics&quot;&gt;create branches&lt;/a&gt;, &lt;a href=&quot;https://docs.github.com/en/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/about-commits&quot;&gt;commit changes&lt;/a&gt;, and &lt;a href=&quot;https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request&quot;&gt;open pull requests&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Git Client and GitHub CLI:&lt;/strong&gt; You should have the &lt;a href=&quot;https://git-scm.com/docs/git&quot;&gt;&lt;code&gt;git&lt;/code&gt;&lt;/a&gt; client configured in your command line. Additionally, you’ll need the &lt;a href=&quot;https://cli.github.com/&quot;&gt;GitHub CLI&lt;/a&gt; tool, as it simplifies common GitHub-related tasks. Make sure you’re running &lt;a href=&quot;https://github.com/cli/cli/releases/tag/v2.88.0&quot;&gt;v2.88.0&lt;/a&gt; or later, which introduced support for &lt;a href=&quot;https://github.blog/changelog/2026-03-11-request-copilot-code-review-from-github-cli/&quot;&gt;requesting Copilot code reviews&lt;/a&gt; from the command line.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub Account:&lt;/strong&gt; You need a GitHub account with a paid &lt;a href=&quot;https://github.com/features/copilot/plans&quot;&gt;Copilot plan&lt;/a&gt; (Pro, Pro+, Business, or Enterprise). To check your subscription status, visit &lt;a href=&quot;https://github.com/settings/copilot&quot;&gt;GitHub Copilot settings&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Depending on how you use GitHub, you may already have access to GitHub Copilot through your organization. Sometimes, you may qualify for Copilot under special conditions.&lt;/p&gt;
&lt;p&gt;For example, if you’re a student or a teacher, or if you regularly contribute to a popular open-source project, then you might be eligible for &lt;a href=&quot;https://docs.github.com/en/copilot/how-tos/manage-your-account/get-free-access-to-copilot-pro&quot;&gt;free access to GitHub Copilot Pro&lt;/a&gt;. Check out &lt;a href=&quot;https://github.com/education&quot;&gt;GitHub Education&lt;/a&gt; to learn more. Keep in mind that GitHub reassesses whether you qualify for free access on a monthly basis.&lt;/p&gt;
&lt;p&gt;But even on the free plan, you can still try out Copilot’s code review feature for 30 days at no cost. Just &lt;a href=&quot;https://github.com/github-copilot/pro/signup&quot;&gt;subscribe to GitHub Copilot Pro&lt;/a&gt; and cancel before the first billing cycle begins. The trial period is a one-time offer per account, so you won’t be able to start another one after the first one ends.&lt;/p&gt;
&lt;div class=&quot;alert alert-primary&quot;&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; At the time of writing, GitHub has temporarily paused new paid subscriptions for Copilot due to exceptionally high demand and the associated infrastructure costs. You can read the &lt;a href=&quot;https://github.blog/news-insights/company-news/changes-to-github-copilot-individual-plans/&quot;&gt;official announcement&lt;/a&gt; on GitHub’s blog to learn more.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;To follow along with this tutorial, you’ll also need a &lt;strong&gt;GitHub repository&lt;/strong&gt; where you can freely create branches and pull requests. Although you can &lt;a href=&quot;https://github.com/new&quot;&gt;create a new repository&lt;/a&gt; from scratch or &lt;a href=&quot;https://github.com/new/import&quot;&gt;import one&lt;/a&gt; from another Git-based hosting service, the quickest option is to download the provided supporting materials. They include a small, hands-on project you’ll be working on:&lt;/p&gt;
&lt;div class=&quot;alert alert-warning&quot;&gt;
&lt;p&gt;&lt;strong&gt;Get Your Code:&lt;/strong&gt; &lt;a href=&quot;https://realpython.com/bonus/github-copilot-code-review-code/&quot; class=&quot;alert-link&quot;&gt;Click here to download the free sample code&lt;/a&gt; you’ll use to practice AI-assisted code review on a sample FastAPI pull request with GitHub Copilot.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;container border rounded text-wrap-pretty my-3&quot;&gt;

  &lt;p class=&quot;my-3&quot;&gt;&lt;strong&gt;&lt;span class=&quot;icon baseline&quot;&gt;&lt;/span&gt; Take the Quiz:&lt;/strong&gt; Test your knowledge with our interactive “How to Use GitHub Copilot Code Review in Pull Requests” quiz. You’ll receive a score upon completion to help you track your learning progress:&lt;/p&gt;

  &lt;hr /&gt;

  &lt;div class=&quot;row my-3&quot;&gt;
    &lt;div class=&quot;col-xs-12 col-sm-4 col-md-3 align-self-center&quot;&gt;

      &lt;a href=&quot;https://realpython.com/quizzes/github-copilot-code-review/&quot; tabindex=&quot;-1&quot;&gt;
        &lt;div class=&quot;embed-responsive embed-responsive-16by9&quot;&gt;

            &lt;img class=&quot;card-img-top m-0 p-0 embed-responsive-item rounded&quot; alt=&quot;A robot labeled Copilot says &quot; /&gt;


          &lt;div class=&quot;card-img-overlay d-flex align-items-center&quot;&gt;
            &lt;div class=&quot;mx-auto&quot;&gt;
              &lt;span class=&quot;text-light&quot;&gt;&lt;span class=&quot;icon baseline scale2x&quot;&gt;&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/a&gt;

    &lt;/div&gt;
    &lt;div class=&quot;col&quot;&gt;
      &lt;div class=&quot;mt-3 d-md-none&quot;&gt;&lt;/div&gt; 
      &lt;p class=&quot;small text-muted mb-0&quot;&gt;&lt;strong&gt;Interactive Quiz&lt;/strong&gt;&lt;/p&gt;
      &lt;a href=&quot;https://realpython.com/quizzes/github-copilot-code-review/&quot; class=&quot;stretched-link&quot;&gt;&lt;span class=&quot;my-0 h4&quot;&gt;How to Use GitHub Copilot Code Review in Pull Requests&lt;/span&gt;&lt;/a&gt; 
      &lt;p class=&quot;text-muted mb-0 small&quot;&gt;Test your knowledge of GitHub Copilot code review in pull requests, including custom instructions and automatic reviews.&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;

&lt;/div&gt;

&lt;p&gt;The sample project is a real-time quiz application inspired by &lt;a href=&quot;https://kahoot.it/&quot;&gt;Kahoot!&lt;/a&gt; and &lt;a href=&quot;https://www.mentimeter.com/&quot;&gt;Mentimeter&lt;/a&gt;, featuring a &lt;a href=&quot;https://realpython.com/get-started-with-fastapi/&quot;&gt;FastAPI&lt;/a&gt; backend and a mobile-first &lt;a href=&quot;https://realpython.com/python-vs-javascript/&quot;&gt;JavaScript&lt;/a&gt;, &lt;a href=&quot;https://realpython.com/html-css-python/&quot;&gt;HTML, and CSS&lt;/a&gt; frontend. It allows you to make your own quizzes from scratch—and store them in the human-readable &lt;a href=&quot;https://realpython.com/python-yaml/&quot;&gt;YAML format&lt;/a&gt;—or generate a random quiz on the fly using &lt;a href=&quot;https://realpython.com/chatgpt-api-python/&quot;&gt;ChatGPT’s API&lt;/a&gt;:&lt;/p&gt;
&lt;div&gt;





&lt;div class=&quot;embed-responsive rounded mb-3 bg-light&quot;&gt;
  
    
  
&lt;/div&gt;


&lt;/div&gt;

&lt;p&gt;Each player is assigned a randomly generated name with an emoji, such as 🐯 Grumpy Tiger, 🦨 Gentle Skunk, or 🐮 Lazy Cow, to keep things light and fun. You can start the server on a local network and have your friends or family connect from their mobile devices using a &lt;a href=&quot;https://realpython.com/python-generate-qr-code/&quot;&gt;QR code&lt;/a&gt; or a &lt;a href=&quot;https://en.wikipedia.org/wiki/Personal_identification_number&quot;&gt;PIN&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Are you ready to dive in?&lt;/p&gt;
&lt;h2 id=&quot;step-1-request-a-code-review-from-github-copilot&quot;&gt;Step 1: Request a Code Review From GitHub Copilot&lt;a class=&quot;headerlink&quot; href=&quot;https://realpython.com/atom.xml#step-1-request-a-code-review-from-github-copilot&quot; title=&quot;Permanent link&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you haven’t already, go ahead and grab the supporting materials. The sample Git repository includes a &lt;a href=&quot;https://martinfowler.com/bliki/FeatureBranch.html&quot;&gt;feature branch&lt;/a&gt; with intentional code issues that GitHub Copilot can catch when you request a review. For reference, you’ll also find another branch with the completed code to explore at your own pace:&lt;/p&gt;
&lt;div class=&quot;alert alert-warning&quot;&gt;
&lt;p&gt;&lt;strong&gt;Get Your Code:&lt;/strong&gt; &lt;a href=&quot;https://realpython.com/bonus/github-copilot-code-review-code/&quot; class=&quot;alert-link&quot;&gt;Click here to download the free sample code&lt;/a&gt; you’ll use to practice AI-assisted code review on a sample FastAPI pull request with GitHub Copilot.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;After downloading the materials, upload the local &lt;code&gt;pop-quiz&lt;/code&gt; repository—including all branches—to your GitHub account. This will create a remote copy of the repository for your own experimentation. There are several ways to accomplish this. Although you can handle most tasks through the &lt;a href=&quot;https://github.com/&quot;&gt;GitHub web interface&lt;/a&gt;, the GitHub CLI is often faster and more convenient.&lt;/p&gt;
&lt;p&gt;One straightforward approach is to use the GitHub CLI (&lt;code&gt;gh&lt;/code&gt;) alongside standard &lt;code&gt;git&lt;/code&gt; commands. This allows you to create the repository and push all branches in just two steps once you’re in the downloaded &lt;code&gt;pop-quiz/&lt;/code&gt; directory:&lt;/p&gt;
&lt;/div&gt;&lt;h2&gt;&lt;a href=&quot;https://realpython.com/github-copilot-code-review/?utm_source=realpython&amp;utm_medium=rss&quot;&gt;Read the full article at https://realpython.com/github-copilot-code-review/ »&lt;/a&gt;&lt;/h2&gt;
        &lt;hr /&gt;
        &lt;p&gt;&lt;em&gt;[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short &amp;amp; sweet Python Trick delivered to your inbox every couple of days. &lt;a href=&quot;https://realpython.com/python-tricks/?utm_source=realpython&amp;utm_medium=rss&amp;utm_campaign=footer&quot;&gt;&amp;gt;&amp;gt; Click here to learn more and see examples&lt;/a&gt; ]&lt;/em&gt;&lt;/p&gt;</content:encoded>
	<dc:date>2026-06-03T14:00:00+00:00</dc:date>
</item>
<item rdf:about="https://realpython.com/quizzes/github-copilot-code-review/">
	<title>Real Python: Quiz: How to Use GitHub Copilot Code Review in Pull Requests</title>
	<link>https://realpython.com/quizzes/github-copilot-code-review/</link>
	<content:encoded>&lt;p&gt;In this quiz, you&amp;rsquo;ll test your understanding of &lt;a href=&quot;https://realpython.com/github-copilot-code-review/&quot;&gt;How to Use GitHub Copilot Code Review in Pull Requests&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;By working through this quiz, you&amp;rsquo;ll revisit how to request a review from Copilot on your pull requests, apply or push back on its suggestions, configure automatic reviews, and use custom instructions to make Copilot&amp;rsquo;s feedback follow your team&amp;rsquo;s conventions.&lt;/p&gt;
        &lt;hr /&gt;
        &lt;p&gt;&lt;em&gt;[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short &amp;amp; sweet Python Trick delivered to your inbox every couple of days. &lt;a href=&quot;https://realpython.com/python-tricks/?utm_source=realpython&amp;utm_medium=rss&amp;utm_campaign=footer&quot;&gt;&amp;gt;&amp;gt; Click here to learn more and see examples&lt;/a&gt; ]&lt;/em&gt;&lt;/p&gt;</content:encoded>
	<dc:date>2026-06-03T12:00:00+00:00</dc:date>
</item>
<item rdf:about="https://www.djangoproject.com/weblog/2026/jun/03/security-releases/">
	<title>Django Weblog: Django security releases issued: 6.0.6 and 5.2.15</title>
	<link>https://www.djangoproject.com/weblog/2026/jun/03/security-releases/</link>
	<content:encoded>&lt;p&gt;In accordance with &lt;a href=&quot;https://docs.djangoproject.com/en/dev/internals/security/&quot;&gt;our security release policy&lt;/a&gt;,
the Django team is issuing releases for
&lt;a href=&quot;https://docs.djangoproject.com/en/dev/releases/6.0.6/&quot;&gt;Django 6.0.6&lt;/a&gt; and
&lt;a href=&quot;https://docs.djangoproject.com/en/dev/releases/5.2.15/&quot;&gt;Django 5.2.15&lt;/a&gt;.
These releases address the security issues detailed below. We encourage all
users of Django to upgrade as soon as possible.&lt;/p&gt;
&lt;h2 id=&quot;s-cve-2026-6873-signed-cookie-salt-namespace-collision-in-djangohttphttprequestget_signed_cookie&quot;&gt;CVE-2026-6873: Signed cookie salt namespace collision in &lt;code&gt;django.http.HttpRequest.get_signed_cookie&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;get_signed_cookie()&lt;/code&gt; derived the signing salt by concatenating the cookie name (&lt;code&gt;key&lt;/code&gt;) and &lt;code&gt;salt&lt;/code&gt; arguments. When distinct name and salt pairs produced the same concatenation, cookies could be accepted
in a context different from the one where they were signed.&lt;/p&gt;
&lt;p&gt;Cookies are now signed with an unambiguous salt derivation. For backwards compatibility, cookies signed by older Django versions are accepted until Django 7.0.&lt;/p&gt;
&lt;p&gt;This issue has severity &quot;low&quot; according to the &lt;a href=&quot;https://docs.djangoproject.com/en/dev/internals/security/#security-issue-severity-levels&quot;&gt;Django security policy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thanks to Peng Zhou for the report.&lt;/p&gt;
&lt;h2 id=&quot;s-cve-2026-7666-potential-unencrypted-email-transmission-via-starttls-in-the-smtp-backend&quot;&gt;CVE-2026-7666: Potential unencrypted email transmission via &lt;code&gt;STARTTLS&lt;/code&gt; in the SMTP backend&lt;/h2&gt;
&lt;p&gt;When using &lt;code&gt;EMAIL_USE_TLS&lt;/code&gt;, a failed &lt;code&gt;STARTTLS&lt;/code&gt; handshake could leave a partially-initialized connection that would subsequently be reused for sending email without encryption. This can occur with &lt;code&gt;fail_silently=True&lt;/code&gt;, as used by &lt;code&gt;send_mail()&lt;/code&gt; and &lt;code&gt;BrokenLinkEmailsMiddleware&lt;/code&gt;, among others. Connections configured with &lt;code&gt;EMAIL_USE_SSL&lt;/code&gt; are not affected.&lt;/p&gt;
&lt;p&gt;This issue has severity &quot;low&quot; according to the &lt;a href=&quot;https://docs.djangoproject.com/en/dev/internals/security/#security-issue-severity-levels&quot;&gt;Django security policy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thanks to Kasper Dupont for the report.&lt;/p&gt;
&lt;h2 id=&quot;s-cve-2026-8404-potential-exposure-of-private-data-via-case-sensitive-cache-control-directives-in-updatecachemiddleware&quot;&gt;CVE-2026-8404: Potential exposure of private data via case-sensitive &lt;code&gt;Cache-Control&lt;/code&gt; directives in &lt;code&gt;UpdateCacheMiddleware&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;django.middleware.cache.UpdateCacheMiddleware&lt;/code&gt; and &lt;code&gt;django.views.decorators.cache.cache_page&lt;/code&gt; decorator incorrectly cached responses marked with private &lt;code&gt;Cache-Control&lt;/code&gt; directives when using mixed or uppercase values (e.g. &lt;code&gt;Private&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;django.views.decorators.cache.cache_control&lt;/code&gt; decorator and &lt;code&gt;django.utils.cache.patch_cache_control()&lt;/code&gt; function were not affected, since they normalize directives to lowercase. This issue only affects responses where &lt;code&gt;Cache-Control&lt;/code&gt; is set manually.&lt;/p&gt;
&lt;p&gt;This issue has severity &quot;low&quot; according to the &lt;a href=&quot;https://docs.djangoproject.com/en/dev/internals/security/#security-issue-severity-levels&quot;&gt;Django security policy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thanks to Ahmed Badawe for the report.&lt;/p&gt;
&lt;h2 id=&quot;s-cve-2026-35193-potential-exposure-of-private-data-via-missing-vary-authorization-in-updatecachemiddleware&quot;&gt;CVE-2026-35193: Potential exposure of private data via missing &lt;code&gt;Vary: Authorization&lt;/code&gt; in &lt;code&gt;UpdateCacheMiddleware&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;django.middleware.cache.UpdateCacheMiddleware&lt;/code&gt; and &lt;code&gt;django.views.decorators.cache.cache_page&lt;/code&gt; decorator allowed responses to requests bearing an &lt;code&gt;Authorization&lt;/code&gt; header (and without &lt;code&gt;Cache-Control: public&lt;/code&gt;) to be cached. To conform with the existing mechanism for constructing cache keys, responses to these requests will now vary on &lt;code&gt;Authorization&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This issue has severity &quot;low&quot; according to the &lt;a href=&quot;https://docs.djangoproject.com/en/dev/internals/security/#security-issue-severity-levels&quot;&gt;Django security policy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thanks to Shai Berger for the report.&lt;/p&gt;
&lt;h2 id=&quot;s-cve-2026-48587-potential-exposure-of-private-data-via-whitespace-padding-in-vary-header&quot;&gt;CVE-2026-48587: Potential exposure of private data via whitespace padding in &lt;code&gt;Vary&lt;/code&gt; header&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;django.middleware.cache.UpdateCacheMiddleware&lt;/code&gt; incorrectly cached responses whose &lt;code&gt;Vary&lt;/code&gt; header values contained leading or trailing whitespace. Because &lt;code&gt;has_vary_header()&lt;/code&gt; failed to strip that whitespace, a response with a &lt;code&gt;Vary: *&lt;/code&gt; header (note the trailing space) was not recognized as containing the wildcard, causing it to be stored and potentially served from the cache when it should not have been.&lt;/p&gt;
&lt;p&gt;This issue has severity &quot;low&quot; according to the &lt;a href=&quot;https://docs.djangoproject.com/en/dev/internals/security/#security-issue-severity-levels&quot;&gt;Django security policy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thanks to Navid Rezazadeh for the report.&lt;/p&gt;
&lt;h2 id=&quot;s-affected-supported-versions&quot;&gt;Affected supported versions&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Django main&lt;/li&gt;
&lt;li&gt;Django 6.1 (currently at alpha status)&lt;/li&gt;
&lt;li&gt;Django 6.0&lt;/li&gt;
&lt;li&gt;Django 5.2&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;s-resolution&quot;&gt;Resolution&lt;/h2&gt;
&lt;p&gt;Patches to resolve the issue have been applied to Django's
main, 6.1 (currently at alpha status), 6.0, and 5.2 branches.
The patches may be obtained from the following changesets.&lt;/p&gt;
&lt;h3 id=&quot;s-cve-2026-6873-signed-cookie-salt-namespace-collision-in-djangohttphttprequestget_signed_cookie_1&quot;&gt;CVE-2026-6873: Signed cookie salt namespace collision in &lt;code&gt;django.http.HttpRequest.get_signed_cookie&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;On the &lt;a href=&quot;https://github.com/django/django/commit/70d36515b9cc71700105a14b275583070d48b689&quot;&gt;main branch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;On the &lt;a href=&quot;https://github.com/django/django/commit/42bdfd74ff85eb9ddf8fd444e2359afd9add59c8&quot;&gt;6.1 branch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;On the &lt;a href=&quot;https://github.com/django/django/commit/c807d9c398022d23cb27518fa6ecaf343efb30cf&quot;&gt;6.0 branch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;On the &lt;a href=&quot;https://github.com/django/django/commit/594360cbf58be7f56eb6da96d58644297c99ef85&quot;&gt;5.2 branch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;s-cve-2026-7666-potential-unencrypted-email-transmission-via-starttls-in-the-smtp-backend_1&quot;&gt;CVE-2026-7666: Potential unencrypted email transmission via &lt;code&gt;STARTTLS&lt;/code&gt; in the SMTP backend&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;On the &lt;a href=&quot;https://github.com/django/django/commit/df887f50198593a0e5b4638bfddbbd43a30fd276&quot;&gt;main branch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;On the &lt;a href=&quot;https://github.com/django/django/commit/afd82a544910dc79941a44af078bbc59e3e18f1c&quot;&gt;6.1 branch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;On the &lt;a href=&quot;https://github.com/django/django/commit/625a670c467aa3118c0f8ae1e0df14dbebb3bf68&quot;&gt;6.0 branch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;On the &lt;a href=&quot;https://github.com/django/django/commit/4e47d2b800435bcbfd1301ef3250b9c7fb8fa670&quot;&gt;5.2 branch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;s-cve-2026-8404-potential-exposure-of-private-data-via-case-sensitive-cache-control-directives-in-updatecachemiddleware_1&quot;&gt;CVE-2026-8404: Potential exposure of private data via case-sensitive &lt;code&gt;Cache-Control&lt;/code&gt; directives in &lt;code&gt;UpdateCacheMiddleware&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;On the &lt;a href=&quot;https://github.com/django/django/commit/d618d7ae4fec727d5b582bd24f803c28d17bf7cd&quot;&gt;main branch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;On the &lt;a href=&quot;https://github.com/django/django/commit/130467c8b4d05a69b885363aa7d47386e4f5d6a9&quot;&gt;6.1 branch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;On the &lt;a href=&quot;https://github.com/django/django/commit/b4330259ffbe1a031ed14daab1f35697460f10f2&quot;&gt;6.0 branch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;On the &lt;a href=&quot;https://github.com/django/django/commit/366d9ae6e8d1469c04e9ebdc1bcd098fc14a3b1e&quot;&gt;5.2 branch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;s-cve-2026-35193-potential-exposure-of-private-data-via-missing-vary-authorization-in-updatecachemiddleware_1&quot;&gt;CVE-2026-35193: Potential exposure of private data via missing &lt;code&gt;Vary: Authorization&lt;/code&gt; in &lt;code&gt;UpdateCacheMiddleware&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;On the &lt;a href=&quot;https://github.com/django/django/commit/a2faa8e895926ac5d63f72879b5ccf671b5b4ba9&quot;&gt;main branch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;On the &lt;a href=&quot;https://github.com/django/django/commit/b7b23f4697850e232486d787df3459f19bd16dba&quot;&gt;6.1 branch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;On the &lt;a href=&quot;https://github.com/django/django/commit/664652f1a2dd80d8a4cd491b4313cad915ae6669&quot;&gt;6.0 branch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;On the &lt;a href=&quot;https://github.com/django/django/commit/050a3dc276f9142067260e990e4d8d42d5e32863&quot;&gt;5.2 branch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;s-cve-2026-48587-potential-exposure-of-private-data-via-whitespace-padding-in-vary-header_1&quot;&gt;CVE-2026-48587: Potential exposure of private data via whitespace padding in &lt;code&gt;Vary&lt;/code&gt; header&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;On the &lt;a href=&quot;https://github.com/django/django/commit/42aa0b3364d312e7c6472258d8b0e9c0277fbf22&quot;&gt;main branch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;On the &lt;a href=&quot;https://github.com/django/django/commit/e06958dbfff789d6a72efb1d38c65b4b22b6690e&quot;&gt;6.1 branch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;On the &lt;a href=&quot;https://github.com/django/django/commit/1721035a72624aad7b38dd19b14013efd94b24b8&quot;&gt;6.0 branch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;On the &lt;a href=&quot;https://github.com/django/django/commit/9b62b0af71a14c657d19d95371630ba839e83d9a&quot;&gt;5.2 branch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;s-the-following-releases-have-been-issued&quot;&gt;The following releases have been issued&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Django 6.0.6 (&lt;a href=&quot;https://www.djangoproject.com/download/6.0.6/tarball/&quot;&gt;tarball&lt;/a&gt; | &lt;a href=&quot;https://www.djangoproject.com/download/6.0.6/checksum/&quot;&gt;checksums&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Django 5.2.15 (&lt;a href=&quot;https://www.djangoproject.com/download/5.2.15/tarball/&quot;&gt;tarball&lt;/a&gt; | &lt;a href=&quot;https://www.djangoproject.com/download/5.2.15/checksum/&quot;&gt;checksums&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The PGP key ID used for this release is Natalia Bidart: &lt;a href=&quot;https://github.com/nessita.gpg&quot;&gt;2EE82A8D9470983E&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;s-general-notes-regarding-security-reporting&quot;&gt;General notes regarding security reporting&lt;/h2&gt;
&lt;p&gt;As always, we ask that potential security issues be reported via private email
to &lt;code&gt;security@djangoproject.com&lt;/code&gt;, and not via Django's Trac instance, nor via
the Django Forum. Please see
&lt;a href=&quot;https://www.djangoproject.com/security/&quot;&gt;our security policies&lt;/a&gt; for further
information.&lt;/p&gt;</content:encoded>
	<dc:date>2026-06-03T11:00:00+00:00</dc:date>
</item>
<item rdf:about="https://www.pythonguis.com/faq/authentication-and-authorization-with-pyqt6-or-pyside6/">
	<title>Python GUIs: Authentication and Authorization with PyQt6 or PySide6 — Secure your desktop applications with login flows, token-based auth, and role-based access control</title>
	<link>https://www.pythonguis.com/faq/authentication-and-authorization-with-pyqt6-or-pyside6/</link>
	<content:encoded>&lt;blockquote&gt;
&lt;p&gt;How can I add authentication and authorization to a PyQt6 application? Is there something built into Qt to make this easier?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When you build a desktop application with PyQt6 or PySide6, sooner or later you'll need to control who can use it and what they can do. Maybe your app connects to a cloud service. Maybe certain features should only be available to administrators. Either way, you need &lt;strong&gt;authentication&lt;/strong&gt; (verifying who the user is) and &lt;strong&gt;authorization&lt;/strong&gt; (deciding what they're allowed to do).&lt;/p&gt;
&lt;p&gt;Qt doesn't provide a built-in authentication framework. But that's fine. You can combine Qt's capabilities with Python's networking and security tools to build a solid auth flow for your application.&lt;/p&gt;
&lt;p&gt;In this tutorial, we'll walk through the full process: creating a login dialog, authenticating against a remote server, handling tokens, and enabling or disabling parts of your UI based on a user's role.&lt;/p&gt;
&lt;h2 id=&quot;approaches-to-authentication-in-desktop-apps&quot;&gt;Approaches to Authentication in Desktop Apps&lt;/h2&gt;
&lt;p&gt;Before writing any code, it helps to understand the options available when securing a desktop application. The right approach depends on how much security you need and what infrastructure you have.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Simple login check&lt;/strong&gt; Your app sends credentials to a remote server at startup. If authentication fails, you disable the UI (partially or entirely). This deters casual users, but a determined hacker could modify the client to bypass the check.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Token-based unlock&lt;/strong&gt; After a successful login, the server returns a token or key that unlocks functionality in the app. Without the token, the app can't perform certain operations. This is more secure &amp;mdash; the app is genuinely non-functional without a valid token &amp;mdash; though once data is decoded into memory, it's theoretically still accessible.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Server-side execution&lt;/strong&gt; After authentication, the app sends work to the server, which performs the actual operations. The sensitive logic never runs on the client at all. This is the most secure approach, but it requires server infrastructure to handle the workload.&lt;/li&gt;
&lt;/ol&gt;
&lt;p class=&quot;admonition admonition-note&quot;&gt;&lt;span class=&quot;admonition-kind&quot;&gt;&lt;i class=&quot;fas fa-sticky-note&quot;&gt;&lt;/i&gt;&lt;/span&gt;  In the &lt;strong&gt;Server-side execution&lt;/strong&gt; model, the work done on the server doesn't necessarily need to be complex. Transforming or pre-processing some data from one format to another will be enough to deter most attempts at circumvention. However, it's common to to use this technique to hide the algorithmic &quot;secret sauce&quot; completely.&lt;/p&gt;
&lt;p&gt;For most applications, the middle ground &amp;mdash; authenticating against a remote API and using the returned token to gate access &amp;mdash; provides a good balance of security and simplicity. That's what we'll build here.&lt;/p&gt;
&lt;p&gt;Your app shouldn't care about the database directly. Instead, it should talk to an &lt;strong&gt;API&lt;/strong&gt; (Application Programming Interface) on your server. The API handles user lookups, password verification, and token generation. Your desktop app just sends HTTP requests and processes the responses.&lt;/p&gt;
&lt;h2 id=&quot;setting-up-a-simple-auth-server-for-testing&quot;&gt;Setting Up a Simple Auth Server (For Testing)&lt;/h2&gt;
&lt;p&gt;To test our client application, we need something to authenticate against. We'll create a minimal Flask server that accepts login requests and returns a JSON Web Token (JWT). In a real project, this would be your existing backend, but having a self-contained example makes it easier to experiment.&lt;/p&gt;
&lt;p&gt;Install the dependencies for the server:&lt;/p&gt;
&lt;div class=&quot;code-block&quot;&gt;
&lt;span class=&quot;code-block-language code-block-sh&quot;&gt;sh&lt;/span&gt;
&lt;pre&gt;&lt;code class=&quot;sh&quot;&gt;pip install flask pyjwt
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Here's a minimal auth server:&lt;/p&gt;
&lt;div class=&quot;code-block&quot;&gt;
&lt;span class=&quot;code-block-language code-block-python&quot;&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;import datetime

import jwt
from flask import Flask, jsonify, request

app = Flask(__name__)
SECRET_KEY = &quot;your-secret-key-change-this&quot;

# In production, use a real database with hashed passwords.
USERS = {
    &quot;admin&quot;: {&quot;password&quot;: &quot;admin123&quot;, &quot;role&quot;: &quot;admin&quot;},
    &quot;viewer&quot;: {&quot;password&quot;: &quot;viewer123&quot;, &quot;role&quot;: &quot;viewer&quot;},
}


@app.route(&quot;/auth/login&quot;, methods=[&quot;POST&quot;])
def login():
    data = request.get_json()
    username = data.get(&quot;username&quot;, &quot;&quot;)
    password = data.get(&quot;password&quot;, &quot;&quot;)

    user = USERS.get(username)
    if user and user[&quot;password&quot;] == password:
        token = jwt.encode(
            {
                &quot;username&quot;: username,
                &quot;role&quot;: user[&quot;role&quot;],
                &quot;exp&quot;: datetime.datetime.utcnow()
                + datetime.timedelta(hours=1),
            },
            SECRET_KEY,
            algorithm=&quot;HS256&quot;,
        )
        return jsonify(
            {&quot;token&quot;: token, &quot;role&quot;: user[&quot;role&quot;], &quot;username&quot;: username}
        )

    return jsonify({&quot;error&quot;: &quot;Invalid credentials&quot;}), 401


@app.route(&quot;/auth/verify&quot;, methods=[&quot;GET&quot;])
def verify():
    auth_header = request.headers.get(&quot;Authorization&quot;, &quot;&quot;)
    if not auth_header.startswith(&quot;Bearer &quot;):
        return jsonify({&quot;error&quot;: &quot;Missing token&quot;}), 401

    token = auth_header.split(&quot; &quot;, 1)[1]
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[&quot;HS256&quot;])
        return jsonify(
            {&quot;username&quot;: payload[&quot;username&quot;], &quot;role&quot;: payload[&quot;role&quot;]}
        )
    except jwt.ExpiredSignatureError:
        return jsonify({&quot;error&quot;: &quot;Token expired&quot;}), 401
    except jwt.InvalidTokenError:
        return jsonify({&quot;error&quot;: &quot;Invalid token&quot;}), 401


if __name__ == &quot;__main__&quot;:
    app.run(port=5000, debug=True)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Save this as &lt;code&gt;auth_server.py&lt;/code&gt; and run it in a separate terminal:&lt;/p&gt;
&lt;div class=&quot;code-block&quot;&gt;
&lt;span class=&quot;code-block-language code-block-sh&quot;&gt;sh&lt;/span&gt;
&lt;pre&gt;&lt;code class=&quot;sh&quot;&gt;python auth_server.py
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The server exposes two endpoints:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;POST /auth/login&lt;/code&gt; &amp;mdash; accepts a JSON body with &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt;, returns a JWT token.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GET /auth/verify&lt;/code&gt; &amp;mdash; accepts an &lt;code&gt;Authorization: Bearer &amp;lt;token&amp;gt;&lt;/code&gt; header and returns the user info if the token is valid.&lt;/li&gt;
&lt;/ul&gt;
&lt;p class=&quot;admonition admonition-important&quot;&gt;&lt;span class=&quot;admonition-kind&quot;&gt;&lt;i class=&quot;fas fa-exclamation&quot;&gt;&lt;/i&gt;&lt;/span&gt;  This server stores passwords in plain text and uses a hardcoded secret key. In production, you'd hash passwords (using &lt;code&gt;bcrypt&lt;/code&gt; or similar) and store the secret key securely. &lt;strong&gt;This is purely for demonstration.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;building-the-login-dialog&quot;&gt;Building the Login Dialog&lt;/h2&gt;
&lt;p&gt;Now let's build the PyQt6 side. We'll start with a login dialog &amp;mdash; a modal window where the user enters their credentials. If you're new to dialogs in Qt, see our tutorial on &lt;a href=&quot;https://www.pythonguis.com/tutorials/pyqt6-dialogs/&quot;&gt;creating dialogs in PyQt6&lt;/a&gt; for a thorough introduction.&lt;/p&gt;
&lt;p&gt;Install the client dependencies:&lt;/p&gt;
&lt;div class=&quot;code-block&quot;&gt;
&lt;span class=&quot;code-block-language code-block-sh&quot;&gt;sh&lt;/span&gt;
&lt;pre&gt;&lt;code class=&quot;sh&quot;&gt;pip install PyQt6 requests
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;If you're using PySide6, replace &lt;code&gt;from PyQt6.QtWidgets import ...&lt;/code&gt; with &lt;code&gt;from PySide6.QtWidgets import ...&lt;/code&gt; (and similarly for other Qt modules). The rest of the code is identical.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;code-block&quot;&gt;
&lt;span class=&quot;code-block-language code-block-python&quot;&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import (
    QDialog,
    QFormLayout,
    QLabel,
    QLineEdit,
    QPushButton,
    QVBoxLayout,
)


class LoginDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle(&quot;Login&quot;)
        self.setFixedSize(350, 200)

        layout = QVBoxLayout()

        self.form_layout = QFormLayout()

        self.username_input = QLineEdit()
        self.username_input.setPlaceholderText(&quot;Enter your username&quot;)
        self.form_layout.addRow(&quot;Username:&quot;, self.username_input)

        self.password_input = QLineEdit()
        self.password_input.setPlaceholderText(&quot;Enter your password&quot;)
        self.password_input.setEchoMode(QLineEdit.Password)
        self.form_layout.addRow(&quot;Password:&quot;, self.password_input)

        layout.addLayout(self.form_layout)

        self.login_button = QPushButton(&quot;Login&quot;)
        self.login_button.clicked.connect(self.accept)
        layout.addWidget(self.login_button)

        self.status_label = QLabel(&quot;&quot;)
        self.status_label.setAlignment(Qt.AlignCenter)
        self.status_label.setStyleSheet(&quot;color: red;&quot;)
        layout.addWidget(self.status_label)

        self.setLayout(layout)

        # Allow pressing Enter to submit.
        self.password_input.returnPressed.connect(self.login_button.click)
        self.username_input.returnPressed.connect(
            self.password_input.setFocus
        )

    def get_credentials(self):
        return (
            self.username_input.text().strip(),
            self.password_input.text(),
        )

    def set_status(self, message):
        self.status_label.setText(message)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This dialog inherits from &lt;code&gt;QDialog&lt;/code&gt;, which gives us the modal behavior we need &amp;mdash; when shown with &lt;code&gt;.exec_()&lt;/code&gt;, it blocks interaction with the rest of the application until the user either logs in or closes the dialog.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;get_credentials&lt;/code&gt; method returns the entered username and password as a tuple. The &lt;code&gt;set_status&lt;/code&gt; method lets us display error messages (like &quot;Invalid credentials&quot;) directly in the dialog.&lt;/p&gt;
&lt;h2 id=&quot;creating-an-auth-manager&quot;&gt;Creating an Auth Manager&lt;/h2&gt;
&lt;p&gt;Rather than scattering authentication logic throughout the application, we'll encapsulate it in a dedicated class. This &lt;code&gt;AuthManager&lt;/code&gt; handles login requests, stores the token, and provides the user's role.&lt;/p&gt;
&lt;div class=&quot;code-block&quot;&gt;
&lt;span class=&quot;code-block-language code-block-python&quot;&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;import requests


class AuthManager:
    def __init__(self, base_url=&quot;http://localhost:5000&quot;):
        self.base_url = base_url
        self.token = None
        self.username = None
        self.role = None

    def login(self, username, password):
        &quot;&quot;&quot;
        Attempt to log in. Returns True on success, False on failure.
        Raises an exception on network errors.
        &quot;&quot;&quot;
        response = requests.post(
            f&quot;{self.base_url}/auth/login&quot;,
            json={&quot;username&quot;: username, &quot;password&quot;: password},
            timeout=10,
        )

        if response.status_code == 200:
            data = response.json()
            self.token = data[&quot;token&quot;]
            self.username = data[&quot;username&quot;]
            self.role = data[&quot;role&quot;]
            return True

        return False

    def is_authenticated(self):
        return self.token is not None

    def get_auth_header(self):
        &quot;&quot;&quot;Return headers dict with the Bearer token for API requests.&quot;&quot;&quot;
        if self.token:
            return {&quot;Authorization&quot;: f&quot;Bearer {self.token}&quot;}
        return {}

    def has_role(self, role):
        return self.role == role

    def logout(self):
        self.token = None
        self.username = None
        self.role = None
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;get_auth_header&lt;/code&gt; method is especially useful. Once a user has logged in, you can include this header in any subsequent API call to prove that the request is coming from an authenticated user:&lt;/p&gt;
&lt;div class=&quot;code-block&quot;&gt;
&lt;span class=&quot;code-block-language code-block-python&quot;&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;response = requests.get(
    &quot;http://localhost:5000/some/protected/endpoint&quot;,
    headers=auth_manager.get_auth_header(),
    timeout=10,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id=&quot;wiring-up-the-login-flow&quot;&gt;Wiring Up the Login Flow&lt;/h2&gt;
&lt;p&gt;Now we connect the login dialog to the auth manager. The pattern is: show the dialog, grab the credentials, try to authenticate, and either proceed to the main window or show an error.&lt;/p&gt;
&lt;div class=&quot;code-block&quot;&gt;
&lt;span class=&quot;code-block-language code-block-python&quot;&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;import sys

from PyQt6.QtWidgets import QApplication, QMessageBox


def attempt_login(auth_manager):
    &quot;&quot;&quot;
    Show the login dialog repeatedly until the user either
    successfully authenticates or cancels.
    Returns True on successful login, False if cancelled.
    &quot;&quot;&quot;
    dialog = LoginDialog()

    while True:
        result = dialog.exec_()

        if result != QDialog.Accepted:
            # User closed the dialog or pressed Cancel.
            return False

        username, password = dialog.get_credentials()

        if not username or not password:
            dialog.set_status(&quot;Please enter both fields.&quot;)
            continue

        try:
            if auth_manager.login(username, password):
                return True
            else:
                dialog.set_status(&quot;Invalid username or password.&quot;)
        except requests.exceptions.ConnectionError:
            dialog.set_status(&quot;Cannot connect to server.&quot;)
        except requests.exceptions.Timeout:
            dialog.set_status(&quot;Connection timed out.&quot;)
        except requests.exceptions.RequestException as e:
            dialog.set_status(f&quot;Error: {e}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This function keeps showing the login dialog until either the login succeeds or the user dismisses it. Network errors are caught and displayed in the dialog, so the user gets useful feedback without the app crashing.&lt;/p&gt;
&lt;h2 id=&quot;building-the-main-window-with-role-based-access&quot;&gt;Building the Main Window with Role-Based Access&lt;/h2&gt;
&lt;p&gt;The main window of our application will show different features depending on the user's role. Admin users see everything; viewers have a restricted experience. We'll use &lt;a href=&quot;https://www.pythonguis.com/tutorials/pyqt6-actions-toolbars-menus/&quot;&gt;actions, toolbars, and menus&lt;/a&gt; to structure the interface.&lt;/p&gt;
&lt;div class=&quot;code-block&quot;&gt;
&lt;span class=&quot;code-block-language code-block-python&quot;&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;from PyQt6.QtWidgets import (
    QAction,
    QMainWindow,
    QMenu,
    QMenuBar,
    QStatusBar,
    QTextEdit,
    QToolBar,
)


class MainWindow(QMainWindow):
    def __init__(self, auth_manager):
        super().__init__()
        self.auth_manager = auth_manager

        self.setWindowTitle(&quot;My Application&quot;)
        self.setMinimumSize(600, 400)

        # Central widget.
        self.text_edit = QTextEdit()
        self.setCentralWidget(self.text_edit)

        # Menu bar.
        menu_bar = self.menuBar()

        file_menu = menu_bar.addMenu(&quot;&amp;amp;File&quot;)

        self.save_action = QAction(&quot;&amp;amp;Save&quot;, self)
        self.save_action.triggered.connect(self.save_document)
        file_menu.addAction(self.save_action)

        file_menu.addSeparator()

        logout_action = QAction(&quot;&amp;amp;Logout&quot;, self)
        logout_action.triggered.connect(self.handle_logout)
        file_menu.addAction(logout_action)

        quit_action = QAction(&quot;&amp;amp;Quit&quot;, self)
        quit_action.triggered.connect(self.close)
        file_menu.addAction(quit_action)

        # Admin-only menu.
        self.admin_menu = menu_bar.addMenu(&quot;&amp;amp;Admin&quot;)

        manage_users_action = QAction(&quot;&amp;amp;Manage Users&quot;, self)
        manage_users_action.triggered.connect(self.manage_users)
        self.admin_menu.addAction(manage_users_action)

        server_settings_action = QAction(&quot;&amp;amp;Server Settings&quot;, self)
        server_settings_action.triggered.connect(self.server_settings)
        self.admin_menu.addAction(server_settings_action)

        # Status bar.
        self.status_bar = QStatusBar()
        self.setStatusBar(self.status_bar)

        # Apply role-based restrictions.
        self.apply_permissions()

    def apply_permissions(self):
        &quot;&quot;&quot;Enable or disable UI elements based on the user's role.&quot;&quot;&quot;
        role = self.auth_manager.role
        username = self.auth_manager.username

        self.status_bar.showMessage(
            f&quot;Logged in as {username} ({role})&quot;
        )

        if role == &quot;admin&quot;:
            # Admins get full access.
            self.admin_menu.setEnabled(True)
            self.save_action.setEnabled(True)
            self.text_edit.setReadOnly(False)
        elif role == &quot;viewer&quot;:
            # Viewers can see content but not edit or access admin.
            self.admin_menu.setEnabled(False)
            self.save_action.setEnabled(False)
            self.text_edit.setReadOnly(True)
            self.text_edit.setPlaceholderText(
                &quot;You have read-only access.&quot;
            )
        else:
            # Unknown role: disable everything as a safe default.
            self.admin_menu.setEnabled(False)
            self.save_action.setEnabled(False)
            self.text_edit.setReadOnly(True)

    def save_document(self):
        QMessageBox.information(
            self, &quot;Save&quot;, &quot;Document saved (placeholder).&quot;
        )

    def manage_users(self):
        QMessageBox.information(
            self, &quot;Admin&quot;, &quot;User management (placeholder).&quot;
        )

    def server_settings(self):
        QMessageBox.information(
            self, &quot;Admin&quot;, &quot;Server settings (placeholder).&quot;
        )

    def handle_logout(self):
        self.auth_manager.logout()
        self.close()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;apply_permissions&lt;/code&gt; method is where authorization happens. After a successful login, we check the user's role and adjust the UI accordingly. Disabled menu items are grayed out and non-clickable, and the text editor is set to read-only for viewers.&lt;/p&gt;
&lt;p&gt;This approach &amp;mdash; enabling and disabling widgets based on roles &amp;mdash; is the standard pattern for authorization in desktop apps. You can extend it as far as you need: hide entire toolbar sections, show different pages in a stacked widget, or restrict access to specific actions.&lt;/p&gt;
&lt;h2 id=&quot;making-authenticated-api-requests&quot;&gt;Making Authenticated API Requests&lt;/h2&gt;
&lt;p&gt;Once a user is logged in, you'll often need to make further API calls &amp;mdash; fetching data, submitting forms, etc. Each of these requests should include the authentication token so the server can verify the user. For long-running API calls, consider using &lt;a href=&quot;https://www.pythonguis.com/tutorials/multithreading-pyqt6-applications-qthreadpool/&quot;&gt;multithreading with QThreadPool&lt;/a&gt; to keep the UI responsive while waiting for server responses.&lt;/p&gt;
&lt;p&gt;Here's how you might fetch some protected data:&lt;/p&gt;
&lt;div class=&quot;code-block&quot;&gt;
&lt;span class=&quot;code-block-language code-block-python&quot;&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;def fetch_protected_data(auth_manager):
    &quot;&quot;&quot;Example of making an authenticated API request.&quot;&quot;&quot;
    try:
        response = requests.get(
            f&quot;{auth_manager.base_url}/auth/verify&quot;,
            headers=auth_manager.get_auth_header(),
            timeout=10,
        )

        if response.status_code == 200:
            return response.json()
        elif response.status_code == 401:
            # Token expired or invalid &amp;mdash; user needs to log in again.
            return None
    except requests.exceptions.RequestException:
        return None
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If the server responds with a &lt;code&gt;401 Unauthorized&lt;/code&gt;, that means the token has expired or been revoked. You should handle this gracefully &amp;mdash; for example, by showing the login dialog again.&lt;/p&gt;
&lt;h2 id=&quot;handling-token-expiration&quot;&gt;Handling Token Expiration&lt;/h2&gt;
&lt;p&gt;Tokens expire. When they do, your app needs to respond appropriately rather than silently failing. A common approach is to wrap your API calls in a method that checks for 401 responses and triggers a re-login:&lt;/p&gt;
&lt;div class=&quot;code-block&quot;&gt;
&lt;span class=&quot;code-block-language code-block-python&quot;&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;def authenticated_request(auth_manager, method, url, **kwargs):
    &quot;&quot;&quot;
    Make an HTTP request with authentication.
    Returns the response, or None if re-authentication fails.
    &quot;&quot;&quot;
    kwargs.setdefault(&quot;headers&quot;, {})
    kwargs[&quot;headers&quot;].update(auth_manager.get_auth_header())
    kwargs.setdefault(&quot;timeout&quot;, 10)

    try:
        response = requests.request(method, url, **kwargs)

        if response.status_code == 401:
            # Token expired &amp;mdash; try to re-authenticate.
            if attempt_login(auth_manager):
                kwargs[&quot;headers&quot;].update(
                    auth_manager.get_auth_header()
                )
                response = requests.request(method, url, **kwargs)
            else:
                return None

        return response

    except requests.exceptions.RequestException:
        return None
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This function automatically retries the request with a new token if the first attempt gets a 401. The user sees the login dialog, re-enters their credentials, and the request proceeds as if nothing happened.&lt;/p&gt;
&lt;p&gt;To try it out:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start the auth server in one terminal: &lt;code&gt;python auth_server.py&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Run the client application in another terminal: &lt;code&gt;python app.py&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Log in as &lt;code&gt;admin&lt;/code&gt; / &lt;code&gt;admin123&lt;/code&gt; to see full access, or &lt;code&gt;viewer&lt;/code&gt; / &lt;code&gt;viewer123&lt;/code&gt; to see restricted access.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Try logging in with the wrong password &amp;mdash; the dialog stays open and shows an error. Close the dialog without logging in and the app exits cleanly.&lt;/p&gt;
&lt;h2 id=&quot;security-considerations&quot;&gt;Security Considerations&lt;/h2&gt;
&lt;p&gt;A few things to keep in mind when implementing auth in a desktop application:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Never store passwords in the client.&lt;/strong&gt; Your app should only ever send credentials to the server and receive a token back. The token is what you store (in memory, or securely on disk if you want &quot;remember me&quot; functionality).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use HTTPS in production.&lt;/strong&gt; Our example uses plain HTTP because it's running locally. In a real deployment, all communication between the client and server should be encrypted with TLS. The &lt;code&gt;requests&lt;/code&gt; library handles HTTPS transparently &amp;mdash; just change the URL to &lt;code&gt;https://&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tokens are temporary.&lt;/strong&gt; JWTs (and most authentication tokens) have an expiration time. Design your app to handle expired tokens gracefully, as shown in the token expiration section above.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Client-side checks are not enough.&lt;/strong&gt; Disabling a button in the UI doesn't prevent a technically savvy user from calling the underlying function. Any action that matters should be validated on the server side too. The client-side restrictions are a UX convenience, not a security boundary.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Store tokens securely.&lt;/strong&gt; If you implement a &quot;remember me&quot; feature that persists the token between sessions, use your platform's secure storage &amp;mdash; &lt;code&gt;keyring&lt;/code&gt; is a good cross-platform Python library for this. Don't write tokens to plain text files. You can also use &lt;a href=&quot;https://www.pythonguis.com/faq/pyqt6-qsettings-how-to-use-qsettings/&quot;&gt;QSettings&lt;/a&gt; to persist non-sensitive user preferences like the last-used username, but avoid storing tokens or credentials there since QSettings does not provide encryption.&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PySide6 see my book, &lt;a href=&quot;https://www.pythonguis.com/pyside6-book/&quot;&gt;Create GUI Applications with Python &amp;amp; Qt6.&lt;/a&gt;&lt;/p&gt;</content:encoded>
	<dc:date>2026-06-03T06:00:00+00:00</dc:date>
</item>
<item rdf:about="https://belderbos.dev/blog/python-mock-patch-verify-interception/">
	<title>Bob Belderbos: How to Tell if Your Python Mock Is Actually Working</title>
	<link>https://belderbos.dev/blog/python-mock-patch-verify-interception/</link>
	<content:encoded>&lt;p&gt;A test can pass for the wrong reason. When you're mocking a third-party API call, the test might look green because the real API happened to return an error, not because your mock did anything at all.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;
&lt;p&gt;This came up in a recent session in our &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://pythonagenticai.com&quot;&gt;agentic AI cohort&lt;/a&gt; where we were looking at a test to verify that converting to an invalid currency raised an exception. The test passed. But something felt off.&lt;/p&gt;
&lt;h2 id=&quot;the-test-that-passed-for-the-wrong-reason&quot;&gt;The test that passed for the wrong reason&lt;/h2&gt;
&lt;p&gt;The code under test calls the ExchangeRate API and raises &lt;code&gt;CurrencyConversionError&lt;/code&gt; when the response signals failure:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; convert_currency&lt;/span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;(amount: Decimal, from_currency:&lt;/span&gt;&lt;span class=&quot;z-support&quot;&gt; str&lt;/span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;, to_currency:&lt;/span&gt;&lt;span class=&quot;z-support&quot;&gt; str&lt;/span&gt;&lt;span&gt;) -&amp;gt; Decimal:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    if&lt;/span&gt;&lt;span&gt; from_currency&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ==&lt;/span&gt;&lt;span&gt; to_currency:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        return&lt;/span&gt;&lt;span&gt; amount&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    response&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;/span&gt;&lt;span&gt; requests.get(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;        f&lt;/span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;https://v6.exchangerate-api.com/v6/&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt;{EXCHANGE_RATE_API_KEY}&lt;/span&gt;&lt;span class=&quot;z-string&quot;&gt;/pair/&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt;{&lt;/span&gt;&lt;span&gt;from_currency&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-string&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt;{&lt;/span&gt;&lt;span&gt;to_currency&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    data&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;/span&gt;&lt;span&gt; response.json()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    if&lt;/span&gt;&lt;span&gt; data[&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;result&amp;quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; !=&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;success&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        raise&lt;/span&gt;&lt;span&gt; CurrencyConversionError(&lt;/span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt;{&lt;/span&gt;&lt;span&gt;data[&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;'error-type'&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;/span&gt;&lt;span&gt; Decimal(data[&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;conversion_rate&amp;quot;&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;/span&gt;&lt;span&gt; amount&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The test set up a &lt;code&gt;mock_response&lt;/code&gt;, patched &lt;code&gt;requests.get&lt;/code&gt; to return it (&lt;code&gt;mock_get.return_value = mock_response&lt;/code&gt;), but configured it as a &lt;em&gt;successful&lt;/em&gt; response:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mock_response.json.return_value&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;    &amp;quot;result&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;success&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;   # &amp;lt;-- this will never raise CurrencyConversionError&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;    &amp;quot;conversion_rate&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1.5&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the mock was intercepting, the function would return normally and &lt;code&gt;pytest.raises&lt;/code&gt; would fail. But the test was passing. That meant the mock wasn't intercepting at all: the real API was being hit, and it was returning an error for the bogus &quot;CTM&quot; code.&lt;/p&gt;
&lt;h2 id=&quot;proving-the-mock-actually-intercepted&quot;&gt;Proving the mock actually intercepted&lt;/h2&gt;
&lt;p&gt;My instinct was to add &lt;code&gt;print(&quot;calling external api&quot;)&lt;/code&gt; before &lt;code&gt;requests.get&lt;/code&gt;. That proves the code reached that line. It does not prove whether the mock intercepted the call or the real network was hit.&lt;/p&gt;
&lt;p&gt;At this point you can put a &lt;code&gt;breakpoint()&lt;/code&gt; in the actual &lt;code&gt;requests.get&lt;/code&gt; code in your venv, but there is a better way: &lt;code&gt;mock_get.assert_called_once()&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;with&lt;/span&gt;&lt;span&gt; pytest.raises(CurrencyConversionError):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    convert_currency(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable&quot;&gt;        amount&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;/span&gt;&lt;span&gt;Decimal(&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;1.00&amp;quot;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable&quot;&gt;        from_currency&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;CAD&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable&quot;&gt;        to_currency&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;CTM&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;  # Canadian Tire Money, not a real currency&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mock_get.assert_called_once()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the mock was never called, this assertion fails and tells you directly: your patch didn't intercept the request. If the mock was called, the assertion passes and you know for sure that the test is relying on the mock, not the real API.&lt;/p&gt;
&lt;p&gt;Running the test with this assertion in place settled it. Once the patch targeted the right name (the fix in the next section), the mock intercepted the call and &lt;code&gt;pytest.raises&lt;/code&gt; failed with &lt;code&gt;DID NOT RAISE&lt;/code&gt;. That flip is the proof: a real call for &quot;CTM&quot; would have raised, so a non-raising run means the mock was in control. The earlier green had been the real API answering, never the mock. With the success response still in place, nothing raised. Fixing the response to signal an error made the test pass for the right reason, and &lt;code&gt;assert_called_once()&lt;/code&gt; then confirmed the call went through the mock and not the network:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mock_get.return_value.json.return_value&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;    &amp;quot;result&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;error&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;    &amp;quot;error-type&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;unknown-code&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;patch-where-the-name-is-used-not-where-it-s-defined&quot;&gt;Patch where the name is used, not where it's defined&lt;/h2&gt;
&lt;p&gt;The currency module does &lt;code&gt;import requests&lt;/code&gt; then calls &lt;code&gt;requests.get(...)&lt;/code&gt;, so patching &lt;code&gt;expenses_ai_agent.utils.currency.requests.get&lt;/code&gt; targets the call site. With this &lt;code&gt;import requests&lt;/code&gt; style, patching &lt;code&gt;requests.get&lt;/code&gt; happens to work too, since both names point at the same module object. The rule bites when a module does &lt;code&gt;from requests import get&lt;/code&gt;: now &lt;code&gt;get&lt;/code&gt; is a local name in the currency module, and you must patch &lt;code&gt;expenses_ai_agent.utils.currency.get&lt;/code&gt;, not &lt;code&gt;requests.get&lt;/code&gt;. Patching the wrong location is a common mistake that leads to the mock not intercepting and the real API being called.&lt;/p&gt;
&lt;h2 id=&quot;the-cleaned-up-test-with-pytest-mock&quot;&gt;The cleaned-up test with pytest-mock&lt;/h2&gt;
&lt;p&gt;Once the mock response was correct and interception was verified, the test got two more improvements. First, the intermediate &lt;code&gt;mock_response&lt;/code&gt; variable is unnecessary: chain directly off &lt;code&gt;mock_get.return_value&lt;/code&gt;, as in the snippet above. Second, &lt;code&gt;pytest-mock&lt;/code&gt; (added with &lt;code&gt;uv add --dev pytest-mock&lt;/code&gt;) replaces the nested &lt;code&gt;with patch(...)&lt;/code&gt; context managers with a &lt;code&gt;mocker&lt;/code&gt; fixture. The result is flatter and easier to scan. Annotated:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; test_bad_currency_conversion_raises&lt;/span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;(self, mocker):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;    &amp;quot;&amp;quot;&amp;quot;Converting to a non-existing currency should raise an exception.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;    # Patch requests.get *as imported inside the currency module* so no&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;    # real HTTP call is made; patch target must match where the name is used&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    mock_get&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;/span&gt;&lt;span&gt; mocker.patch(&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;expenses_ai_agent.utils.currency.requests.get&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;    # Simulate the API response for an unrecognised currency code&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    mock_get.return_value.json.return_value&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;        &amp;quot;result&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;error&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;        &amp;quot;error-type&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;unknown-code&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    with&lt;/span&gt;&lt;span&gt; pytest.raises(CurrencyConversionError):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        convert_currency(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable&quot;&gt;            amount&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;/span&gt;&lt;span&gt;Decimal(&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;1.00&amp;quot;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable&quot;&gt;            from_currency&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;CAD&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable&quot;&gt;            to_currency&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;CTM&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;    # Confirm the mock intercepted the call; if this fails, the real API was hit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    mock_get.assert_called_once()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;mocker&lt;/code&gt; also handles teardown automatically via the fixture lifecycle, so you don't need &lt;code&gt;with&lt;/code&gt; to ensure cleanup.&lt;/p&gt;
&lt;h2 id=&quot;another-reason-to-mock-forcing-a-collision&quot;&gt;Another reason to mock: forcing a collision&lt;/h2&gt;
&lt;p&gt;So far the mock has stood in for a network call. That's not the only reason to reach for one. Here's a test from &lt;a href=&quot;https://belderbos.dev/blog/build-the-simplest-thing-that-works/&quot;&gt;my simple CRM&lt;/a&gt; that stores contacts as files on disk:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; create_contact&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;    name:&lt;/span&gt;&lt;span class=&quot;z-support&quot;&gt; str&lt;/span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;, email:&lt;/span&gt;&lt;span class=&quot;z-support&quot;&gt; str&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;, company:&lt;/span&gt;&lt;span class=&quot;z-support&quot;&gt; str&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;, product:&lt;/span&gt;&lt;span class=&quot;z-support&quot;&gt; str&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;) -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-support&quot;&gt; str&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    contacts_dir().mkdir(&lt;/span&gt;&lt;span class=&quot;z-variable&quot;&gt;parents&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt;True&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-variable&quot;&gt; exist_ok&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt;True&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    code&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;/span&gt;&lt;span&gt; next_code(name)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    path&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;/span&gt;&lt;span&gt; contact_path(code)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    if&lt;/span&gt;&lt;span&gt; path.exists():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        raise&lt;/span&gt;&lt;span class=&quot;z-support&quot;&gt; FileExistsError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;Contact &lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt;{&lt;/span&gt;&lt;span&gt;code&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-string&quot;&gt; already exists&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    path.write_text(&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt;...&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;/span&gt;&lt;span&gt; code&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;next_code&lt;/code&gt; generates a unique code from the name. To test that creating two contacts with the same code raises &lt;code&gt;FileExistsError&lt;/code&gt;, you need both calls to produce the &lt;em&gt;same&lt;/em&gt; code. That's nondeterministic by design, so you patch &lt;code&gt;next_code&lt;/code&gt; to pin it:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;@patch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;crm.data.next_code&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; test_cannot_create_contact_with_same_code&lt;/span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;(mock_next_code):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    mock_next_code.return_value&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;jd1&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    data.create_contact(&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;Jane Doe&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    with&lt;/span&gt;&lt;span&gt; pytest.raises(&lt;/span&gt;&lt;span class=&quot;z-support&quot;&gt;FileExistsError&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        data.create_contact(&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;Jane Doe&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note the patch target again: &lt;code&gt;crm.data.next_code&lt;/code&gt;, where the function is &lt;em&gt;used&lt;/em&gt;. Same rule as before. And note that's the &lt;em&gt;only&lt;/em&gt; mock here.&lt;/p&gt;
&lt;p&gt;Isolation matters as much as the mock, but it doesn't belong in this test. An autouse fixture already points the data dir at a fresh &lt;code&gt;tmp_path&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;@pytest.fixture&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-variable&quot;&gt;autouse&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt;True&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; crm_data&lt;/span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;(tmp_path, monkeypatch):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    monkeypatch.setenv(&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;CRM_DATA&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-support&quot;&gt; str&lt;/span&gt;&lt;span&gt;(tmp_path))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    (tmp_path&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; /&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;contacts&amp;quot;&lt;/span&gt;&lt;span&gt;).mkdir()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;/span&gt;&lt;span&gt; tmp_path&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;create_contact&lt;/code&gt; calls &lt;code&gt;path.write_text(...)&lt;/code&gt;, so the first call writes a real &lt;code&gt;jd1&lt;/code&gt; file. Because every test runs against a fresh &lt;code&gt;tmp_path&lt;/code&gt;, that file lives only for the test: the collision can only come from the second call, nothing leaks between runs, and the test fails solely when the duplicate guard fires. Without that isolation, a leftover &lt;code&gt;jd1&lt;/code&gt; from a previous run makes the &lt;em&gt;first&lt;/em&gt; call raise, &lt;code&gt;pytest.raises&lt;/code&gt; still passes, and you've tested nothing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update: I later dropped this mock for an explicit override parameter.&lt;/strong&gt; Instead of patching &lt;code&gt;next_code&lt;/code&gt;, I gave &lt;code&gt;create_contact&lt;/code&gt; an optional &lt;code&gt;code&lt;/code&gt; parameter (keyword-only, so it can't be passed by accident):&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; create_contact&lt;/span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;(name:&lt;/span&gt;&lt;span class=&quot;z-support&quot;&gt; str&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;, email:&lt;/span&gt;&lt;span class=&quot;z-support&quot;&gt; str&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;, company:&lt;/span&gt;&lt;span class=&quot;z-support&quot;&gt; str&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;                    product:&lt;/span&gt;&lt;span class=&quot;z-support&quot;&gt; str&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;, code:&lt;/span&gt;&lt;span class=&quot;z-support&quot;&gt; str&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt; None&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt; None&lt;/span&gt;&lt;span&gt;) -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-support&quot;&gt; str&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    code&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;/span&gt;&lt;span&gt; code&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; if&lt;/span&gt;&lt;span&gt; code&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; is not&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt; None&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; else&lt;/span&gt;&lt;span&gt; next_code(name)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The test pins the code through the public surface, no patching:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; test_cannot_create_contact_with_same_code&lt;/span&gt;&lt;span&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    data.create_contact(&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;Jane Doe&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    with&lt;/span&gt;&lt;span&gt; pytest.raises(&lt;/span&gt;&lt;span class=&quot;z-support&quot;&gt;FileExistsError&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        data.create_contact(&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;Jane Doe&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-variable&quot;&gt; code&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;jd1&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One naming caveat, since this post points to Harry Percival's &quot;Stop Using Mocks&quot; below: this isn't dependency injection, tempting as it is to call it that. DI would pass &lt;code&gt;next_code&lt;/code&gt; itself in and let the test swap a fake. Here I pass the &lt;em&gt;value&lt;/em&gt; the dependency would have produced, so it's really an explicit override parameter, the simpler tool. Real DI, with an injected collaborator, comes up at the end of this post.&lt;/p&gt;
&lt;p&gt;The trade-off is worth being honest about: I added a production parameter partly to make the test simpler. That's the &quot;test-induced design damage&quot; critics of mocking warn about: a seam that exists &lt;em&gt;only&lt;/em&gt; to serve tests. I think it's justified here because &lt;code&gt;code&lt;/code&gt; doubles as a real feature: an explicit-code escape hatch for imports or restoring from backup. The test just happens to use it. If the parameter was only added for the test, I'd consider leaving the mock.&lt;/p&gt;
&lt;h2 id=&quot;unit-vs-integration-where-does-this-test-belong&quot;&gt;Unit vs integration: where does this test belong?&lt;/h2&gt;
&lt;p&gt;All this then led to a related question:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;How should you organize tests that hit real external services?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The convention that holds up in practice:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;tests/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── unit/        # fast, fully mocked, no network, no secrets&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;└── integration/ # slower, hits real DB / LLM / API endpoints&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The currency test above belongs in &lt;code&gt;unit/&lt;/code&gt;: it mocks &lt;code&gt;requests.get&lt;/code&gt; and never touches the network. A test that actually calls the ExchangeRate API to verify end-to-end behavior belongs in &lt;code&gt;integration/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;@pytest.mark.integration&lt;/code&gt; marker is a lighter-weight way to get the same split without moving files. Register it in &lt;code&gt;pyproject.toml&lt;/code&gt;, then skip those tests in CI with &lt;code&gt;pytest -m 'not integration'&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Both work, but the directory structure makes the distinction obvious at a glance. Explicit is better than implicit.&lt;/p&gt;
&lt;p&gt;The practical rule: if your test needs an environment variable or some external service to do its real work, it's an integration test. Mock that dependency out and it becomes a unit test. Or &lt;a href=&quot;https://belderbos.dev/blog/repository-pattern-swappable-data-sources/&quot;&gt;put it at the boundary&lt;/a&gt; so you can inject a fake in unit tests and the real thing in integration tests (if still needed).&lt;/p&gt;
&lt;p&gt;For a practical example of test organization, see this video: &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://www.youtube.com/watch?v=krb9b6eRinY&quot;&gt;Python Unit vs. Functional Testing: Understanding the Difference + Practical Example&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;when-mocks-are-the-wrong-tool&quot;&gt;When mocks are the wrong tool&lt;/h2&gt;
&lt;p&gt;There's a broader point underneath all this. Every time you patch &lt;code&gt;requests.get&lt;/code&gt; you're writing a test that's tightly coupled to one import path. Change &lt;code&gt;import requests&lt;/code&gt; to &lt;code&gt;from requests import get&lt;/code&gt; and every patch breaks. The tests test implementation, not behavior.&lt;/p&gt;
&lt;p&gt;I highly recommend watching &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://www.youtube.com/watch?v=rk-f3B-eMkI&quot;&gt;Harry Percival's PyCon talk &quot;Stop Using Mocks&quot;&lt;/a&gt;. He makes the case for alternatives: build an adapter class that owns the external call, write a fake in-memory implementation of it, and use dependency injection to pass it in. The &lt;a href=&quot;https://belderbos.dev/blog/repository-pattern-swappable-data-sources/&quot;&gt;repository pattern&lt;/a&gt; is the same idea: your test passes in a fake, your production code passes in the real thing, and neither needs patching.&lt;/p&gt;
&lt;p&gt;Mocks are still the right choice here: we want to test one small unit whose only external dependency is well contained.&lt;/p&gt;
&lt;h2 id=&quot;keep-reading&quot;&gt;Keep reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://belderbos.dev/blog/two-scoping-bugs-object-lifetimes/&quot;&gt;Two Interesting Scoping Bugs That Made Me Reflect on Object Lifetimes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://belderbos.dev/blog/repository-pattern-swappable-data-sources/&quot;&gt;The Repository Pattern: Swap Data Sources in One Line&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded>
	<dc:date>2026-06-03T00:00:00+00:00</dc:date>
</item>
<item rdf:about="https://pycoders.com/issues/737">
	<title>PyCoder’s Weekly: Issue #737: Polars 1.41, Email, Great Docs, and More (2026-06-02)</title>
	<link>https://pycoders.com/issues/737</link>
	<content:encoded>&lt;p&gt; &lt;span&gt;#737 – JUNE 2, 2026&lt;/span&gt;&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/issues/737/feed&quot;&gt;View in Browser »&lt;/a&gt;&lt;/span&gt; &lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://pycoders.com&quot;&gt;&lt;img alt=&quot;The PyCoder&amp;rsquo;s Weekly Logo&quot; src=&quot;https://cdn.pycoders.com/37bdf31dc645f968ffb90196e5d38ff5&quot; /&gt;&lt;/a&gt;&lt;/p&gt; &lt;hr /&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16546/feed&quot; target=&quot;_blank&quot;&gt;Announcing Polars 1.41&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; Polars 1.41 is out and this post covers the new features it includes. Learn about faster parquet metadata decoding, nested subplan elimination, and more.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16546/feed&quot; target=&quot;_blank&quot;&gt;POLA.RS&lt;/a&gt;&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16536/feed&quot; target=&quot;_blank&quot;&gt;Sending Emails With Python&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; Learn how to send emails with Python using SMTP, attach files, format HTML messages, and personalize bulk emails for your contact list.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16536/feed&quot; target=&quot;_blank&quot;&gt;REAL PYTHON&lt;/a&gt;&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16554/feed&quot; target=&quot;_blank&quot;&gt;Quiz: Sending Emails With Python&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; Use Python&amp;rsquo;s standard library to send email through secure SMTP connections, attach files, include HTML content, and route replies.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16554/feed&quot; target=&quot;_blank&quot;&gt;REAL PYTHON&lt;/a&gt;&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16555/feed&quot; target=&quot;_blank&quot;&gt;Your Coding Agent Gets Dumber the Longer It Runs. Here&amp;rsquo;s the Fix.&lt;/a&gt;&lt;/h3&gt; &lt;a href=&quot;https://pycoders.com/link/16555/feed&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;https://cdn.pycoders.com/a6d0baeffa46763ad6b5d8fd4064eed8&quot; alt=&quot;alt&quot; /&gt;&lt;/a&gt; &lt;p&gt; Coding agents degrade as context grows. The fix: a multi-role loop where the planner, builder, and reviewer each get isolated context — no stale assumptions, no compounding noise. A practical breakdown from someone who built it. &lt;a href=&quot;https://pycoders.com/link/16555/feed&quot; target=&quot;_blank&quot;&gt;Read the full breakdown&lt;/a&gt;&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16555/feed&quot; target=&quot;_blank&quot;&gt;DEPOT&lt;/a&gt;&lt;/span&gt; &lt;span&gt;sponsor&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16545/feed&quot; target=&quot;_blank&quot;&gt;Great Docs&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; Talk Python interviews Rich Iannone and Michael Chow from Posit and they talk about a new Python documentation tool called Great Docs.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16545/feed&quot; target=&quot;_blank&quot;&gt;TALK PYTHON&lt;/a&gt;&lt;/span&gt; &lt;span&gt;podcast&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16553/feed&quot; target=&quot;_blank&quot;&gt;PyPy v7.3.23 Released&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16553/feed&quot; target=&quot;_blank&quot;&gt;PYPY.ORG&lt;/a&gt;&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;h2&gt;Articles &amp;amp; Tutorials&lt;/h2&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16529/feed&quot; target=&quot;_blank&quot;&gt;Improving Python Through PEPs and Protocols&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; Have you ever been confused by the naming of modules you&amp;rsquo;re importing from a package? Is there a standard way to organize and name your Python virtual environments? This week on the show, Brett Cannon returns to discuss the Python Enhancement Proposals (PEPs) he&amp;rsquo;s been working on recently.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16529/feed&quot; target=&quot;_blank&quot;&gt;REAL PYTHON&lt;/a&gt;&lt;/span&gt; &lt;span&gt;podcast&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16530/feed&quot; target=&quot;_blank&quot;&gt;Tame Your Pesky Little Scripts&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; Over time it is common to accumulate little helper scripts, whether they&amp;rsquo;re shell scripts, aliases, or custom functions. They are typically tiny things that can become unwieldy to manage. This post shares a few ideas that might help you take back control.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16530/feed&quot; target=&quot;_blank&quot;&gt;JUHA-MATTI SANTALA&lt;/a&gt;&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16556/feed&quot; target=&quot;_blank&quot;&gt;5-Day Live OOP Workshop (Final Chance to Enroll)&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; The Object-Oriented Python live cohort begins June 8. Five 2-hour sessions Mon to Fri build one growing application end to end, with OOP features introduced as the code starts needing them: classes, the data model, inheritance vs composition, properties, dataclasses.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16556/feed&quot; target=&quot;_blank&quot;&gt;REAL PYTHON&lt;/a&gt;&lt;/span&gt; &lt;span&gt;sponsor&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16551/feed&quot; target=&quot;_blank&quot;&gt;Free-Threading vs the GIL in &lt;code&gt;mod_wsgi&lt;/code&gt; 6.0.0&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; Free-threading in mod_wsgi 6.0.0 lets a single process spread Python work across multiple cores. This post is a metrics based comparison between the GIL being enabled and disabled.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16551/feed&quot; target=&quot;_blank&quot;&gt;GRAHAM DUMPLETON&lt;/a&gt;&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16544/feed&quot; target=&quot;_blank&quot;&gt;Notes About Python Email Packages&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; Chris recently upgraded his personal mail program from Python 2 to Python 3 and this post talks about what needed to change and notes how the newer code works.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16544/feed&quot; target=&quot;_blank&quot;&gt;CHRIS SIEBENMANN&lt;/a&gt;&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16528/feed&quot; target=&quot;_blank&quot;&gt;Learning Path: Perfect Your Python Development Setup&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; Set up a Python development environment with VS Code, PyCharm, virtual environments, Git, pyenv, Docker, and AI coding tools like Claude Code and Cursor.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16528/feed&quot; target=&quot;_blank&quot;&gt;REAL PYTHON&lt;/a&gt;&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16534/feed&quot; target=&quot;_blank&quot;&gt;Top 7 Python Libraries for Large-Scale Data Processing&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; This article covers Python libraries that make large-scale data processing faster, more scalable, and easier to manage across modern data workflows.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16534/feed&quot; target=&quot;_blank&quot;&gt;BALA PRIYA C&lt;/a&gt;&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16552/feed&quot; target=&quot;_blank&quot;&gt;Connecting LLMs to Your Data With Python MCP Servers&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; Build an MCP server in Python that exposes tools, resources, and prompts so AI agents like Cursor can interact with your data.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16552/feed&quot; target=&quot;_blank&quot;&gt;REAL PYTHON&lt;/a&gt;&lt;/span&gt; &lt;span&gt;course&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16548/feed&quot; target=&quot;_blank&quot;&gt;How to Make a Scatter Plot in Python With plt.scatter()&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; Learn how to make scatter plots in Python with plt.scatter() and customize markers by size, color, shape, and transparency.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16548/feed&quot; target=&quot;_blank&quot;&gt;REAL PYTHON&lt;/a&gt;&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16543/feed&quot; target=&quot;_blank&quot;&gt;Quiz: How to Make a Scatter Plot in Python With plt.scatter()&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16543/feed&quot; target=&quot;_blank&quot;&gt;REAL PYTHON&lt;/a&gt;&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16549/feed&quot; target=&quot;_blank&quot;&gt;Two Python Scoping Bugs: A Lesson in Object Lifetimes&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; Two Python bugs with opposite symptoms but the same root cause: picking the wrong scope for a stateful object.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16549/feed&quot; target=&quot;_blank&quot;&gt;BOB BELDERBOS&lt;/a&gt;&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16541/feed&quot; target=&quot;_blank&quot;&gt;Sentinel Built-In&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; A quick post about Python 3.15&amp;rsquo;s new sentinel built-in.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16541/feed&quot; target=&quot;_blank&quot;&gt;RODRIGO GIRÃO SERRÃO&lt;/a&gt;&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;h2&gt;Projects &amp;amp; Code&lt;/h2&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16533/feed&quot; target=&quot;_blank&quot;&gt;dj-lite-tenant: Multi-Tenant SQLite Databases for Django&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16533/feed&quot; target=&quot;_blank&quot;&gt;GITHUB.COM/ADAMGHILL&lt;/a&gt;&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16542/feed&quot; target=&quot;_blank&quot;&gt;Lifeguard: Detect Lazy Imports Incompatibilities&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16542/feed&quot; target=&quot;_blank&quot;&gt;GITHUB.COM/FACEBOOK&lt;/a&gt;&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16535/feed&quot; target=&quot;_blank&quot;&gt;nbpipe: Run Sequences of Jupyter Notebooks as a Workflow&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16535/feed&quot; target=&quot;_blank&quot;&gt;GITHUB.COM/NGAFAR&lt;/a&gt;&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16537/feed&quot; target=&quot;_blank&quot;&gt;httpx2: A Next Generation HTTP Client for Python&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16537/feed&quot; target=&quot;_blank&quot;&gt;GITHUB.COM/PYDANTIC&lt;/a&gt;&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16547/feed&quot; target=&quot;_blank&quot;&gt;mkdocs-marimo: Mkdocs Plugin for Marimo&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16547/feed&quot; target=&quot;_blank&quot;&gt;GITHUB.COM/MARIMO-TEAM&lt;/a&gt;&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;h2&gt;Events&lt;/h2&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16540/feed&quot; target=&quot;_blank&quot;&gt;Weekly Real Python Office Hours Q&amp;amp;A (Virtual)&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; June 3, 2026&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16540/feed&quot; target=&quot;_blank&quot;&gt;REALPYTHON.COM&lt;/a&gt;&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16531/feed&quot; target=&quot;_blank&quot;&gt;Canberra Python Meetup&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; June 4, 2026&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16531/feed&quot; target=&quot;_blank&quot;&gt;MEETUP.COM&lt;/a&gt;&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16532/feed&quot; target=&quot;_blank&quot;&gt;Sydney Python User Group (SyPy)&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; June 4, 2026&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16532/feed&quot; target=&quot;_blank&quot;&gt;SYPY.ORG&lt;/a&gt;&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16539/feed&quot; target=&quot;_blank&quot;&gt;GeoPython 2026&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; June 8 to June 11, 2026&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16539/feed&quot; target=&quot;_blank&quot;&gt;GEOPYTHON.NET&lt;/a&gt;&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16538/feed&quot; target=&quot;_blank&quot;&gt;PiterPy Meetup&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; June 9, 2026&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16538/feed&quot; target=&quot;_blank&quot;&gt;PITERPY.COM&lt;/a&gt;&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16550/feed&quot; target=&quot;_blank&quot;&gt;SciPy 2026, Minneapolis, MN&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; July 13-19, 2026&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16550/feed&quot; target=&quot;_blank&quot;&gt;SCIPY.ORG&lt;/a&gt; • Shared by SciPy Organizers&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;hr /&gt; &lt;p&gt;Happy Pythoning!&lt;br /&gt;This was PyCoder&amp;rsquo;s Weekly Issue #737.&lt;br /&gt;&lt;a href=&quot;https://pycoders.com/issues/737/feed&quot;&gt;View in Browser »&lt;/a&gt;&lt;/p&gt; &lt;img src=&quot;https://pycoders.com/issues/737/open/feed&quot; width=&quot;1&quot; height=&quot;1&quot; alt=&quot;alt&quot; /&gt; 
        &lt;hr /&gt;
        &lt;p&gt;&lt;em&gt;[ Subscribe to 🐍 PyCoder&amp;rsquo;s Weekly 💌 – Get the best Python news, articles, and tutorials delivered to your inbox once a week &lt;a href=&quot;https://pycoders.com/?utm_source=pycoders&amp;utm_medium=feed&amp;utm_campaign=footer&quot;&gt;&amp;gt;&amp;gt; Click here to learn more&lt;/a&gt; ]&lt;/em&gt;&lt;/p&gt;</content:encoded>
	<dc:date>2026-06-02T19:30:00+00:00</dc:date>
</item>
<item rdf:about="https://realpython.com/courses/structuring-your-python-script/">
	<title>Real Python: Structuring Your Python Script</title>
	<link>https://realpython.com/courses/structuring-your-python-script/</link>
	<content:encoded>&lt;p&gt;You may have begun your Python journey interactively, exploring ideas within Jupyter Notebooks or through the Python REPL. While that&amp;rsquo;s great for quick experimentation and immediate feedback, you&amp;rsquo;ll likely find yourself saving code into &lt;code&gt;.py&lt;/code&gt; files. However, as your codebase grows, knowing where things should go in your script becomes increasingly important.&lt;/p&gt;
&lt;p&gt;Transitioning from interactive environments to structured scripts helps promote readability, enabling better collaboration and more robust development practices. This video course shows you the foundations of organizing a Python script: where the runnable bits go, how to arrange your imports, and how to refactor with constants and a fixed entry point.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;By the end of this video course, you&amp;rsquo;ll know how to:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Make a script &lt;strong&gt;directly executable&lt;/strong&gt; on Unix-like systems with a &lt;strong&gt;shebang line&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Organize your &lt;strong&gt;import statements&lt;/strong&gt; using standard grouping conventions&lt;/li&gt;
&lt;li&gt;Automatically sort imports and format your code with the &lt;strong&gt;&lt;code&gt;ruff&lt;/code&gt;&lt;/strong&gt; linter&lt;/li&gt;
&lt;li&gt;Replace hard-coded values with meaningful &lt;strong&gt;constants&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Define a clear script entry point using &lt;code&gt;if __name__ == &quot;__main__&quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Without further ado, it&amp;rsquo;s time to start working through a concrete script and progressively shape it into well-organized, shareable code.&lt;/p&gt;
        &lt;hr /&gt;
        &lt;p&gt;&lt;em&gt;[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short &amp;amp; sweet Python Trick delivered to your inbox every couple of days. &lt;a href=&quot;https://realpython.com/python-tricks/?utm_source=realpython&amp;utm_medium=rss&amp;utm_campaign=footer&quot;&gt;&amp;gt;&amp;gt; Click here to learn more and see examples&lt;/a&gt; ]&lt;/em&gt;&lt;/p&gt;</content:encoded>
	<dc:date>2026-06-02T14:00:00+00:00</dc:date>
</item>

</rdf:RDF>
