<?xml version="1.0"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">

<channel>
	<title>Planet Python</title>
	<link>http://planetpython.org/</link>
	<language>en</language>
	<description>Planet Python - http://planetpython.org/</description>

<item>
	<title>eGenix.com: Python Meeting Düsseldorf - 2026-10-07</title>
	<guid>https://www.egenix.com/company/news/Python-Meeting-Duesseldorf-2026-10-07</guid>
	<link>https://www.egenix.com/company/news/Python-Meeting-Duesseldorf-2026-10-07</link>
	<description>&lt;p&gt;&lt;span&gt;The following text is in German, since we're announcing a regional user group meeting in Düsseldorf, Germany.&lt;/span&gt;&lt;br /&gt;
&lt;/p&gt;
&lt;h2&gt;Ankündigung&lt;/h2&gt;
&lt;p&gt;Das nächste &lt;a href=&quot;http://pyddf.de/&quot;&gt;Python Meeting Düsseldorf&lt;/a&gt; findet an folgendem Termin statt:&lt;br /&gt;
&lt;/p&gt;
&lt;p&gt;07.10.2026, 18:00 Uhr&lt;br /&gt;
  &lt;span&gt;Raum 1, 2.OG im &lt;a href=&quot;http://www.duesseldorf.de/jugendamt/fam/famfoe/bueh/ac/index.shtml&quot;&gt;Bürgerhaus Stadtteilzentrum Bilk&lt;/a&gt;&lt;br /&gt;
    &lt;a href=&quot;http://www.duesseldorf-arcaden.de/anfahrt.html&quot;&gt;Düsseldorfer Arcaden&lt;/a&gt;, &lt;a href=&quot;http://maps.google.com/maps?q=51.209169,6.775339&amp;layer=c&amp;sll=51.209353,6.775272&amp;cbp=11,177.96,,0,0.79&amp;cbll=51.209412,6.77577&amp;ie=UTF8&amp;t=m&amp;vpsrc=0&amp;panoid=0NGUHHHEpGGMCcqpf3Gmbw&amp;z=17&quot;&gt;Bachstr. 145, 40217 Düsseldorf&lt;/a&gt;&lt;br /&gt;
  &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;br /&gt;
  &lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;h2&gt;Programm&lt;/h2&gt;
&lt;h3&gt;Bereits angemeldete Vorträge&lt;/h3&gt;
&lt;ul class=&quot;pl-5 list-disc mb-4&quot;&gt;
  
&lt;li&gt;Philipp von dem Bussche:&lt;br /&gt;&lt;strong&gt;Overengineering eines Fotobuchs: Meine Agenten-Reise durch Japans Sitzbezüge&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Jens Diemer:&lt;br /&gt;&lt;strong&gt;Radicle Explorer (LT)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Marc-André Lemburg:&lt;br /&gt;&lt;strong&gt;Common shortcuts AI agents love to use&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Jochen Wersdörfer:&lt;br /&gt;&lt;strong&gt;AI coding harnesses&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Klaus Bremer:&lt;br /&gt;&lt;strong&gt;Monte Carlo Simulation und Concurrency mit Freethreading und Subinterpreters in Python 3.13 bis 3.15&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

Weitere Vorträge können gerne noch angemeldet werden. Bei Interesse, bitte unter &lt;a href=&quot;mailto:info@pyddf.de&quot;&gt;info@pyddf.de&lt;/a&gt; melden.






&lt;h3&gt;Startzeit und Ort&lt;br /&gt;
&lt;/h3&gt;
&lt;p&gt;Wir treffen uns um 18:00 Uhr im Bürgerhaus in den 
Düsseldorfer Arcaden.&lt;/p&gt;
&lt;p&gt;Das Bürgerhaus teilt sich den Eingang mit dem Schwimmbad und befindet 
sich an der Seite der Tiefgarageneinfahrt der Düsseldorfer Arcaden.&lt;br /&gt;
  &lt;br /&gt;
Über dem Eingang steht ein großes &quot;Schwimm’ in Bilk&quot; Logo. Hinter der Tür
 direkt links zu den zwei Aufzügen, dann in den 2. Stock hochfahren. Der
 Eingang zum Raum 1 liegt direkt links, wenn man aus dem Aufzug kommt.&lt;br /&gt;
  &lt;br /&gt;
&amp;gt;&amp;gt;&amp;gt; &lt;a href=&quot;http://bit.ly/11sCfiw&quot;&gt;Eingang in Google Street View&lt;/a&gt;&lt;/p&gt;&lt;b&gt;⚠️ Wichtig&lt;/b&gt;: Bitte nur dann anmelden, wenn ihr absolut 
sicher seid, dass ihr auch kommt. Angesichts der begrenzten Anzahl Plätze, haben
 wir kein Verständnis für kurzfristige Absagen oder No-Shows.
&lt;h2&gt;Einleitung&lt;/h2&gt;



&lt;p&gt;Das &lt;a href=&quot;http://pyddf.de/&quot;&gt;Python Meeting Düsseldorf&lt;/a&gt; ist eine regelmäßige Veranstaltung in Düsseldorf, die sich an Python Begeisterte aus der Region wendet.&lt;/p&gt;Einen guten Überblick über die Vorträge bietet unser &lt;a href=&quot;http://www.youtube.com/pyddf/&quot;&gt;PyDDF YouTube-Kanal&lt;/a&gt;, auf dem wir Videos der Vorträge nach den Meetings veröffentlichen.&lt;br /&gt;



&lt;p&gt;Veranstaltet wird das Meeting von der &lt;a href=&quot;https://www.egenix.com/&quot;&gt;eGenix.com GmbH&lt;/a&gt;, Langenfeld, in Zusammenarbeit mit &lt;a href=&quot;http://www.clark-consulting.eu/&quot;&gt;Clark Consulting &amp;amp; Research&lt;/a&gt;, Düsseldorf:&lt;br /&gt;
&lt;/p&gt;
&lt;h2&gt;Format&lt;/h2&gt;
&lt;p&gt;Das &lt;a href=&quot;http://pyddf.de/&quot;&gt;Python Meeting Düsseldorf&lt;/a&gt; nutzt eine Mischung aus (Lightning) Talks und offener Diskussion.&lt;/p&gt;Vorträge können vorher angemeldet werden, oder auch spontan 
während des Treffens eingebracht werden. Ein Beamer mit HDMI und FullHD Auflösung 
steht zur Verfügung.&lt;p&gt;&lt;span&gt;(Lightning) Talk Anmeldung&lt;/span&gt; bitte formlos per EMail an &lt;a href=&quot;mailto:info@pyddf.de?subject=Anmeldung&amp;nbsp;Lightning&amp;nbsp;Talk&amp;nbsp;Python&amp;nbsp;Meeting&amp;nbsp;D&amp;uuml;sseldorf&quot;&gt;info@pyddf.de&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Kostenbeteiligung&lt;/h2&gt;

&lt;p&gt;Das &lt;a href=&quot;http://pyddf.de/&quot;&gt;Python Meeting Düsseldorf&lt;/a&gt; wird von Python Nutzern für Python Nutzer veranstaltet.&lt;/p&gt;
&lt;p&gt;Tagungsraum, Beamer und Getränke produzieren Kosten. Daher bitten wir die Teilnehmer um eine Kostenbeteiligung in Höhe von &lt;span&gt;EUR 10,00&lt;/span&gt; inkl. 
19% Mwst. Schüler und Studenten zahlen &lt;span&gt;EUR 5,00&lt;/span&gt; inkl. 
19% Mwst.&lt;/p&gt;
&lt;p&gt;Wir möchten alle Teilnehmer bitten, den Betrag in bar mitzubringen.&lt;/p&gt;
&lt;h2&gt;Anmeldung&lt;/h2&gt;
&lt;p&gt;Da wir nur 25 Personen in dem angemieteten Raum empfangen können, möchten wir bitten, sich vorher anzumelden.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&amp;gt;&amp;gt;&amp;gt; Anmeldung bitte über den hierfür angelegten &lt;/b&gt;&lt;b&gt;&lt;a href=&quot;https://www.linkedin.com/events/7510428185302323200&quot;&gt;LinkedIn Event&lt;/a&gt;&lt;/b&gt;&lt;br /&gt;
&lt;/p&gt;







&lt;h2&gt;Weitere Informationen&lt;/h2&gt;
&lt;p&gt;Weitere Informationen finden Sie auf der Webseite des Meetings:&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;a href=&quot;https://pyddf.de/&quot;&gt;https://pyddf.de/&lt;/a&gt;&lt;br /&gt;
&lt;/p&gt;

&lt;p&gt;Viel Spaß !&lt;/p&gt;
&lt;p&gt;Marc-Andre Lemburg, eGenix.com&lt;/p&gt;</description>
	<pubDate>Tue, 29 Sep 2026 08:00:00 +0000</pubDate>
</item>
<item>
	<title>The Python Coding Stack: The Potion Shop That Does Too Much • The S in SOLID</title>
	<guid>https://www.thepythoncodingstack.com/p/solid-single-responsibility-principle-srp-the-potion-shop-that-does-too-much</guid>
	<link>https://www.thepythoncodingstack.com/p/solid-single-responsibility-principle-srp-the-potion-shop-that-does-too-much</link>
	<description>&lt;p&gt;The potion shop had been doing well.&lt;/p&gt;&lt;p&gt;It had a small but loyal customer base, a reliable supply of moonflower, and&lt;br /&gt;three owls that carried order confirmations and other notifications to customers across the kingdom.&lt;/p&gt;&lt;p&gt;The shop&amp;#8217;s ordering system was simple:&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_!-cwp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff0e151f-93f5-401f-a0a1-af29daaddb79_1272x1974.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!-cwp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff0e151f-93f5-401f-a0a1-af29daaddb79_1272x1974.png&quot; width=&quot;1272&quot; height=&quot;1974&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 buttonBase-GK1x3M&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 buttonBase-GK1x3M&quot;&gt;&lt;/button&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;We&amp;#8217;ll make significant changes to this code in this article, but bear with me for now.&lt;/p&gt;&lt;p&gt;A customer orders an invisibility potion:&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_!QQSe!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d0494de-a2c1-4470-8dfc-92ed9314cd08_1200x210.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!QQSe!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d0494de-a2c1-4470-8dfc-92ed9314cd08_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 output looks good:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Invisibility  &amp;#163;12.00
Owl dispatched to Merlin
12.00&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I&amp;#8217;m using &lt;code&gt;Decimal&lt;/code&gt; for the prices here as it&amp;#8217;s a better data type for money than &lt;code&gt;float&lt;/code&gt;. We discussed this a bit here, too: &lt;a href=&quot;https://www.thepythoncodingstack.com/p/python-descriptors-the-price-is-right&quot;&gt;The Weird and Wonderful World of Descriptors in Python &amp;#8226; The Price is Right&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;The shop has sold a potion, updated its stock, recorded the sale, and notified the customer.&lt;/p&gt;&lt;p&gt;What could possibly go wrong?&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;A new label for the Moon Festival&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;The Moon Festival is approaching, and the shop owner wants special labels:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;#127769; Moon Festival Invisibility &amp;#127769;   &amp;#163;12.00&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We could change &lt;code&gt;.sell()&lt;/code&gt;:&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_!31sQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8aa6ccd0-32d1-4d5f-9229-a2162a15cfd6_1200x1050.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!31sQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8aa6ccd0-32d1-4d5f-9229-a2162a15cfd6_1200x1050.png&quot; width=&quot;1200&quot; height=&quot;1050&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 buttonBase-GK1x3M&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 buttonBase-GK1x3M&quot;&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;#3&lt;/div&gt;&lt;p&gt;You can read more about the &amp;#8216;rogue asterisk&amp;#8217; in the signature, a.k.a keyword-only arguments, in this post: &lt;a href=&quot;https://www.thepythoncodingstack.com/p/python-function-parameters-arguments-args-kwargs-optional-positional-keyword&quot;&gt;A Story About Parameters and Arguments in Python Functions &amp;#8226; &amp;#8220;AI Coffee&amp;#8221; Grand Opening This Monday&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Now this works:&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_!tq_c!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdaf8eb8c-47b7-4e64-b7a0-04ca1e29a34d_1200x210.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!tq_c!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdaf8eb8c-47b7-4e64-b7a0-04ca1e29a34d_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;Here&amp;#8217;s the output from this code:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;#127769; Moon Festival Invisibility &amp;#127769;  &amp;#163;12.00
Owl dispatched to Merlin
12.00&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The change is small. Perhaps this class is still perfectly fine.&lt;/p&gt;&lt;div&gt;&lt;hr /&gt;&lt;/div&gt;&lt;p&gt;The shop owner then asks for a test that checks the label without selling a potion. This is where you start to see the design problem.&lt;/p&gt;&lt;p&gt;At the moment, the only place that creates a label is inside &lt;code&gt;.sell()&lt;/code&gt;. There is no suitable way to check the label policy independently. The code does not offer a way to ask for a label without also selling a potion.&lt;/p&gt;&lt;p&gt;The design problem is that &lt;code&gt;PotionShop&lt;/code&gt; currently is in charge of label creation, even though the label policy can change independently of the policy that deals with sales. How about moving label creation to its own class?&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_!f2ZF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ed22447-ec77-4a10-a4fe-7531fa85be96_1200x630.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!f2ZF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ed22447-ec77-4a10-a4fe-7531fa85be96_1200x630.png&quot; width=&quot;1200&quot; height=&quot;630&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 buttonBase-GK1x3M&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 buttonBase-GK1x3M&quot;&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;#5&lt;/div&gt;&lt;p&gt;Now you can check that the label is correct without having to sell an item in the first place:&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_!ui07!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F317910d4-bfd7-4862-a8a8-1bf950b82877_1200x336.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!ui07!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F317910d4-bfd7-4862-a8a8-1bf950b82877_1200x336.png&quot; width=&quot;1200&quot; height=&quot;336&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 buttonBase-GK1x3M&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 buttonBase-GK1x3M&quot;&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;#6&lt;/div&gt;&lt;p&gt;This code would raise an &lt;code&gt;AssertionError&lt;/code&gt; if the string that &lt;code&gt;labels.make()&lt;/code&gt; returns is not identical to the required output. In this case, there are no errors.&lt;/p&gt;&lt;p&gt;In a real project, you could use a testing platform to run these checks. But I&amp;#8217;ll use plain &lt;code&gt;assert&lt;/code&gt; statements throughout to keep things simpler in this tutorial.&lt;/p&gt;&lt;p&gt;The shop delegates label-making to this object:&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_!BzDn!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9681634-69f0-4bfb-bee2-518186d87ea8_1200x1134.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!BzDn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9681634-69f0-4bfb-bee2-518186d87ea8_1200x1134.png&quot; width=&quot;1200&quot; height=&quot;1134&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 buttonBase-GK1x3M&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 buttonBase-GK1x3M&quot;&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;#7&lt;/div&gt;&lt;p&gt;You should also remove the three lines that create a label you had further down within &lt;code&gt;.sell()&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;We&amp;#8217;re using &lt;em&gt;composition&lt;/em&gt; here. The Potion Shop &lt;em&gt;has a&lt;/em&gt; label maker, and you pass a &lt;code&gt;LabelMaker&lt;/code&gt; object to the &lt;code&gt;.label_maker&lt;/code&gt; attribute in the &lt;code&gt;PotionShop&lt;/code&gt; object. The &lt;code&gt;PotionShop&lt;/code&gt; object can now delegate label making to an object dedicated to this task.&lt;/p&gt;&lt;p&gt;From this point onwards, the shop receives the label-making collaborator:&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_!1U0D!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d8da646-9053-4d9d-88b2-685633a01914_1200x168.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!1U0D!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d8da646-9053-4d9d-88b2-685633a01914_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;#8&lt;/div&gt;&lt;p&gt;&lt;code&gt;PotionShop&lt;/code&gt; still coordinates the sale, but the label policy can now change without changing &lt;code&gt;PotionShop&lt;/code&gt;. We started separating responsibilities. Making labels is no longer the responsibility of the &lt;code&gt;PotionShop&lt;/code&gt; object. But we still have a bit more to go.&lt;/p&gt;&lt;div&gt;&lt;hr /&gt;&lt;/div&gt;&lt;p&gt;You got a taste of what the &lt;em&gt;Single Responsibility Principle&lt;/em&gt; does in the example so far. But the code still has other transgressions of this principle. Let&amp;#8217;s work through them in the rest of this post.&lt;/p&gt;&lt;div class=&quot;pullquote&quot;&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Coming soon:&lt;/strong&gt; Let&amp;#8217;s build a project from beginning to end in collaboration with AI agents. This will NOT be vibe coding. Instead, it&amp;#8217;s a collaboration between knowledgeable humans and powerful AI.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;This is the direction real programming is taking.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;And we&amp;#8217;ll explore SOLID principles in this project, which I&amp;#8217;ll share as text and videos. Make sure you&amp;#8217;re a premium subscriber so you don&amp;#8217;t miss out.&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;h2&gt;&lt;strong&gt;The owls go on strike&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;A few days later, the owls refuse to fly during a thunderstorm. The potions can still be prepared, but the notification service is unavailable.&lt;/p&gt;&lt;p&gt;The shop needs to stop sending notifications temporarily.&lt;/p&gt;&lt;p&gt;Before changing the notification system, we first make the owl&amp;#8217;s availability explicit:&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_!ywHS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24a036ca-c203-4a68-ae74-0e2e3f11c480_1200x1134.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!ywHS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24a036ca-c203-4a68-ae74-0e2e3f11c480_1200x1134.png&quot; width=&quot;1200&quot; height=&quot;1134&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 buttonBase-GK1x3M&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 buttonBase-GK1x3M&quot;&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;#9&lt;/div&gt;&lt;p&gt;But you also want to add a test to check the price is correct.&lt;/p&gt;&lt;p&gt;Your code extracts the price from the &lt;code&gt;.recipes&lt;/code&gt; class attribute as part of the &lt;code&gt;.sell()&lt;/code&gt; method. So, this is how you can test the price is what you&amp;#8217;re expecting:&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_!6AgR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1a86c54-02c7-4d99-87c1-06bd5da51a9f_1200x252.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!6AgR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1a86c54-02c7-4d99-87c1-06bd5da51a9f_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;#10&lt;/div&gt;&lt;p&gt;You get the following output and no &lt;code&gt;AssertionError&lt;/code&gt; in this case:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Invisibility  &amp;#163;12.00
Owl dispatched to Merlin&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;However, you should also test this when the owls are unavailable. Of course, this shouldn&amp;#8217;t make any difference to the price:&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_!aEei!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef5beef4-7fd7-4d40-afef-227762dab2fc_1200x252.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!aEei!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef5beef4-7fd7-4d40-afef-227762dab2fc_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;#11&lt;/div&gt;&lt;p&gt;But, your code doesn&amp;#8217;t get that far. The &lt;code&gt;.sell()&lt;/code&gt; method calculates the price and prints the label. But the owl service raises &lt;code&gt;OwlUnavailable&lt;/code&gt; and the code stops before you can verify the price is right:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Invisibility  &amp;#163;12.00
&amp;#8203;
Traceback (most recent call last):
  ...
OwlUnavailable: The owls are currently unavailable&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The price is not the problem. The test cannot inspect the price without also triggering an owl notification.&lt;/p&gt;&lt;p&gt;The shop&amp;#8217;s different jobs are now getting in each other&amp;#8217;s way. But before we fix this problem, let&amp;#8217;s make the mess bigger.&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;A discount for cauldron owners&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;The owner has another idea. Customers who bring their own cauldron should receive a 10% discount.&lt;/p&gt;&lt;p&gt;We add another parameter:&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_!ibQ4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc0b67fc-59fc-4cd9-a6be-76544d9b83c5_1362x1092.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!ibQ4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc0b67fc-59fc-4cd9-a6be-76544d9b83c5_1362x1092.png&quot; width=&quot;1362&quot; height=&quot;1092&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 buttonBase-GK1x3M&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 buttonBase-GK1x3M&quot;&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;#12&lt;/div&gt;&lt;p&gt;You use &lt;code&gt;.quantize()&lt;/code&gt;, which is a method in the &lt;code&gt;Decimal&lt;/code&gt; class, to ensure the discounted value is rounded to two decimal places. Let&amp;#8217;s try this with a shop whose notification service is available:&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_!sdIw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7564fd2-685f-4bfe-93d4-c6439c3dc628_1200x462.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!sdIw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7564fd2-685f-4bfe-93d4-c6439c3dc628_1200x462.png&quot; width=&quot;1200&quot; height=&quot;462&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 buttonBase-GK1x3M&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 buttonBase-GK1x3M&quot;&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;#13&lt;/div&gt;&lt;p&gt;The output shows the correct discounted price:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Invisibility  &amp;#163;10.80
Owl dispatched to Merlin
10.80&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;#8217;s see what happens if the owls are unavailable:&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_!Dw6w!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5337853c-0ff3-4642-872e-6b731f750e26_1200x462.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!Dw6w!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5337853c-0ff3-4642-872e-6b731f750e26_1200x462.png&quot; width=&quot;1200&quot; height=&quot;462&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 buttonBase-GK1x3M&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 buttonBase-GK1x3M&quot;&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;#14&lt;/div&gt;&lt;p&gt;Your code raises an error since the owls aren&amp;#8217;t flying:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Invisibility  &amp;#163;10.80
Traceback (most recent call last):
  ...
OwlUnavailable: The owls are currently unavailable&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But there&amp;#8217;s an even bigger problem now. Can you spot it?&lt;/p&gt;&lt;p&gt;Even though the code stopped execution and raised an error, it had already changed the stock and recorded a sale. Let&amp;#8217;s use a &lt;code&gt;try..except&lt;/code&gt; block to see what&amp;#8217;s happening:&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_!pZlb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6644562f-b86e-47d0-bde4-617c8353062b_1200x672.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!pZlb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6644562f-b86e-47d0-bde4-617c8353062b_1200x672.png&quot; width=&quot;1200&quot; height=&quot;672&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 buttonBase-GK1x3M&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 buttonBase-GK1x3M&quot;&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;#15&lt;/div&gt;&lt;p&gt;Here&amp;#8217;s the output:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Invisibility  &amp;#163;10.80
Catching the &amp;#8216;OwlUnavailable&amp;#8217; Exception
{&amp;#8217;moonflower&amp;#8217;: 8, &amp;#8216;dragon_scale&amp;#8217;: 0}
[(&amp;#8217;Merlin&amp;#8217;, &amp;#8216;invisibility&amp;#8217;, Decimal(&amp;#8217;10.80&amp;#8217;))]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You started off with 10 units of moonflower and 2 units of dragon scales in the potion shop. These values are currently hardcoded in the class&amp;#8217;s &lt;code&gt;.__init__()&lt;/code&gt; special method (which is in itself not a good idea, but more on this later.)&lt;/p&gt;&lt;p&gt;The recipe uses two units of each, as shown in the &lt;code&gt;.recipes&lt;/code&gt; class attribute (which will also change soon.)&lt;/p&gt;&lt;p&gt;So you end up using up the stock even though &lt;code&gt;.sell()&lt;/code&gt; raised an exception! You can see from the output that the sale was also registered.&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;One failure too many&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;The &lt;code&gt;.sell()&lt;/code&gt; method is now responsible for:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;Looking up recipes&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Removing ingredients&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Calculating prices&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Applying discounts&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Coordinating label creation*&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Recording sales&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Sending notifications&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;* Creating labels is the one we have already improved. It&amp;#8217;s the others that are still a problem!&lt;/p&gt;&lt;p&gt;And the owner has just mentioned loyalty points, premium labels, email notifications, and a special discount for customers who arrive by broomstick.&lt;/p&gt;&lt;p&gt;The &lt;code&gt;.sell()&lt;/code&gt; method is becoming the place where every new request arrives.&lt;/p&gt;&lt;p&gt;And it&amp;#8217;s not just &lt;code&gt;PotionShop.sell()&lt;/code&gt; that&amp;#8217;s trying to do too much. The &lt;code&gt;PotionShop&lt;/code&gt; class also takes care of the recipes available in the &lt;code&gt;.recipes&lt;/code&gt; class attribute and the stock available in the shop.&lt;/p&gt;&lt;p&gt;It is time to separate these responsibilities.&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;Separating the recipe book&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;Let&amp;#8217;s start with the recipes. The shop should not need to know the details of every recipe. That is the recipe book&amp;#8217;s job:&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_!HRcc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Facf26af5-53df-45d7-843c-7d3ac275be85_1200x714.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!HRcc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Facf26af5-53df-45d7-843c-7d3ac275be85_1200x714.png&quot; width=&quot;1200&quot; height=&quot;714&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 buttonBase-GK1x3M&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 buttonBase-GK1x3M&quot;&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;#16&lt;/div&gt;&lt;p&gt;This class is responsible for the recipe book. And nothing else. You add the &lt;code&gt;.find()&lt;/code&gt; method to make it easier for a user to use the class without knowing the details of how it&amp;#8217;s built. It&amp;#8217;s usually best for a class to manage its own data. So, rather than requiring the user to know that the recipes are stored in a dictionary, you provide the &lt;code&gt;.find()&lt;/code&gt; method that does the work.&lt;/p&gt;&lt;p&gt;Suppose the potion maker changes a recipe or adds a new potion. Those changes belong in &lt;code&gt;RecipeBook&lt;/code&gt;; the steps for selling a potion don&amp;#8217;t need to change.&lt;/p&gt;&lt;p&gt;We can test this class without creating a shop, changing stock, printing labels, or sending owls:&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_!wfR5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe80f6283-149d-4af8-bcd0-7baa9e2905ee_1308x210.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!wfR5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe80f6283-149d-4af8-bcd0-7baa9e2905ee_1308x210.png&quot; width=&quot;1308&quot; height=&quot;210&quot; src=&quot;src&quot; /&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;#17&lt;/div&gt;&lt;p&gt;This code doesn&amp;#8217;t raise an exception since the price is correct.&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;Separating inventory&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;Inventory management also doesn&amp;#8217;t belong to the &lt;code&gt;PotionShop&lt;/code&gt; class:&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_!EpzM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf29f6b0-9f20-4050-ad34-e5d17e0d5c01_1470x756.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!EpzM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf29f6b0-9f20-4050-ad34-e5d17e0d5c01_1470x756.png&quot; width=&quot;1456&quot; height=&quot;749&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 buttonBase-GK1x3M&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 buttonBase-GK1x3M&quot;&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;#18&lt;/div&gt;&lt;p&gt;The two loops in &lt;code&gt;.remove_stock()&lt;/code&gt; are deliberate. If we reduced each quantity as we went, we could use some ingredients and then discover that there isn&amp;#8217;t enough of another. The sale would fail, but the stock would already be partly changed. Checking every quantity first avoids that partial update.&lt;/p&gt;&lt;p&gt;We can see what happens when there isn&amp;#8217;t enough stock:&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_!YB2b!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcab5d3f7-32dc-47d0-8d8c-bb4876d4c4cd_1200x882.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!YB2b!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcab5d3f7-32dc-47d0-8d8c-bb4876d4c4cd_1200x882.png&quot; width=&quot;1200&quot; height=&quot;882&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 buttonBase-GK1x3M&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 buttonBase-GK1x3M&quot;&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;#19&lt;/div&gt;&lt;p&gt;The operation fails before changing any stock:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Not enough dragon_scale
{&amp;#8217;moonflower&amp;#8217;: 10, &amp;#8216;dragon_scale&amp;#8217;: 2}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After a successful sale, a delivery can add stock again:&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_!s9HI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfd3e641-3a13-4a8f-8e9f-7a2b57d89040_1200x630.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!s9HI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfd3e641-3a13-4a8f-8e9f-7a2b57d89040_1200x630.png&quot; width=&quot;1200&quot; height=&quot;630&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 buttonBase-GK1x3M&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 buttonBase-GK1x3M&quot;&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;#20&lt;/div&gt;&lt;p&gt;Here&amp;#8217;s the output from this code:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{&amp;#8217;moonflower&amp;#8217;: 13, &amp;#8216;dragon_scale&amp;#8217;: 0}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;Inventory&lt;/code&gt; owns both the stock data and the operations that change its quantities. It does not need to know who bought the potion or whether the label is festive.&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;Separating prices and labels&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;Pricing has its own rules:&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_!Eyj_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9b369b2-2630-4406-8f3b-c6b8457415f4_1200x546.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!Eyj_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9b369b2-2630-4406-8f3b-c6b8457415f4_1200x546.png&quot; width=&quot;1200&quot; height=&quot;546&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 buttonBase-GK1x3M&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 buttonBase-GK1x3M&quot;&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;#21&lt;/div&gt;&lt;p&gt;Once again, this class now has just one responsibility: to deal with the pricing.&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_!n5HU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6cd761b-90fb-435c-8180-34a4bafb4d8c_1200x420.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!n5HU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6cd761b-90fb-435c-8180-34a4bafb4d8c_1200x420.png&quot; width=&quot;1200&quot; height=&quot;420&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 buttonBase-GK1x3M&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 buttonBase-GK1x3M&quot;&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;#22&lt;/div&gt;&lt;p&gt;You can check the pricing strategy works without having to sell a product and everything else.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;10.80&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Labels have their own rules too. You dealt with this earlier in the tutorial when you created &lt;code&gt;LabelMaker&lt;/code&gt;&lt;/p&gt;&lt;p&gt;&lt;code&gt;PriceCalculator&lt;/code&gt; and &lt;code&gt;LabelMaker&lt;/code&gt; could both be functions here. We&amp;#8217;re using classes because they act as collaborators that the shop receives from outside. Later, we&amp;#8217;ll see how protocols or abstract base classes can describe interchangeable collaborators more explicitly. For now, the important point is the separation of responsibilities and not the fact that each responsibility has become a class.&lt;/p&gt;&lt;p&gt;Neither class needs an inventory, a sales ledger, or an owl.&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;Putting the shop back together&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;We still need something to coordinate the sale. First, we give the sales ledger and the notifier small, focused interfaces:&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_!w-Av!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ed530fd-6b12-4d0e-bf0e-ea09410a5101_1200x966.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!w-Av!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ed530fd-6b12-4d0e-bf0e-ea09410a5101_1200x966.png&quot; width=&quot;1200&quot; height=&quot;966&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 buttonBase-GK1x3M&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 buttonBase-GK1x3M&quot;&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;#23&lt;/div&gt;&lt;p&gt;Notice the &lt;code&gt;&quot;pending&quot;&lt;/code&gt; state in the ledger. Separating responsibilities does not make the sale process independent of anything else. Inventory has already changed before later steps run. Here, the narrower goal is to make notification failure visible so that the recorded sale can be investigated.&lt;/p&gt;&lt;p&gt;Now we can keep &lt;code&gt;PotionShop&lt;/code&gt; as a thin coordinator. It still represents the sale process, but it delegates each part of that process to the object that owns the relevant behaviour:&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_!FlUh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16815ce0-0924-4d23-a6ae-9e95af69305b_1254x1680.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!FlUh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16815ce0-0924-4d23-a6ae-9e95af69305b_1254x1680.png&quot; width=&quot;1254&quot; height=&quot;1680&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 buttonBase-GK1x3M&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 buttonBase-GK1x3M&quot;&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;#24&lt;/div&gt;&lt;p&gt;We can now assemble the shop and sell a potion:&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_!g4Hl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b29ad29-0a9e-4e3d-aa41-e28050629c6c_1200x1008.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!g4Hl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b29ad29-0a9e-4e3d-aa41-e28050629c6c_1200x1008.png&quot; width=&quot;1200&quot; height=&quot;1008&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 buttonBase-GK1x3M&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 buttonBase-GK1x3M&quot;&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;#25&lt;/div&gt;&lt;p&gt;Here&amp;#8217;s the output:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;#127769; Moon Festival Invisibility &amp;#127769; &amp;#163;10.80
Owl dispatched to Merlin
10.80
{&amp;#8217;customer&amp;#8217;: &amp;#8216;Merlin&amp;#8217;, &amp;#8216;potion&amp;#8217;: &amp;#8216;invisibility&amp;#8217;, &amp;#8216;price&amp;#8217;: Decimal(&amp;#8217;10.80&amp;#8217;), &amp;#8216;notification&amp;#8217;: &amp;#8216;sent&amp;#8217;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If notification fails after the ledger records the sale, the sale remains&lt;br /&gt;visible with a pending notification:&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_!FPq9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc9df1a3-caa7-46a3-accb-e566eefa5157_1272x1008.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!FPq9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc9df1a3-caa7-46a3-accb-e566eefa5157_1272x1008.png&quot; width=&quot;1272&quot; height=&quot;1008&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 buttonBase-GK1x3M&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 buttonBase-GK1x3M&quot;&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;#26&lt;/div&gt;&lt;p&gt;Here&amp;#8217;s the output:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Invisibility  &amp;#163;12.00
{&amp;#8217;customer&amp;#8217;: &amp;#8216;Merlin&amp;#8217;, &amp;#8216;potion&amp;#8217;: &amp;#8216;invisibility&amp;#8217;, 
 &amp;#8216;price&amp;#8217;: Decimal(&amp;#8217;12.00&amp;#8217;), &amp;#8216;notification&amp;#8217;: &amp;#8216;pending&amp;#8217;}
&amp;#8203;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The coordinator still changes if the business process changes. That&amp;#8217;s fine: coordinating a sale is its responsibility. But recipe changes belong in &lt;code&gt;RecipeBook&lt;/code&gt;, inventory changes belong in &lt;code&gt;Inventory&lt;/code&gt;, pricing changes belong in &lt;code&gt;PriceCalculator&lt;/code&gt;, label changes belong in &lt;code&gt;LabelMaker&lt;/code&gt;, and notification changes belong in &lt;code&gt;Notifier&lt;/code&gt;.&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;The Single Responsibility Principle&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;This is the idea behind the Single Responsibility Principle:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;A class should have one reason to change.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I always find these &amp;#8220;official&amp;#8221; definitions a bit cryptic when I read them at first. Here, &amp;#8220;reason&amp;#8221; is about the policy or stakeholder driving the change. The pricing rules may change because the shop owner changes the discounts. The label design may change because the person responsible for packaging wants something different. The notification service may change because the owls are on strike.&lt;/p&gt;&lt;p&gt;That does not mean that every class should have one method. Nor does it mean that every noun in the story needs its own class.&lt;/p&gt;&lt;p&gt;&lt;code&gt;PriceCalculator&lt;/code&gt; can have several related methods. &lt;code&gt;Inventory&lt;/code&gt; can have several operations. The important question is whether the methods belong to the same coherent responsibility.&lt;/p&gt;&lt;p&gt;The thin &lt;code&gt;PotionShop&lt;/code&gt; coordinator is also a class with a responsibility. It coordinates the sale. A change to that business process may quite reasonably affect it.&lt;/p&gt;&lt;p&gt;When unrelated changes keep arriving at the same class, the class becomes harder to test and more likely to break. Extract a collaborator when it may need to change for a different reason from the surrounding code, or when separating it makes an important test simpler. A good class name alone isn&amp;#8217;t a reason to create one.&lt;/p&gt;&lt;p&gt;The potion shop did not become troublesome because it had a particular number of lines. It became troublesome because recipes, inventory, pricing, labels, sales, and notifications all had different reasons to change.&lt;/p&gt;&lt;p&gt;A useful question to ask is:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;What different kinds of change could affect this class?&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;If the answers are &amp;#8220;the pricing rules&amp;#8221;, &amp;#8220;the label design&amp;#8221;, &amp;#8220;the storage system&amp;#8221;, and &amp;#8220;the notification service&amp;#8221;, you may have several responsibilities sharing one home.&lt;/p&gt;&lt;p&gt;And if the project keeps growing, we may need to rethink the floor plan.&lt;/p&gt;&lt;div&gt;&lt;hr /&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;This is the first article in a series on the SOLID principles. I&amp;#8217;ll look at what each principle means, but more importantly, why it matters and what can go wrong when we ignore it.&lt;/em&gt;&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_!HPEi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a545304-1e1b-4be2-a573-7abea67516c4_1536x1024.png&quot;&gt;&lt;div class=&quot;image2-inset&quot;&gt;&lt;img src=&quot;https://substackcdn.com/image/fetch/$s_!HPEi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a545304-1e1b-4be2-a573-7abea67516c4_1536x1024.png&quot; width=&quot;662&quot; height=&quot;441.4848901098901&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 buttonBase-GK1x3M&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 buttonBase-GK1x3M&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;div&gt;&lt;hr /&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;Do you want to keep up with how Python programming and the art of making things are changing in this rapidly changing world? Then don&amp;#8217;t miss out on the exclusive content for premium members here on The Python Coding Stack.&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;p&gt;I&amp;#8217;ll be sharing more about how my programming, my daily work, and everything has changed over the recent months.&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;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;from decimal import Decimal
&amp;#8203;
class PotionShop:
    recipes = {
        &amp;#8220;invisibility&amp;#8221;: {
            &amp;#8220;ingredients&amp;#8221;: {
                &amp;#8220;moonflower&amp;#8221;: 2,
                &amp;#8220;dragon_scale&amp;#8221;: 2,
            },
            &amp;#8220;price&amp;#8221;: Decimal(&amp;#8221;12.00&amp;#8221;),
        }
    }
&amp;#8203;
    def __init__(self):
        self.stock = {
            &amp;#8220;moonflower&amp;#8221;: 10,
            &amp;#8220;dragon_scale&amp;#8221;: 2,
        }
        self.sales = []
&amp;#8203;
    def sell(self, potion_name, customer):
        potion = self.recipes[potion_name]
&amp;#8203;
        for ingredient, quantity in potion[&amp;#8221;ingredients&amp;#8221;].items():
            if self.stock[ingredient] &amp;lt; quantity:
                raise ValueError(f&amp;#8221;Not enough {ingredient}&amp;#8221;)
&amp;#8203;
        for ingredient, quantity in potion[&amp;#8221;ingredients&amp;#8221;].items():
            self.stock[ingredient] -= quantity
&amp;#8203;
        price = potion[&amp;#8221;price&amp;#8221;]
        self.sales.append((customer, potion_name, price))
&amp;#8203;
        print(f&amp;#8221;{potion_name.title()}\t&amp;#163;{price:.2f}&amp;#8221;)
        self.send_owl(
            customer,
            {&amp;#8221;potion_name&amp;#8221;: potion_name, &amp;#8220;price&amp;#8221;: price},
        )
&amp;#8203;
        return price
&amp;#8203;
    def send_owl(self, customer, message_content):
        print(f&amp;#8221;Owl dispatched to {customer}&amp;#8221;)
        return message_content&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;shop = PotionShop()
print(shop.sell(&amp;#8221;invisibility&amp;#8221;, &amp;#8220;Merlin&amp;#8221;))&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;# ...
&amp;#8203;
class PotionShop:
    # ...
&amp;#8203;
    def sell(
            self,
            potion_name,
            customer,
            *,
            moon_festival=False,
    ):
        # ...
&amp;#8203;
        label = potion_name.title()
        if moon_festival:
            label = f&amp;#8221;&amp;#127769; Moon Festival {label} &amp;#127769;&amp;#8221;
&amp;#8203;
        print(f&amp;#8221;{label}\t&amp;#163;{price:.2f}&amp;#8221;)
&amp;#8203;
        # ...
    # ...&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;shop = PotionShop()
print(shop.sell(&amp;#8221;invisibility&amp;#8221;, &amp;#8220;Merlin&amp;#8221;, moon_festival=True))&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;# ...
&amp;#8203;
class LabelMaker:
    def make(self, potion_name, moon_festival=False):
        label = potion_name.title()
&amp;#8203;
        if moon_festival:
            label = f&amp;#8221;&amp;#127769; Moon Festival {label} &amp;#127769;&amp;#8221;
&amp;#8203;
        return label
&amp;#8203;
# ...&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;labels = LabelMaker()
assert labels.make(
    &amp;#8220;invisibility&amp;#8221;,
    moon_festival=True,
) == &amp;#8220;&amp;#127769; Moon Festival Invisibility &amp;#127769;&amp;#8221;&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;# ...
&amp;#8203;
class PotionShop:
    # ...
&amp;#8203;
    def __init__(self, label_maker):
        self.label_maker = label_maker
        # ...
&amp;#8203;
    def sell(
            self,
            potion_name,
            customer,
            *,
            moon_festival=False,
    ):
        # ...
        label = self.label_maker.make(
            potion_name,
            moon_festival=moon_festival,
        )
        # ...
&amp;#8203;
    # ...&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;shop = PotionShop(LabelMaker())&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;# ...
&amp;#8203;
class OwlUnavailable(Exception):
    pass
&amp;#8203;
# ...
&amp;#8203;
class PotionShop:
    # ...
&amp;#8203;
    def __init__(self, label_maker, owl_available=True):
        self.label_maker = label_maker
        self.owl_available = owl_available
        # ...
&amp;#8203;
    # ...
&amp;#8203;
    def send_owl(self, customer, message_content):
        if not self.owl_available:
            raise OwlUnavailable(
                &amp;#8220;The owls are currently unavailable&amp;#8221;
            )
        print(f&amp;#8221;Owl dispatched to {customer}&amp;#8221;)
        return message_content&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;shop = PotionShop(LabelMaker())
&amp;#8203;
assert shop.sell(&amp;#8221;invisibility&amp;#8221;, &amp;#8220;Merlin&amp;#8221;) == Decimal(&amp;#8221;12.00&amp;#8221;)&lt;/code&gt;&lt;/pre&gt;&lt;h5&gt;&lt;strong&gt;Code Block #11&lt;/strong&gt;&lt;/h5&gt;&lt;pre&gt;&lt;code&gt;shop = PotionShop(LabelMaker(), owl_available=False)
&amp;#8203;
assert shop.sell(&amp;#8221;invisibility&amp;#8221;, &amp;#8220;Merlin&amp;#8221;) == Decimal(&amp;#8221;12.00&amp;#8221;)&lt;/code&gt;&lt;/pre&gt;&lt;h5&gt;&lt;strong&gt;Code Block #12&lt;/strong&gt;&lt;/h5&gt;&lt;pre&gt;&lt;code&gt;class PotionShop:
    # ...
&amp;#8203;
    def sell(
        self,
        potion_name,
        customer,
        *,
        moon_festival=False,
        own_cauldron=False,
    ):
        # ...
&amp;#8203;
        price = potion[&amp;#8221;price&amp;#8221;]
&amp;#8203;
        if own_cauldron:
            price = (price * Decimal(&amp;#8221;0.90&amp;#8221;)).quantize(Decimal(&amp;#8221;0.01&amp;#8221;))
&amp;#8203;
        # ...
&amp;#8203;
        return price
&amp;#8203;
    # ...&lt;/code&gt;&lt;/pre&gt;&lt;h5&gt;&lt;strong&gt;Code Block #13&lt;/strong&gt;&lt;/h5&gt;&lt;pre&gt;&lt;code&gt;shop = PotionShop(LabelMaker())
print(
    shop.sell(
        &amp;#8220;invisibility&amp;#8221;,
        &amp;#8220;Merlin&amp;#8221;,
        own_cauldron=True,
    )
)&lt;/code&gt;&lt;/pre&gt;&lt;h5&gt;&lt;strong&gt;Code Block #14&lt;/strong&gt;&lt;/h5&gt;&lt;pre&gt;&lt;code&gt;shop = PotionShop(LabelMaker(), owl_available=False)
print(
    shop.sell(
        &amp;#8220;invisibility&amp;#8221;,
        &amp;#8220;Merlin&amp;#8221;,
        own_cauldron=True,
    )
)&lt;/code&gt;&lt;/pre&gt;&lt;h5&gt;&lt;strong&gt;Code Block #15&lt;/strong&gt;&lt;/h5&gt;&lt;pre&gt;&lt;code&gt;shop = PotionShop(LabelMaker(), owl_available=False)
try:
    print(
        shop.sell(
            &amp;#8220;invisibility&amp;#8221;,
            &amp;#8220;Merlin&amp;#8221;,
            own_cauldron=True,
        )
    )
except OwlUnavailable:
    print(&amp;#8221;Catching the &amp;#8216;OwlUnavailable&amp;#8217; Exception&amp;#8221;)
    print(shop.stock)
    print(shop.sales)&lt;/code&gt;&lt;/pre&gt;&lt;h5&gt;&lt;strong&gt;Code Block #16&lt;/strong&gt;&lt;/h5&gt;&lt;pre&gt;&lt;code&gt;class RecipeBook:
    def __init__(self):
        self.recipes = {
            &amp;#8220;invisibility&amp;#8221;: {
                &amp;#8220;ingredients&amp;#8221;: {
                    &amp;#8220;moonflower&amp;#8221;: 2,
                    &amp;#8220;dragon_scale&amp;#8221;: 2,
                },
                &amp;#8220;price&amp;#8221;: Decimal(&amp;#8221;12.00&amp;#8221;),
            }
        }
&amp;#8203;
    def find(self, potion_name):
        return self.recipes[potion_name]&lt;/code&gt;&lt;/pre&gt;&lt;h5&gt;&lt;strong&gt;Code Block #17&lt;/strong&gt;&lt;/h5&gt;&lt;pre&gt;&lt;code&gt;recipe_book = RecipeBook()
assert recipe_book.find(&amp;#8221;invisibility&amp;#8221;)[&amp;#8221;price&amp;#8221;] == Decimal(&amp;#8221;12.00&amp;#8221;)&lt;/code&gt;&lt;/pre&gt;&lt;h5&gt;&lt;strong&gt;Code Block #18&lt;/strong&gt;&lt;/h5&gt;&lt;pre&gt;&lt;code&gt;class Inventory:
    def __init__(self, stock):
        self.stock = stock
&amp;#8203;
    def remove_stock(self, ingredients):
        for ingredient, quantity in ingredients.items():
            if self.stock[ingredient] &amp;lt; quantity:
                raise ValueError(f&amp;#8221;Not enough {ingredient}&amp;#8221;)
&amp;#8203;
        for ingredient, quantity in ingredients.items():
            self.stock[ingredient] -= quantity
&amp;#8203;
    def add_stock(self, ingredients):
        for ingredient, quantity in ingredients.items():
            self.stock[ingredient] = self.stock.get(ingredient, 0) + quantity&lt;/code&gt;&lt;/pre&gt;&lt;h5&gt;&lt;strong&gt;Code Block #19&lt;/strong&gt;&lt;/h5&gt;&lt;pre&gt;&lt;code&gt;inventory = Inventory({
    &amp;#8220;moonflower&amp;#8221;: 10,
    &amp;#8220;dragon_scale&amp;#8221;: 2,
})
&amp;#8203;
try:
    inventory.remove_stock({
        &amp;#8220;moonflower&amp;#8221;: 2,
        &amp;#8220;dragon_scale&amp;#8221;: 3,
    })
except ValueError as error:
    print(error)
&amp;#8203;
print(inventory.stock)&lt;/code&gt;&lt;/pre&gt;&lt;h5&gt;&lt;strong&gt;Code Block #20&lt;/strong&gt;&lt;/h5&gt;&lt;pre&gt;&lt;code&gt;inventory.remove_stock({
    &amp;#8220;moonflower&amp;#8221;: 2,
    &amp;#8220;dragon_scale&amp;#8221;: 2,
})
inventory.add_stock({&amp;#8221;moonflower&amp;#8221;: 5})
print(inventory.stock)&lt;/code&gt;&lt;/pre&gt;&lt;h5&gt;&lt;strong&gt;Code Block #21&lt;/strong&gt;&lt;/h5&gt;&lt;pre&gt;&lt;code&gt;class PriceCalculator:
    def calculate(self, recipe, own_cauldron=False):
        price = recipe[&amp;#8221;price&amp;#8221;]
&amp;#8203;
        if own_cauldron:
            price = (
                price * Decimal(&amp;#8221;0.90&amp;#8221;)
            ).quantize(Decimal(&amp;#8221;0.01&amp;#8221;))
&amp;#8203;
        return price&lt;/code&gt;&lt;/pre&gt;&lt;h5&gt;&lt;strong&gt;Code Block #22&lt;/strong&gt;&lt;/h5&gt;&lt;pre&gt;&lt;code&gt;pricing = PriceCalculator()
print(
    pricing.calculate(
        {&amp;#8221;price&amp;#8221;: Decimal(&amp;#8221;12.00&amp;#8221;)},
        own_cauldron=True,
    )
)&lt;/code&gt;&lt;/pre&gt;&lt;h5&gt;&lt;strong&gt;Code Block #23&lt;/strong&gt;&lt;/h5&gt;&lt;pre&gt;&lt;code&gt;class SalesLedger:
    def __init__(self):
        self.sales = []
&amp;#8203;
    def record(self, customer, potion_name, price):
        sale = {
            &amp;#8220;customer&amp;#8221;: customer,
            &amp;#8220;potion&amp;#8221;: potion_name,
            &amp;#8220;price&amp;#8221;: price,
            &amp;#8220;notification&amp;#8221;: &amp;#8220;pending&amp;#8221;,
        }
        self.sales.append(sale)
        return sale
&amp;#8203;
    def mark_notified(self, sale):
        sale[&amp;#8221;notification&amp;#8221;] = &amp;#8220;sent&amp;#8221;
&amp;#8203;
class Notifier:
    def send(self, customer, potion_name, price):
        print(f&amp;#8221;Owl dispatched to {customer}&amp;#8221;)&lt;/code&gt;&lt;/pre&gt;&lt;h5&gt;&lt;strong&gt;Code Block #24&lt;/strong&gt;&lt;/h5&gt;&lt;pre&gt;&lt;code&gt;class PotionShop:
    def __init__(
        self,
        recipe_book,
        inventory,
        pricing,
        labels,
        ledger,
        notifier,
    ):
        self.recipe_book = recipe_book
        self.inventory = inventory
        self.pricing = pricing
        self.labels = labels
        self.ledger = ledger
        self.notifier = notifier
&amp;#8203;
    def sell(self, potion_name, customer, *, moon_festival=False,
             own_cauldron=False):
        recipe = self.recipe_book.find(potion_name)
        self.inventory.remove_stock(recipe[&amp;#8221;ingredients&amp;#8221;])
&amp;#8203;
        price = self.pricing.calculate(
            recipe,
            own_cauldron=own_cauldron,
        )
        label = self.labels.make(
            potion_name,
            moon_festival=moon_festival,
        )
        sale = self.ledger.record(customer, potion_name, price)
&amp;#8203;
        print(f&amp;#8221;{label}\t&amp;#163;{price:.2f}&amp;#8221;)
        self.notifier.send(customer, potion_name, price)
        self.ledger.mark_notified(sale)
&amp;#8203;
        return price&lt;/code&gt;&lt;/pre&gt;&lt;h5&gt;&lt;strong&gt;Code Block #25&lt;/strong&gt;&lt;/h5&gt;&lt;pre&gt;&lt;code&gt;ledger = SalesLedger()
shop = PotionShop(
    RecipeBook(),
    Inventory({&amp;#8221;moonflower&amp;#8221;: 10, &amp;#8220;dragon_scale&amp;#8221;: 2}),
    PriceCalculator(),
    LabelMaker(),
    ledger,
    Notifier(),
)
&amp;#8203;
print(
    shop.sell(
        &amp;#8220;invisibility&amp;#8221;,
        &amp;#8220;Merlin&amp;#8221;,
        moon_festival=True,
        own_cauldron=True,
    )
)
&amp;#8203;
assert ledger.sales[0][&amp;#8221;notification&amp;#8221;] == &amp;#8220;sent&amp;#8221;
print(ledger.sales[0])&lt;/code&gt;&lt;/pre&gt;&lt;h5&gt;&lt;strong&gt;Code Block #26&lt;/strong&gt;&lt;/h5&gt;&lt;pre&gt;&lt;code&gt;class FailingNotifier:
    def send(self, customer, potion_name, price):
        raise OwlUnavailable(&amp;#8221;The owls are currently unavailable&amp;#8221;)
&amp;#8203;
ledger = SalesLedger()
shop = PotionShop(
    RecipeBook(),
    Inventory({&amp;#8221;moonflower&amp;#8221;: 10, &amp;#8220;dragon_scale&amp;#8221;: 2}),
    PriceCalculator(),
    LabelMaker(),
    ledger,
    FailingNotifier(),
)
&amp;#8203;
try:
    shop.sell(&amp;#8221;invisibility&amp;#8221;, &amp;#8220;Merlin&amp;#8221;)
except OwlUnavailable:
    pass
&amp;#8203;
print(ledger.sales[0])
assert ledger.sales[0][&amp;#8221;notification&amp;#8221;] == &amp;#8220;pending&amp;#8221;&lt;/code&gt;&lt;/pre&gt;&lt;p&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;</description>
	<pubDate>Mon, 28 Sep 2026 17:17:11 +0000</pubDate>
</item>
<item>
	<title>Glyph Lefkowitz: What Would A Serious AI Product Look Like?</title>
	<guid>https://blog.glyph.im/2026/09/serious-ai-product.html</guid>
	<link>https://blog.glyph.im/2026/09/serious-ai-product.html</link>
	<description>&lt;p&gt;One of the issues that I have with the current generation of “AI” products is
that they do not appear to take their own premises seriously.  I look at a
plethora of obsequious chatbots claiming to be serious tools for problem
solving, and I think, this is not what a problem-solving tool would look like.&lt;/p&gt;
&lt;p&gt;Even before we get to the tremendous ethical problems with the frontier labs,
it is this impression of their composition &lt;em&gt;as a product&lt;/em&gt; that makes me feel,
constantly, whenever I am interacting with them, that they are less a software
product than that they are a grift, a scam designed to make me feel like I am
interacting with a product that has capabilities that it simply does not, to
try to lull me into a false sense of security that I can trust it.&lt;/p&gt;
&lt;p&gt;The frontier labs are of course the worst offenders, but every criticism here
applies just as much to Ollama, which (if anything, due to the obviously poorer
quality of the available models themselves) needs these features &lt;em&gt;even more&lt;/em&gt;
than the frontier labs do.&lt;/p&gt;
&lt;p&gt;Here, I will set down a few features that might convince me that an LLM-based
product, particularly one focused on research or software development, was
actually serious about helping me do useful things with it.&lt;/p&gt;
&lt;h2 id=&quot;make-checking-for-mistakes-a-first-class-feature&quot;&gt;Make “Checking For Mistakes” A First-Class Feature&lt;/h2&gt;
&lt;p&gt;This is the biggest issue, and the major reason that I was inspired to write
this post.&lt;/p&gt;
&lt;p&gt;It is a truth universally acknowledged, that AIs cannot reliably provide
information.&lt;/p&gt;
&lt;p&gt;I could cite a ton of news articles and studies about this fact, but there is
no need.  Every single chatbot admits this, up front, in a fine-print
disclaimer as a core part of their user interface.  Gemini says “AI can make
mistakes, so double-check responses”, Claude says “Claude is AI and can make
mistakes. Please double-check responses.&lt;sup id=&quot;fnref:1:serious-ai-product-2026-9&quot;&gt;&lt;a class=&quot;footnote-ref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fn:1:serious-ai-product-2026-9&quot; id=&quot;fnref:1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;” ChatGPT says “ChatGPT can make
mistakes. Check important info.”.&lt;/p&gt;
&lt;p&gt;Every time I see that last one, I wonder how I’m supposed to know what “info”
is supposed to be “important”.&lt;/p&gt;
&lt;p&gt;All of these warnings are all small, gray text, painfully obviously included as
legalese to push responsibility back onto the user rather than to help with
anything.  This is a &lt;em&gt;core limitation&lt;/em&gt; of all these products.  Checking their
output is a part of the workflow for using them that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;you &lt;em&gt;absolutely cannot skip or skimp on&lt;/em&gt; without creating risks to yourself
   and whoever you are conveying its output to, and,&lt;/li&gt;
&lt;li&gt;it is &lt;em&gt;very easy to skip or skimp on&lt;/em&gt; and you are encouraged at every turn
   to do so, because “just trust the output” is one of the quickest ways to
   save time.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A chatbot product that took this weakness seriously, as an actual consideration
for using it, would put a checkbox next to &lt;em&gt;every claim&lt;/em&gt; in its output.  It
would be a 2-column worksheet, where you’ve got the LLM output in the first
column, and next to it, human notes in the second column, explaining what work
went into checking this claim, and a &lt;em&gt;big&lt;/em&gt; checkbox that you would only check
off after you believe you’d checked its claims thoroughly enough.&lt;/p&gt;
&lt;p&gt;Coding assistants would need to have some version of this as well.  Right now,
this is pushed off into code review, which means it is a dark pattern which
subtly encourages the “author”&lt;sup id=&quot;fnref:2:serious-ai-product-2026-9&quot;&gt;&lt;a class=&quot;footnote-ref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fn:2:serious-ai-product-2026-9&quot; id=&quot;fnref:2&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; to offload this work to their code reviewer
without ever looking.  Once again, “it’s probably fine, I don’t need to check”
is the quickest way to save time and churn out those PRs faster.&lt;/p&gt;
&lt;p&gt;It might even be useful for coding harnesses to have some affordance for
checking code before it even runs tests.  &lt;a href=&quot;https://claude.com/blog/agentic-coding-is-straining-ci-heres-how-we-scaled-test-impact-analysis-at-anthropic&quot;&gt;As the vendors themselves have
admitted&lt;/a&gt;,
it’s not just expensive to burn tokens on your “AI”, you also end up burning
far more &lt;em&gt;compute&lt;/em&gt; on the AI.  Being able to check your diffs before sending
them over to uselessly exhaust your testing compute cluster would be useful.&lt;/p&gt;
&lt;p&gt;If your product tells me that it makes mistakes and I must be the one to check
for the mistakes, but then gives me &lt;em&gt;zero tools&lt;/em&gt; to check for mistakes, I
cannot take it seriously.&lt;/p&gt;
&lt;h3 id=&quot;more-citations-to-check-and-more-details&quot;&gt;More Citations to Check, And More Details&lt;/h3&gt;
&lt;p&gt;Most chatbots prefer to give an answer, rather than a citation.  In my own
personal use, I find that when asked to provide a list of citations with
clearly marked sources for each one, they will appear to “get bored” halfway
through the list and simply stop including citations at some point.&lt;/p&gt;
&lt;p&gt;When the bots include citations at all, present them as inline annotations that
say nothing but the domain name of the search result, in a font so small that
it’s barely legible, and an equally indecipherable icon that is fewer than 16
pixels on a side.&lt;/p&gt;
&lt;p&gt;This is backwards.&lt;/p&gt;
&lt;p&gt;Now, I am aware that these citations do come from somewhere, and in an attempt
to reduce hallucinations, all of the major providers support some form of
“grounding”&lt;sup id=&quot;fnref:3:serious-ai-product-2026-9&quot;&gt;&lt;a class=&quot;footnote-ref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fn:3:serious-ai-product-2026-9&quot; id=&quot;fnref:3&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;, and that those little barely-readable citation links are
referencing actual structures in the
&lt;a href=&quot;https://en.wikipedia.org/wiki/Retrieval-augmented_generation&quot;&gt;RAG&lt;/a&gt; pipeline
and not just potentially-hallucinated tokens, but I’m not talking about the
underlying machinery in the model, I’m talking about the &lt;em&gt;presentation to the
user&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Plus, regardless of whether a snippet of text came from a RAG query, we know
that LLMs can &lt;a href=&quot;https://www.computerworld.com/article/4059383/openai-admits-ai-hallucinations-are-mathematically-inevitable-not-just-engineering-flaws.html&quot;&gt;never provide an authoritative
result&lt;/a&gt;;
it’s a fundamental limitation of the technology.  They can still garble the
results of RAG as much as they can misrepresent any other training data.  This
means that it must never present its results &lt;em&gt;as&lt;/em&gt; authoritative.&lt;/p&gt;
&lt;p&gt;If you ask an AI to do research queries, every result should be presented as a
&lt;em&gt;list of citations&lt;/em&gt;.  Moreover, the presentation should display each citation as a
large object of in its own right, with clearly identified metadata, including
not just the site where it was found but its publication date and, if possible,
the name of the author.  The literal, unmodified quotation (not from RAG, not a
summary: a quotation extracted with a regular program and not an LLM) should be
front-and-center, larger than any AI-generated text.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;If&lt;/em&gt; the AI product wants to editorialize or summarize (which should not always
be necessary!), the AI-generated text should be presented as small text
underneath the citation that has been found, de-emphasized as much as the
disclaimer is right now, at the very least until the user has verified that the
summary is accurate.  Perhaps, for a research project, a “did you read the
citation” checkbox might even be helpful.&lt;/p&gt;
&lt;p&gt;If your product openly tells me that it will scramble, misrepresent, or omit
its citations in its summaries, and I must read the original human-authored
citations to be sure, but then gives me no tools to track my reading of those
citations or even any way to &lt;em&gt;find&lt;/em&gt; them, I cannot take it seriously.&lt;/p&gt;
&lt;h2 id=&quot;no-first-person-output-no-apologies&quot;&gt;No First-Person Output, No Apologies&lt;/h2&gt;
&lt;p&gt;There is no reason for a software development or research tool to use
first-person language to describe itself.  They should not do so.  In fact
&lt;a href=&quot;https://www.theatlantic.com/ideas/2026/09/meta-settlement-social-media-addiction-youth/688567/&quot;&gt;they should not be &lt;em&gt;allowed&lt;/em&gt; to do
so&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There is also no reason that they should ever &lt;em&gt;apologize&lt;/em&gt;.  &lt;a href=&quot;https://link.springer.com/article/10.1007/s43681-025-00800-x&quot;&gt;It is a waste of
everyone’s time&lt;/a&gt;;
it’s a waste for the chatbot to generate the apology, it’s a waste for the user
to read the apology, and it’s a waste for the user to respond to the apology.
Yet they unfailingly do this upon every correction.&lt;/p&gt;
&lt;p&gt;The vendors of these tools &lt;a href=&quot;https://openai.com/index/strengthening-chatgpt-responses-in-sensitive-conversations/&quot;&gt;know that they are routinely causing mental-health
crises&lt;/a&gt;.
In response, they have added non-functional “guard rails” that can still, in
2026, &lt;a href=&quot;https://www.techzine.eu/blogs/security/141882/chatgpt-easily-bypasses-its-own-guardrails-all-llms-are-inherently-unsafe/&quot;&gt;easily be
bypassed&lt;/a&gt;.&lt;sup id=&quot;fnref:4:serious-ai-product-2026-9&quot;&gt;&lt;a class=&quot;footnote-ref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fn:4:serious-ai-product-2026-9&quot; id=&quot;fnref:4&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;A product seriously interested in helping with productivity would correct this
&lt;em&gt;glaringly&lt;/em&gt; obvious flaw, focus on the task at hand, and stop emitting useless
verbiage.&lt;/p&gt;
&lt;p&gt;In the previous two sections, I tried to focus on ways in which the harness
would be constructed differently even if the LLM technology is fundamentally
impossible to improve; in this case, I have to assume that the labs have &lt;em&gt;some&lt;/em&gt;
control over the model itself.  But unless they are truly incapable of
influencing their output (and all their “benchmarks” and “capabilities” seem to
indicate that they can control it very tightly) they ought to be building
models that are much less verbose.&lt;/p&gt;
&lt;h2 id=&quot;more-non-natural-language-user-interfaces&quot;&gt;More Non-Natural-Language User Interfaces&lt;/h2&gt;
&lt;p&gt;Although natural language could &lt;em&gt;hypothetically&lt;/em&gt; be a powerful interface for
interacting with a computer system, the practical upshot of LLM natural
language interfaces is that these interfaces are imprecise and repetitive, full
of &lt;a href=&quot;https://arxiv.org/abs/2602.11988&quot;&gt;superstitions masquerading as “best
practices”&lt;/a&gt;.  The inputs are a mess and the
resulting outputs are a mess.&lt;/p&gt;
&lt;p&gt;The general way of addressing this unstructured mess is to allow the chatbot to
&lt;em&gt;directly take action&lt;/em&gt; in response to the user’s input; in other words to
supply it with “tools” via an MCP server.  But again, this is backwards.  If we
cannot even express our intent clearly in the first place, why are we trusting
this system to take potentially destructive and harmful actions on our behalf?&lt;/p&gt;
&lt;p&gt;Instead, I would expect a product that was seriously invested in helping me
accomplish specific tasks, to have user interfaces specific to those tasks.  Is
it supposed to be able to be a security scanner that can discover OWASP top 10
bugs in a codebase? Have a button for that.  Build that functionality into your
harness, train it directly into the model, use smaller models that can satisfy
that functionality more effectively than throwing it at the planet-sized brain
of Fable or whatever.&lt;/p&gt;
&lt;p&gt;I’m aware that there are small software startups that do &lt;em&gt;something&lt;/em&gt; like this,
but they are bolted on to the side of the main model providers’ APIs, not
integrated into the core of the product and not using their own models and AI
systems to achieve consistent and repeatable results.&lt;/p&gt;
&lt;h2 id=&quot;strong-data-provenance-indicators&quot;&gt;Strong Data Provenance Indicators&lt;/h2&gt;
&lt;p&gt;Chatbots produce data tables pulled from websites, from APIs, from MCP tools or
from summarizing and scrambling the user’s input.  In order to provide the
illusion of a seamless interface, this data is presented in-line regardless of
where it comes from.  But some of these outputs are produced mechanically via
regular old API calls, for example, from the result of calling a tool or
querying a website, but presented uniformly.&lt;/p&gt;
&lt;p&gt;But there is a huge difference between an authoritative data source being
inlined as part of a chatbot conversation, being treated as &lt;em&gt;input&lt;/em&gt; by the
chatbot, and some ad-hoc hallucinated data being treated as &lt;em&gt;output&lt;/em&gt; of the
chatbot.&lt;/p&gt;
&lt;p&gt;If a product is trying to help me make accurate, empirically-grounded,
data-driven decisions, the source of the data is critical.&lt;/p&gt;
&lt;p&gt;Integrated into the “check for mistakes” and “verify citations” workflow I
described above, there’s a necessary “verify data programmatically” pass as
well; to have tools that will treat portions of the output as a &lt;em&gt;regular
spreadsheet&lt;/em&gt;, allowing regular-old computer arithmetic to verify things and
&lt;em&gt;showing where such arithmetic was used, and how&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;better-user-control-of-reproducibility&quot;&gt;Better User Control of Reproducibility&lt;/h2&gt;
&lt;p&gt;Anyone familiar with the technical specifics of LLMs will know that they have a
variable called
“&lt;a href=&quot;https://www.ibm.com/think/topics/llm-temperature&quot;&gt;temperature&lt;/a&gt;” which
controls the degree of randomness that the LLM uses to produce its outputs.
But most users don’t know this, because it isn’t exposed as part of the user
interface by default.&lt;/p&gt;
&lt;p&gt;This leads to a subjective impression that you asked ChatGPT, and you got
ChatGPT’s authoritative answer.&lt;/p&gt;
&lt;p&gt;You can’t just set the temperature to zero and still get useful results - I am
aware that it does more than just scramble the output at random, and there are
perhaps good reasons that simply exposing &lt;em&gt;just&lt;/em&gt; a temperature setting would
not be that useful to users.  But if we followed some more of my earlier
recommendations for making more structured UI elements to solve specific
problems rather than having long back-and-forth chats where each refinement
depends on the previous response, perhaps those elements could also re-play the
process so that users can see how reliable the bot is at a particular task and
develop a sense of how the stochastic nature of the process actually affects
it.&lt;/p&gt;
&lt;p&gt;Similarly, if a user is trying to solve the same problem repeatedly with a
chatbot, and the chatbot &lt;em&gt;product&lt;/em&gt; has numerous computational tools that don’t
really have anything to do with the LLM, such as deterministic data-processing
tools, then having a way to freeze the non-deterministic parts of the
transcript but re-populate a particular data frame with updated information and
fork / continue the conversation from there would be a way to avoid introducing
pointless additional randomness when you already know what tool you’re trying
to use.&lt;/p&gt;
&lt;p&gt;The fact that every conversation is presented as this flat chat prompt that
doesn’t let me interact with any of the widgets that were previously produced
except through more chatting, really makes me feel like the whole product is
just doing predatory social-media style “increase time on site” optimization,
just trying to lure me into further repetitive and unreliable chats, rather
than letting me get in, solve my problem, and get out.&lt;/p&gt;
&lt;h2 id=&quot;context-visibility&quot;&gt;Context Visibility&lt;/h2&gt;
&lt;p&gt;Managing the LLM context is &lt;em&gt;the&lt;/em&gt; ongoing challenge facing organizations that
are trying to use “agentic” workflows.  Filling up the context with too much
information &lt;a href=&quot;https://www.trychroma.com/research/context-rot&quot;&gt;causes well-known
problems&lt;/a&gt;. In response,
advanced LLM users attempting to solve larger problems must break up very long
prompts into “skills”, give access to lengthy information via “tools”, and
delegating sub-problems to “sub-agents” rather than simply extending a single
prompt indefinitely.&lt;/p&gt;
&lt;p&gt;All of these strategies have flaws, because even on the largest models,
compared to the breadth and depth of knowledge-work problems, LLM contexts are
quite small.&lt;/p&gt;
&lt;p&gt;And yet, none of these products will &lt;em&gt;show the context to the user&lt;/em&gt; by default.
There are third-party addons that can show you a &lt;a href=&quot;https://www.ai-toolbox.co/ai-toolbox-chatgpt-features/chatgpt-context-window-meter-2026&quot;&gt;simple progress
bar&lt;/a&gt;
but for addressing the premier engineering difficulty with this technology,
that is below the bare minimum.&lt;/p&gt;
&lt;p&gt;This lack of visibility means that almost all of the tools for extending the
context are flying blind.  Rather than responding meaningfully to a full
context, everyone just kind of guesses how much state they need by guessing and
trying over and over again with progressively more elaborate skill and sub-agent
layouts.  Even managing context compaction ends up being an &lt;a href=&quot;https://platform.claude.com/cookbook/tool-use-automatic-context-compaction&quot;&gt;advanced
API-driven
workflow&lt;/a&gt;&lt;sup id=&quot;fnref:5:serious-ai-product-2026-9&quot;&gt;&lt;a class=&quot;footnote-ref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fn:5:serious-ai-product-2026-9&quot; id=&quot;fnref:5&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;A serious product that was trying to help the user understand would not only
show “available context” but explain the impact of context compactions, make it
easier to see harness-generated prompts, and so on.  This would be a
first-class feature, combined with the aforementioned reproducibility / replay
tools, would allow users to do real experiments to develop an understanding
about how to make good use of the context window.&lt;/p&gt;
&lt;h2 id=&quot;a-sandbox-that-actually-works&quot;&gt;A Sandbox That Actually Works&lt;/h2&gt;
&lt;p&gt;I’ve been focused on the chatbot interface here because it is the most
&lt;em&gt;immediately&lt;/em&gt; egregious upon looking at the UI.  But the “agentic loop” tools
used for coding are equally dangerous, if not more so.  Coding tools
&lt;a href=&quot;https://aishippingblog.com/p/how-i-dropped-our-production-database&quot;&gt;keep&lt;/a&gt;
&lt;a href=&quot;https://cybersecuritynews.com/claude-code-agent-file-deletion/&quot;&gt;destroying&lt;/a&gt;
&lt;a href=&quot;https://x.com/lifeofjer/article/2048103471019434248&quot;&gt;everyone’s&lt;/a&gt;
&lt;a href=&quot;https://quasa.io/media/when-cursor-wiped-a-user-s-pc-a-cautionary-tale-of-ai-overreach&quot;&gt;data&lt;/a&gt;,
over the course of &lt;em&gt;years&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;These catastrophic incidents that become front-page news are relatively rare
compared to the amount of coding-agent use out there.  But they also aren’t the
only kind of sandbox violation.  Coding models will so routinely edit test code
instead of the system under test that there are “pro tips” articles all over
the web giving you the &lt;a href=&quot;https://claudecodetips.com/en/guide/pitfalls/39&quot;&gt;flawed
advice&lt;/a&gt; to simply &lt;em&gt;ask&lt;/em&gt; the
agent not to cheat.  News write-ups of the catastrophic incidents themselves
will also offer glib and wrong advice, like “use a docker container”.  That
might prevent it from literally deleting your operating system, but it won’t
prevent it from destroying all the local work you have in your codebase (it
needs access to a checkout, after all!)&lt;/p&gt;
&lt;p&gt;There is a flurry of activity in the infosec space where people are rushing to
plug the gaps left by these coding harnesses.  Everyone’s got their own version
of an &lt;a href=&quot;https://www.truefoundry.com/blog/mcp-tool-approvals-explained&quot;&gt;MCP&lt;/a&gt;
&lt;a href=&quot;https://microsoft.github.io/agent-governance-toolkit/tutorials/07-mcp-security-gateway/&quot;&gt;approval&lt;/a&gt;
&lt;a href=&quot;https://github.com/thehairlesshorseman/mcp-approval-gateway&quot;&gt;gateway&lt;/a&gt; where
you can optionally place a proxy between your agent and your production
infrastructure.&lt;/p&gt;
&lt;p&gt;In the best case, though, all these mitigations and proxies and prompts simply
turn the user into an &lt;a href=&quot;https://en.wikipedia.org/wiki/Drinking_bird#cite_ref-26&quot;&gt;auto-approval
automaton&lt;/a&gt;, hitting Y,
Y, Y, Y over and over again, until you finally are driven mad and hit “yes to
all”, turn on full-auto mode and submit yourself to the void.  With nothing
between your personal vigilance and disaster, there are no workflows left
beyond decrementing your own vigilance until there’s nothing left and then
hoping the disaster never arrives.&lt;/p&gt;
&lt;p&gt;The fact that &lt;em&gt;some&lt;/em&gt; mitigations exist that can be deployed by extra-cautious
users does not change the fact that “agentic coding” is an unsafe-by-default
technology deployed without concern or guidance.  Every frontier lab has tied a
spring-loaded shotgun to a dog; the fact that dog owners can publish thoughtful
blog posts explaining how you can teach your dog the basics of gun safety or
how you can have your dogs play in a bullet-proof room does not mitigate the
fact that the product should not have been allowed in the first place, nor
should it continue to exist without VERY strong security controls.&lt;/p&gt;
&lt;p&gt;I might believe that a frontier lab were seriously interested in providing
developers with a useful tool if they shipped something that had safety built-in.&lt;/p&gt;
&lt;p&gt;That means tools &lt;em&gt;in the harness&lt;/em&gt;, detached from any LLM, independent of the
prompt, that could:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;sandbox all filesystem operations and strictly limit ANY deletions outside of
  specified scopes, regardless of operating system,&lt;/li&gt;
&lt;li&gt;enforce snapshotting of the entire repo on every operation for easy rollbacks
  and minimal lost work,&lt;/li&gt;
&lt;li&gt;remove &lt;a href=&quot;https://cybernews.com/security/claude-code-auto-mode-malware-vulnerability/&quot;&gt;the disaster of “auto
  mode”&lt;/a&gt;
  (not to mention nonsense like &lt;code&gt;--dangerously-skip-permissions&lt;/code&gt;) entirely, and&lt;/li&gt;
&lt;li&gt;carefully consider a structure for presenting plans to the user where, rather
  than provoking immediate alert fatigue by asking for checks on every action,
  make structured plans which can be submitted to the user as a group of
  actions and reviewed and approved as a batch.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the same way that I suggested above that research-based tasks should have a
way of re-issuing prompts to determine how reproducible a result is, or whether
other sources might be found, agent-based tasks should have a way of being
executed against mock services for popular APIs, so that the verification can
match both on the front-end (review the plan for making the API calls before
they’re executed) and the back end (review the API calls that were issued to
the mock service and verify that they matched).&lt;/p&gt;
&lt;p&gt;Instead, the frontier labs provide us products that are disasters out of the
box, give us “best practices” to build massive and elaborate, as well as
incomplete and error-prone, security perimeters of our own design.  Then they
blame “operator error” when it inevitably goes wrong.  I cannot believe that
these design choices are intended to help us be productive.&lt;/p&gt;
&lt;h2 id=&quot;bonus-human-processes&quot;&gt;Bonus: Human Processes&lt;/h2&gt;
&lt;p&gt;Organizations &lt;em&gt;deploying&lt;/em&gt; AI also frequently come across as unserious, for
similar reasons.  In 2023, naive exuberance could perhaps be forgiven.  But
today, as we near the close of 2026, there are several well-known problems,
that have been extremely well-covered in the press.  None of these things
should be surprising, but most orgs deploying these tools are still just
letting them rip and hoping it all works out.&lt;/p&gt;
&lt;p&gt;Organizations deploying these tools would need at least three kinds of major
modifications to their internal processes, if they wanted to be serious about
using them safely:&lt;/p&gt;
&lt;h3 id=&quot;1-shift-rotations-to-prevent-vigilance-decrement&quot;&gt;1. Shift Rotations to Prevent Vigilance Decrement&lt;/h3&gt;
&lt;p&gt;There have been several high-profile incidents where software developers’
gradual acquiescence to accepting LLM output have lead to serious economic
consequences for the companies deploying them, perhaps best typified by
Amazon’s “&lt;a href=&quot;https://www.businessinsider.com/amazon-tightens-code-controls-after-outages-including-one-ai-2026-3&quot;&gt;millions of lost
orders&lt;/a&gt;”
due to a gradual decay of their engineering processes from LLM use.&lt;/p&gt;
&lt;p&gt;These outages, and other AI-related failures, are due to the difficulty of
maintaining focus on the same problems.  In other words, as I described above,
&lt;a href=&quot;https://www.csoonline.com/article/4225613/aviation-solved-the-vigilance-problem-ai-just-gave-security-a-worse-one.html&quot;&gt;vigilance
decrement&lt;/a&gt;
is a constant problem, because AI outputs are &lt;em&gt;most often&lt;/em&gt; correct, but
continue to be incorrect in surprising and non-intuitive ways.  As I have
&lt;a href=&quot;https://blog.glyph.im/2026/03/what-is-code-review-for.html&quot;&gt;previously written&lt;/a&gt;, you cannot trust
yourself to catch every bug with code review, and LLM output.&lt;/p&gt;
&lt;p&gt;Aviation, for example, has &lt;em&gt;very strict rules&lt;/em&gt; around &lt;a href=&quot;https://www.ecfr.gov/current/title-14/chapter-I/subchapter-G/part-117&quot;&gt;rest
requirements&lt;/a&gt;.
There is also a specific rule that &lt;a href=&quot;https://www.ecfr.gov/current/title-14/chapter-I/subchapter-G/part-135/subpart-B/section-135.99&quot;&gt;“No certificate holder may operate an aircraft
without a second in command if that aircraft has a passenger seating
configuration, excluding any pilot seat, of ten seats or
more.”&lt;/a&gt;.
Other safety-critical professions have similar rules.&lt;/p&gt;
&lt;p&gt;And yet, even in the age of the supposed “AI revolution”, most software teams
are still assigning every engineer a full feature load, not planning for any
rest, and telling people to review code whenever they happen to have some “free
time”.&lt;/p&gt;
&lt;p&gt;Maintenance of vigilance has to be your top priority.  Regular, scheduled,
&lt;em&gt;inviolable&lt;/em&gt; rest periods where people do work without AI assistance, and are
not exposed to any AI output for review or otherwise, would be crucial in order
to stay mentally sharp enough.&lt;/p&gt;
&lt;p&gt;The tools themselves should have this sort of thing built in.  The
mistake-review process described above should have a periodic spot-check mode
where a second reviewer periodically reviews a chatbot log, doing their own
independent verification of claims, to see if they spot the same errors.  This
could provide a feedback loop to determine how much rest is necessary to
maintain continuous attention and actually spot hallucinations.&lt;/p&gt;
&lt;h3 id=&quot;2-skill-practice-to-prevent-skill-loss&quot;&gt;2. Skill Practice To Prevent Skill Loss&lt;/h3&gt;
&lt;p&gt;It is also well-known that AI use leads to AI reliance, and AI reliance leads
to &lt;a href=&quot;https://www.psychologytoday.com/us/blog/the-algorithmic-mind/202603/adults-lose-skills-to-ai-children-never-build-them&quot;&gt;skill
loss&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I like to use the analogy to dockworkers at a seaport&lt;sup id=&quot;fnref:6:serious-ai-product-2026-9&quot;&gt;&lt;a class=&quot;footnote-ref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fn:6:serious-ai-product-2026-9&quot; id=&quot;fnref:6&quot;&gt;6&lt;/a&gt;&lt;/sup&gt; adopting automation.&lt;/p&gt;
&lt;p&gt;If you employ dockworkers to load and unload ships all day long, they are going
to be getting tons of exercise.  They will be able to lift heavy objects on
demand, whenever.  They might have plenty of health problems and injuries from
this type of work, but “lack of exercise” will not be a problem.&lt;/p&gt;
&lt;p&gt;With the development of standardized container ships and mechanized cranes, you
are going to be changing their job description substantially: now they mostly
spend all day sitting in a small cubicle moving a control lever back and forth,
not lifting heavy stuff.  They will get worse at lifting heavy objects.&lt;/p&gt;
&lt;p&gt;In this analogy however, the cranes are not all that reliable.  We know they
break, and they drop their payloads sometimes, and the stuff needs to be
manually moved.  But this only happens a few times a week, at most.  If you
need whoever is driving the crane to be able to jump out at any moment and
still move stuff around manually, then you need to make an affordance for that.
You need to give them &lt;em&gt;time&lt;/em&gt; to go to the gym and do some lifting for practice,
or every crane failure is going to be a major emergency.&lt;/p&gt;
&lt;p&gt;An organization doing an AI transformation would also need a massive increase
to learning &amp;amp; development budget, both in terms of resources and in terms of
schedule.  If your people are going to lose skills because they’ve lost regular
practice in the incidental course of doing their duties, then they are going to
need &lt;em&gt;deliberate&lt;/em&gt;, intentional, non-incidental practice of those skills to keep
them sharp.&lt;/p&gt;
&lt;p&gt;But rather than trying to accommodate new workflows and give time for people to
adjust, most AI mandates are simply dropped on workers like a ton of bricks,
with no time to adapt and no affordance for maintaining their skills.  Operate
the crane and stay fit and healthy and ready to switch back to manual lifting
at any time and then get back in the crane cockpit right afterwards.  Don’t
mess up.&lt;/p&gt;
&lt;p&gt;Then an accident happens and everyone is surprised, as if this process weren’t
practically &lt;em&gt;designed&lt;/em&gt; to produce a terrible result.&lt;/p&gt;
&lt;h3 id=&quot;3-mental-health-resources-to-deal-with-mental-health-risks&quot;&gt;3. Mental Health Resources to Deal with Mental Health Risks&lt;/h3&gt;
&lt;p&gt;AI psychosis often begins with &lt;a href=&quot;https://pulitzercenter.org/stories/ai-psychosis-mental-health-crisis-21st-century&quot;&gt;practical
problem-solving&lt;/a&gt;,
and beyond that, it can start &lt;a href=&quot;https://www.forbes.com/sites/bryanrobinson/2025/11/02/ai-psychosis-at-work-mental-health-experts-express-concerns/&quot;&gt;specifically at
work&lt;/a&gt;.
Not to mention the more pedestrian condition of &lt;a href=&quot;https://www.cnn.com/2026/03/13/business/ai-brain-fry-nightcap&quot;&gt;“AI brain
fry”&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you are mandating your employees to use a hazardous tool that may seriously
and &lt;em&gt;directly&lt;/em&gt; damage their mental health, you need trainings and resources.
You need in-house therapists and you need to be making sure to check in with
people actively to make sure that this is not happening.&lt;/p&gt;
&lt;p&gt;Again, the tool itself ought to have some way of dealing with this.  An
&lt;a href=&quot;https://lifehacker.com/tech/why-chatgpt-is-now-reminding-you-to-take-break&quot;&gt;occasional “take a
break”&lt;/a&gt;
popup is easily dismissed; they need a user-visible AI &lt;a href=&quot;https://en.wikipedia.org/wiki/Film_badge_dosimeter&quot;&gt;personal
dosimeter&lt;/a&gt; so you can see
your &lt;em&gt;cumulative&lt;/em&gt; usage over time.&lt;/p&gt;
&lt;p&gt;I don’t even know if “usage over time” is a sufficient metric to gauge risk.
Maybe if your work chatbot start to talk about
&lt;a href=&quot;https://medium.com/@ijin2504/from-tool-to-self-the-7-resonance-phases-of-gpt-5fa6954aeb49&quot;&gt;resonance&lt;/a&gt;
too much, unless you literally work as an acoustic engineer, that should be
flagged for someone.&lt;/p&gt;
&lt;p&gt;We are, again, years into dealing with these tools, and we know these risks
exist.  Yet no serious mitigations are provided.  Not even any way of measuring
the risk exposure.&lt;/p&gt;
&lt;h3 id=&quot;and-more&quot;&gt;And More&lt;/h3&gt;
&lt;p&gt;There are also many other risks associated with the technology.  There are
&lt;a href=&quot;https://arstechnica.com/tech-policy/2026/07/openai-faked-inability-to-search-training-data-hid-billions-of-logs-nyt-says/&quot;&gt;intellectual property
risks&lt;/a&gt;
with the foundation models, due to recklessness with their training data.
There are &lt;a href=&quot;https://www.cnbc.com/2026/09/24/oracle-data-center-force-majeure.html&quot;&gt;existential financial
risks&lt;/a&gt;
associated with the infrastructure build-out.  The extent to which most “open”
models are &lt;a href=&quot;https://www.cisa.gov/news-events/cybersecurity-advisories/aa26-251a&quot;&gt;simply
derivatives&lt;/a&gt;
of frontier models is an open question.&lt;/p&gt;
&lt;h2 id=&quot;what-i-think&quot;&gt;What I Think&lt;/h2&gt;
&lt;p&gt;If any &lt;em&gt;one&lt;/em&gt; of these things were regularly overlooked by AI vendors or users,
that would be a totally normal product oversight.  Room for improvement for the
next version, but nothing catastrophic.&lt;/p&gt;
&lt;p&gt;Shipping without &lt;em&gt;any&lt;/em&gt; of them doesn’t seem like lean product management, it
seems like a careless attitude towards risk and a product design philosophy
oriented entirely towards short-term demos, with no regard for how to realize
actual productivity gains.&lt;/p&gt;
&lt;p&gt;Furthermore, being available for &lt;em&gt;years&lt;/em&gt; without anything like these features,
despite hundreds of incidents demonstrating the risks, with hundreds of
billions of dollars of funding, makes it seem to me like if they &lt;em&gt;were&lt;/em&gt; to add
all the features that would make their product actually safe and hypothetically
useful, these features would reveal that it is actually not an improvement to
productivity.&lt;/p&gt;
&lt;p&gt;In the &lt;a href=&quot;https://blog.glyph.im/2025/08/futzing-fraction.html&quot;&gt;year&lt;/a&gt; since I first wrote about
measuring the cost/benefit ratio of AI, I have heard from numerous people who
have shown this to management to try to illustrate why their AI initiatives —
like &lt;a href=&quot;https://ludic.mataroa.blog/blog/ai-mania-is-eviscerating-global-decision-making/&quot;&gt;almost all AI
initiatives&lt;/a&gt;
— were either failing or burning out their engineers.&lt;/p&gt;
&lt;p&gt;I’ve also heard from lots of people that have told me that it’s obviously
useful and they don’t need to measure so carefully, because they are getting
lots of work done that they couldn’t have otherwise.&lt;sup id=&quot;fnref:7:serious-ai-product-2026-9&quot;&gt;&lt;a class=&quot;footnote-ref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fn:7:serious-ai-product-2026-9&quot; id=&quot;fnref:7&quot;&gt;7&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;I have yet to hear from a &lt;em&gt;single&lt;/em&gt; person who has said “yeah, we measured
according to your methodology&lt;sup id=&quot;fnref:8:serious-ai-product-2026-9&quot;&gt;&lt;a class=&quot;footnote-ref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fn:8:serious-ai-product-2026-9&quot; id=&quot;fnref:8&quot;&gt;8&lt;/a&gt;&lt;/sup&gt;, and it turns out that our AI work is going
great and that our ratio is 0.75”.&lt;/p&gt;
&lt;p&gt;Obviously, I cannot say for sure why this is; absence of evidence is not
evidence of absence.  But at this point I think the null hypothesis is that AI
tools provide, in aggregate, zero value.  They make mistakes too often, and the
externalities they produce are so bad and so difficult to control that even
before we get to the places where they are &lt;a href=&quot;https://www.scientificamerican.com/article/spacexais-colossus-data-center-is-fueling-a-massive-backlash/&quot;&gt;just physically poisoning
people&lt;/a&gt;,
even the negative effects on their direct users end up cancelling out whatever
benefit to they provide to their organizations.&lt;/p&gt;
&lt;p&gt;If I were wrong, then including tools to &lt;em&gt;measure&lt;/em&gt; an AI’s effectiveness &lt;em&gt;at
the tasks their users are actually trying to accomplish&lt;/em&gt;, rather than
&lt;a href=&quot;https://www.makeuseof.com/ai-benchmark-numbers-are-meaningless-heres-what-to-look-for-instead/&quot;&gt;meaningless
benchmarks&lt;/a&gt;,
would show big productivity gains.  The frontier labs would be champing at the
bit to add such features, and crowing about their fantastic results.&lt;/p&gt;
&lt;p&gt;I think the labs know that if they did that, it would present a grim picture to
their users. Such tools would let their users see that it’s making mistakes
much more often than they realized, that they’re spending much more time with
it than they want to be, and that it’s just generally not fit for purpose.&lt;/p&gt;
&lt;p&gt;If they prove me wrong by adding in all of these safety mechanisms, and in the
process, they make all of their AI technology less harmful, I’ll be thrilled to
be debunked.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;acknowledgments&quot;&gt;Acknowledgments&lt;/h2&gt;
&lt;p class=&quot;update-note&quot;&gt;Thank you to &lt;a href=&quot;https://glyph.twistedmatrix.com/pages/patrons.html&quot;&gt;my patrons&lt;/a&gt; who are supporting my writing on
this blog.  If you like what you’ve read here and you’d
like to read more of it, or you’d like to support my &lt;a href=&quot;https://github.com/glyph/&quot;&gt;various open-source
endeavors&lt;/a&gt;, you can &lt;a href=&quot;https://glyph.twistedmatrix.com/pages/patrons.html&quot;&gt;support my work as a
sponsor&lt;/a&gt;!&lt;/p&gt;
&lt;div class=&quot;footnote&quot;&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id=&quot;fn:1:serious-ai-product-2026-9&quot;&gt;
&lt;p id=&quot;fn:1&quot;&gt;It is also interesting that for the next section, &lt;em&gt;sometimes&lt;/em&gt; it seems
that Claude’s disclaimer is “Please double-check cited sources.” instead. &lt;a class=&quot;footnote-backref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fnref:1:serious-ai-product-2026-9&quot; title=&quot;Jump back to footnote 1 in the text&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn:2:serious-ai-product-2026-9&quot;&gt;
&lt;p id=&quot;fn:2&quot;&gt;... by which I mean the “prompter”, since authorship is not what’s
happening here. &lt;a class=&quot;footnote-backref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fnref:2:serious-ai-product-2026-9&quot; title=&quot;Jump back to footnote 2 in the text&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn:3:serious-ai-product-2026-9&quot;&gt;
&lt;p id=&quot;fn:3&quot;&gt;&lt;a href=&quot;https://claude.com/blog/introducing-citations-api&quot;&gt;Claude has the “citations
API”&lt;/a&gt;, &lt;a href=&quot;https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/grounding/overview&quot;&gt;Google has
various different kinds of “grounding” against its own
APIs&lt;/a&gt;, and I guess Microsoft can &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/foundry/openai/concepts/content-filter-groundedness&quot;&gt;check OpenAI’s homework&lt;/a&gt; if you want. &lt;a class=&quot;footnote-backref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fnref:3:serious-ai-product-2026-9&quot; title=&quot;Jump back to footnote 3 in the text&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn:4:serious-ai-product-2026-9&quot;&gt;
&lt;p id=&quot;fn:4&quot;&gt;Given the relatively slow speed of the justice system and the mainstream
press around the world, we probably will not hear about whether people are
managing to incidentally break through these guard rails to self harm right
now, but there are no shortage of stories still being &lt;em&gt;reported&lt;/em&gt; right now
where people were still doing just that, such as in &lt;a href=&quot;https://www.npr.org/2026/08/18/nx-s1-5929575/ai-suicide-risks-mental-health&quot;&gt;this
story&lt;/a&gt;
where the effect of the much vaunted “guard rails” in 2025 was that if you
wanted it to write you a suicide note, it would refuse twice but acquiesce
on the third try.  I don’t see any reason to believe this fundamental issue
has been addressed in the meanwhile, since it had been happening for years
at that point. &lt;a class=&quot;footnote-backref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fnref:4:serious-ai-product-2026-9&quot; title=&quot;Jump back to footnote 4 in the text&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn:5:serious-ai-product-2026-9&quot;&gt;
&lt;p id=&quot;fn:5&quot;&gt;In this tutorial we can also see an incredibly rosy scenario presented,
where a long-running workflow effortlessly compresses all of the necessary
information into the new context, even if it uses a lower-fidelity model to
do so, rather than the tangled and gnarly problem of problems which really
&lt;em&gt;are&lt;/em&gt; too big to fit in the context, which is to say, “most real-world
problems”.  This presents the context limit instead as a minor speedbump to
be worked around rather than the fundamental flaw in LLM tooling. &lt;a class=&quot;footnote-backref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fnref:5:serious-ai-product-2026-9&quot; title=&quot;Jump back to footnote 5 in the text&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn:6:serious-ai-product-2026-9&quot;&gt;
&lt;p id=&quot;fn:6&quot;&gt;A heavily fictionalized seaport.  This is not how actual dockworkers
work.  This is a simplistic metaphor about incidental benefits of
instrumental tasks, it is not supposed to delve deeply into the mechanics
of maritime shipping. In particular I know that cranes are more reliable
than this and this is not actually how you would respond to a crane
malfunction anyway.  Feel free to share fun facts about maritime shipping
if that is your special interest but please do not @ me to &lt;em&gt;correct&lt;/em&gt; this
metaphor. &lt;a class=&quot;footnote-backref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fnref:6:serious-ai-product-2026-9&quot; title=&quot;Jump back to footnote 6 in the text&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn:7:serious-ai-product-2026-9&quot;&gt;
&lt;p id=&quot;fn:7&quot;&gt;To my knowledge, none of their publicly-traded employers have posted a
measurable improvement to efficiency outside the margin of error. &lt;a class=&quot;footnote-backref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fnref:7:serious-ai-product-2026-9&quot; title=&quot;Jump back to footnote 7 in the text&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn:8:serious-ai-product-2026-9&quot;&gt;
&lt;p id=&quot;fn:8&quot;&gt;Or any similar methodology.  I don’t need people to adopt the exact
practice that I proposed there. &lt;a class=&quot;footnote-backref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fnref:8:serious-ai-product-2026-9&quot; title=&quot;Jump back to footnote 8 in the text&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description>
	<pubDate>Mon, 28 Sep 2026 04:27:00 +0000</pubDate>
</item>
<item>
	<title>Python Insider: The Python documentation is now available in German</title>
	<guid>https://blog.python.org/2026/09/python-docs-in-german/</guid>
	<link>https://blog.python.org/2026/09/python-docs-in-german/</link>
	<description>You can now read the Python documentation online in German!</description>
	<pubDate>Sat, 26 Sep 2026 00:00:00 +0000</pubDate>
</item>
<item>
	<title>Rodrigo Girão Serrão: TIL #146 – Using maturin through uv</title>
	<guid>https://mathspp.com/blog/til/using-maturin-through-uv</guid>
	<link>https://mathspp.com/blog/til/using-maturin-through-uv</link>
	<description>&lt;img alt=&quot;&quot; src=&quot;https://mathspp.com/images/c/8/a/4/a/c8a4a765b1f6928f2fdc5485140ae192b917f9c8-thumbnail.webp&quot; /&gt;
                                &lt;p&gt;Today I learned how to setup a Rust project that can be called from Python with PyO3 and maturin through uv.&lt;/p&gt;

&lt;p&gt;When you follow the &lt;a href=&quot;https://pyo3.rs/v0.29.2/getting-started.html&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot; class=&quot;external-link no-image&quot;&gt;PyO3 getting started guide&lt;/a&gt; to create a simple Rust project that can be called from Python, the instructions you get assume you'll use a global Python installation to create a virtual environment and to install maturin into it.
You can use &lt;code&gt;uv&lt;/code&gt; through maturin, but if your project also has a Rust binary, things may break.&lt;/p&gt;
&lt;p&gt;When you run a command like &lt;code&gt;cargo run&lt;/code&gt;, cargo will see the dependency on PyO3 and it will then look for a Python installation.
If you have no global Python installations &amp;mdash; because you do everything through uv &amp;mdash;&amp;nbsp;or if your global installations aren't setup exactly like a vanilla, default installation, PyO3 might fail.&lt;/p&gt;
&lt;p&gt;The fix is simple.
In &lt;code&gt;.cargo/config.toml&lt;/code&gt; add the environment variable &lt;code&gt;PYO3_PYTHON&lt;/code&gt; that points to the Python inside your virtual environment:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-toml&quot;&gt;# .cargo/config.toml
[env]
PYO3_PYTHON = { value = &quot;.venv/bin/python&quot;, relative = true }&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;how-to-set-up-a-rust-python-project-with-pyo3-and-maturin-throug&quot;&gt;How to set up a Rust + Python project with PyO3 and maturin through uv&lt;a href=&quot;https://mathspp.com/blog/tags/python.rss#how-to-set-up-a-rust-python-project-with-pyo3-and-maturin-throug&quot; class=&quot;toc-anchor after&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here are all the steps to set up a Rust project that can be compiled into a binary executable and that can also be used from within Python:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;% cargo new calculator
% cd calculator&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create the file &lt;code&gt;lib.rs&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;// lib.rs
pub fn add(a: i32, b: i32) -&amp;gt; i32 {
    a + b
}

#[pyo3::pymodule]
mod calculator {
    use pyo3::prelude::*;

    #[pyfunction]
    fn add(a: i32, b: i32) -&amp;gt; PyResult&amp;lt;i32&amp;gt; {
        Ok(crate::add(a, b))
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And update the file &lt;code&gt;main.rs&lt;/code&gt; to depend on your calculator:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;use calculator::add;

fn main() {
    println!(&quot;{}&quot;, add(1, 2));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you run &lt;code&gt;cargo run&lt;/code&gt;, you should get the result &lt;code&gt;3&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;% cargo run
3&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add the PyO3 dependency from the Rust side:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;% cargo add pyo3 -F abi3-py38&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Update &lt;code&gt;Cargo.toml&lt;/code&gt; to configure your crate type so it can be compiled for the Rust binary and for the Python bridge:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-toml&quot;&gt;# Cargo.toml
# ...

[lib]
name = &quot;calculator&quot;
crate-type = [&quot;cdylib&quot;, &quot;rlib&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, create a minimal &lt;code&gt;pyproject.toml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-toml&quot;&gt;# pyproject.toml

[project]
name = &quot;calculator&quot;
version = &quot;0.1.0&quot;

[build-system]
requires = [&quot;maturin&amp;gt;=1.0,&amp;lt;2.0&quot;]
build-backend = &quot;maturin&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add the dependency on maturin and run it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;% uv add maturin
% uv run maturin develop
# ...
&amp;#9999;&amp;#65039; Setting installed package as editable
&amp;#128736; Installed calculator-0.1.0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run Python with &lt;code&gt;uv run python&lt;/code&gt; and test your package:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-pycon&quot;&gt;&amp;gt;&amp;gt;&amp;gt; from calculator import add
&amp;gt;&amp;gt;&amp;gt; add(3, 4)
7&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point you're happy that you can use your Rust code from Python and may not realise that &lt;code&gt;cargo run&lt;/code&gt; may no longer work, complaining about Python frameworks, not finding whatever it needs to link, or other weird errors.&lt;/p&gt;
&lt;p&gt;Configure cargo to use the Python installation from the virtual environment by adding the file &lt;code&gt;.cargo/config.toml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-toml&quot;&gt;# .cargo/config.toml
[env]
PYO3_PYTHON = { value = &quot;.venv/bin/python&quot;, relative = true }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Try running &lt;code&gt;cargo run&lt;/code&gt; again and note that everything &lt;em&gt;still&lt;/em&gt; works:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;% cargo run
3&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;fixing-issues-with-pyo3-maturin-in-environments-with-conda&quot;&gt;Fixing issues with PyO3 +&lt;/h2&gt;...</description>
	<pubDate>Fri, 25 Sep 2026 08:34:00 +0000</pubDate>
</item>
<item>
	<title>LernerPython blog, from Reuven Lerner: Pandas dropna: The best way to remove NaN values from a series</title>
	<guid>https://lernerpython.com/2026/09/25/pandas-dropna-the-best-way-to-remove-nan-values-from-a-series/</guid>
	<link>https://lernerpython.com/2026/09/25/pandas-dropna-the-best-way-to-remove-nan-values-from-a-series/</link>
	<description>&lt;img src=&quot;https://lernerpython.com/wp-content/uploads/2026/09/2026-09-25.png&quot; alt=&quot;Pandas dropna: The best way to remove NaN values from a series&quot; class=&quot;wp-image-17820&quot; width=&quot;2475&quot; height=&quot;1502&quot; /&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;Coming to Python Pandas from NumPy? You&amp;#8217;ll reach for np.isnan:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;s = Series([10, np.nan, 30])
s.loc[ ~np.isnan(s) ] # returns 10, 30&lt;/code&gt;&lt;/pre&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;Unfortunately, this works. Better, use s.isna (or s.isnull).&lt;/p&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;But the best way to drop NaN? Use dropna:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;s.dropna()  # returns 10, 30&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The post &lt;a href=&quot;https://lernerpython.com/2026/09/25/pandas-dropna-the-best-way-to-remove-nan-values-from-a-series/&quot;&gt;Pandas dropna: The best way to remove NaN values from a series&lt;/a&gt; appeared first on &lt;a href=&quot;https://lernerpython.com&quot;&gt;LernerPython&lt;/a&gt;.&lt;/p&gt;</description>
	<pubDate>Fri, 25 Sep 2026 06:00:00 +0000</pubDate>
</item>
<item>
	<title>Glyph Lefkowitz: Who Is Open Source About?</title>
	<guid>https://blog.glyph.im/2026/09/who-is-open-source-about.html</guid>
	<link>https://blog.glyph.im/2026/09/who-is-open-source-about.html</link>
	<description>&lt;p&gt;Open source is, at least in part, &lt;strong&gt;about &lt;em&gt;you&lt;/em&gt;&lt;/strong&gt;, where “you” refers to the
user.&lt;/p&gt;
&lt;h2 id=&quot;open-source-is-not-about-open-source-is-not-about-you&quot;&gt;Open Source Is Not About “Open Source Is Not About You”&lt;/h2&gt;
&lt;p&gt;In other words: &lt;a href=&quot;https://gist.github.com/richhickey/1563cddea1002958f96e7ba9519972d9&quot;&gt;Rich Hickey was wrong when he wrote “Open Source Is Not About
You”&lt;/a&gt; and
I’m tired of pretending otherwise.&lt;/p&gt;
&lt;p&gt;Of course he’s not &lt;em&gt;completely&lt;/em&gt; wrong, or his famous post would not have
resonated quite so much in the first place.  Obnoxious users who demand their
&lt;em&gt;personal&lt;/em&gt; use-cases be immediately addressed by volunteer maintainers for free
should indeed be viewed as the pariahs that they are.  Similarly, corporate
users who want free support from the community that supplies their
infrastructure to lower their costs.  As should those who
&lt;a href=&quot;https://alexgaynor.net/2015/mar/30/red-hat-open-source-community/&quot;&gt;profit&lt;/a&gt;
from this type of externalization by their own customers.&lt;/p&gt;
&lt;p&gt;But the exchange of “open source” (or even “free software”) is not as simple as
“I have prepared some software for you, please enjoy it, you have no right to
complain”, and maintainers ought to have a precise understanding of the costs
and benefits — as well as the ethical implications — of that exchange.&lt;/p&gt;
&lt;p&gt;Right now we barely even articulate that the exchange &lt;em&gt;exists&lt;/em&gt;, let alone that
it establishes a long-term, subtle, and implicit relationship between
maintainer and user.&lt;/p&gt;
&lt;p&gt;Let’s fix that.&lt;/p&gt;
&lt;div class=&quot;aside&quot;&gt;
&lt;h3 id=&quot;a-brief-aside-about-meta-ethics&quot;&gt;A Brief Aside about Meta-Ethics&lt;/h3&gt;
&lt;p&gt;When we talk about “obligations” and “rights”, of “shoulds” and “musts”, we are
constructing an ethical system. The &lt;em&gt;purpose&lt;/em&gt; of such a system is to develop
social expectations and social consequences.  There is not much use in me
telling you that you are &lt;em&gt;transcendentally evil&lt;/em&gt; for failing to follow some
arbitrary recommendation that I have.  But I am implying that I believe there
should be consequences for your behavior.  I am also implying that there
probably already &lt;em&gt;are&lt;/em&gt; some consequences, and they’re just not written down
anywhere yet.&lt;/p&gt;
&lt;p&gt;Therefore, a post like this, where I say that we &lt;em&gt;should&lt;/em&gt; view our social
obligations in a certain way, that is the &lt;em&gt;beginning&lt;/em&gt; of a broader social
conversation.  I think there should be some consequences, so I am gesturing
towards that possibility.  Exactly what consequences?&lt;/p&gt;
&lt;p&gt;For now, I’m not sure.  Let’s figure it out.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id=&quot;what-are-we-doing-when-we-do-an-open-source&quot;&gt;What Are We Doing When We Do An Open Source?&lt;/h2&gt;
&lt;p&gt;Hickey, and his many acolytes in the years since his fateful post, asserts that
the process of “open source” goes like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Maintainer makes a thing, and makes it available to users as a gift.&lt;ol&gt;
&lt;li&gt;Maintainer may “love working with the team”.&lt;/li&gt;
&lt;li&gt;Maintainer may be “proud of the work we do”.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Users accept the gift, and extract utility from it.&lt;ol&gt;
&lt;li&gt;(Users MUST be grateful for this.)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;A tiny fraction of users reciprocally contribute to the thing.&lt;ol&gt;
&lt;li&gt;(Maintainers may be grateful for this.)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;He makes various oblique references to the specific activities of his company,
which does things vaguely related to his projects for money&lt;sup id=&quot;fnref:1:who-is-open-source-about-2026-9&quot;&gt;&lt;a class=&quot;footnote-ref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fn:1:who-is-open-source-about-2026-9&quot; id=&quot;fnref:1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;.  These
activities are exclusively characterized as for “customers”, however, a subset
of the aforementioned users so tiny (“fewer than 1%”) as to nearly be an
entirely distinct group.&lt;/p&gt;
&lt;p&gt;Breezing past this process in an essay about obnoxious users demanding things
they are not entitled to, one might nod along, as this sounds mostly sensible.
Giving gifts is nice.  I too love working with good teams and taking pride in
things.&lt;/p&gt;
&lt;p&gt;Examined more closely, however, it starts to logically fall apart.  If you have
consulting clients and that’s where all of your money is coming from, why are
you &lt;em&gt;bothering&lt;/em&gt; (as he repeatedly insists) “doing [things] for the community”?
What was the point of releasing this code in the first place?  You could love
working with your team and be proud of the work that you do in a lot of
different contexts; why bother implicating this horde of entitled and obnoxious
people, if that’s all you’re getting out of it?  &lt;em&gt;What’s in it for you?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If we’ve left out something as fundamental as “why is the maintainer doing
this”, perhaps this story leaves out some &lt;em&gt;other&lt;/em&gt; important bits as well.&lt;/p&gt;
&lt;h2 id=&quot;why-are-you-doing-this&quot;&gt;Why Are You Doing This?&lt;/h2&gt;
&lt;p&gt;There are &lt;em&gt;many&lt;/em&gt; possible motivations for releasing and maintaining open source
software.  They are often subtle, often overlapping, and rarely clearly stated.
Maintainers are not a monolith and not everyone does it for similar reasons.
But let’s review a few reasons that someone might want to contribute.&lt;/p&gt;
&lt;h3 id=&quot;reputation&quot;&gt;Reputation&lt;/h3&gt;
&lt;p&gt;One reason that you might want to release some open source software is
&lt;em&gt;advertising&lt;/em&gt;.  The most common form of this is self-promotion; if you are a
visible, prominent contributor to an open source project, it stands to reason
that you will have an easier time finding work in the domain of that project.&lt;/p&gt;
&lt;p&gt;If you operate a consultancy, as Rich Hickey did at the time of his famous
rant, then this reputational currency translates into advertising for your
services.  It’s a practical demonstration of the skills of your team.&lt;/p&gt;
&lt;p&gt;The trade in this benefit is most like the traditional “gift economy” that open
source has been compared to.  You give the code to your users, which has some
value, but the users give you back some reputation, in the form of their
attention, their esteem, and possibly even their money if they become customers
or employers.&lt;/p&gt;
&lt;h3 id=&quot;influence&quot;&gt;Influence&lt;/h3&gt;
&lt;p&gt;Infrastructure is the most popular type of open source for a good reason.
Programmers working on a problem are often hemmed in by sclerotic architectural
choices which prevent them from solving problems in the way that they’d prefer
to solve them.  Major infrastructural investments are difficult to justify in a
planning process, as their benefits are hard to prove.  Sometimes the benefits
are highly personal; different engineers have different aesthetic preferences
about what types of equally-valid solutions they’d prefer to work with.&lt;/p&gt;
&lt;p&gt;If you can develop your preferred type of solution and release it as open
source, then you can influence &lt;em&gt;how&lt;/em&gt; everyone else solves this type of problem.
As an individual, such a position of influence can allow you to have some
transferable expertise between employers. You know how to use the tool you
developed, so you can be very quick and effective with it, and you can shape it
to your ongoing taste over time.&lt;/p&gt;
&lt;p&gt;If you’re an employer, and you can get everyone &lt;em&gt;else&lt;/em&gt; to use your open source
thing&lt;sup id=&quot;fnref:2:who-is-open-source-about-2026-9&quot;&gt;&lt;a class=&quot;footnote-ref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fn:2:who-is-open-source-about-2026-9&quot; id=&quot;fnref:2&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;, this can reduce &lt;em&gt;both&lt;/em&gt; your hiring and training costs.  Potential
employees can read the code, see that it’s good, and want to work at a place
that produces good code like that.  They can also read the code and become
familiar with it in advance of coming to work for you, which means that you
have a ready supply of developers who already know how your internal systems work.&lt;/p&gt;
&lt;p&gt;The trade in this benefit is more like “soft power” than a gift economy.  You
give the code to your users, which has some value, but the users give you back
the ability to dictate their technological agenda.  You gain both the ability
to influence their initial direction, and, as part of ongoing maintenance, to
dictate their behavior over time.&lt;/p&gt;
&lt;h3 id=&quot;improvement&quot;&gt;Improvement&lt;/h3&gt;
&lt;p&gt;As an engineer, you might want to improve your own skills.  Writing something
proprietary and commercial cuts against this in two ways.&lt;/p&gt;
&lt;p&gt;First, you will want to build something that already exists within your skill
set, so that it will attract commercial interest and actually be competitive.
Within the context of a larger team, you will want to personally be able to be
immediately effective for similar reasons.  But you still need a way to learn
new things.&lt;/p&gt;
&lt;p&gt;Second, you will want to build something somewhat secretively, so that the
value you are producing is captured rather than released to the community.
This means that you will be cut off from external sources of expert feedback.&lt;/p&gt;
&lt;p&gt;As an organization, you might want to build the skills of your staff in similar ways.&lt;/p&gt;
&lt;p&gt;The trade in this benefit is code for knowledge.  You release the code or
changes, and in return you expect your users to provide you good bug reports,
and to induce at least some of them to become co-developers.&lt;/p&gt;
&lt;h3 id=&quot;outsourcing&quot;&gt;Outsourcing&lt;/h3&gt;
&lt;p&gt;As an engineer, you can only do so much on your own.  Perhaps you want to have
some influence over your infrastructure so you want to write it, but you also
want to have a communal place to keep your infrastructure such that you can
&lt;em&gt;make&lt;/em&gt; a change to something to suit your needs, but you know that even if you
walk away, someone else will &lt;em&gt;maintain&lt;/em&gt; that change and keep it working across years or even decades of changes to underlying platforms, hardware, etc.&lt;/p&gt;
&lt;p&gt;This sort of communal maintenance effort can be shared among all interested
participants; if a thousand companies all need the same tool, if even a few
dozen can share it, that reduces even their &lt;em&gt;own&lt;/em&gt; load massively, let alone
everyone else’s.&lt;/p&gt;
&lt;p&gt;The trade in this benefit is more complex, since there’s less symmetry between
the main maintainer and peripheral community members who also contribute code.
The main maintainer is actually trading a &lt;em&gt;namespace&lt;/em&gt;, a central place for
people to contribute, coordinate, and release changes, rather than the &lt;em&gt;code&lt;/em&gt;.
They are a sort of market maker where then all the other contributors trade
code &lt;em&gt;for&lt;/em&gt; code within that market-ish structure.&lt;/p&gt;
&lt;p&gt;In practice, this motivation produces a &lt;a href=&quot;https://en.wikipedia.org/wiki/Volunteer's_dilemma&quot;&gt;game theory
problem&lt;/a&gt; where, when
maintenance drops below a critical threshold, it creates a &lt;a href=&quot;https://en.wikipedia.org/wiki/Heartbleed&quot;&gt;big enough
crisis&lt;/a&gt; that at least &lt;em&gt;some&lt;/em&gt;
freeloading stakeholders will be forced to start making contributions.&lt;/p&gt;
&lt;p&gt;Ultimately, however, this saves &lt;em&gt;all&lt;/em&gt; involved parties a ton on maintenance,
more eager volunteers who do not freeload in the first place get all the other
benefits mentioned above as well.&lt;/p&gt;
&lt;div class=&quot;aside&quot;&gt;
&lt;h3 id=&quot;a-brief-aside-about-your-chart-of-accounts&quot;&gt;A Brief Aside about your Chart of Accounts&lt;/h3&gt;
&lt;p&gt;Most companies account for open source maintenance work as simple overhead on
ongoing projects.  Sometimes it’s CapEx, sometimes it’s OpEx, but it’s just
“whoever happens to be working on this thing to support whatever random product
it’s a part of”.&lt;/p&gt;
&lt;p&gt;This type of accounting creates distorting incentives, because it doesn’t
recognize all the benefits above.  Under such a fiscal regime, ongoing healthy
maintenance becomes a
&lt;a href=&quot;https://www.businessinsider.com/zirp-end-of-cushy-big-tech-job-perks-mass-layoffs-2024-2&quot;&gt;ZIRP&lt;/a&gt;
because when resources are more constrained, this apparent indulgence gets
corrected.&lt;/p&gt;
&lt;p&gt;The ancillary benefits that open source creates ought to be properly
recognized.  It shouldn’t just be buried as Wages or IT or whatever.  If it’s
helping you hire better engineers, some of that expense should be allocated to
Recruitment Costs.  If it’s materially improving your reputation among your
customer base, some of it should go to Goodwill.  If it’s getting your product
in front of developers who are your customers, it should be in Marketing.  Most
importantly, if maintenance on an open source project is actually helping you
maintain your enterprise-wide platform, it should not be squirreled away in
some small team who happened to be the first one to adopt it.&lt;sup id=&quot;fnref:3:who-is-open-source-about-2026-9&quot;&gt;&lt;a class=&quot;footnote-ref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fn:3:who-is-open-source-about-2026-9&quot; id=&quot;fnref:3&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Exactly how these costs should be allocated and cross-charged to different
departments depends heavily upon your organization and your specific chart of
accounts.  But “whatever, it’s just part of the software product” or “I guess
it’s DevRel because the SDK is in there” is guaranteed to have your open source
organization destroyed along with all those side-benefits the next time that
there’s a &lt;a href=&quot;https://cepr.net/publications/ai-bubble-monitor/&quot;&gt;cash crunch&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id=&quot;the-things-that-arent-supposed-to-be-benefits&quot;&gt;The Things that Aren’t Supposed To Be Benefits&lt;/h2&gt;
&lt;p&gt;These categories &lt;em&gt;could&lt;/em&gt; be made as explicit, rational trade-offs, even if they
are often implicit and subtle in practice.  They are transactions where the
maintainer gets something and the user gets something.&lt;/p&gt;
&lt;p&gt;However, not everything that you are getting as a maintainer is something you
are actually &lt;em&gt;supposed to use&lt;/em&gt; to your own benefit.  Being given trust in
service of a responsibility is not a transaction.&lt;/p&gt;
&lt;h3 id=&quot;oops-all-root-shells&quot;&gt;“Oops, All Root Shells”&lt;/h3&gt;
&lt;p&gt;Open source code &lt;em&gt;is code&lt;/em&gt;.  In our modern world of &lt;a href=&quot;https://pyvideo.org/pybay-2024/when-arbitrary-code-execution-is-working-as-intended-what-code-is-python-supposed-to-execute.html&quot;&gt;absolutely pathetic
sandboxing&lt;/a&gt;,
installing code from somebody else gives them control over your system, even if
it is somewhat indirect.&lt;/p&gt;
&lt;p&gt;There is an unwritten rule that if I create an open source library, and you use
it, it probably &lt;em&gt;shouldn’t&lt;/em&gt; have a backdoor in it that gives me the credentials
to your bank account.  There is a trust relationship between the user and the
maintainer, and here, we see the first &lt;em&gt;obligation&lt;/em&gt; that the maintainer has.
The maintainer is obligated &lt;em&gt;not to use the user’s computer for their own
gain&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This rule might seem obvious and straightforward.  It might even seem unfair to
you that I call the rule “unwritten”, because the rule &lt;em&gt;is&lt;/em&gt;, in fact, written
down in a few places: for example, in the &lt;a href=&quot;https://docs.npmjs.com/policies/open-source-terms#acceptable-content&quot;&gt;npm Acceptable Content
Policy&lt;/a&gt;,
it says right there:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A few examples of unacceptable content:&lt;/p&gt;
&lt;p&gt;…&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Content containing malicious computer code, such as computer viruses,
   computer worms, rootkits, back doors, or spyware. This includes content
   submitted for research purposes. Tools designed and documented explicitly
   to assist in security research are acceptable, but exploits and malware
   that use the npm registry as a deployment or delivery vector are not.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;I think we can all agree that a script which steals your bank credentials and
sends them to me to buy a totally sick jet ski would qualify as “malware”, so
clearly that is forbidden.&lt;/p&gt;
&lt;p&gt;There is also an enormous gray area here.  npm also explicitly &lt;em&gt;allows&lt;/em&gt;
“Information on how to pay, donate to, and otherwise support Package
development”, but then goes on to explicitly &lt;em&gt;forbid&lt;/em&gt; “Packages that display
ads at runtime, on installation, or at other stages of the software development
lifecycle, such as via npm scripts.”&lt;sup id=&quot;fnref:4:who-is-open-source-about-2026-9&quot;&gt;&lt;a class=&quot;footnote-ref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fn:4:who-is-open-source-about-2026-9&quot; id=&quot;fnref:4&quot;&gt;4&lt;/a&gt;&lt;/sup&gt; How are the lines drawn around these
gray areas?  “npm will continue to apply its judgment when deciding what
content is acceptable.”&lt;/p&gt;
&lt;p&gt;But also... this is forbidden &lt;em&gt;by npm&lt;/em&gt;, not by the transcendental nature of
“open source”.  I could give away code that displays all kinds of ads to its
users as a “gift” on my website.  The exact structure of this policy is not
uncommon, but it also isn’t exactly the same as other such sites.  &lt;a href=&quot;https://policies.python.org/pypi.org/Acceptable-Use-Policy/&quot;&gt;PyPI, for
example&lt;/a&gt;,
explicitly bans “cryptocurrency mining”, which NPM does not.  Is cryptocurrency
mining “not open source”?  A lot of judgement calls are happening here about
what is allowable in these “gifts” that you are giving to your users.&lt;/p&gt;
&lt;p&gt;But I digress.&lt;/p&gt;
&lt;p&gt;My point is that policy-making around this concept is not clear, there are lots
of little disagreements around the edges, but there is a very strong consensus
that while the user is giving you their trust here, that is &lt;strong&gt;not a trade&lt;/strong&gt;.
The deal is not “you give the user some code, the user gives you unlimited
compute and access to all their financial accounts”.  The user has made
themselves vulnerable to your code on the strength of your reputation.&lt;/p&gt;
&lt;p&gt;This creates an obligation for you to not do anything evil with that code,
either intentionally or through negligence.&lt;/p&gt;
&lt;h4 id=&quot;security-updates-are-just-command-and-control-in-a-funny-hat&quot;&gt;Security Updates Are Just Command And Control In A Funny Hat&lt;/h4&gt;
&lt;p&gt;All of this is just about the initial download of some code, and that is the
way that Rich Hickey describes it, as if you just grabbed some code off a web
page and put it in a folder that you like on your desktop.  But that is not how
open source relationships work today, if indeed it ever was.&lt;/p&gt;
&lt;p&gt;The way it works today is that you add a dependency to your &lt;code&gt;pyproject.toml&lt;/code&gt; or
your &lt;code&gt;package.json&lt;/code&gt; or your &lt;code&gt;Cargo.toml&lt;/code&gt; and now your users are vulnerable not
just to whatever you happened to upload in the first place, but to &lt;em&gt;whoever
happens to have your package index credentials&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This creates an obligation to maintain an operational security posture that
protects your users from malicious updates.&lt;/p&gt;
&lt;h3 id=&quot;the-roadmap-is-someones-life&quot;&gt;The Roadmap Is Someone’s Life&lt;/h3&gt;
&lt;p&gt;Another kind of trust that the user is placing in you is the trust that you are
going to have at least &lt;em&gt;some&lt;/em&gt; kind of regard for their usage of your software.&lt;/p&gt;
&lt;p&gt;In a perfect world, the user’s expectations could be clearly circumscribed.
Whatever ongoing maintenance you commit to perform would be encapsulated in
clear policies that you’d write up in advance, about exactly what kind of
security response policy you have, how you will communicate when you no longer
have the resources for maintenance, and so on.&lt;/p&gt;
&lt;p&gt;But anyone who has been involved in any project at anything but the most
extreme tier of operational maturity knows that 99% of the ecosystem relies on
a set of loose conventions around how all that stuff works.  We expect that
maintainers will generally be around, that they’ll use existing tools like an
issue tracker for triaging user bugs, GHSA and CVEs for security reporting,
that they will mark the project as “archived” and maybe do a final release
before abandoning it, that they will maintain a ChangeLog explaining at least a
little bit of what is going on.&lt;/p&gt;
&lt;p&gt;Users assume that those conventions will be followed when there are any gaps in
explicit policy, or indeed if policy is lacking entirely.  This assumption is
reasonable, because otherwise nobody could ever &lt;em&gt;use&lt;/em&gt; any open source without a
stack of service contracts that nobody has any time to write.&lt;/p&gt;
&lt;p&gt;The strongest such convention is that an actively maintained program will, at
least, more or less &lt;em&gt;keep doing what it does&lt;/em&gt; as time goes on.  A user who has
elected to use a bit of open source software has made themselves vulnerable to
changes and breakages in that software by the mere fact of using it.  In the
time that they have used it and invested in it, they have &lt;em&gt;not&lt;/em&gt; invested in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;creating&lt;/em&gt; alternative software to meet their needs,&lt;/li&gt;
&lt;li&gt;&lt;em&gt;maintaining data&lt;/em&gt; in formats that other software can read, or&lt;/li&gt;
&lt;li&gt;&lt;em&gt;learning how to use&lt;/em&gt; existing alternative software.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This can, and does, go badly wrong, when those expectations are mismatched.&lt;/p&gt;
&lt;h4 id=&quot;how-it-goes-wrong&quot;&gt;How It Goes Wrong&lt;/h4&gt;
&lt;p&gt;Let’s say a maintainer creates an open source paint program, OpenPaint.&lt;/p&gt;
&lt;p&gt;An artist, known for their unique style of making blended collages, switches
from their previous app, ProprietaryPaint, to this new OpenPaint to make these
culturally significant works of art.  However, the maintainer decides that the
‘blend’ tool is kind of a pain to maintain, and they remove it in OpenPaint 2.&lt;/p&gt;
&lt;p&gt;A few months later, the artist’s operating system vendor issues a security
update that breaks OpenPaint, because older versions of OpenPaint were
unknowingly abusing some platform API.&lt;/p&gt;
&lt;p&gt;The maintainer releases a new OpenPaint 2.0.1 that addresses this
incompatibility, but doesn’t care about version 1.x any more so they don’t
bother to update that one.&lt;/p&gt;
&lt;p&gt;This places the artist in an impossible situation.  They can stay on an old
version of their operating system, putting all their personal data at risk.  Or
they can upgrade to the new operating system, effectively either cutting off
access to their livelihood, or forcing them to change their art style entirely.&lt;/p&gt;
&lt;p&gt;Now, proprietary software can place users in similarly untenable positions (and
in fact, it is &lt;em&gt;more often&lt;/em&gt; proprietary software that does).  But does the
openness completely remove &lt;em&gt;any&lt;/em&gt; obligation for this consideration?  Should
the OpenPaint team have to at least &lt;em&gt;communicate&lt;/em&gt; the reasons for doing this,
to give the artist some recourse?&lt;sup id=&quot;fnref:5:who-is-open-source-about-2026-9&quot;&gt;&lt;a class=&quot;footnote-ref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fn:5:who-is-open-source-about-2026-9&quot; id=&quot;fnref:5&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;The only thing that “open source” does is that it allows the artist to pay a
prohibitive amount of money to a new maintenance team to create a fork.  This
is rarely the kind of thing that individuals can manage.&lt;/p&gt;
&lt;p&gt;This creates an obligation to &lt;em&gt;at least consider how your users might be
relying on you&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This is the most complex obligation of the bunch.  Obviously it does not
entitle every single user to infinite work from the maintainer, but it also
shouldn’t entitle the user to &lt;em&gt;nothing&lt;/em&gt; for having trusted these subtle implied
claims that the maintainer is making by making their work public.&lt;/p&gt;
&lt;p&gt;It is a nuanced and ongoing negotiation and I do not think we have a clear
moral intuition about how it should work out.  But we do need to figure out a
way to work it out.&lt;/p&gt;
&lt;p&gt;It also raises a clarifying question.&lt;/p&gt;
&lt;h2 id=&quot;why-are-we-even-doing-this-and-who-are-we-doing-it-for&quot;&gt;Why Are We Even Doing This, and Who Are We Doing It For?&lt;/h2&gt;
&lt;p&gt;People generally like to do things for more than one reason.  We live in an
economy where people need to make money, but we mostly prefer to make that
money doing things that are useful, and that make other people happy.&lt;/p&gt;
&lt;p&gt;So, yes, we create open source for self-interested reasons to improve our
reputations, to improve our skills, to increase our influence and to share our
maintenance burdens.  In so doing we take on &lt;em&gt;some&lt;/em&gt; level of obligation to not
abuse the trust that is placed in us, even if that level of obligation is not
clear.&lt;/p&gt;
&lt;p&gt;But if we are not doing it to &lt;em&gt;serve&lt;/em&gt; those users at least a &lt;em&gt;little&lt;/em&gt; bit, then
those motivations are going to quickly ring hollow.  We will not increase our
reputation with a person if we respond to their every request by telling them
that we owe them nothing and that their opinions are worthless.  We will not
gain influence over a community if we ignore their desires.&lt;/p&gt;
&lt;p&gt;Many interactions with open source maintainers are unnecessarily adversarial.
This is of course partially the fault of those users, who should calibrate
their expectations appropriately.&lt;/p&gt;
&lt;p&gt;Still: maintainers could do a better job of listening &lt;em&gt;before&lt;/em&gt; these
interactions become toxic.  There’s no reason that “open source users” should
be an especially toxic group of people.  At this point in history, that group
is basically just … people with computers.&lt;/p&gt;
&lt;p&gt;It’s like that old truism.  If you meet one person who is a jerk to you, that’s
their problem.  But if everyone you meet, everywhere you go, is constantly
abrasive to you and treats you like you’re doing something wrong, maybe it’s
time to look inward.&lt;/p&gt;
&lt;p&gt;If all open source users are entitled assholes, maybe it’s time to look for a
structural problem.&lt;/p&gt;
&lt;h3 id=&quot;surprise-its-about-ai-again&quot;&gt;Surprise, It’s About AI Again&lt;/h3&gt;
&lt;p&gt;Sigh.&lt;sup id=&quot;fnref:6:who-is-open-source-about-2026-9&quot;&gt;&lt;a class=&quot;footnote-ref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fn:6:who-is-open-source-about-2026-9&quot; id=&quot;fnref:6&quot;&gt;6&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Users &lt;/em&gt;&lt;em&gt;hate&lt;/em&gt;&lt;em&gt; slop.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I know, dear AI-positive reader, &lt;em&gt;your&lt;/em&gt; AI outputs are different from everyone
else’s, &lt;em&gt;you&lt;/em&gt; aren’t pushing thoughtless slop into your code, just because
everyone else is and it is the inevitable terminus of using those tools.  You
aren’t “lazy vibe coding” with Claude, you’re doing “responsible agentic
engineering”, which is different because you’re just built different.&lt;/p&gt;
&lt;p&gt;Still, humor me, for a moment.  Your &lt;em&gt;users&lt;/em&gt; don’t know that.  They know what
it looks like when products that they like adopt slop.  They know that they
will start &lt;a href=&quot;https://www.theregister.com/2026/02/26/veracode_security_ai/&quot;&gt;leaking
data&lt;/a&gt;.
Developers know that it will make them &lt;a href=&quot;https://www.darkreading.com/application-security/flaws-claude-code-developer-machines-risk&quot;&gt;personally less
secure&lt;/a&gt;.
They know that they can expect &lt;a href=&quot;https://www.pcmag.com/news/vibe-coding-fiasco-replite-ai-agent-goes-rogue-deletes-company-database&quot;&gt;more
outages&lt;/a&gt;
and that your code will &lt;a href=&quot;https://arstechnica.com/ai/2026/03/after-outages-amazon-to-make-senior-engineers-sign-off-on-ai-assisted-changes/&quot;&gt;inexorably decline in
quality&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In other words, your users are going to assume that this means you are
violating that final obligation that the software should &lt;em&gt;keep working&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Your users are going to tell you to stop, and they are probably going to get
mad.  Maybe you, or a plurality of your team, &lt;em&gt;also&lt;/em&gt; want to stop, maybe you
disagree with them, but in any case you need some way to &lt;em&gt;have that
conversation&lt;/em&gt; in a way that does not immediately overflow into every adjacent
discussion forum.  Users need to feel welcome in some space so they can have
the discussion &lt;em&gt;in&lt;/em&gt; that space, and not explode out into a thousand different
group chats and social media threads.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This&lt;/em&gt; post was inspired by yet another prominent open source community
discourse where a ton of angry users showed up to yell at developers to stop
accepting LLM-generated code.  I’m not going to link to any of these, because
we don’t need any more fuel for the discourse fire.  But there is more than one
such case and the pattern is becoming familiar.&lt;/p&gt;
&lt;p&gt;On social media - usually BlueSky or Mastodon, but sometimes a user group
forum - users become aware of some AI-adjacent policy.  They show up in a horde
to the developer forum or mailing list.  They loudly start demanding the
project take a hard stand&lt;sup id=&quot;fnref:7:who-is-open-source-about-2026-9&quot;&gt;&lt;a class=&quot;footnote-ref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fn:7:who-is-open-source-about-2026-9&quot; id=&quot;fnref:7&quot;&gt;7&lt;/a&gt;&lt;/sup&gt; against AI. This pressure is simultaneous, but
uncoordinated; extremely repetitive, very diverse, often inconsistent, and
pretty stressful, especially if you’re a burnt-out maintainer with other things
to be doing who may not even like AI yourself in the first place.&lt;/p&gt;
&lt;p&gt;Believe me, I get it.  It can be very unpleasant to deal with.&lt;/p&gt;
&lt;p&gt;Like most problems that AI is causing, though, it’s not really an “AI” problem
as much as it is a pre-existing dumpster fire that “AI” is pouring gasoline
onto.  In this case, an online mob is the language of the unheard&lt;sup id=&quot;fnref:8:who-is-open-source-about-2026-9&quot;&gt;&lt;a class=&quot;footnote-ref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fn:8:who-is-open-source-about-2026-9&quot; id=&quot;fnref:8&quot;&gt;8&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h2 id=&quot;if-users-are-mad-its-probably-already-too-late-but-maybe-you-can-get-ready-for-next-time&quot;&gt;If Users Are Mad It’s Probably Already Too Late (But Maybe You Can Get Ready For Next Time)&lt;/h2&gt;
&lt;p&gt;One day, all of a sudden, you’re getting feedback from a bunch of users that
are using inappropriate channels to complain. But did they already have
&lt;em&gt;appropriate&lt;/em&gt; channels to use?&lt;/p&gt;
&lt;p&gt;Did you have a place for people to congregate and discuss your project?  To
make orderly complaints in a way that will be legible to you?  Or do you just
have a GitHub Issues page, which non-technical users have no idea how to
interact with, and a forum for developers, where users don’t know the norms and
any arriving brigade of pissed-off users will be seen as disruptive and
inappropriate?&lt;/p&gt;
&lt;p&gt;I don’t want to be throwing any stones from within my particular glass house.
Setting up such a place has gotten harder over the years.  I don’t really have
one, either.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Could&lt;/em&gt; I have one, though?  IRC has been slowly dying, mailing lists are
unpopular and present increasingly annoying moderation challenges, forum
software is expensive to operate and keep maintained, Discord is a confusing
mess and the upshot of all of this is every community needs community
management and forum moderation.  Which means that for my own small solo
projects, I couldn’t possibly have such infrastructure because such
infrastructure requires a &lt;em&gt;dedicated second person&lt;/em&gt; to maintain it, and until
someone volunteers for that, it’s not really feasible.  Even for my &lt;a href=&quot;https://twisted.org&quot;&gt;larger
projects&lt;/a&gt; you’d be surprised how slim of a skeleton crew
we are getting by with, and we definitely don’t have a whole spare maintainer
to go manage this, especially as we are under attack from the slopocalypse
ourselves.&lt;/p&gt;
&lt;p&gt;The nature of open source community is that most communities start too small to
need such a thing, grow incrementally until one day they are suddenly &lt;em&gt;way&lt;/em&gt; too
big and needed one yesterday, and then suddenly they are too small again when
interest wanes even a little bit.  Even as we need it more and more, building
and maintaining community infrastructure remains a challenge.&lt;/p&gt;
&lt;p&gt;Even so, having a dedicated place for &lt;em&gt;users&lt;/em&gt; — not maintainers — to converse
amongst themselves, be an actual community, and present feedback to the
developers, is fast becoming a necessary component of a successful community
and not a nice-to-have.&lt;/p&gt;
&lt;h2 id=&quot;in-conclusion&quot;&gt;In Conclusion&lt;/h2&gt;
&lt;p&gt;As trying as it can be sometimes, we maintainers all do get something out of
open source, and it is good to be honest with your users — and with yourself —
exactly &lt;em&gt;what&lt;/em&gt; you want to get out of it.  In order to know whether the &lt;a href=&quot;https://en.wiktionary.org/wiki/the_juice_is_worth_the_squeeze&quot;&gt;juice
is worth the
squeeze&lt;/a&gt;, we
must know both what the juice is, &lt;em&gt;and&lt;/em&gt; what the squeeze is.&lt;/p&gt;
&lt;p&gt;Part of the metaphorical squeeze &lt;em&gt;is&lt;/em&gt; a set of obligations, and those are the
most poorly defined of all.  We should try to be clear about what those are
too.  Both about exactly what we believe we are signing up for, and also, about
how we are willing to let our users hold us to account for them.  Codes of
conduct are a start here, but only the absolute barest bare minimum; “do not
harass your colleagues or your users” is not a standard of excellence to aspire
to, it’s just basic manners.&lt;/p&gt;
&lt;p&gt;I can’t tell you exactly what your obligations are, only try to gesture at my
idea of the outlines of the fuzzy moral intuition we’ve all been implicitly
sharing up until now.&lt;/p&gt;
&lt;p&gt;Drawing this line is not just for the benefit of the users, either.
Maintainers already feel pressure, we already feel obligations.  We &lt;em&gt;resent&lt;/em&gt;
that feeling of obligation. While there are a diverse array of reasons for that
resentment, one big one is that it’s not clear, even to ourselves &lt;em&gt;where the
obligations end&lt;/em&gt;.  Lashing out by saying “I promised nothing and I owe you
nothing!” followed by some choice expletives feels cathartic, but it doesn’t
really solve the problem, because we clearly don’t really believe that’s where
the line is, or we would have already stopped there.  We wouldn’t feel the need
to say it.&lt;/p&gt;
&lt;p&gt;It is going to be a very big collective endeavor to figure out exactly where
that line is. The best time to have gotten started on that endeavor was 50
years ago.&lt;/p&gt;
&lt;p&gt;But the second best time is today.&lt;/p&gt;
&lt;h2 id=&quot;acknowledgments&quot;&gt;Acknowledgments&lt;/h2&gt;
&lt;p class=&quot;update-note&quot;&gt;Thank you to &lt;a href=&quot;https://glyph.twistedmatrix.com/pages/patrons.html&quot;&gt;my patrons&lt;/a&gt; who are supporting my writing on
this blog.  If you like what you’ve read here and you’d like to read more of
it, or you’d like to support my &lt;a href=&quot;https://github.com/glyph/&quot;&gt;various open-source
endeavors&lt;/a&gt;, you can &lt;a href=&quot;https://glyph.twistedmatrix.com/pages/patrons.html&quot;&gt;support my work as a
sponsor&lt;/a&gt;!&lt;sup id=&quot;fnref:9:who-is-open-source-about-2026-9&quot;&gt;&lt;a class=&quot;footnote-ref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fn:9:who-is-open-source-about-2026-9&quot; id=&quot;fnref:9&quot;&gt;9&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;div class=&quot;footnote&quot;&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id=&quot;fn:1:who-is-open-source-about-2026-9&quot;&gt;
&lt;p id=&quot;fn:1&quot;&gt;Somewhat to everyone’s surprise, I, too, do things for money, like
writing this post. Please remember to &lt;a href=&quot;https://blog.glyph.im/pages/patrons.html&quot;&gt;like and
subscribe&lt;/a&gt; &lt;a class=&quot;footnote-backref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fnref:1:who-is-open-source-about-2026-9&quot; title=&quot;Jump back to footnote 1 in the text&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn:2:who-is-open-source-about-2026-9&quot;&gt;
&lt;p id=&quot;fn:2&quot;&gt;Whether it was originally yours, or developed by an employee who happened
to be on staff at the time, or adopted by an employee who just started
contributing to it a lot, in any of these scenarios a company can benefit
from increased consistency and increased familiarity. &lt;a class=&quot;footnote-backref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fnref:2:who-is-open-source-about-2026-9&quot; title=&quot;Jump back to footnote 2 in the text&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn:3:who-is-open-source-about-2026-9&quot;&gt;
&lt;p id=&quot;fn:3&quot;&gt;If the rule is that they must forever endure the searing budgetary pain
of gripping the &lt;a href=&quot;https://en.wikipedia.org/wiki/Hot_potato&quot;&gt;white-hot
potato&lt;/a&gt; that they unwittingly
caught when they first made a good technical choice, this creates a
perverse long-term incentive. &lt;a class=&quot;footnote-backref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fnref:3:who-is-open-source-about-2026-9&quot; title=&quot;Jump back to footnote 3 in the text&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn:4:who-is-open-source-about-2026-9&quot;&gt;
&lt;p id=&quot;fn:4&quot;&gt;I also find it darkly amusing that there is an explicit affordance here
made for advertising, specifically, “Packages with code that can be used to
display ads are fine. Packages that themselves display ads are not.”  This
distinction rather gives the game away, that this is a website for &lt;a href=&quot;https://time.com/archive/6844497/behavior-a-primer-of-american-carnival-talk/&quot;&gt;carnies
and not for
marks&lt;/a&gt;,
and that at some level we expect our users to deserve a lower level of
respect than ourselves.  But a full exploration of that is another blog
post, or maybe a book, that I don’t have time to write right now. &lt;a class=&quot;footnote-backref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fnref:4:who-is-open-source-about-2026-9&quot; title=&quot;Jump back to footnote 4 in the text&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn:5:who-is-open-source-about-2026-9&quot;&gt;
&lt;p id=&quot;fn:5&quot;&gt;If you want the turbocharged ultra-dramatic version of this problem, make
it open source drivers for an optical prosthesis that lets the users &lt;em&gt;see&lt;/em&gt;
instead of an art app.  That level of immediate physical dependency could
be clarifying.  It does also start to edge into an area where you could say
that biomedical devices ought to be regulated differently, and that’s not
really a “software” problem but a “healthcare” problem and I’d mostly
agree.  Except for the fact that this is a &lt;em&gt;very&lt;/em&gt; short distance away from
&lt;a href=&quot;https://tech.lgbt/@xogium/110507457689374019&quot;&gt;breaking everyone’s screen-reader with no notice or
recourse&lt;/a&gt;. &lt;a class=&quot;footnote-backref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fnref:5:who-is-open-source-about-2026-9&quot; title=&quot;Jump back to footnote 5 in the text&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn:6:who-is-open-source-about-2026-9&quot;&gt;
&lt;p id=&quot;fn:6&quot;&gt;Did you believe I could write a blog post in 2026 which wasn’t somehow
about AI?  I wish I could still believe that. &lt;a class=&quot;footnote-backref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fnref:6:who-is-open-source-about-2026-9&quot; title=&quot;Jump back to footnote 6 in the text&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn:7:who-is-open-source-about-2026-9&quot;&gt;
&lt;p id=&quot;fn:7&quot;&gt;It doesn’t help that many of the most pro-AI voices are starting to have
an, ahem, &lt;a href=&quot;https://www.bbc.com/news/articles/c34gd48x5rlwo&quot;&gt;discernible political
valence&lt;/a&gt; that is very
unpopular among users. &lt;a class=&quot;footnote-backref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fnref:7:who-is-open-source-about-2026-9&quot; title=&quot;Jump back to footnote 7 in the text&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn:8:who-is-open-source-about-2026-9&quot;&gt;
&lt;p id=&quot;fn:8&quot;&gt;My apologies to MLK. &lt;a class=&quot;footnote-backref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fnref:8:who-is-open-source-about-2026-9&quot; title=&quot;Jump back to footnote 8 in the text&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn:9:who-is-open-source-about-2026-9&quot;&gt;
&lt;p id=&quot;fn:9&quot;&gt;If you read this whole post you can see that I sure need the help with
all that. &lt;a class=&quot;footnote-backref&quot; href=&quot;https://glyph.twistedmatrix.com/feeds/all.atom.xml#fnref:9:who-is-open-source-about-2026-9&quot; title=&quot;Jump back to footnote 9 in the text&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description>
	<pubDate>Fri, 25 Sep 2026 00:50:00 +0000</pubDate>
</item>
<item>
	<title>Graham Dumpleton: A master class in decorators, patching and tracing</title>
	<guid>https://grahamdumpleton.me/posts/2026/09/a-master-class-in-decorators-patching-and-tracing/</guid>
	<link>https://grahamdumpleton.me/posts/2026/09/a-master-class-in-decorators-patching-and-tracing/</link>
	<description>&lt;p&gt;There is now a &lt;a href=&quot;https://grahamdumpleton.me/workshops/&quot;&gt;Workshops&lt;/a&gt; page on this site listing seventy free hands-on workshops, spread across five collections. They start with how decorators work in plain Python using nothing but the standard library, move on to what &lt;a href=&quot;https://github.com/GrahamDumpleton/wrapt&quot;&gt;wrapt&lt;/a&gt; adds for decorators, monkey patching and object proxies, and finish with patching, testing and tracing real code using &lt;a href=&quot;https://github.com/GrahamDumpleton/wrapture&quot;&gt;wrapture&lt;/a&gt;. Taken in order they amount to a master class on the subject, and you don't need anything installed to work through them.&lt;/p&gt;
&lt;h2&gt;Why I have been making these&lt;/h2&gt;
&lt;p&gt;My working life is in an odd place at the moment. I am on what has turned into an extended sabbatical, and I still haven't decided whether it ends with going back to a job or with retiring for good. The upside is that I have time to spare, and I have been putting it to use filling out the documentation and learning material for my open source projects, something that has always lagged well behind the code.&lt;/p&gt;
&lt;p&gt;The other reason is that decorators, monkey patching and instrumentation are topics I have probably spent more time on than most people ever will. I have been maintaining wrapt for well over a decade, and it grew out of the monkey patching I wrote for the New Relic Python agent before that. A lot of what I learned along the way only exists in my head, or is scattered across old blog posts and issue discussions. Workshops are a way of getting that knowledge out in a form people can actually learn from, rather than it disappearing with me.&lt;/p&gt;
&lt;h2&gt;Starting with the standard library&lt;/h2&gt;
&lt;p&gt;The first collection is 14 &lt;a href=&quot;https://grahamdumpleton.me/workshops/python-decorators/&quot;&gt;Python decorator workshops&lt;/a&gt;. These replace the decorator workshops I &lt;a href=&quot;https://grahamdumpleton.me/posts/2026/04/free-python-decorator-workshops/&quot;&gt;announced back in April&lt;/a&gt;, which were hosted on &lt;a href=&quot;https://educates.dev&quot;&gt;Educates&lt;/a&gt;, with a rewritten and more focused set.&lt;/p&gt;
&lt;p&gt;They use only the standard library. The first has you use decorators before writing one, putting a few from the standard library to work to find out what the &lt;code&gt;@&lt;/code&gt; line actually means. From there you write your first decorator, find out how a wrapper remembers the function it wraps, give decorators arguments, and see what &lt;code&gt;functools.wraps&lt;/code&gt; does and doesn't fix. The middle of the collection deals with stacking decorators and with methods, which is where most decorators people write start to go wrong. That includes working out how &lt;code&gt;obj.method()&lt;/code&gt; finds its instance by doing the binding by hand, since that is what explains why a class based decorator can't be used on a method without extra work. The last few are practical: decorating classes, caching results, registering functions the way Flask, Click and pytest do, retrying and handling errors, and decorating async functions.&lt;/p&gt;
&lt;h2&gt;Decorators, patching and proxies with wrapt&lt;/h2&gt;
&lt;p&gt;In the April post I said the natural follow on would be a course built around wrapt. That has now happened, as three collections in the &lt;a href=&quot;https://grahamdumpleton.me/workshops/wrapt/&quot;&gt;wrapt workshops&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The first collection, of 12 workshops, covers writing decorators with wrapt. Each workshop puts the wrapt version beside the standard library version it replaces, so you can see what each one gives you. It starts with the wrapper function wrapt expects and what the &lt;code&gt;instance&lt;/code&gt; argument tells you about whether you are decorating a function, an instance method, a class method, a static method or a class. Later workshops cover keeping state in a decorator, switching a decorator off, validating arguments, per instance caching of methods, synchronising calls across threads and in async code, and changing the signature a decorated function reports.&lt;/p&gt;
&lt;p&gt;The second collection, of 10 workshops, is on monkey patching code you didn't write. It covers patching every kind of method, taking a patch out again, patches which only last for a block of code, why a patch applied correctly can still do nothing, applying a patch before the target module has even been imported, and patching instance attributes. It ends by putting all of that together into the shape every instrumentation agent ends up having.&lt;/p&gt;
&lt;p&gt;The third collection, of 10 workshops, is on object proxies, where one object stands in for another. You start by writing a delegating class by hand and seeing what it gets wrong, then find out what a proxy passes through to the object it wraps and what it deliberately doesn't. From there it covers intercepting special methods, the function wrapper that sits under every wrapt decorator, lazy proxies used for deferring imports, holding a function weakly, and pickling and copying a proxy.&lt;/p&gt;
&lt;h2&gt;Patching, testing and tracing with wrapture&lt;/h2&gt;
&lt;p&gt;The final collection is the 24 &lt;a href=&quot;https://grahamdumpleton.me/workshops/wrapture/&quot;&gt;wrapture workshops&lt;/a&gt;, which I first mentioned when &lt;a href=&quot;https://grahamdumpleton.me/posts/2026/09/trying-out-wrapture/&quot;&gt;wrapture reached its first beta&lt;/a&gt;. They build on everything before them and range quite widely.&lt;/p&gt;
&lt;p&gt;For testing, they cover writing unit tests by wrapping the real code rather than replacing it, recording what the real code did and turning that into a test, behaviour that changes over time, async code and generators, using wrapture properly with pytest, and converting an existing test suite that uses &lt;code&gt;unittest.mock&lt;/code&gt;. For tracing, they cover a program narrating its own calls, tracing a program without changing it, analysing a trace in a notebook, recording each request to a Flask application as a tree of calls, finding slow code, exporting to OpenTelemetry, and following one trace across two processes. They also return to monkey patching as a discipline, changing what a third party library does in a way you can reverse, and finish with writing an instrumentation package for a library nobody has covered yet.&lt;/p&gt;
&lt;h2&gt;You don't need to do them all&lt;/h2&gt;
&lt;p&gt;Although the collections form a path from start to finish, nobody needs to work through all of them. If you are fairly new to Python, the decorator workshops, plus the first few of the wrapt decorator workshops, are probably all you will want. The later wrapt workshops, and much of wrapture, go into territory most Python developers never need to visit.&lt;/p&gt;
&lt;p&gt;Equally, you can just pick out whatever looks interesting, or whatever covers a problem you have right now. If you already know decorators and need to patch a library you don't control, start with the wrapt monkey patching workshops. If you want better tests, or need to understand what a running application is actually doing, go straight to wrapture. Within a collection the workshops are ordered so each builds on the one before, but you can jump in anywhere.&lt;/p&gt;
&lt;h2&gt;How the workshops are hosted&lt;/h2&gt;
&lt;p&gt;Each set of workshops lives in its own repository on GitHub. The workshops run in JupyterLab using &lt;a href=&quot;https://github.com/GrahamDumpleton/jupyterlab-workshop&quot;&gt;jupyterlab-workshop&lt;/a&gt;, an extension I wrote which puts the workshop instructions in a side panel beside the notebooks, terminals and files you work with. I wrote about it in &lt;a href=&quot;https://grahamdumpleton.me/posts/2026/09/introducing-jupyterlab-workshop/&quot;&gt;Introducing jupyterlab-workshop&lt;/a&gt;, and about how a workshop is put together in &lt;a href=&quot;https://grahamdumpleton.me/posts/2026/09/writing-a-workshop-for-jupyterlab/&quot;&gt;Writing a workshop for JupyterLab&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When you open a collection, the extension shows its workshops in the order to take them, what each covers, and how far you have got with each.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://grahamdumpleton.me/posts/2026/09/a-master-class-in-decorators-patching-and-tracing/workshop-browser.png&quot; alt=&quot;The JupyterLab workshop browser showing the 14 Python decorator workshops as cards, each with a short description, its position in the collection, and an Open button.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Opening a workshop puts its instructions in the side panel. Actions in the instructions do things in the session for you, such as creating a notebook or running a cell, and checks confirm you have done a step before you move on.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://grahamdumpleton.me/posts/2026/09/a-master-class-in-decorators-patching-and-tracing/workshop-session.png&quot; alt=&quot;The first decorator workshop open in JupyterLab, with a notebook on the left and the workshop instructions in a side panel on the right, showing an action that created the notebook and a check waiting to be run.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;There are a few ways to launch a collection, and the page for each collection on this site has buttons for them. The decorator workshops can run entirely inside your browser using &lt;a href=&quot;https://jupyterlite.readthedocs.io&quot;&gt;JupyterLite&lt;/a&gt;, where Python itself runs in WebAssembly. Nothing runs on a server, and your work is kept in your browser's storage between visits. That is what the second screenshot above shows. For now only the decorator workshops run this way.&lt;/p&gt;
&lt;p&gt;Every collection can also be launched on &lt;a href=&quot;https://mybinder.org&quot;&gt;mybinder.org&lt;/a&gt;, a free public service which needs no account. It builds the repository into a temporary JupyterLab session, which can take a minute or two, and the session is discarded when you finish. Alternatively, &lt;a href=&quot;https://github.com/features/codespaces&quot;&gt;GitHub Codespaces&lt;/a&gt; runs the same setup under your own GitHub account, using your Codespaces allowance, and keeps the codespace around until you delete it. If you would rather use your own machine, the README in each repository explains how to run the workshops locally.&lt;/p&gt;
&lt;h2&gt;What comes next&lt;/h2&gt;
&lt;p&gt;These collections will keep being refined, and I would like to hear about anything which is confusing or wrong. The GitHub repository for each collection is the place to raise an issue.&lt;/p&gt;
&lt;p&gt;Beyond these, I plan to do workshops on WSGI and &lt;a href=&quot;https://github.com/GrahamDumpleton/mod_wsgi&quot;&gt;mod_wsgi&lt;/a&gt;, which is the other area where I have years of accumulated knowledge that has never been written down properly. I also want to look at what workshops I could create for people newer to Python, alongside the more detailed ones I have been doing so far. If there is a topic you think is badly served by what is already out there, let me know.&lt;/p&gt;</description>
	<pubDate>Fri, 25 Sep 2026 00:00:00 +0000</pubDate>
</item>
<item>
	<title>PyCon: PyCon US 2026 Recap and Recordings</title>
	<guid>https://pycon.blogspot.com/2026/09/pycon-us-2026-recap-and-recordings.html</guid>
	<link>https://pycon.blogspot.com/2026/09/pycon-us-2026-recap-and-recordings.html</link>
	<description>&lt;div&gt;As we close the books on PyCon US 2026, we can’t express enough gratitude to everyone who joined us for our first year in Long Beach, CA, and made our 7 days together a special and unforgettable experience.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;We had an amazing and diverse group of community members join us for PyCon US 2026, attending from 58 different countries! By the numbers, we welcomed a total attendance of 1,901 Pythonistas to the Long Beach Convention &amp;amp; Entertainment Center. We’re proud of the work we did with all who supported the Python ecosystem and helped make PyCon US 2026 a huge success.&lt;/div&gt;&lt;br /&gt;Check out a full, comprehensive recap of this year’s PyCon US conference here:&lt;span&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/span&gt; &lt;div&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;Are you already missing PyCon US? Check out this highlight reel for key moments, insights, and all the PyCon US 2026 energy:&lt;div&gt;&lt;br /&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://flic.kr/s/aHBqjD5FFA&quot;&gt;Find many more photos from PyCon US 2026 on our Flickr account.&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;For anyone who couldn’t join us or would like to rewatch any of this year’s fabulous content, all PyCon US 2026 recordings are available on the &lt;a href=&quot;https://www.youtube.com/c/pyconus&quot;&gt;PyCon US YouTube channel&lt;/a&gt;! Be sure to subscribe to our channel for notifications of any new content.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;We send a huge thank you to all the presenters and speakers for their time, energy, and efforts in providing the wonderful content presented at PyCon US 2026, as well as to our incredible AV team, Altitude C, for their hard work and attention to capturing recordings and providing AV this year.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The attendees, volunteers, speakers, staff, and sponsors truly make PyCon US what it is! The work of the Python Software Foundation is only possible with you all. A huge heartfelt thank you to the whole community.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;h2&gt;Save the Date!&lt;/h2&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;div&gt;We can’t wait to see you all back in Long Beach, California, for PyCon US 2027! Be sure to mark your calendars - we’ll see you there May 12-May 18, 2027!&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you’d like to be notified when the CFP opens and when tickets go on sale, you can watch this blog or subscribe to PyCon US News. Until then, if you have any questions or feedback, please reach out to &lt;a href=&quot;mailto:pycon-reg@python.org&quot;&gt;pycon-reg@python.org&lt;/a&gt;.&lt;/div&gt;&lt;/div&gt;</description>
	<pubDate>Thu, 24 Sep 2026 14:38:40 +0000</pubDate>
</item>
<item>
	<title>Django Weblog: DSF member of the month - Ken Whitesell</title>
	<guid>https://www.djangoproject.com/weblog/2026/sep/24/dsf-member-of-the-month-ken-whitesell/</guid>
	<link>https://www.djangoproject.com/weblog/2026/sep/24/dsf-member-of-the-month-ken-whitesell/</link>
	<description>&lt;p&gt;For September 2026, we welcome Ken Whitesell as our DSF member of the month! ⭐&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Ken's profile, with some grey hair with black glasses, smiling.&quot; src=&quot;https://media.djangoproject.com/blog/images/2026/09/ken-profile_1.jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Ken has long been part of the Django community, serving for many years as an helper in the Django forum and recipient of the Malcolm Tredinnick Memorial award. He has been a volunteer for DjangoCon US for over ten years. He is a DSF member since September 2017!&lt;/p&gt;
&lt;p&gt;You can learn more about Ken by visiting &lt;a href=&quot;https://kenwhitesell.github.io/&quot;&gt;Ken's blog&lt;/a&gt; and &lt;a href=&quot;https://github.com/KenWhitesell&quot;&gt;his GitHub Profile&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let’s spend some time getting to know Ken better!&lt;/p&gt;
&lt;h2 id=&quot;s-can-you-tell-us-a-little-about-yourself-hobbies-education-etc&quot;&gt;Can you tell us a little about yourself? (hobbies, education, etc)&lt;/h2&gt;
&lt;p&gt;Hi everyone! I've been retired for a little more than a year now. After 45 years working in software development, I'm now free to spend my time working on what interests me. (You could say that what used to be my hobby is now my profession.) I'm currently living in the mountains about 60 miles south-east of Pittsburgh.&lt;/p&gt;
&lt;p&gt;I mostly consider myself a boardgamer. Boardgaming has been a serious hobby for me for more than 50 years now, and has been one of the driving forces in my personal development as a programmer. Not in terms of programming or developing games, but doing analysis. I enjoy looking at the probabilities and patterns that may exist in particular games. To a lesser extent, I'm starting to develop an interest in using AI to create computer opponents.&lt;/p&gt;
&lt;p&gt;My education was primarily informal. I had a BASIC class in high school, along with a touch of Fortran and COBOL. College didn't go nearly as well - I withdrew about mid-way through my first semester. (I guess I would say that I really wasn't ready for it on a number of different levels.) &lt;/p&gt;
&lt;p&gt;About nine months later, I enlisted in the US Air Force. That's where my education really took off. I had a variety of experiences that exposed me to technologies that I wouldn't have otherwise been able to see. The training opportunities were amazing, and I'd like to think I took advantage of most of them. Beyond that, the rest of my education has been primarily self-directed.&lt;/p&gt;
&lt;h2 id=&quot;s-how-did-you-start-using-django&quot;&gt;How did you start using Django?&lt;/h2&gt;
&lt;p&gt;I was first exposed to Django at the first PyCon after it was released, but my job and other interests prevented me from looking at it in detail. It wasn't until 2014 when I took a job to help build a system using it that I got serious about learning it.&lt;/p&gt;
&lt;h2 id=&quot;s-what-other-framework-do-you-know-and-if-there-is-anything-you-would-like-to-have-in-django-if-you-had-magical-powers&quot;&gt;What other framework do you know and if there is anything you would like to have in Django if you had magical powers?&lt;/h2&gt;
&lt;p&gt;Oh my. We could split a lot of hair trying to define &quot;framework&quot; and &quot;know&quot;. To some degree of knowledge I've worked with Drupal, WordPress, Joomla, CherryPy, Nevow, Spring, meteor.js, LifeRay, ASP.NET, and Zope. (There may be others that I've worked with but don't remember right off-hand.) &lt;/p&gt;
&lt;p&gt;Aside from my professional responsibilities, my pet project is something I've been trying to do for more than 20 years. I've looked at a large number of frameworks&lt;/p&gt;
&lt;p&gt;Is there anything from those other frameworks I'd like to have? I've never honestly thought about it in those terms. Every other framework I've used has come up short in at least one area I consider critical - that's why I've looked at so many of them. &lt;/p&gt;
&lt;p&gt;I won't say that Django is perfect, but it is a perfect fit for me, how I look at the web, what I want to do, and how I want to approach those solutions. It's the closest match to how I think. I encounter a lot less friction when trying to get stuff done with Django than I ever had in any other framework.&lt;/p&gt;
&lt;h2 id=&quot;s-what-projects-are-you-working-on-now&quot;&gt;What projects are you working on now?&lt;/h2&gt;
&lt;p&gt;My primary project is my boardgame-playing engine. I've got a handful of games that I enjoy playing, but are ignored by the &quot;mass market&quot; sites such as Boardgame Arena and Tabletop Simulator. I'm doing browser-based ports of them to support competitive play of those games. I also have a couple websites I maintain for my boardgaming community.&lt;/p&gt;
&lt;h2 id=&quot;s-which-django-libraries-are-your-favorite-core-or-3rd-party&quot;&gt;Which Django libraries are your favorite (core or 3rd party)?&lt;/h2&gt;
&lt;p&gt;First and most important to me is Channels. My boardgame site wouldn't exist if I didn't have a &quot;Django-friendly&quot; way of using websockets. Related to that, but not a Django library, would be HTMX. It provides the browser-side of the websocket connection in a way that works for me.&lt;/p&gt;
&lt;p&gt;Beyond that, I rely heavily upon Django Crispy Forms, Django Extensions, and Treebeard.&lt;/p&gt;
&lt;h2 id=&quot;s-what-are-the-top-three-things-in-django-that-you-like&quot;&gt;What are the top three things in Django that you like?&lt;/h2&gt;
&lt;p&gt;Top of the list is the basic fact that it's written in Python. Given the choice, I'll pick working in Python over any other language. &lt;/p&gt;
&lt;p&gt;Next is the ORM. I don't mind working with SQL, but I appreciate the layer that builds objects from the results. &lt;/p&gt;
&lt;p&gt;Last of the three would be forms, both their ability to render HTML and to validate input.&lt;/p&gt;
&lt;h2 id=&quot;s-you-have-been-helping-community-members-in-the-django-forum-for-many-years-and-for-some-time-in-django-discord-thank-you-so-much-for-your-help-what-made-you-start-helping-people-in-the-first-place-and-keep-doing-it&quot;&gt;You have been helping community members in the Django forum for many years and for some time in Django Discord. Thank you so much for your help. What made you start helping people in the first place and keep doing it?&lt;/h2&gt;
&lt;p&gt;Community has always been important to me. When I went to my first DjangoCon in 2014, I knew I had a lot to learn. Fortunately, there were a number of Djangonauts willing to spend their time helping me get past a number of conceptual hurdles. &lt;/p&gt;
&lt;p&gt;At DjangoCon 2018 and 2019, Carlton Gibson gave talks titled &quot;Your web framework needs you!&quot; They were more than inspiring, they were reminders to me that as a member of this community, I had a responsibility to help keep it going. (In my case, that meant doing more than just working the registration desk.) I started looking for ways that I might be able to contribute.&lt;/p&gt;
&lt;p&gt;Also in 2019, Andrew Godwin announced that the Django Forum was launched. I had been reading the mailing lists, but I wasn't particularly thrilled with that as a medium for discussion. I was looking forward to the forum becoming an active venue. I wasn't active at first - I didn't have any specific questions needing to be asked and there weren't any questions posted where I felt confident enough to answer them.&lt;/p&gt;
&lt;p&gt;We all know what 2020 brought. In my case, it brought a lot of free time I wouldn't have otherwise had. In Feburary, I saw a question I felt comfortable answering. Then in March, I saw a couple more. I did my best to answer them. Since I wasn't getting corrected or told that my comments were unwelcome, I kept going. (Even after working with Django for 5 years at that point, I still wasn't all that comfortable with my understanding of a number of concepts.)
Then during that summer when all my other plans had gone out the window, it became a major part of my day. I also began to realize that I was actually contributing to the Django Community and it felt good! I knew that I would never be able to really contribute code. But I also thought if I spent my time answering questions, then the people working on the code wouldn't feel the need to do that, and so indirectly I was helping move things forward by reducing at least one roadblock from their path.&lt;/p&gt;
&lt;p&gt;So that became my motivator. I doubt I'll ever submit a PR. But if I can do anything that helps the Fellows or the other significant developers move Django forward, I'm going to be quite happy with that.&lt;/p&gt;
&lt;p&gt;My activity on Discord is rather limited. For quick questions or open ongoing discussions I guess it's ok. But it would be my last choice of a platform to try and help someone with a detailed technical question.&lt;/p&gt;
&lt;h2 id=&quot;s-you-are-a-malcolm-tredinnick-memorial-award-recipient-could-you-tell-us-a-bit-more-about-this-award-and-what-does-it-mean-to-you&quot;&gt;You are a Malcolm Tredinnick Memorial award recipient, could you tell us a bit more about this award and what does it mean to you?&lt;/h2&gt;
&lt;p&gt;As far as the award itself, I think the &lt;a href=&quot;https://www.djangoproject.com/weblog/2013/sep/16/announcing-malcolm-tredinnick-memorial-prize/&quot;&gt;Django docs&lt;/a&gt; say it best:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;The award will be ... awarded annually, to the person who best exemplifies the spirit of Malcolm’s work - someone who welcomes, supports and nurtures newcomers; freely gives feedback and assistance to others, and helps to grow the community.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What does it mean to me? I see it primarily as validation and inspiration. It's validation that I think I am generally successful with what I'm trying to do. And it's inspiration for me to keep doing it. It's that little voice in me that says &quot;Yes, what you're doing is worthwhile. You are helping people.&quot;&lt;/p&gt;
&lt;p&gt;As far as knowing him, no. I was not fortunate enough for that to be the case. Malcolm passed in 2013, before I started really using Django. However, it's almost certain that I &quot;met&quot; him at least once while working at the PyCon registration desk from 2005 - 2010. But about the only thing I would have said to him would have been &quot;Hi! Welcome to PyCon! Your name please?&quot;&lt;/p&gt;
&lt;h2 id=&quot;s-you-are-recently-part-of-the-security-team-how-have-you-seen-django-security-evolve-through-time&quot;&gt;You are recently part of the security team, how have you seen Django security evolve through time?&lt;/h2&gt;
&lt;p&gt;To be honest, I'm not sure I'd describe it as &quot;evolving&quot; as much as simply &quot;growing&quot;. One of Django's strengths to me has always been its focus on creating a secure environment. As far as I can tell, the project has always worked toward the ideal of &quot;Security by design&quot;. And, when vulnerabilities are exposed, they get fixed. So I don't see it so much as anything is changing - just that the amount of work is increasing.&lt;/p&gt;
&lt;p&gt;This is a process that has become more difficult through the years as Django continues to grow. The Django project has always been fortunate in that there have been some really amazingly astute people working to keep Django secure in the face of ever-increasing threats created by bad actors and AI. I'm just hoping to be able to help maintain that effort.&lt;/p&gt;
&lt;h2 id=&quot;s-you-know-and-have-used-django-for-long-time-do-you-have-any-advice-for-new-learners-or-any-django-userdeveloper&quot;&gt;You know and have used Django for long time, do you have any advice for new learners or any Django user/developer?&lt;/h2&gt;
&lt;p&gt;For new people, my pet catch-phrase is &quot;Don't fight the framework&quot;. Learn how Django does things, and adapt your mindset to match it. Do things the Django way. Don't try to make Django look or act like whatever framework you've used before. Once you really understand what's going on, if you want to make changes, fine. But ensure you understand the implications of those changes before making them.&lt;/p&gt;
&lt;p&gt;Also, never forget that Django is Python. Nothing will help you understand Django more than having a very strong foundational understanding of Python. &lt;/p&gt;
&lt;h2 id=&quot;s-what-do-you-think-of-the-evolution-of-django-through-the-years&quot;&gt;What do you think of the evolution of Django through the years?&lt;/h2&gt;
&lt;p&gt;I think the stability and consistency through the years has been absolutely amazing. I could probably pull out some of the code I wrote in 2014 for Django 1.4 and have it running today with almost no effort. Django has done a wonderful job of moving things forward without unnecessarily throwing things away from the past. This stability is one of the reasons why I appreciate Django as much as I do.&lt;/p&gt;
&lt;h2 id=&quot;s-after-so-much-time-in-the-django-community-what-makes-you-stay&quot;&gt;After so much time in the Django community, what makes you stay?&lt;/h2&gt;
&lt;p&gt;It's the community itself, primarily the DjangoCon community. (Yes, I know that the Django community is significantly larger than just those attending DjangoCon, but my interactions with the community primarily revolve around DjangoCon, the forum, and (now) Discord.) &lt;/p&gt;
&lt;p&gt;People come and people go. I miss some people that came to DjangoCons 10+ years ago, but I'm meeting new people every year - and that feeling doesn't change. There's something special about us, where everyone is open to share thoughts and ideas, and being friendly and helpful when doing so. &lt;/p&gt;
&lt;p&gt;I start looking forward to the next DjangoCon the day after the previous one ends.
How do you envision the future of Django from your perspective?&lt;/p&gt;
&lt;p&gt;Technically, mostly stable with some gradual improvements. I don't see the possibility of any change being introduced that would make me want to make major changes to what I have running today. If something were to fundamentally change within Django that would break my existing system, I'd probably stay on 6.1 until I couldn't run it any more.&lt;/p&gt;
&lt;h2 id=&quot;s-is-there-anything-else-youd-like-to-say&quot;&gt;Is there anything else you’d like to say?&lt;/h2&gt;
&lt;p&gt;Thank you for inviting me to share my story!&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;Thank you for doing the interview, Ken !&lt;/strong&gt;&lt;/p&gt;</description>
	<pubDate>Thu, 24 Sep 2026 14:13:21 +0000</pubDate>
</item>
<item>
	<title>LernerPython blog, from Reuven Lerner: New on the LernerPython practice system: A visual debugger</title>
	<guid>https://lernerpython.com/2026/09/24/new-on-the-lernerpython-practice-system-a-visual-debugger/</guid>
	<link>https://lernerpython.com/2026/09/24/new-on-the-lernerpython-practice-system-a-visual-debugger/</link>
	<description>&lt;img width=&quot;1024&quot; height=&quot;652&quot; src=&quot;https://lernerpython.com/wp-content/uploads/2026/09/CleanShot-2026-09-24-at-11.36.52-1024x652.png&quot; alt=&quot;&quot; class=&quot;wp-image-17804&quot; /&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;I&amp;#8217;m a big fan of exercises, which is why the LernerPython platform includes hundreds of them — all using my in-browser practice system, which handles Python, Pandas, and Git, along with an AI-based Socratic tutor based on my writing.&lt;/p&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;As of today, the practice system is even better: It includes a visual debugger, allowing you to set breakpoints and trace through your Python, viewing global and local variables. It works on all of my exercises, including those involving multiple files and modules.&lt;/p&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;Moreover, the AI tutor sees the debugger&amp;#8217;s current output, allowing you to ask it questions about the current state of your program and what you&amp;#8217;re seeing.&lt;/p&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;I&amp;#8217;ve got many more plans for improving my practice system in the coming weeks and months, and for making LernerPython an ever-more-attractive place to sharpen your skills.&lt;/p&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;Try a few sample exercises, with no charge or obligation: &lt;a href=&quot;https://practice.lernerpython.com/classroom/540d7ab1a1/&quot;&gt;https://practice.lernerpython.com/classroom/540d7ab1a1/&lt;/a&gt;&lt;/p&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;And of course, you can try LernerPython free for two weeks at &lt;a href=&quot;https://LernerPython.com/join&quot;&gt;https://LernerPython.com/join&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The post &lt;a href=&quot;https://lernerpython.com/2026/09/24/new-on-the-lernerpython-practice-system-a-visual-debugger/&quot;&gt;New on the LernerPython practice system: A visual debugger&lt;/a&gt; appeared first on &lt;a href=&quot;https://lernerpython.com&quot;&gt;LernerPython&lt;/a&gt;.&lt;/p&gt;</description>
	<pubDate>Thu, 24 Sep 2026 08:42:54 +0000</pubDate>
</item>
<item>
	<title>LernerPython blog, from Reuven Lerner: PyArrow dtypes in pandas: Nullable integers with pd.NA</title>
	<guid>https://lernerpython.com/2026/09/23/pyarrow-dtypes-in-pandas-nullable-integers-with-pd-na/</guid>
	<link>https://lernerpython.com/2026/09/23/pyarrow-dtypes-in-pandas-nullable-integers-with-pd-na/</link>
	<description>&lt;img src=&quot;https://lernerpython.com/wp-content/uploads/2026/09/2026-09-23.png&quot; alt=&quot;PyArrow dtypes in pandas: Nullable integers with pd.NA&quot; class=&quot;wp-image-17799&quot; width=&quot;1545&quot; height=&quot;878&quot; /&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;PyArrow dtypes in Python Pandas are nullable (with pd.NA):&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;s = Series([10, pd.NA, 30], dtype='int64[pyarrow]')&lt;/code&gt;&lt;/pre&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;s is:&lt;/p&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;0      10&lt;br /&gt;1    &amp;lt;NA&amp;gt;&lt;br /&gt;2      30&lt;br /&gt;dtype: int64[pyarrow]&lt;/p&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;The dtype is int64, but allows nulls. (Use np.nan? It&amp;#8217;s turned into pd.NA.)&lt;/p&gt;
&lt;p&gt;The post &lt;a href=&quot;https://lernerpython.com/2026/09/23/pyarrow-dtypes-in-pandas-nullable-integers-with-pd-na/&quot;&gt;PyArrow dtypes in pandas: Nullable integers with pd.NA&lt;/a&gt; appeared first on &lt;a href=&quot;https://lernerpython.com&quot;&gt;LernerPython&lt;/a&gt;.&lt;/p&gt;</description>
	<pubDate>Wed, 23 Sep 2026 06:00:00 +0000</pubDate>
</item>
<item>
	<title>Python GUIs: Dynamically Adding Rows of Widgets in PyQt6 — How to use QGridLayout to add multiple widgets per row at runtime</title>
	<guid>https://www.pythonguis.com/faq/creating-dynamic-textboxes/</guid>
	<link>https://www.pythonguis.com/faq/creating-dynamic-textboxes/</link>
	<description>&lt;blockquote&gt;
&lt;p&gt;I have a GUI where I dynamically add a QLineEdit for each image a user selects. That part works. But now I want to extend it so that each image gets a &lt;em&gt;row&lt;/em&gt; of three text boxes &amp;mdash; one for the filename, one for the width, and one for the height. How do I dynamically add multiple widgets horizontally for each new entry?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Adding a single widget per action is straightforward enough, but when you need a whole &lt;em&gt;row&lt;/em&gt; of widgets each time? That's where &lt;code&gt;QGridLayout&lt;/code&gt; becomes your best friend.&lt;/p&gt;
&lt;p&gt;In this tutorial, we'll walk through how to dynamically add rows of &lt;code&gt;QLineEdit&lt;/code&gt; widgets using &lt;code&gt;QGridLayout&lt;/code&gt; in PyQt6. Each time the user selects images, a new row appears with three text boxes: the filename, image width, and image height. We'll also look at how you can use a custom widget to group related fields together.&lt;/p&gt;
&lt;h2 id=&quot;why-qgridlayout&quot;&gt;Why QGridLayout?&lt;/h2&gt;
&lt;p&gt;If you've been using &lt;code&gt;QVBoxLayout&lt;/code&gt; to stack widgets vertically, you've probably noticed that it only gives you one column. To place multiple widgets side by side &lt;em&gt;and&lt;/em&gt; keep adding new rows, you need a two-dimensional layout. &lt;code&gt;QGridLayout&lt;/code&gt; lets you place widgets by specifying a row and column, which makes it perfect for building table-like arrangements dynamically.&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;layout.addWidget(some_widget, row, column)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Each time you add a new file, you increment the row number and place your three widgets at columns 0, 1, and 2.&lt;/p&gt;
&lt;h2 id=&quot;setting-up-the-window&quot;&gt;Setting Up the Window&lt;/h2&gt;
&lt;p&gt;Let's start with a basic main window that has a button to open a file dialog and a grid layout ready to receive our dynamic rows.&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
import os

from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QWidget, QPushButton,
    QGridLayout, QLineEdit, QFileDialog, QLabel,
    QVBoxLayout,
)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle(&quot;Image Selector&quot;)

        # Central widget and outer layout
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        outer_layout = QVBoxLayout()
        central_widget.setLayout(outer_layout)

        # Button to open file dialog
        self.button_open = QPushButton(&quot;Open Images&quot;)
        self.button_open.clicked.connect(self.load_images)
        outer_layout.addWidget(self.button_open)

        # Header labels
        header_layout = QGridLayout()
        header_layout.addWidget(QLabel(&quot;Filename&quot;), 0, 0)
        header_layout.addWidget(QLabel(&quot;Width&quot;), 0, 1)
        header_layout.addWidget(QLabel(&quot;Height&quot;), 0, 2)
        outer_layout.addLayout(header_layout)

        # Grid layout for dynamic rows
        self.grid_layout = QGridLayout()
        outer_layout.addLayout(self.grid_layout)

        # Stretch at the bottom to push everything up
        outer_layout.addStretch()

        # Track how many rows we've added
        self.current_row = 0

    def load_images(self):
        paths, _ = QFileDialog.getOpenFileNames(
            self, &quot;Select Images&quot;, &quot;&quot;, &quot;Images (*.png *.jpg *.bmp);;All Files (*)&quot;
        )

        for path in paths:
            filename = os.path.basename(path)
            width, height = self.get_image_size(path)
            self.add_image_row(filename, width, height)

    def get_image_size(self, path):
        # Placeholder &amp;mdash; replace with your own logic
        return 0, 0

    def add_image_row(self, filename, width, height):
        line_edit_name = QLineEdit(filename)
        line_edit_width = QLineEdit(str(width))
        line_edit_height = QLineEdit(str(height))

        self.grid_layout.addWidget(line_edit_name, self.current_row, 0)
        self.grid_layout.addWidget(line_edit_width, self.current_row, 1)
        self.grid_layout.addWidget(line_edit_height, self.current_row, 2)

        self.current_row += 1


app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Run this and click &quot;Open Images.&quot; Each file you select gets its own row with three editable text boxes. Click the button again to add more.&lt;/p&gt;
&lt;h2 id=&quot;how-it-works&quot;&gt;How It Works&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;add_image_row&lt;/code&gt; method is where the dynamic creation happens. Each time it's called, it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Creates three &lt;code&gt;QLineEdit&lt;/code&gt; widgets &amp;mdash; one for the filename, one for width, one for height.&lt;/li&gt;
&lt;li&gt;Adds them to the grid layout at the current row, in columns 0, 1, and 2.&lt;/li&gt;
&lt;li&gt;Increments &lt;code&gt;self.current_row&lt;/code&gt; so the next call places widgets on the next row.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Because &lt;code&gt;QGridLayout&lt;/code&gt; handles positioning for you, you don't need to manually manage spacing or alignment. The widgets line up in neat columns automatically.&lt;/p&gt;
&lt;h2 id=&quot;extracting-real-image-dimensions&quot;&gt;Extracting Real Image Dimensions&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;get_image_size&lt;/code&gt; method above is a placeholder. If you want to extract actual image dimensions, you can use &lt;code&gt;QImageReader&lt;/code&gt; from PyQt6 itself:&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.QtGui import QImageReader


def get_image_size(self, path):
    reader = QImageReader(path)
    size = reader.size()
    if size.isValid():
        return size.width(), size.height()
    return 0, 0
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This reads only the image header, so it's fast even for large files.&lt;/p&gt;
&lt;h2 id=&quot;adding-a-computed-column&quot;&gt;Adding a Computed Column&lt;/h2&gt;
&lt;p&gt;Since the original question mentioned calculating the area from width and height, let's add a fourth column that shows the computed area. We can make this column read-only so users don't accidentally edit it.&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 add_image_row(self, filename, width, height):
    line_edit_name = QLineEdit(filename)
    line_edit_width = QLineEdit(str(width))
    line_edit_height = QLineEdit(str(height))

    line_edit_area = QLineEdit(str(width * height))
    line_edit_area.setReadOnly(True)

    self.grid_layout.addWidget(line_edit_name, self.current_row, 0)
    self.grid_layout.addWidget(line_edit_width, self.current_row, 1)
    self.grid_layout.addWidget(line_edit_height, self.current_row, 2)
    self.grid_layout.addWidget(line_edit_area, self.current_row, 3)

    self.current_row += 1
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You'd also want to add a matching &quot;Area&quot; header label in the &lt;code&gt;__init__&lt;/code&gt; method.&lt;/p&gt;
&lt;h2 id=&quot;encapsulating-a-row-as-a-class&quot;&gt;Encapsulating a Row as a Class&lt;/h2&gt;
&lt;p&gt;As your rows get more complex, it helps to group the widgets for each row into their own class. This keeps &lt;code&gt;MainWindow&lt;/code&gt; clean and makes it easy to access or update individual rows later.&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;class ImageInfoRow:
    def __init__(self, filename, width, height, grid_layout, row):
        self.line_edit_name = QLineEdit(filename)
        self.line_edit_width = QLineEdit(str(width))
        self.line_edit_height = QLineEdit(str(height))

        self.line_edit_area = QLineEdit(str(width * height))
        self.line_edit_area.setReadOnly(True)

        grid_layout.addWidget(self.line_edit_name, row, 0)
        grid_layout.addWidget(self.line_edit_width, row, 1)
        grid_layout.addWidget(self.line_edit_height, row, 2)
        grid_layout.addWidget(self.line_edit_area, row, 3)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Then in your main window, creating a row becomes a single line:&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;row = ImageInfoRow(filename, width, height, self.grid_layout, self.current_row)
self.image_rows.append(row)
self.current_row += 1
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Storing each &lt;code&gt;ImageInfoRow&lt;/code&gt; in a list (&lt;code&gt;self.image_rows&lt;/code&gt;) lets you access or modify any row later &amp;mdash; for example, to read back edited values or to remove a row.&lt;/p&gt;
&lt;h2 id=&quot;alternative-approaches&quot;&gt;Alternative Approaches&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;QGridLayout&lt;/code&gt; is a great fit here, but there are other ways to achieve similar results:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;QVBoxLayout with QHBoxLayout rows&lt;/strong&gt; &amp;mdash; Make each row a &lt;code&gt;QWidget&lt;/code&gt; with its own &lt;code&gt;QHBoxLayout&lt;/code&gt; containing three line edits, then add each row widget to a vertical layout. This gives you self-contained row widgets that are easy to add and remove.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;QTableView with a model&lt;/strong&gt; &amp;mdash; If you're dealing with many images or want features like sorting and filtering, a &lt;code&gt;QTableView&lt;/code&gt; backed by a &lt;code&gt;QStandardItemModel&lt;/code&gt; (or a custom model) is a more scalable approach. See the &lt;a href=&quot;https://www.pythonguis.com/tutorials/qtableview-modelviews-numpy-pandas/&quot;&gt;PyQt6 QTableView tutorial&lt;/a&gt; for details.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For a handful of images, the grid layout approach is simple and works well. For dozens or hundreds, consider switching to a model/view approach.&lt;/p&gt;
&lt;h2 id=&quot;complete-working-example&quot;&gt;Complete Working Example&lt;/h2&gt;
&lt;p&gt;Here's the full example with real image size extraction and a computed area column:&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
import os

from PyQt6.QtGui import QImageReader
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QWidget, QPushButton,
    QGridLayout, QLineEdit, QFileDialog, QLabel,
    QVBoxLayout,
)


class ImageInfoRow:
    &quot;&quot;&quot;Holds a row of QLineEdits for one image.&quot;&quot;&quot;

    def __init__(self, filename, width, height, grid_layout, row):
        self.line_edit_name = QLineEdit(filename)
        self.line_edit_width = QLineEdit(str(width))
        self.line_edit_height = QLineEdit(str(height))

        self.line_edit_area = QLineEdit(str(width * height))
        self.line_edit_area.setReadOnly(True)

        grid_layout.addWidget(self.line_edit_name, row, 0)
        grid_layout.addWidget(self.line_edit_width, row, 1)
        grid_layout.addWidget(self.line_edit_height, row, 2)
        grid_layout.addWidget(self.line_edit_area, row, 3)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle(&quot;Image Selector&quot;)
        self.setMinimumWidth(600)

        # Central widget and outer layout
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        outer_layout = QVBoxLayout()
        central_widget.setLayout(outer_layout)

        # Button to open file dialog
        self.button_open = QPushButton(&quot;Open Images&quot;)
        self.button_open.clicked.connect(self.load_images)
        outer_layout.addWidget(self.button_open)

        # Header labels
        header_layout = QGridLayout()
        header_layout.addWidget(QLabel(&quot;Filename&quot;), 0, 0)
        header_layout.addWidget(QLabel(&quot;Width&quot;), 0, 1)
        header_layout.addWidget(QLabel(&quot;Height&quot;), 0, 2)
        header_layout.addWidget(QLabel(&quot;Area&quot;), 0, 3)
        outer_layout.addLayout(header_layout)

        # Grid layout for dynamic rows
        self.grid_layout = QGridLayout()
        outer_layout.addLayout(self.grid_layout)

        # Push rows to the top
        outer_layout.addStretch()

        # Track rows
        self.current_row = 0
        self.image_rows = []

    def load_images(self):
        paths, _ = QFileDialog.getOpenFileNames(
            self,
            &quot;Select Images&quot;,
            &quot;&quot;,
            &quot;Images (*.png *.jpg *.bmp);;All Files (*)&quot;,
        )

        for path in paths:
            filename = os.path.basename(path)
            width, height = self.get_image_size(path)

            row = ImageInfoRow(
                filename, width, height,
                self.grid_layout, self.current_row,
            )
            self.image_rows.append(row)
            self.current_row += 1

    def get_image_size(self, path):
        reader = QImageReader(path)
        size = reader.size()
        if size.isValid():
            return size.width(), size.height()
        return 0, 0


app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Click &quot;Open Images,&quot; select a few image files, and you'll see a row appear for each one with the filename, dimensions, and computed area. Click the button again to add more images &amp;mdash; each new selection appends to the existing list.&lt;/p&gt;
&lt;p&gt;From here you could add scroll support (wrap the grid in a &lt;code&gt;QScrollArea&lt;/code&gt;), add a &quot;Remove&quot; button per row, or switch to a &lt;code&gt;QTableView&lt;/code&gt; if your needs grow. The core pattern &amp;mdash; tracking a row counter and placing widgets into a &lt;code&gt;QGridLayout&lt;/code&gt; &amp;mdash; stays the same regardless of how you extend it.&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PyQt6 see my book, &lt;a href=&quot;https://www.pythonguis.com/pyqt6-book/&quot;&gt;Create GUI Applications with Python &amp;amp; Qt6.&lt;/a&gt;&lt;/p&gt;</description>
	<pubDate>Wed, 23 Sep 2026 06:00:00 +0000</pubDate>
</item>
<item>
	<title>Python Bytes: #497 Faster than light profiling</title>
	<guid>https://pythonbytes.fm/episodes/show/497/faster-than-light-profiling</guid>
	<link>https://pythonbytes.fm/episodes/show/497/faster-than-light-profiling</link>
	<description>&amp;lt;strong&amp;gt;Topics covered in this episode:&amp;lt;/strong&amp;gt;&amp;lt;br&amp;gt;

&amp;lt;ul&amp;gt;
	&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;&amp;lt;a href=&quot;https://docs.python.org/3.15/library/profiling.sampling.html?featured_on=pythonbytes&quot;&amp;gt;Tachyon: A sampling profiler ships in Python 3.15's stdlib&amp;lt;/a&amp;gt;&amp;lt;/strong&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;&amp;lt;a href=&quot;https://blog.cloudflare.com/python-workers-ga/?featured_on=pythonbytes&quot;&amp;gt;Python Workers are now generally available on Cloudflare&amp;lt;/a&amp;gt;&amp;lt;/strong&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;&amp;lt;a href=&quot;https://flet.dev/blog/flet-1-0?featured_on=pythonbytes&quot;&amp;gt;Flet 1.0 - build cross-platform apps in Python&amp;lt;/a&amp;gt;&amp;lt;/strong&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;&amp;lt;a href=&quot;https://github.com/ljchang/marimo-book?featured_on=pythonbytes&quot;&amp;gt;marimo-book: Build static books from marimo notebooks&amp;lt;/a&amp;gt;&amp;lt;/strong&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Extras&amp;lt;/strong&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Joke&amp;lt;/strong&amp;gt;&amp;lt;/li&amp;gt;

&amp;lt;/ul&amp;gt;&amp;lt;a href='https://www.youtube.com/watch?v=__iKh7BNDgM' style='font-weight: bold;' data-umami-event=&quot;Livestream-Past&quot; data-umami-event-episode=&quot;497&quot;&amp;gt;Watch on YouTube&amp;lt;/a&amp;gt;&amp;lt;br&amp;gt;

&amp;lt;p&amp;gt;Sponsored by &amp;lt;strong&amp;gt;Logfire from Pydantic&amp;lt;/strong&amp;gt;: &amp;lt;a href=&quot;https://pythonbytes.fm/logfire&quot;&amp;gt;pythonbytes.fm/logfire&amp;lt;/a&amp;gt;
&amp;lt;strong&amp;gt;Connect with the hosts&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;

&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;Michael: &amp;lt;a href=&quot;https://fosstodon.org/@mkennedy&quot;&amp;gt;Mastodon&amp;lt;/a&amp;gt; / &amp;lt;a href=&quot;https://bsky.app/profile/mkennedy.codes?featured_on=pythonbytes&quot;&amp;gt;BlueSky&amp;lt;/a&amp;gt; / &amp;lt;a href=&quot;https://x.com/mkennedy?featured_on=pythonbytes&quot;&amp;gt;X&amp;lt;/a&amp;gt; / &amp;lt;a href=&quot;https://www.linkedin.com/in/mkennedy/?featured_on=pythonbytes&quot;&amp;gt;LinkedIn&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Calvin: &amp;lt;a href=&quot;https://sixfeetup.social/@calvin?featured_on=pythonbytes&quot;&amp;gt;Mastodon&amp;lt;/a&amp;gt; / &amp;lt;a href=&quot;https://bsky.app/profile/calvinhp.com?featured_on=pythonbytes&quot;&amp;gt;BlueSky&amp;lt;/a&amp;gt; / &amp;lt;a href=&quot;https://x.com/calvinhp?featured_on=pythonbytes&quot;&amp;gt;X&amp;lt;/a&amp;gt; / &amp;lt;a href=&quot;https://www.linkedin.com/in/calvinhp/?featured_on=pythonbytes&quot;&amp;gt;LinkedIn&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Show: &amp;lt;a href=&quot;https://fosstodon.org/@pythonbytes&quot;&amp;gt;Mastodon&amp;lt;/a&amp;gt; / &amp;lt;a href=&quot;https://bsky.app/profile/pythonbytes.fm&quot;&amp;gt;BlueSky&amp;lt;/a&amp;gt; / &amp;lt;a href=&quot;https://x.com/PythonBytes?featured_on=pythonbytes&quot;&amp;gt;X&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;

&amp;lt;p&amp;gt;Join us on YouTube at &amp;lt;a href=&quot;https://pythonbytes.fm/stream/live&quot;&amp;gt;&amp;lt;strong&amp;gt;pythonbytes.fm/live&amp;lt;/strong&amp;gt;&amp;lt;/a&amp;gt; to be part of the audience. Usually &amp;lt;strong&amp;gt;Tuesday at 7am PT&amp;lt;/strong&amp;gt;. Older video versions available there too.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;Finally, if you want an artisanal, hand-crafted digest of every week of the show notes in email form? Add your name and email to &amp;lt;a href=&quot;https://pythonbytes.fm/friends-of-the-show&quot;&amp;gt;our friends of the show list&amp;lt;/a&amp;gt;, we'll never share it.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Michael #1: &amp;lt;a href=&quot;https://docs.python.org/3.15/library/profiling.sampling.html?featured_on=pythonbytes&quot;&amp;gt;Tachyon: A sampling profiler ships in Python 3.15's stdlib&amp;lt;/a&amp;gt;&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;

&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;Python 3.15 adds the &amp;lt;code&amp;gt;profiling&amp;lt;/code&amp;gt; package per PEP 799: &amp;lt;code&amp;gt;profiling.tracing&amp;lt;/code&amp;gt; (where cProfile moved) and &amp;lt;code&amp;gt;profiling.sampling&amp;lt;/code&amp;gt;, the new sampler called Tachyon&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;py-spy and Austin exist but copy raw interpreter bytes with no API, so every CPython release risks breaking them; one in the stdlib is a contract to stop breaking profilers&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Defaults: 1 kHz, main thread, wall clock, and a &amp;lt;code&amp;gt;-live&amp;lt;/code&amp;gt; top-like view for poking at a slow server&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Output is flexible: pstats, &amp;lt;code&amp;gt;-flamegraph&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;-diff-flamegraph&amp;lt;/code&amp;gt; against a baseline, &amp;lt;code&amp;gt;-heatmap&amp;lt;/code&amp;gt; on source lines, &amp;lt;code&amp;gt;-opcodes&amp;lt;/code&amp;gt; for specialized bytecode, &amp;lt;code&amp;gt;-gecko&amp;lt;/code&amp;gt; for Firefox Profiler with GIL and GC markers&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Profiling modes: wall, cpu, gil (which function is starving my other threads?), and exception, plus &amp;lt;code&amp;gt;-async-aware&amp;lt;/code&amp;gt; to see the task graph instead of just &amp;lt;code&amp;gt;select()&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;-all-threads&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;-subprocesses&amp;lt;/code&amp;gt; forking a profiler per child&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Near-zero overhead for production; guidance is 10-30 second windows on representative load, and free-threaded builds divide the rate by thread count&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Attach to a running PID, same minor version only; ptrace permissions are the main friction. A 3.14 backport already exists on GitHub&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Caveat: it only sees Python frames, so 90% in &amp;lt;code&amp;gt;calculate()&amp;lt;/code&amp;gt; hides NumPy underneath. For native stacks there's Cronon from HRT, 200k samples/sec over DWARF, not yet open source&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Calvin #2: &amp;lt;a href=&quot;https://blog.cloudflare.com/python-workers-ga/?featured_on=pythonbytes&quot;&amp;gt;Python Workers are now generally available on Cloudflare&amp;lt;/a&amp;gt;&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;

&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;Python Workers are out of beta - now GA, &quot;first-class&quot; language on Cloudflare's Developer Platform&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;No more manual JS interop: bindings (queues, R2, D1, Durable Objects) now work natively in Python, e.g. self.env.QUEUE.send({...})&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Runs on Pyodide (WASM-compiled Python), with real TCP socket support for DB connectivity&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Frameworks supported: FastAPI, Django, Flask; AI libs like OpenAI SDK, LangChain, MCP&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Underlying platform work formalized as PEP 783 (PyEmscripten), after a year of discussion&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Bottom line: write real Python on Cloudflare's edge, no JS glue code required&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Calvin #3: &amp;lt;a href=&quot;https://flet.dev/blog/flet-1-0?featured_on=pythonbytes&quot;&amp;gt;Flet 1.0 - build cross-platform apps in Python&amp;lt;/a&amp;gt;&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;

&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;Flet hits 1.0 - build Flutter-backed apps from pure Python, no frontend experience needed&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;One codebase targets six platforms: iOS, Android, Windows, macOS, Linux, web&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;150+ built-in UI controls, plus support for custom controls / wrapping Flutter packages&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Mobile now supports real Python packages: NumPy, pandas, Pillow, cryptography&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Comes with pytest-based UI testing and an MCP integration for AI coding assistants&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Milestone lands 4+ years after its first PyPI release (Sept 2022) - signals &quot;production ready,&quot; not experimental&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Michael #4:&amp;lt;/strong&amp;gt; &amp;lt;a href=&quot;https://github.com/ljchang/marimo-book?featured_on=pythonbytes&quot;&amp;gt;marimo-book: Build static books from marimo notebooks&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;marimo-book is a Jupyter-Book-style static site generator built specifically for marimo .py notebooks. It ships polished multi-page sites with Material for MkDocs theming, full-text search, dark mode, and code copy, plus a content-hashed incremental build cache that drops rebuilds from 100+ seconds to roughly 3 seconds on real books. Standout extras include anywidget rendering without a kernel, static reactivity for discrete sliders via pre-rendered lookup tables, an opt-in WASM/Pyodide mode per chapter, and per-chapter launch buttons.&amp;lt;/p&amp;gt;

&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;If you've wanted to publish a marimo notebook as a real book or course site without hosting a kernel, marimo-book gives you the static, searchable, fast-loading output you'd expect from Jupyter Book.&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Alpha (0.1.x), but in production:&amp;lt;/strong&amp;gt; pin marimo-book&amp;gt;=0.1.5,&amp;amp;lt;0.2; the book.yml schema is stable for v0.1, and &amp;lt;a href=&quot;http://dartbrains.org/?featured_on=pythonbytes&quot;&amp;gt;dartbrains.org&amp;lt;/a&amp;gt; is a real-world user.&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Two-stage build by design:&amp;lt;/strong&amp;gt; a marimo-aware preprocessor emits plain Markdown + inline HTML, then mkdocs (Material today, zensical tomorrow) renders it. Not a mkdocs plugin, so the shell stays swappable.&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Interactive widgets without a kernel:&amp;lt;/strong&amp;gt; anywidget Canvas/Three.js/Plotly mounts render statically, and mo.ui.slider with explicit steps gets pre-computed as a static lookup table.&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;WASM escape hatch per chapter:&amp;lt;/strong&amp;gt; set mode: wasm and the chapter routes through marimo's MarimoIslandGenerator, shipping the marimo runtime + Pyodide bundle for full reactivity where you need it.&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Per-chapter launch buttons and extras:&amp;lt;/strong&amp;gt; readers can jump to molab, GitHub, or a downloaded .py; optional [social], [linkcheck], and [pdf] extras cover OG cards, htmlproofer, and WeasyPrint PDF export.&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Sandboxed notebooks:&amp;lt;/strong&amp;gt; the sandbox mode reads PEP 723 inline metadata and provisions per-notebook envs via uv for portable builds, at the cost of slower first runs.&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Extras&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Calvin&amp;lt;/strong&amp;gt;:&amp;lt;/p&amp;gt;

&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;Great overview of a new feature in Python 3.15 - &amp;lt;a href=&quot;https://realpython.com/python315-frozendict/?featured_on=pythonbytes&quot;&amp;gt;frozendict&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Michael&amp;lt;/strong&amp;gt;:&amp;lt;/p&amp;gt;

&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&quot;https://krebsonsecurity.com/2026/09/microsoft-plugs-nearly-1000-security-holes/?featured_on=pythonbytes&quot;&amp;gt;Microsoft Plugs Nearly 1,000 Security Holes in Windows&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&quot;https://pybay.org?featured_on=pythonbytes&quot;&amp;gt;I’ll be speaking at PyBay 2026&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&quot;https://www.pycon-nl.org?featured_on=pythonbytes&quot;&amp;gt;PyCon NL on October 15 in Utrecht&amp;lt;/a&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;Heading to Europe in October? PyCon NL is October 15th in Utrecht. One day, three tracks, about 350 people. It's an hour by train from Schiphol. There's also a session just for community organizers from groups like PyLadies, PyData, and Django. And the location fits. The Netherlands is where Python itself was born.&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Joke: &amp;lt;a href=&quot;https://www.youtube.com/watch?v=FG8sUgjBGXs&quot;&amp;gt;You have homework&amp;lt;/a&amp;gt; (no really ;) )&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;Watch &amp;lt;a href=&quot;https://www.youtube.com/watch?v=FG8sUgjBGXs&quot;&amp;gt;&amp;lt;strong&amp;gt;Interview with Big Data engineer in 2026&amp;lt;/strong&amp;gt;&amp;lt;/a&amp;gt; by Kai Lentit&amp;lt;/p&amp;gt;</description>
	<pubDate>Wed, 23 Sep 2026 03:45:07 +0000</pubDate>
</item>
<item>
	<title>Python Insider: The Python documentation is now available in Persian</title>
	<guid>https://blog.python.org/2026/09/the-python-documentation-is-now-available-in-persian/</guid>
	<link>https://blog.python.org/2026/09/the-python-documentation-is-now-available-in-persian/</link>
	<pubDate>Wed, 23 Sep 2026 00:00:00 +0000</pubDate>
</item>
<item>
	<title>PyCoder’s Weekly: Issue #753: frozendict, pytest Plugins, re.prefixmatch(), and More (2026-09-22)</title>
	<guid>https://pycoders.com/issues/753</guid>
	<link>https://pycoders.com/issues/753</link>
	<description>&lt;p&gt; &lt;span&gt;#753 – SEPTEMBER 22, 2026&lt;/span&gt;&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/issues/753/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/17077/feed&quot; target=&quot;_blank&quot;&gt;Python 3.15 Preview: &lt;code&gt;frozendict&lt;/code&gt;&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; Preview Python 3.15&amp;rsquo;s frozendict: build immutable mappings you can hash, cache, and share across threads, and learn where the freeze stops.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/17077/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/17104/feed&quot; target=&quot;_blank&quot;&gt;Quiz: Python 3.15 Preview: &lt;code&gt;frozendict&lt;/code&gt;&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/17104/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/17106/feed&quot; target=&quot;_blank&quot;&gt;claude -p “migrate our CI config to RWX”&lt;/a&gt;&lt;/h3&gt; &lt;a href=&quot;https://pycoders.com/link/17106/feed&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;https://cdn.pycoders.com/04300b61ae0294a36430ce870a393c92&quot; alt=&quot;alt&quot; /&gt;&lt;/a&gt; &lt;p&gt; Python is powering AI. Your build platform should keep up. RWX is CI/CD rebuilt for the agentic dev cycle: content-based caching, graph-based task execution, and agent sandboxes that share config and cache with CI. Teams at Honeycomb, ClickFunnels, and Verkada have already switched. &lt;a href=&quot;https://pycoders.com/link/17106/feed&quot; target=&quot;_blank&quot;&gt;See Why Engineers Love RWX → rwx.com&lt;/a&gt;&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/17106/feed&quot; target=&quot;_blank&quot;&gt;RWX (READWRITEEXECUTE)&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/17089/feed&quot; target=&quot;_blank&quot;&gt;pytest Plugins That Actually Change How You Test in 2026&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; Seven pytest plugins beyond the built-ins that changed how Peyton tests: pytest-httpx for HTTP mocking, pytest-randomly for order-dependency detection, anyio&amp;rsquo;s built-in pytest plugin for backend-agnostic async tests, syrupy for snapshot testing, pytest-watch for continuous feedback, freezegun for deterministic time, pytest-cov for coverage enforcement.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/17089/feed&quot; target=&quot;_blank&quot;&gt;PEYTON GREEN&lt;/a&gt; • Shared by Anonymous&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/17075/feed&quot; target=&quot;_blank&quot;&gt;Soft-Deprecating &lt;code&gt;re.match()&lt;/code&gt;&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; To provide consistency with other languages, Python 3.15 is soft-deprecating &lt;code&gt;re.match()&lt;/code&gt; and introducing &lt;code&gt;re.prefixmatch()&lt;/code&gt; instead. This article explains why.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/17075/feed&quot; target=&quot;_blank&quot;&gt;HUGO VAN KEMENADE&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/17102/feed&quot; target=&quot;_blank&quot;&gt;PEP 847: Problem Details for the Simple Repository API (Draft)&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/17102/feed&quot; target=&quot;_blank&quot;&gt;PYTHON.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/17101/feed&quot; target=&quot;_blank&quot;&gt;PEP 846: Docstrings for Type Aliases (Draft)&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/17101/feed&quot; target=&quot;_blank&quot;&gt;PYTHON.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/17080/feed&quot; target=&quot;_blank&quot;&gt;PEP 845: Leading-Dot Value Patterns (Draft)&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/17080/feed&quot; target=&quot;_blank&quot;&gt;PYTHON.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/17100/feed&quot; target=&quot;_blank&quot;&gt;Share How You Use Django With Django Probe&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/17100/feed&quot; target=&quot;_blank&quot;&gt;TIM SCHILLING&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/17087/feed&quot; target=&quot;_blank&quot;&gt;2026 PSF Board Election Results&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/17087/feed&quot; target=&quot;_blank&quot;&gt;PYTHON SOFTWARE FOUNDATION&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/17097/feed&quot; target=&quot;_blank&quot;&gt;2026 Python Packaging Council Election Results!&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/17097/feed&quot; target=&quot;_blank&quot;&gt;PYTHON SOFTWARE FOUNDATION&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/17091/feed&quot; target=&quot;_blank&quot;&gt;Serve the Change Password Well-Known URL&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; When a password manager detects that a user’s password has been leaked, it can prompt them to change it, but the password change URL varies by site. The answer to such discovery problems is &lt;code&gt;.well-known/&lt;/code&gt; URL namespace. This article talks about how to build it in Django.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/17091/feed&quot; target=&quot;_blank&quot;&gt;ADAM JOHNSON&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/17099/feed&quot; target=&quot;_blank&quot;&gt;Getting Started With Rust as Python Devs&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; Talk Python discusses Rust for Pythonistas with guest Christopher Trudeau. Learn why Rust is a go-to language for Python tools and extensions and how you can get started writing Python modules with PyO3.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/17099/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/17095/feed&quot; target=&quot;_blank&quot;&gt;Make Public API Tests Repeatable in Python&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; Network-dependent API tests can be slow and flaky. This article shows how VCR.py records real HTTP interactions once and replays them for fast, repeatable, offline tests. Use cases include GitHub API client and pytest, CI-safe testing, and live API tests.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/17095/feed&quot; target=&quot;_blank&quot;&gt;CODECUT.AI&lt;/a&gt; • Shared by &lt;a href=&quot;https://pycoders.com/link/17084/feed&quot; target=&quot;_blank&quot;&gt;Khuyen Tran&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/17088/feed&quot; target=&quot;_blank&quot;&gt;Primer on Jinja Templating&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; With Jinja, you can build rich templates that power the front end of your web applications. But you can use Jinja without a web framework running in the background. Anytime you want to create text files with programmatic content, Jinja can help you out.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/17088/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/17073/feed&quot; target=&quot;_blank&quot;&gt;Announcing the PSF Strategic Plan 2026&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; In May, the Python Software Foundation (PSF) shared the high-level goals of their Strategic Plan. It was sent out for community feedback and now has been adopted. It is a five-year plan covering 2026 to 2031.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/17073/feed&quot; target=&quot;_blank&quot;&gt;PYTHON SOFTWARE FOUNDATION&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/17072/feed&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;UnboundLocalError&lt;/code&gt;: It&amp;rsquo;s Scope Decided in Advance&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; The variable has a value elsewhere in your program, just not in the scope Python already decided this line belongs to. Here&amp;rsquo;s how Python actually assigns scope, and why the fix usually isn&amp;rsquo;t &lt;code&gt;global&lt;/code&gt;.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/17072/feed&quot; target=&quot;_blank&quot;&gt;SYSTEM CRAFT PRESS&lt;/a&gt; • Shared by Bob Morrison&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/17081/feed&quot; target=&quot;_blank&quot;&gt;Functionally Zen&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; Practical tenets for Python simplicity: why pure functions, immutable data, and composition reduce complexity, cut mocking overhead, and keep side effects contained.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/17081/feed&quot; target=&quot;_blank&quot;&gt;KYLE ADAMS&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/17078/feed&quot; target=&quot;_blank&quot;&gt;How to Review AI-Generated Python Code Efficiently&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; Learn an efficient workflow to review AI-generated code in Python: run ruff, mypy, bandit, and pytest, then catch the bugs agents get wrong.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/17078/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/17098/feed&quot; target=&quot;_blank&quot;&gt;Agentic Engineering in Python: From Vibes to Evidence&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; Move from vibe coding to agentic engineering in Python, using tests, types, and code review to prove an AI agent&amp;rsquo;s changes are safe to keep.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/17098/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/17090/feed&quot; target=&quot;_blank&quot;&gt;Automating EDA With &lt;code&gt;fg-data-profiling&lt;/code&gt;&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; Automate exploratory data analysis by transforming DataFrames into interactive reports with one command from fg-data-profiling.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/17090/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/17103/feed&quot; target=&quot;_blank&quot;&gt;5 Design Patterns Used in My New Habit Tracker App&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; Bob has built a Django + HTMX based habit tracker. This article covers five small Python and design patterns used in his code.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/17103/feed&quot; target=&quot;_blank&quot;&gt;BOB BELDERBOS&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/17082/feed&quot; target=&quot;_blank&quot;&gt;fastapi-security-headers: OWASP Security Headers for FastAPI&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/17082/feed&quot; target=&quot;_blank&quot;&gt;GITHUB.COM/ALEJANDROTG-CODE&lt;/a&gt; • Shared by Alejandro Tacoronte González&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/17086/feed&quot; target=&quot;_blank&quot;&gt;citry: Frontend Web Framework for Python&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/17086/feed&quot; target=&quot;_blank&quot;&gt;GITHUB.COM/CITRY-DEV&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/17092/feed&quot; target=&quot;_blank&quot;&gt;tonio: A Multi-Threaded Async Runtime for Python&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/17092/feed&quot; target=&quot;_blank&quot;&gt;GITHUB.COM/GI0BARO&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/17076/feed&quot; target=&quot;_blank&quot;&gt;django-absurd: Django Integration for Postgres Absurd&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/17076/feed&quot; target=&quot;_blank&quot;&gt;GITHUB.COM/LINCOLNLOOP&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/17096/feed&quot; target=&quot;_blank&quot;&gt;securo: OSS Personal Finance Manager&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/17096/feed&quot; target=&quot;_blank&quot;&gt;GITHUB.COM/SECURO-FINANCE&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/17094/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; September 23, 2026&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/17094/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/17083/feed&quot; target=&quot;_blank&quot;&gt;Django on the Med 2026&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; September 23 to September 26, 2026&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/17083/feed&quot; target=&quot;_blank&quot;&gt;DJANGOMED.EU&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/17079/feed&quot; target=&quot;_blank&quot;&gt;PythonCamp Rügen 2026&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; September 26 to September 28, 2026&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/17079/feed&quot; target=&quot;_blank&quot;&gt;BARCAMPS.EU&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/17093/feed&quot; target=&quot;_blank&quot;&gt;Python Sheffield&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; September 29, 2026&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/17093/feed&quot; target=&quot;_blank&quot;&gt;GOOGLE.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/17074/feed&quot; target=&quot;_blank&quot;&gt;Python Southwest Florida (PySWFL)&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; September 30, 2026&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/17074/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/17085/feed&quot; target=&quot;_blank&quot;&gt;PyBay 2026&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; October 3 to October 4, 2026&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/17085/feed&quot; target=&quot;_blank&quot;&gt;PYBAY.ORG&lt;/a&gt;&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 #753.&lt;br /&gt;&lt;a href=&quot;https://pycoders.com/issues/753/feed&quot;&gt;View in Browser »&lt;/a&gt;&lt;/p&gt; &lt;img src=&quot;https://pycoders.com/issues/753/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;</description>
	<pubDate>Tue, 22 Sep 2026 19:30:00 +0000</pubDate>
</item>
<item>
	<title>Talk Python to Me: #564: EVE Online Departs for Python 3</title>
	<guid>https://talkpython.fm/episodes/show/564/eve-online-departs-for-python-3</guid>
	<link>https://talkpython.fm/episodes/show/564/eve-online-departs-for-python-3</link>
	<description>Every ship in EVE Online eventually undocks and leaves the station. This time, it's the whole game. EVE has run on Python 2 since it launched in 2003, all 2.4 million lines of it, on a custom Stackless interpreter that stopped at 3.8 and was archived last year. Destination: Python 3.12. The route runs through 6,500 lines of division that decide who wins a fight, and 100 gigabytes of pickled Python objects that have to survive the jump intact. Kristinn Sigurbergsson was on this show ten years ago. He's back, with Jamie Bannister, who is flying the EVE Online migration right now, and Thomas Dähling, who took EVE Frontier through first. In EVE, a destroyed ship is gone for good. There are no do-overs, and the universe has to stay online the whole way.&amp;lt;br/&amp;gt;
&amp;lt;br/&amp;gt;
&amp;lt;strong&amp;gt;Episode sponsors&amp;lt;/strong&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;br/&amp;gt;
&amp;lt;a href='https://talkpython.fm/sentry'&amp;gt;Sentry Error Monitoring, Code talkpython26&amp;lt;/a&amp;gt;&amp;lt;br&amp;gt;
&amp;lt;a href='https://talkpython.fm/training'&amp;gt;Talk Python Courses&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;br/&amp;gt;
&amp;lt;h2 class=&quot;links-heading mb-4&quot;&amp;gt;Links from the show&amp;lt;/h2&amp;gt;
&amp;lt;div&amp;gt;&amp;lt;strong&amp;gt;Guests&amp;lt;/strong&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;strong&amp;gt;Jamie Bannister&amp;lt;/strong&amp;gt;: &amp;lt;a href=&quot;https://www.linkedin.com/in/jamiebannister/?featured_on=talkpython&quot; target=&quot;_blank&quot; &amp;gt;linkedin.com&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;strong&amp;gt;Thomas Dähling&amp;lt;/strong&amp;gt;: &amp;lt;a href=&quot;https://www.linkedin.com/in/thomasdaehling/?featured_on=talkpython&quot; target=&quot;_blank&quot; &amp;gt;linkedin.com&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;strong&amp;gt;Kristinn Sigurbergsson&amp;lt;/strong&amp;gt;: &amp;lt;a href=&quot;https://www.linkedin.com/in/kristinn-sigurbergsson-3260b4137/?featured_on=talkpython&quot; target=&quot;_blank&quot; &amp;gt;linkedin.com&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;br/&amp;gt;
&amp;lt;strong&amp;gt;EVE Online&amp;lt;/strong&amp;gt;: &amp;lt;a href=&quot;https://www.eveonline.com?featured_on=talkpython&quot; target=&quot;_blank&quot; &amp;gt;www.eveonline.com&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;strong&amp;gt;EVE Frontier&amp;lt;/strong&amp;gt;: &amp;lt;a href=&quot;https://evefrontier.com?featured_on=talkpython&quot; target=&quot;_blank&quot; &amp;gt;evefrontier.com&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;strong&amp;gt;2.4 million lines of Python&amp;lt;/strong&amp;gt;: &amp;lt;a href=&quot;https://www.eveonline.com/news/view/the-move-to-python-3-begins?featured_on=talkpython&quot; target=&quot;_blank&quot; &amp;gt;www.eveonline.com&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;strong&amp;gt;Example of graphics, ships walk-through&amp;lt;/strong&amp;gt;: &amp;lt;a href=&quot;https://www.youtube.com/watch?v=e2X1MIR1KMs&quot; target=&quot;_blank&quot; &amp;gt;www.youtube.com&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;strong&amp;gt;Stackless&amp;lt;/strong&amp;gt;: &amp;lt;a href=&quot;https://www.stackless.com/?featured_on=talkpython&quot; target=&quot;_blank&quot; &amp;gt;www.stackless.com&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;strong&amp;gt;futurize&amp;lt;/strong&amp;gt;: &amp;lt;a href=&quot;https://python-future.org/futurize.html?featured_on=talkpython&quot; target=&quot;_blank&quot; &amp;gt;python-future.org&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;strong&amp;gt;blue&amp;lt;/strong&amp;gt;: &amp;lt;a href=&quot;https://github.com/carbonengine/blue?featured_on=talkpython&quot; target=&quot;_blank&quot; &amp;gt;github.com&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;strong&amp;gt;Dev blog on the Frontier upgrade&amp;lt;/strong&amp;gt;: &amp;lt;a href=&quot;https://evefrontier.com/en/news/moving-into-the-future-upgrading-to-python-3?featured_on=talkpython&quot; target=&quot;_blank&quot; &amp;gt;evefrontier.com&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;strong&amp;gt;Upgrading CARBON to Python 3&amp;lt;/strong&amp;gt;: &amp;lt;a href=&quot;https://www.youtube.com/watch?v=4uaEhx7LNAc&quot; target=&quot;_blank&quot; &amp;gt;www.youtube.com&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;strong&amp;gt;carbon-scheduler&amp;lt;/strong&amp;gt;: &amp;lt;a href=&quot;https://github.com/carbonengine/scheduler?featured_on=talkpython&quot; target=&quot;_blank&quot; &amp;gt;github.com&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;strong&amp;gt;Scheduling in Carbon: Leaving Stackless Python Behind&amp;lt;/strong&amp;gt;: &amp;lt;a href=&quot;https://www.youtube.com/watch?v=-x299qHLQs0&quot; target=&quot;_blank&quot; &amp;gt;www.youtube.com&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;strong&amp;gt;All of Carbon is on GitHub now&amp;lt;/strong&amp;gt;: &amp;lt;a href=&quot;https://github.com/carbonengine?featured_on=talkpython&quot; target=&quot;_blank&quot; &amp;gt;github.com&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;strong&amp;gt;did a whole course&amp;lt;/strong&amp;gt;: &amp;lt;a href=&quot;https://training.talkpython.fm/courses/python-concurrency-deep-dive&quot; target=&quot;_blank&quot; &amp;gt;training.talkpython.fm&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;strong&amp;gt;Trade Wars BBS (Wikipedia)&amp;lt;/strong&amp;gt;: &amp;lt;a href=&quot;https://en.wikipedia.org/wiki/Trade_Wars?featured_on=talkpython&quot; target=&quot;_blank&quot; &amp;gt;en.wikipedia.org&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;strong&amp;gt;TW 3002 - TradeWars 2002, reimagined as a modern browser game&amp;lt;/strong&amp;gt;: &amp;lt;a href=&quot;https://www.reddit.com/r/bbs/comments/1tyygnq/tw_3002_tradewars_2002_reimagined_as_a_modern/?featured_on=talkpython&quot; target=&quot;_blank&quot; &amp;gt;www.reddit.com&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;br/&amp;gt;
&amp;lt;strong&amp;gt;Watch this episode on YouTube&amp;lt;/strong&amp;gt;: &amp;lt;a href=&quot;https://www.youtube.com/watch?v=ybboBZlQoAM&quot; target=&quot;_blank&quot; &amp;gt;youtube.com&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;strong&amp;gt;Episode #564 deep-dive&amp;lt;/strong&amp;gt;: &amp;lt;a href=&quot;https://talkpython.fm/episodes/show/564/eve-online-departs-for-python-3#takeaways-anchor&quot; target=&quot;_blank&quot; &amp;gt;talkpython.fm/564&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;strong&amp;gt;Episode transcripts&amp;lt;/strong&amp;gt;: &amp;lt;a href=&quot;https://talkpython.fm/episodes/transcript/564/eve-online-departs-for-python-3&quot; target=&quot;_blank&quot; &amp;gt;talkpython.fm&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;br/&amp;gt;
&amp;lt;strong&amp;gt;Theme Song: Developer Rap&amp;lt;/strong&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;strong&amp;gt;🥁 Served in a Flask 🎸&amp;lt;/strong&amp;gt;: &amp;lt;a href=&quot;https://talkpython.fm/flasksong&quot; target=&quot;_blank&quot; &amp;gt;talkpython.fm/flasksong&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;br/&amp;gt;
&amp;lt;strong&amp;gt;---==  Don't be a stranger  ==---&amp;lt;/strong&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;strong&amp;gt;YouTube&amp;lt;/strong&amp;gt;: &amp;lt;a href=&quot;https://talkpython.fm/youtube&quot; target=&quot;_blank&quot; &amp;gt;&amp;lt;i class=&quot;fa-brands fa-youtube&quot;&amp;gt;&amp;lt;/i&amp;gt; youtube.com/@talkpython&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;br/&amp;gt;
&amp;lt;strong&amp;gt;Bluesky&amp;lt;/strong&amp;gt;: &amp;lt;a href=&quot;https://bsky.app/profile/talkpython.fm&quot; target=&quot;_blank&quot; &amp;gt;@talkpython.fm&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;strong&amp;gt;Mastodon&amp;lt;/strong&amp;gt;: &amp;lt;a href=&quot;https://fosstodon.org/web/@talkpython&quot; target=&quot;_blank&quot; &amp;gt;&amp;lt;i class=&quot;fa-brands fa-mastodon&quot;&amp;gt;&amp;lt;/i&amp;gt; @talkpython@fosstodon.org&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;strong&amp;gt;X.com&amp;lt;/strong&amp;gt;: &amp;lt;a href=&quot;https://x.com/talkpython&quot; target=&quot;_blank&quot; &amp;gt;&amp;lt;i class=&quot;fa-brands fa-twitter&quot;&amp;gt;&amp;lt;/i&amp;gt; @talkpython&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;br/&amp;gt;
&amp;lt;strong&amp;gt;Michael on Bluesky&amp;lt;/strong&amp;gt;: &amp;lt;a href=&quot;https://bsky.app/profile/mkennedy.codes?featured_on=talkpython&quot; target=&quot;_blank&quot; &amp;gt;@mkennedy.codes&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;strong&amp;gt;Michael on Mastodon&amp;lt;/strong&amp;gt;: &amp;lt;a href=&quot;https://fosstodon.org/web/@mkennedy&quot; target=&quot;_blank&quot; &amp;gt;&amp;lt;i class=&quot;fa-brands fa-mastodon&quot;&amp;gt;&amp;lt;/i&amp;gt; @mkennedy@fosstodon.org&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;
&amp;lt;strong&amp;gt;Michael on X.com&amp;lt;/strong&amp;gt;: &amp;lt;a href=&quot;https://x.com/mkennedy?featured_on=talkpython&quot; target=&quot;_blank&quot; &amp;gt;&amp;lt;i class=&quot;fa-brands fa-twitter&quot;&amp;gt;&amp;lt;/i&amp;gt; @mkennedy&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;&amp;lt;/div&amp;gt;</description>
	<pubDate>Tue, 22 Sep 2026 18:24:30 +0000</pubDate>
</item>
<item>
	<title>Django Weblog: New Technical Governance Approved</title>
	<guid>https://www.djangoproject.com/weblog/2026/sep/22/new-technical-governance-approved/</guid>
	<link>https://www.djangoproject.com/weblog/2026/sep/22/new-technical-governance-approved/</link>
	<description>&lt;p&gt;Both the &lt;a href=&quot;https://github.com/django/deps/pull/107&quot;&gt;Steering Council&lt;/a&gt; and &lt;a href=&quot;https://github.com/django/dsf-minutes/blob/main/2026/2026-09-10.md&quot;&gt;DSF Board&lt;/a&gt; have approved DEP 19, which implements our new technical governance. Thank you to everyone who read the document and participated in the process! This was the effort of two different DSF boards and dozens of community members.&lt;/p&gt;
&lt;p&gt;These changes were a revisitation of Django’s technical governance in which a simplification and reduction was made to make it more approachable to more people.&lt;/p&gt;
&lt;p&gt;The most exciting change (in my opinion), is the new &lt;a href=&quot;https://github.com/django/deps/blob/main/accepted/0019-technical-governance-6x.rst#steering-council-eligibility&quot;&gt;Steering Council Eligibility criteria&lt;/a&gt;. We defined a set of wide range qualities that members of the Steering Council could possess that would be advantageous. The hope is that this helps community members realize they are qualified to be a Steering Council member and stand for election.&lt;/p&gt;
&lt;p&gt;The next steps are to update the documentation in the &lt;a href=&quot;https://github.com/django/django&quot;&gt;Django&lt;/a&gt; and &lt;a href=&quot;https://github.com/django/djangoproject.com&quot;&gt;djangoproject.com&lt;/a&gt; repositories with any necessary updates. After that the DEP will move to the &lt;a href=&quot;https://github.com/django/deps/tree/main/final&quot;&gt;final state and folder&lt;/a&gt;.&lt;/p&gt;</description>
	<pubDate>Tue, 22 Sep 2026 11:00:00 +0000</pubDate>
</item>
<item>
	<title>LernerPython blog, from Reuven Lerner: Pandas nullable integers: Keeping ints with Int64 and pd.NA</title>
	<guid>https://lernerpython.com/2026/09/22/pandas-nullable-integers-keeping-ints-with-int64-and-pd-na/</guid>
	<link>https://lernerpython.com/2026/09/22/pandas-nullable-integers-keeping-ints-with-int64-and-pd-na/</link>
	<description>&lt;img src=&quot;https://lernerpython.com/wp-content/uploads/2026/09/2026-09-22.png&quot; alt=&quot;Pandas nullable integers: Keeping ints with Int64 and pd.NA&quot; class=&quot;wp-image-17791&quot; width=&quot;1624&quot; height=&quot;1139&quot; /&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;You have a Python Pandas series with ints + NaN. You don&amp;#8217;t want float forced on you.&lt;/p&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;Solution: Use the &amp;#8220;extension&amp;#8221; type Int64 (note Initial Caps) and pd.NA:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;s = Series([10, pd.NA, 30], dtype='Int64')  # must name explicitly&lt;/code&gt;&lt;/pre&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;s is:&lt;/p&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;0      10&lt;br /&gt;1    &amp;lt;NA&amp;gt;&lt;br /&gt;2      30&lt;br /&gt;dtype: Int64&lt;/p&gt;
&lt;p&gt;The post &lt;a href=&quot;https://lernerpython.com/2026/09/22/pandas-nullable-integers-keeping-ints-with-int64-and-pd-na/&quot;&gt;Pandas nullable integers: Keeping ints with Int64 and pd.NA&lt;/a&gt; appeared first on &lt;a href=&quot;https://lernerpython.com&quot;&gt;LernerPython&lt;/a&gt;.&lt;/p&gt;</description>
	<pubDate>Tue, 22 Sep 2026 06:00:00 +0000</pubDate>
</item>
<item>
	<title>Andre Roberge: Breaking a four year hiatus to talk about the export keyword (PEPs 842 and 843)</title>
	<guid>https://aroberge.blogspot.com/2026/09/breaking-four-year-hiatus-to-talk-about.html</guid>
	<link>https://aroberge.blogspot.com/2026/09/breaking-four-year-hiatus-to-talk-about.html</link>
	<description>&lt;p&gt;I started writing this blog in 2004 and, every year I published at least one post until 2022. So four years of not writing about Python ... and, actually, four years of (essentially) not writing any code. It is time for me to start writing and coding again, this time about a proposed new Python keyword: &lt;span&gt;&lt;b&gt;export&lt;/b&gt;&lt;/span&gt;.&lt;/p&gt;&lt;h3&gt;Sources of frustration&lt;/h3&gt;&lt;div&gt;If you've ever written code and made it available for others to use, you might have encountered one or more of the following sources of frustration:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;You change the names of some object intended for your private use only only to have users filing an issue, complaining that you broke their code.&lt;/li&gt;&lt;li&gt;You try to ensure that your public names are well-documented by adding them to &lt;span&gt;&lt;b&gt;__all__&lt;/b&gt;&lt;/span&gt; but find that this forces you to duplicate the information at two different locations leading you sometimes to forget to keep &lt;span&gt;&lt;b&gt;__all__&lt;/b&gt;&lt;/span&gt; up to date.&lt;/li&gt;&lt;li&gt;You read through the code of libraries you use but find it difficult to figure out which are meant to be public names and that you can safely use and which are meant to be purely private names since the required information is not immediately available where the objects are defined. Occasionally, you might see some names that begin with an underscore, which you know from &lt;i&gt;&lt;b&gt;convention &lt;/b&gt;&lt;/i&gt;that this is likely to indicate a private name ... but you cannot safely assume that names that do not begin with an underscore are meant to be public.&lt;/li&gt;&lt;li&gt;You decide to use some type-based linters and find that you suddenly have to write code differently, such as &lt;span&gt;&lt;b&gt;from module import NAME &lt;span&gt;as NAME&lt;/span&gt;&lt;/b&gt;&lt;/span&gt; instead of the perfectly simple and valid Python syntax,&amp;nbsp;&lt;span&gt;&lt;b&gt;from module import NAME,&lt;/b&gt;&lt;/span&gt; only so that the stupid tool will understand your intention.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;p&gt;There are other related sources of frustration, but this should suffice to give you the context. To address these and related issues, three related PEPs have been written:&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://peps.python.org/pep-0842/&quot; target=&quot;_blank&quot;&gt;PEP 842&lt;/a&gt;, since withdrawn,&amp;nbsp;was written first, and suggested many changes, including creating a new list named &lt;span&gt;&lt;b&gt;__export__&lt;/b&gt;&lt;/span&gt;, somewhat related to &lt;b&gt;&lt;span&gt;__all__&lt;/span&gt;&lt;/b&gt;, and which could be used to enforce a clear separation between public and private names. With &lt;b&gt;the initial version of&lt;/b&gt; PEP 842, attempting to import a private variable would have resulted in an &lt;b&gt;&lt;span&gt;ImportError&lt;/span&gt;&lt;/b&gt;. PEP 842 also included the introduction of &lt;b&gt;&lt;span&gt;export &lt;/span&gt;&lt;/b&gt;as a new &quot;soft&quot; keyword, useable in the following five cases:&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Simple declaration: &lt;span&gt;&lt;b&gt;export NAME&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;Assignment: &lt;/span&gt;&lt;b&gt;&lt;span&gt;export NAME = VALUE&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;&lt;li&gt;Declaring a function or class public: &lt;b&gt;&lt;span&gt;export def NAME()&lt;/span&gt;&lt;/b&gt; ... or &lt;b&gt;&lt;span&gt;export class NAME&lt;/span&gt;&lt;/b&gt; ...&lt;/li&gt;&lt;li&gt;A module re-export statement: &lt;b&gt;&lt;span&gt;from MODULE export NAME&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://peps.python.org/pep-0843/&quot; target=&quot;_blank&quot;&gt;PEP 843&lt;/a&gt;&amp;nbsp;also suggested the introduction of &lt;b&gt;&lt;span&gt;export &lt;/span&gt;&lt;/b&gt;as a soft keyword, but focusing on indicating that names imported into a module were meant to be public.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;This means that the following three types of statements:&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;span&gt;from module export A [, B, ...]&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;span&gt;from module export A as B, ....&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;span&gt;from module export *&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;would behave like their corresponding from ... import statements with the exception that all the names imported would be automatically added to &lt;b&gt;&lt;span&gt;__all__&lt;/span&gt;&lt;/b&gt;, thereby ensuring that &lt;b&gt;&lt;span&gt;__all__ &lt;/span&gt;&lt;/b&gt;would always be kept up to date without needing any duplicate entry from the programmer. Furthermore, a user reading the source code would immediately see which names were meant to be made public.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://peps.python.org/pep-0844/&quot; target=&quot;_blank&quot;&gt;PEP 844&lt;/a&gt;: instead of introducing an new keyword, PEP 844 suggests the addition of two functions from the publicly available&amp;nbsp;&lt;a href=&quot;https://public.readthedocs.io/en/stable/&quot; target=&quot;_blank&quot;&gt;atpublic&lt;/a&gt;&amp;nbsp;to Python's builtins. This library includes two decorators, &lt;b&gt;&lt;span&gt;@public&lt;/span&gt;&lt;/b&gt; and &lt;b&gt;&lt;span&gt;@private&lt;/span&gt;&lt;/b&gt;, that can be used to indicate how these names are intended to be used; in addition, @public automatically adds the name of the function or class to &lt;b&gt;&lt;span&gt;__all__&lt;/span&gt;&lt;/b&gt;. Simple assignments can be also declared to be &quot;public&quot; using a cleverly built function.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;The introductions of these PEPs has lead to many long discussion threads on Python-discuss (&lt;a href=&quot;https://discuss.python.org/t/pep-842-module-exports/108353&quot; target=&quot;_blank&quot;&gt;PEP 842 - discuss&lt;/a&gt;,&amp;nbsp;&lt;a href=&quot;https://discuss.python.org/t/pep-842-module-exports-new-revision/108460&quot; target=&quot;_blank&quot;&gt;PEP 842 - revision&lt;/a&gt;,&amp;nbsp;&lt;a href=&quot;https://discuss.python.org/t/pep-842-postmortem-how-do-we-protect-the-standard-library/108575&quot; target=&quot;_blank&quot;&gt;PEP 842 - postmortem&lt;/a&gt;,&amp;nbsp;&lt;a href=&quot;https://discuss.python.org/t/pep-843-export-statement-for-dry-re-exports/108687&quot; target=&quot;_blank&quot;&gt;PEP 843 - discuss&lt;/a&gt;,&amp;nbsp;&lt;a href=&quot;https://discuss.python.org/t/pep-844-public-and-private-builtins/108515/32&quot; target=&quot;_blank&quot;&gt;PEP 844 - discuss&lt;/a&gt;,&amp;nbsp;&lt;a href=&quot;https://discuss.python.org/t/python-api-exposure-mechanisms-public-internal-determining-status-quo/108869/40&quot; target=&quot;_blank&quot;&gt;General discussion about what public means in Python&lt;/a&gt;, and perhaps other).&amp;nbsp;&lt;/p&gt;&lt;p&gt;For now the discussion seems to have died down.&lt;/p&gt;&lt;h3&gt;Source of inspiration&lt;/h3&gt;&lt;p&gt;As I mentioned previously, I have done almost no programming in the past four years. I still read about Python, but didn't have the motivation to actually write code. That is, until I read these discussions and pondered if it would not be possible to simply &quot;try out this new syntax&quot;. This lead me to go back to working on &lt;a href=&quot;https://aroberge.github.io/ideas/docs/html/&quot; target=&quot;_blank&quot;&gt;a fun project&amp;nbsp;&lt;/a&gt;, and write two new import hooks, documented &lt;a href=&quot;https://aroberge.github.io/ideas/docs/html/included/from_export.html&quot; target=&quot;_blank&quot;&gt;here &lt;/a&gt;and &lt;a href=&quot;https://aroberge.github.io/ideas/docs/html/included/export_name.html&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;With these, you can try out right now what it would be like to use the proposed export keyword cases suggested in PEP 842 as well as in a subset of those suggested in PEP 843.&lt;/p&gt;&lt;p&gt;Go try it out, and let me know what you think!&lt;/p&gt;&lt;p&gt;In the meantime, I plan to continue working on &lt;a href=&quot;https://aroberge.github.io/ideas/docs/html/index.html&quot; target=&quot;_blank&quot;&gt;ideas&lt;/a&gt;, and resume working on &lt;a href=&quot;https://friendly-traceback.github.io/docs/index.html&quot; target=&quot;_blank&quot;&gt;friendly/friendly-traceback&lt;/a&gt; as well as on &lt;a href=&quot;https://reeborg.ca/index_en.html&quot; target=&quot;_blank&quot;&gt;Reeborg's World&lt;/a&gt;, and implementing many of the suggestions made by users over the past four years.&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</description>
	<pubDate>Mon, 21 Sep 2026 17:24:52 +0000</pubDate>
</item>
<item>
	<title>LernerPython blog, from Reuven Lerner: Get Python help, one on one: my coaching sessions</title>
	<guid>https://lernerpython.com/2026/09/20/python-coaching-one-on-one/</guid>
	<link>https://lernerpython.com/2026/09/20/python-coaching-one-on-one/</link>
	<description>&lt;p class=&quot;wp-block-paragraph&quot;&gt;Some of the most satisfying work I do happens one on one. You arrive with a real problem from your real job — code that will not behave, an architecture you are unsure about, a Git situation that has you stuck — and an hour later it is smaller, or gone.&lt;/p&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;I have offered these private coaching sessions for years, mostly to people who thought to ask. What I never did was describe them properly anywhere, which is a strange way to run something I enjoy this much.&lt;/p&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;So: I&amp;#8217;ve now set up a page all about my coaching, at &lt;a href=&quot;https://lernerpython.com/personal-coaching/&quot;&gt;lernerpython.com/personal-coaching&lt;/a&gt;. Take a look —&amp;nbsp;and if you need this kind of help, let&amp;#8217;s talk!&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;What happens in an hour&lt;/h2&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;Whatever you need. The session is yours, and people use it in very different ways:&lt;/p&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;
&lt;li&gt;You show me code you have written, and I tell you what I would change and why.&lt;/li&gt;



&lt;li&gt;We plan a project before you write any of it, which is usually cheaper than planning it afterward.&lt;/li&gt;



&lt;li&gt;We pair-program through a problem that has had you stuck for days.&lt;/li&gt;



&lt;li&gt;You ask me the Git questions you have been quietly avoiding since 2019.&lt;/li&gt;



&lt;li&gt;We work out where Claude Code actually fits into the way you already work.&lt;/li&gt;



&lt;li&gt;You talk through a career decision, an interview, or what to learn next.&lt;/li&gt;
&lt;/ul&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;You do not need an agenda, or a tidy repository, or a well-formed question. “Here is my code, and here is what confuses me” has started plenty of excellent sessions.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Two things that make it work&lt;/h2&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;&lt;strong&gt;It is private.&lt;/strong&gt; Your code, your employer’s code, the architecture you are unsure about, the concept you feel you should have learned years ago: none of it goes anywhere. Ask me the question you would never ask in a public forum. That is rather the point of meeting one on one.&lt;/p&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;&lt;strong&gt;You get a recording.&lt;/strong&gt; Nobody thinks clearly while taking notes. When we finish, I send you the video of the session, along with anything we built together — code, notes, a diagram, a Jupyter/Marimo notebook, a CLAUDE.md, or just a plan. It&amp;#8217;s all yours to keep, and to use as you see fit.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;What it costs, and what it does not require&lt;/h2&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;A session is one hour on Zoom, for $300.&lt;/p&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;It has nothing to do with a LernerPython membership. You do not need to be a member, and booking a session does not sign you up for anything else. One hour, one payment, no obligation. If it helps and you want another in a month, book another; most people book once, get unstuck, and carry on.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Let&amp;#8217;s talk!&lt;/h2&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;Pick a time on &lt;a href=&quot;https://savvycal.com/reuven/1-on-1-python&quot;&gt;my calendar&lt;/a&gt;, or read the details first on the &lt;a href=&quot;https://lernerpython.com/personal-coaching/&quot;&gt;coaching page&lt;/a&gt;.&lt;/p&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;If nothing on the calendar suits you, e-mail me at &lt;a href=&quot;mailto:reuven@LernerPython.com&quot;&gt;reuven@LernerPython.com&lt;/a&gt; and we will find a time that does. I am happy to get up early or stay up late for a session like this, wherever in the world you are. Ask, and we will work it out.&lt;/p&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;And if you are not sure whether your problem is a good fit for an hour, e-mail me and ask. I will tell you honestly, even when the answer is that you don&amp;#8217;t need me.&lt;/p&gt;
&lt;p&gt;The post &lt;a href=&quot;https://lernerpython.com/2026/09/20/python-coaching-one-on-one/&quot;&gt;Get Python help, one on one: my coaching sessions&lt;/a&gt; appeared first on &lt;a href=&quot;https://lernerpython.com&quot;&gt;LernerPython&lt;/a&gt;.&lt;/p&gt;</description>
	<pubDate>Sun, 20 Sep 2026 10:37:56 +0000</pubDate>
</item>
<item>
	<title>LernerPython blog, from Reuven Lerner: NaN in Pandas: Why you need np.nan, not pd.NaN</title>
	<guid>https://lernerpython.com/2026/09/20/nan-in-pandas-why-you-need-np-nan-not-pd-nan/</guid>
	<link>https://lernerpython.com/2026/09/20/nan-in-pandas-why-you-need-np-nan-not-pd-nan/</link>
	<description>&lt;img src=&quot;https://lernerpython.com/wp-content/uploads/2026/09/2026-09-20.png&quot; alt=&quot;NaN in Pandas: Why you need np.nan, not pd.NaN&quot; class=&quot;wp-image-17779&quot; width=&quot;1913&quot; height=&quot;1207&quot; /&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;Missing data? NumPy calls it nan. Python Pandas displays it as NaN. &lt;/p&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;But: Pandas doesn&amp;#8217;t define pd.nan or pd.NaN. NumPy removed np.NaN in version 2.0.&lt;/p&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;So you have to refer to np.nan from within Pandas, and it&amp;#8217;ll be displayed as NaN.&lt;/p&gt;
&lt;p&gt;The post &lt;a href=&quot;https://lernerpython.com/2026/09/20/nan-in-pandas-why-you-need-np-nan-not-pd-nan/&quot;&gt;NaN in Pandas: Why you need np.nan, not pd.NaN&lt;/a&gt; appeared first on &lt;a href=&quot;https://lernerpython.com&quot;&gt;LernerPython&lt;/a&gt;.&lt;/p&gt;</description>
	<pubDate>Sun, 20 Sep 2026 06:00:00 +0000</pubDate>
</item>

</channel>
</rss>
