<?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>EuroPython: Humans of EuroPython: Daria Linhart Grudzień</title>
	<guid>https://blog.europython.eu/humans-of-europython-daria-linhart-grudzien/</guid>
	<link>https://blog.europython.eu/humans-of-europython-daria-linhart-grudzien/</link>
	<description>&lt;p&gt;EuroPython wouldn&amp;amp;apost exist without the wonderful volunteers who pour countless hours into organising it. From contracting the venue to selecting and confirming talks and workshops, hundreds of hours of loving work go into making each edition the best one yet.&lt;/p&gt;&lt;p&gt;Join us in celebrating one of the humans behind the keyboard. Today, we&amp;amp;aposre delighted to share an interview with Daria Linhart Grudzie&amp;#x144;, our Communications Lead.&lt;/p&gt;&lt;p&gt;Thank you for being the voice of the EuroPython community, Daria!  &lt;/p&gt;&lt;img src=&quot;https://blog.europython.eu/content/images/2026/07/example--2-.png&quot; class=&quot;kg-image&quot; alt=&quot;alt&quot; width=&quot;1500&quot; height=&quot;1500&quot; /&gt;&lt;p&gt;&lt;strong&gt;EP: What first inspired you to volunteer for EuroPython? And which edition of the conference was it?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;I got pulled into the team in 2025, tempted with a chance to work with a friend on organising an event for juniors in tech in Czechia, which became the Beginners Day Unconference. I appreciated that a major European conference offered a program for the local community.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;EP: Did you make any lasting friendships or professional connections through volunteering?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Lots! The EuroPython team is full of kind and fun people who like to do interesting things in their free time. Being a member of the core organising team gave me a chance to get to know a lot of folks. For the first time I feel like I&amp;#x2019;m going to the conference to meet up with friends.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;EP: What was your primary role as a volunteer, and what did a typical day of contributing look like for you?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;After doing the Humans of EuroPython interviews during the winter, I got invited to lead the Communications Team for the 2026 edition. My days include a variety of tasks,which I love. From building a productive team, working on finding media partners, occasional web development, co-ordinating with other teams, building documentation for the next edition, to making sure folks in the team enjoy contributing - I do what&amp;#x2019;s needed to make sure EuroPython speaks to our community with a friendly, slightly quirky, but always inclusive voice.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;EP: Was there a moment when you felt your contribution really made a difference?&amp;#xA0;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;There were a few. Some of the core Python developers reached out to me personally saying that the Communications Team is doing a great job. Seeing our social media posts engage and resonate with the community is another reminder that our work is making an impact. &lt;/p&gt;&lt;p&gt;&lt;strong&gt;EP: Would you volunteer again, and why?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Absolutely. Contributing to EuroPython, I feel empowered to bring ideas, experiment, and work on impactful initiatives which benefit the community. I&amp;#x2019;ve been able to take on roles and projects which allowed me to learn, get out of my comfort zone, and grow. I hope to do more of that in the future, and this is a fantastic group of people to do this with.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;EP: If you could describe the volunteer experience in three words, what would they be?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Ownership. Impact. Collaboration.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;EP: Did you have any unexpected or funny experiences at EuroPython?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;I got invited to talk about the conference on the Real Python Podcast. This wasn&amp;#x2019;t on my bingo card for this year &amp;#x1F642;&lt;/p&gt;</description>
	<pubDate>Thu, 09 Jul 2026 17:05:57 +0000</pubDate>
</item>
<item>
	<title>Python Software Foundation: The PSF D&amp;amp;I Workgroup Are Starting Office Hours in July!</title>
	<guid>https://pyfound.blogspot.com/2026/07/the-psf-d-workgroup-are-starting-office.html</guid>
	<link>https://pyfound.blogspot.com/2026/07/the-psf-d-workgroup-are-starting-office.html</link>
	<description>&lt;p&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;&lt;div class=&quot;separator&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiA_8v_PYRU1VA1HZbzsh-RJrDuwNFSRv-oegK0kRz8J4S52I1fYBExCEalzinUgwI3mId6e5Jbt3WUfBWCU2POo3HbaoRqIkacDKTX0EK5h6gps9GPYauO6EgR0hYK6LCXqklgdcfV7ve3-FMSZNSQEWIID1VXbJrgdwz4Y_mjXAFaqJOOvA/s1200/PSF%20D&amp;I%20Workgroup%20Are%20Starting%20Office%20Hours.png&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;253&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiA_8v_PYRU1VA1HZbzsh-RJrDuwNFSRv-oegK0kRz8J4S52I1fYBExCEalzinUgwI3mId6e5Jbt3WUfBWCU2POo3HbaoRqIkacDKTX0EK5h6gps9GPYauO6EgR0hYK6LCXqklgdcfV7ve3-FMSZNSQEWIID1VXbJrgdwz4Y_mjXAFaqJOOvA/w482-h253/PSF%20D&amp;I%20Workgroup%20Are%20Starting%20Office%20Hours.png&quot; width=&quot;482&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Starting Tuesday 28 July, 2026, the PSF Diversity &amp;amp; Inclusion (D&amp;amp;I) Workgroup is opening its virtual doors once a month on Discord. Come chat with workgroup members from all over the world!&lt;/span&gt;&lt;p&gt;&lt;/p&gt;&lt;span id=&quot;docs-internal-guid-ce5da7fc-7fff-1479-7c54-a9a9420d5aa2&quot;&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;Doing diversity and inclusion work in tech can feel isolating sometimes. You might be organizing a meetup, writing a code of conduct, trying to get funding for your community, or helping people feel welcome, often in your spare time, and wondering if anyone else is wrestling with the same things.&lt;/span&gt;&lt;/p&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;They are. We are! And we would love to get all of us in the same room.&lt;/span&gt;&lt;/p&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;This July, the PSF D&amp;amp;I Workgroup will be hosting monthly office hours within Discord. These will be open, text-based conversations where we encourage you to ask questions, sha&lt;br /&gt;re what you are working on, and connect with other people who care about making the Python community more welcoming.&lt;/span&gt;&lt;/p&gt;&lt;h2 dir=&quot;ltr&quot;&gt;&lt;span&gt;The details&lt;/span&gt;&lt;/h2&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;The PSF D&amp;amp;I Office Hours will be on the &lt;/span&gt;&lt;span&gt;last Tuesday of every month&lt;/span&gt;&lt;span&gt;. Because our community is spread across the globe, we will alternate between two times so we can cover as many time zones as possible:&lt;/span&gt;&lt;/p&gt;&lt;ul&gt;&lt;li dir=&quot;ltr&quot;&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;1 PM UTC&lt;/span&gt;&lt;span&gt; / &lt;/span&gt;&lt;span&gt;9 AM US Eastern&lt;/span&gt;&lt;/p&gt;&lt;/li&gt;&lt;li dir=&quot;ltr&quot;&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;9 PM UTC&lt;/span&gt;&lt;span&gt; / &lt;/span&gt;&lt;span&gt;5 PM US Eastern&lt;/span&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;Our first session will be on &lt;/span&gt;&lt;span&gt;&lt;b&gt;Tuesday, 28 July 2026 at 1 PM UTC.
&lt;/b&gt;&lt;/span&gt;&lt;span&gt;Here is roughly what that looks like around the world:&lt;/span&gt;&lt;/p&gt;&lt;div align=&quot;left&quot; dir=&quot;ltr&quot;&gt;&lt;table&gt;&lt;colgroup&gt;&lt;col width=&quot;306&quot; /&gt;&lt;col width=&quot;162&quot; /&gt;&lt;/colgroup&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;Region&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;Local time on 28 July&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;US Pacific, Los Angeles – (UTC-7h)&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;6:00 AM&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;US Eastern, New York – (UTC-4h)&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;9:00 AM&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;Brazil, São Paulo – (UTC-3h)&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;10:00 AM&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;UTC&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;1:00 PM&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;West Africa, Lagos – (UTC+1h)&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;2:00 PM&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;Central Europe, Amsterdam / Berlin / Madrid – (UTC+2h)&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;3:00 PM&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;East Africa, Nairobi – (UTC+3h)&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;4:00 PM&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;Iran, Tehran – (UTC+3:30h)&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;4:30 PM&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;India, New Delhi – (UTC+5:30h)&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;6:30 PM&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;China, Beijing – (UTC+8h)&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;9:00 PM&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;Japan, Tokyo – (UTC+9h)&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;10:00 PM&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;Australia, Sydney – (UTC+10h)&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;11:00 PM&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;If 6 AM in Los Angeles or 11 PM in Sydney made you wince, do not worry. The August session will be at &lt;/span&gt;&lt;span&gt;9 PM UTC&lt;/span&gt;&lt;span&gt;, and we will keep alternating from there.&lt;/span&gt;&lt;/p&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;You will find us in the &lt;/span&gt;&lt;span&gt;#psf-diversity&lt;/span&gt;&lt;span&gt; channel on the &lt;/span&gt;&lt;a href=&quot;https://discord.gg/z7QtB3wE8&quot;&gt;&lt;span&gt;PSF Discord&lt;/span&gt;&lt;/a&gt;&lt;span&gt;. If you’re new to Discord, check out some&lt;/span&gt;&lt;a href=&quot;https://support.discord.com/hc/en-us/sections/360008206871-Discord-Basics&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Discord Basics&lt;/span&gt;&lt;/a&gt;&lt;span&gt; to help you get started.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;h2 dir=&quot;ltr&quot;&gt;&lt;span&gt;What will we talk about&lt;/span&gt;&lt;/h2&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;Honestly? Whatever is on your mind related to Python, your communities, and D&amp;amp;I.&lt;/span&gt;&lt;/p&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;Since our workgroup exists to advise the PSF on diversity and inclusion, some conversations we are especially hoping to have include:&lt;/span&gt;&lt;/p&gt;&lt;ul&gt;&lt;li dir=&quot;ltr&quot;&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;Ideas for policies, initiatives, and grant proposals&lt;/span&gt;&lt;span&gt; to diversify the PSF missions. Feedback from the community about these topics will help the PSF D&amp;amp;I Workgroup provide recommendations to the PSF Board of Directors.&lt;/span&gt;&lt;/p&gt;&lt;/li&gt;&lt;li dir=&quot;ltr&quot;&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;Your feedback, plain and simple.&lt;/span&gt;&lt;span&gt; We want to understand how the PSF can better serve and grow a diverse membership, and we cannot do that without hearing from the community itself.&lt;/span&gt;&lt;/p&gt;&lt;/li&gt;&lt;li dir=&quot;ltr&quot;&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;How things are actually going.&lt;/span&gt;&lt;span&gt; Part of our job is measuring and sharing the PSF’s progress on its diversity initiatives, and we would rather do that in conversation with you than in a report nobody reads. We also want to understand and learn about the current state of Python communities around the world.&lt;/span&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2 dir=&quot;ltr&quot;&gt;&lt;span&gt;No camera, no mic, no pressure&lt;/span&gt;&lt;/h2&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;Office hours are &lt;/span&gt;&lt;span&gt;text chat only&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;Show up in your pajamas, join from the bus, lurk quietly for the first twenty minutes. It is all fine.&lt;/span&gt;&lt;/p&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;And if you cannot make it at all, the conversation stays in the channel, so you can catch up later when it suits you. If something in the chat sparks a thought you would like to share with us directly, you are always welcome to email the workgroup at diversity-inclusion-wg@python.org.&lt;/span&gt;&lt;/p&gt;&lt;h2 dir=&quot;ltr&quot;&gt;&lt;span&gt;Bring your own language&lt;/span&gt;&lt;/h2&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;Because we are the D&amp;amp;I Workgroup, our members come from around the world! Alongside the main conversation, we will open threads in other languages where possible. Depending on the presence of our members, we would be happy to chat in Spanish, Portuguese, Chinese, Hindi, French or even Persian! Let us know during the office hour if you have a specific language you hope to converse in, or jump in with whichever language thread feels like home.&lt;/span&gt;&lt;/p&gt;&lt;h2 dir=&quot;ltr&quot;&gt;&lt;span&gt;See you on the 28th!&lt;/span&gt;&lt;/h2&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;The first office hour session is on &lt;/span&gt;&lt;span&gt;Tuesday, 28 July 2026 at 1 PM UTC&lt;/span&gt;&lt;span&gt;, in &lt;/span&gt;&lt;span&gt;#psf-diversity&lt;/span&gt;&lt;span&gt; on Discord.&lt;/span&gt;&lt;/p&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;Come say hi, even if it is just to tell us what you are working on with Python. We are really looking forward to meeting you!&lt;/span&gt;&lt;/p&gt;&lt;div&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;</description>
	<pubDate>Thu, 09 Jul 2026 14:11:06 +0000</pubDate>
</item>
<item>
	<title>Django Weblog: Last Call 2026 Django Developer Survey</title>
	<guid>https://www.djangoproject.com/weblog/2026/jul/08/last-call-2026-django-developer-survey/</guid>
	<link>https://www.djangoproject.com/weblog/2026/jul/08/last-call-2026-django-developer-survey/</link>
	<description>&lt;p&gt;Time is running out. This is the last call for the 2026 &lt;a href=&quot;https://surveys.jetbrains.com/s3/wb-django-developers-survey-2026&quot;&gt;Django Developers Survey&lt;/a&gt;, which the Django Software Foundation is running in partnership with JetBrains.&lt;/p&gt;
&lt;p&gt;The survey closes on &lt;strong&gt;July 13, 2026&lt;/strong&gt;. It is one of the best measures we have of how Django is used, and it helps guide future technical and community decisions.&lt;/p&gt;
&lt;p&gt;So far, over 3,100 people have responded, and we would love to push that number past 4,000. Every response helps us better understand the Django community.&lt;/p&gt;
&lt;p&gt;This year's survey was shaped by the Django Steering Council, the Django Fellows, the Django Software Foundation Board of Directors, and several community members. Your feedback helps us understand your needs, see how you use Django, and plan for future development and community needs.&lt;/p&gt;
&lt;h3 id=&quot;s-how-you-can-help&quot;&gt;How you can help&lt;/h3&gt;
&lt;p&gt;Once you’ve done the survey, take a moment to re-share on socials and with your communities. The more diverse the answers, the better the results for all of us. We appreciate everybody helping to get the word out.&lt;/p&gt;
&lt;p&gt;Please use the following links:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Bluesky&lt;br /&gt;
&lt;a href=&quot;https://surveys.jetbrains.com/s3/bs-django-developers-survey-2026&quot;&gt;https://surveys.jetbrains.com/s3/bs-django-developers-survey-2026&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Django Forum&lt;br /&gt;
&lt;a href=&quot;https://surveys.jetbrains.com/s3/df-django-developers-survey-2026&quot;&gt;https://surveys.jetbrains.com/s3/df-django-developers-survey-2026&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;LinkedIn&lt;br /&gt;
&lt;a href=&quot;https://surveys.jetbrains.com/s3/li-django-developers-survey-2026&quot;&gt;https://surveys.jetbrains.com/s3/li-django-developers-survey-2026&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mastodon&lt;br /&gt;
&lt;a href=&quot;https://surveys.jetbrains.com/s3/md-django-developers-survey-2026&quot;&gt;https://surveys.jetbrains.com/s3/md-django-developers-survey-2026&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reddit&lt;br /&gt;
&lt;a href=&quot;https://surveys.jetbrains.com/s3/r-django-developers-survey-2026&quot;&gt;https://surveys.jetbrains.com/s3/r-django-developers-survey-2026&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;X / Twitter&lt;br /&gt;
&lt;a href=&quot;https://surveys.jetbrains.com/s3/x-django-developers-survey-2026&quot;&gt;https://surveys.jetbrains.com/s3/x-django-developers-survey-2026&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For more details, &lt;a href=&quot;https://www.djangoproject.com/weblog/2026/may/12/2026-django-developers-survey/&quot;&gt;read the original announcement on the Django blog&lt;/a&gt;.&lt;/p&gt;</description>
	<pubDate>Wed, 08 Jul 2026 19:31:21 +0000</pubDate>
</item>
<item>
	<title>Mike Driscoll: New Book Release: Python Typing</title>
	<guid>https://blog.pythonlibrary.org/2026/07/08/new-book-release-python-typing/</guid>
	<link>https://blog.pythonlibrary.org/2026/07/08/new-book-release-python-typing/</link>
	<description>&lt;p&gt;I am happy to announce that my latest book, &lt;strong&gt;Python Typing&lt;/strong&gt;, is now available on all platforms. You can get your copy on &lt;a href=&quot;https://driscollis.gumroad.com/l/pytyping&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer nofollow&quot;&gt;Gumroad&lt;/a&gt; or &lt;a href=&quot;https://leanpub.com/pythontyping&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer nofollow&quot;&gt;Leanpub&lt;/a&gt; or &lt;a href=&quot;https://www.amazon.com/dp/B0H5L5V9YZ&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer nofollow&quot;&gt;Amazon&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Python has had type hinting support since Python 3.5, over TEN years ago! However, Python’s type annotations have changed repeatedly over the years. In &lt;strong&gt;Python Typing: Type Checking for Python Programmers&lt;/strong&gt;, you will learn all you need to know to add type hints to your Python applications effectively.&lt;/p&gt;
&lt;p&gt;You will also learn how to use Python type checkers, configure them, and set them up in pre-commit or GitHub Actions. This knowledge will give you the power to check your code and your team’s code automatically before merging, hopefully catching defects before they make it into your products.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://driscollis.gumroad.com/l/pytyping&quot;&gt;&lt;img class=&quot;aligncenter size-large wp-image-12749&quot; src=&quot;https://blog.pythonlibrary.org/wp-content/uploads/2026/02/python_typing_cover-683x1024.webp&quot; alt=&quot;Python Typing Book Cover&quot; width=&quot;683&quot; height=&quot;1024&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;What You’ll Learn&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;You will learn all about Python’s support for type hinting (annotations). Specifically, you will learn about the following topics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Variable annotations&lt;/li&gt;
&lt;li&gt;Function annotations&lt;/li&gt;
&lt;li&gt;Type aliases&lt;/li&gt;
&lt;li&gt;New types&lt;/li&gt;
&lt;li&gt;Generics&lt;/li&gt;
&lt;li&gt;Hinting callables&lt;/li&gt;
&lt;li&gt;Annotating TypedDict&lt;/li&gt;
&lt;li&gt;Annotating Decorators and Generators&lt;/li&gt;
&lt;li&gt;Using Mypy for type checking&lt;/li&gt;
&lt;li&gt;Mypy configuration&lt;/li&gt;
&lt;li&gt;Using ty for type checking&lt;/li&gt;
&lt;li&gt;ty configuration&lt;/li&gt;
&lt;li&gt;and more!&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Where to Purchase&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://driscollis.gumroad.com/l/pytyping&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer nofollow&quot;&gt;Gumroad&lt;/a&gt; (PDF / epub)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://leanpub.com/pythontyping&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer nofollow&quot;&gt;Leanpub&lt;/a&gt; (PDF / epub)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.amazon.com/dp/B0H5L5V9YZ&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer nofollow&quot;&gt;Amazon&lt;/a&gt; (Kindle / paperback)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The post &lt;a href=&quot;https://blog.pythonlibrary.org/2026/07/08/new-book-release-python-typing/&quot;&gt;New Book Release: Python Typing&lt;/a&gt; appeared first on &lt;a href=&quot;https://blog.pythonlibrary.org&quot;&gt;Mouse Vs Python&lt;/a&gt;.&lt;/p&gt;</description>
	<pubDate>Wed, 08 Jul 2026 18:46:18 +0000</pubDate>
</item>
<item>
	<title>Hugo van Kemenade: Fixing the dictionary with Python 3.14</title>
	<guid>https://hugovk.dev/blog/2026/fixing-dict/</guid>
	<link>https://hugovk.dev/blog/2026/fixing-dict/</link>
	<description>&lt;p&gt;Yes, but not the
&lt;a href=&quot;https://docs.python.org/3/library/stdtypes.html#mapping-types-dict&quot;&gt;&lt;code&gt;dict&lt;/code&gt;&lt;/a&gt; kind of
dictionary.&lt;/p&gt;
&lt;p&gt;When working on CPython, we often find obscure bugs elsewhere, in compilers, operating
systems and elsewhere:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://emmatyping.dev/finding-a-miscompilation-in-rustllvm.html&quot;&gt;Rust/LLVM&lt;/a&gt;,
&lt;a href=&quot;https://github.com/llvm/llvm-project/issues/106846&quot;&gt;clang-19&lt;/a&gt;,
&lt;a href=&quot;https://github.com/llvm/llvm-project/issues/179695&quot;&gt;clang 21&lt;/a&gt; and
&lt;a href=&quot;https://github.com/llvm/llvm-project/pull/120267&quot;&gt;BOLT&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/python/cpython/issues/129987#issuecomment-2761279236&quot;&gt;GCC 13&lt;/a&gt;,
&lt;a href=&quot;https://gcc.gnu.org/bugzilla/show_bug.cgi?id=118430&quot;&gt;GCC 15&lt;/a&gt;,
&lt;a href=&quot;https://github.com/python/cpython/issues/127000&quot;&gt;glibc&lt;/a&gt;,
&lt;a href=&quot;https://github.com/python/cpython/issues/122431&quot;&gt;readline&lt;/a&gt; and
&lt;a href=&quot;https://github.com/python/cpython/issues/120378&quot;&gt;curses&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/python/cpython/issues/131032&quot;&gt;musl fma&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since Python 3.8, the release notes have a section called &amp;ldquo;And now for something
completely different&amp;rdquo;. These have included Monty Python sketches, astrophysics facts and
poetry.&lt;/p&gt;
&lt;p&gt;For Python 3.14, I&amp;rsquo;m doing all things
&lt;a href=&quot;https://discuss.python.org/t/python-3-14-0-final-is-here/104210?u=hugovk#p-273519-and-now-for-something-completely-different-9&quot;&gt;&lt;em&gt;π&lt;/em&gt;&lt;/a&gt;,
&lt;a href=&quot;https://discuss.python.org/t/python-3-14-0-alpha-3/74542?u=hugovk#p-214989-and-now-for-something-completely-different-3&quot;&gt;pie&lt;/a&gt;
and
&lt;a href=&quot;https://discuss.python.org/t/python-3-14-0rc2-and-3-13-7-are-go/102403?u=hugovk#p-267064-and-now-for-something-completely-different-13&quot;&gt;[mag]pie&lt;/a&gt;
(&lt;a href=&quot;https://hugovk.dev/2025/and-now/&quot;&gt;more here&lt;/a&gt;). As part of the research for this important task, I
looked up &lt;em&gt;pi&lt;/em&gt; in the
&lt;a href=&quot;https://www.oed.com/dictionary/pi_n1?tab=factsheet&amp;tl=true#30481837&quot;&gt;&lt;em&gt;Oxford English Dictionary&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As we all recall from the
&lt;a href=&quot;https://discuss.python.org/t/python-3-14-0-beta-1-is-here/91117#p-246172-and-now-for-something-completely-different-8&quot;&gt;Python 3.14.0b1 release notes&lt;/a&gt;,
William Jones was the first person to use the &lt;em&gt;π&lt;/em&gt; symbol to denote the circle&amp;rsquo;s
circumference to its diameter in his
&lt;a href=&quot;https://archive.org/details/SynopsisPalmariorumMatheseosOrANewIntroductionToTheMathematics/page/n283/mode/1up?view=theater&quot;&gt;&lt;em&gt;Synopsis Palmariorum Matheseos&lt;/em&gt;&lt;/a&gt;
(1706):&lt;/p&gt;
&lt;p&gt;






 
 








 
 
 
 
 
 
 
 
 &lt;img width=&quot;767&quot; height=&quot;443&quot; class=&quot;mx-auto my-0 rounded-md&quot; alt=&quot;In the Circle, the Diameter is to Circumference as 1 to 16/5 - 4/239 - 1/3 15/(53) - 4/(2393) + 1/5 16/(55) - 4/(2395) -, &amp;c. = 3.1459, &amp;c. = π. This Series..I receiv&amp;rsquo;d from the Excellent Analyst..Mr. John Machin; and by means thereof, Van Ceulen&amp;rsquo;s Number..may be Examin&amp;rsquo;d.&quot; src=&quot;https://hugovk.dev/blog/2026/fixing-dict/og-pi_hu_11079b3c0d4638a7.jpg&quot; /&gt;
 
 



&lt;/p&gt;
&lt;p&gt;However, the &lt;em&gt;OED&lt;/em&gt;&amp;rsquo;s first citation had a markup bug:&lt;/p&gt;
&lt;p&gt;






 
 








 
 
 
 
 
 
 
 
 &lt;img width=&quot;824&quot; height=&quot;249&quot; class=&quot;mx-auto my-0 rounded-md&quot; alt=&quot;As previous image, but: 1 to Misplaced &amp;, where Misplaced and the ampersand are red on yellow background&quot; src=&quot;https://hugovk.dev/blog/2026/fixing-dict/broken-pi_hu_3c12ffb5d11aad82.png&quot; /&gt;
 
 



&lt;/p&gt;
&lt;p&gt;I duly reported this to the &lt;em&gt;OED&lt;/em&gt; in July 2024; and by the next time I looked it up, in
June 2025, it was fixed!&lt;/p&gt;
&lt;p&gt;






 
 








 
 
 
 
 
 
 
 
 &lt;img width=&quot;1704&quot; height=&quot;584&quot; class=&quot;mx-auto my-0 rounded-md&quot; alt=&quot;As previous image, but the sequence is now fixed&quot; src=&quot;https://hugovk.dev/blog/2026/fixing-dict/fixed-pi_hu_c3d86b566f46681c.png&quot; /&gt;
 
 



&lt;/p&gt;
&lt;p&gt;Hooray!&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;small&gt;Header photo: Part of the definition for &amp;ldquo;get&amp;rdquo; in the &lt;em&gt;OED&lt;/em&gt;&amp;rsquo;s 1901 forerunner, &lt;em&gt;A
New English Dictionary on Historical Principles&lt;/em&gt;
(&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://creativecommons.org/licenses/by-nc-sa/2.0/&quot;&gt;CC
BY-NC-SA 2.0&lt;/a&gt;
&lt;a href=&quot;https://www.flickr.com/photos/hugovk/8192259573&quot;&gt;Hugo van Kemenade&lt;/a&gt;).&lt;/small&gt;&lt;/p&gt;</description>
	<pubDate>Wed, 08 Jul 2026 16:40:05 +0000</pubDate>
</item>
<item>
	<title>Marc-André Lemburg: My 25th EuroPython - in a row😊</title>
	<guid>https://malemburg.com/my-25th-europython-in-a-row/</guid>
	<link>https://malemburg.com/my-25th-europython-in-a-row/</link>
	<description>&lt;img src=&quot;https://malemburg.com/content/images/2026/07/EP2026.png&quot; alt=&quot;My 25th EuroPython - in a row&amp;#x1F60A;&quot; /&gt;&lt;p&gt;Next weekend, I&amp;amp;aposll be heading to Krak&amp;#xF3;w, Poland, for my &lt;a href=&quot;https://ep2026.europython.eu/?ref=malemburg.com&quot; rel=&quot;noreferrer&quot;&gt;25th EuroPython conference&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;It&amp;amp;aposs been a long ride since the first &lt;a href=&quot;https://malemburg.com/eps/&quot; rel=&quot;noreferrer&quot;&gt;EuroPython conference&lt;/a&gt; in Charleroi, Belgium, but one I wouldn&amp;amp;apost have wanted to miss.&lt;/p&gt;&lt;p&gt;This year, I&amp;amp;aposll be giving a &lt;a href=&quot;https://ep2026.europython.eu/session/ducklake-take-python-and-duckdb-for-a-swim-in-your-data-lake?ref=malemburg.com&quot; rel=&quot;noreferrer&quot;&gt;talk about DuckLake&lt;/a&gt;, an extension to &lt;a href=&quot;https://duckdb.org/?ref=malemburg.com&quot; rel=&quot;noreferrer&quot;&gt;DuckDB&lt;/a&gt;, one of the most exciting new database systems in the last few years.&lt;/p&gt;&lt;p&gt;Come join in.&lt;/p&gt;&lt;p&gt;Cheers,&lt;br /&gt;Marc-Andr&amp;#xE9;&lt;/p&gt;</description>
	<pubDate>Wed, 08 Jul 2026 15:48:56 +0000</pubDate>
</item>
<item>
	<title>Python GUIs: Why Widgets Appear as Separate Windows — Understanding widget parenting in Qt and how to fix widgets that float outside your main window</title>
	<guid>https://www.pythonguis.com/faq/adding-new-tabs-in-tabwidget-appears-as-a-seperate-window-when-tab-is-selected/</guid>
	<link>https://www.pythonguis.com/faq/adding-new-tabs-in-tabwidget-appears-as-a-seperate-window-when-tab-is-selected/</link>
	<description>&lt;blockquote&gt;
&lt;p&gt;Sometimes when I dynamically add widgets to tabs in my PyQt6 application, they pop out as windows instead. What's going on?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you're dynamically adding widgets to your PyQt6 application and finding that they pop out as separate floating windows instead of appearing neatly inside your application, you're running into one of Qt's gotchas: &lt;em&gt;widget parenting&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This problem usually shows up when widgets are added from a callback, event listener or signal handler. But there are a million different ways to screw this up. Let's look at why this happens and how to fix it.&lt;/p&gt;
&lt;h2 id=&quot;how-qt-decides-whats-a-window&quot;&gt;How Qt decides what's a window&lt;/h2&gt;
&lt;p&gt;In Qt, every widget can optionally have a &lt;strong&gt;parent&lt;/strong&gt; widget. The parent determines where a widget lives visually &amp;mdash; a widget with a parent is drawn &lt;em&gt;inside&lt;/em&gt; that parent. A widget &lt;em&gt;without&lt;/em&gt; a parent becomes a top-level window, floating independently on your desktop.&lt;/p&gt;
&lt;p&gt;This is the root cause of widgets appearing outside your main window. When you create a widget and it doesn't have a parent &amp;mdash; either because you didn't set one, or because the parent was lost somehow &amp;mdash; Qt treats it as a standalone window.&lt;/p&gt;
&lt;h2 id=&quot;three-ways-to-get-a-parent-less-widget&quot;&gt;Three ways to Get a Parent-less Widget&lt;/h2&gt;
&lt;p&gt;Here are the most common reasons widgets end up floating:&lt;/p&gt;
&lt;h3&gt;Creating widgets without a parent&lt;/h3&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;# This widget has no parent &amp;mdash; it will be a floating window
tabs = QTabWidget()

# This widget has a parent &amp;mdash; it will appear inside parent_widget
tabs = QTabWidget(parent_widget)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;When you add a widget to a &lt;a href=&quot;https://www.pythonguis.com/tutorials/pyqt6-layouts/&quot;&gt;layout&lt;/a&gt;, the layout assigns the parent automatically. But if something goes wrong between creation and layout insertion (like an exception, or the widget being shown prematurely), the widget stays parentless.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;safest&lt;/em&gt; approach is to pass a parent when creating widgets:&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 create_new_tab(self):
    wdg = QWidget()
    layout = QGridLayout(wdg)

    tabs = QTabWidget(wdg)  # Explicitly set parent
    tab1 = QWidget(tabs)     # Explicitly set parent
    tab2 = QWidget(tabs)     # Explicitly set parent
    tabs.addTab(tab1, &quot;Start&quot;)
    tabs.addTab(tab2, &quot;Profile&quot;)
    layout.addWidget(tabs)

    return wdg
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;...although, honestly, I don't usually bother. If I know I'll be adding a widget to a layout immediately, I'll omit the parent assignment.&lt;/p&gt;
&lt;p class=&quot;admonition admonition-note&quot;&gt;&lt;span class=&quot;admonition-kind&quot;&gt;&lt;i class=&quot;fas fa-sticky-note&quot;&gt;&lt;/i&gt;&lt;/span&gt;  In an window &lt;code&gt;__init__&lt;/code&gt; the &lt;em&gt;safety&lt;/em&gt; question is less relevant because, if there is an unhandled exception that blocks the adding your sub-widget to a layout, it will also block the creation of the parent window.&lt;/p&gt;
&lt;h3&gt;Accidentally recreating a widget&lt;/h3&gt;
&lt;p&gt;If you have a tab widget stored as &lt;code&gt;self.w&lt;/code&gt; and somewhere in your code you do:&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;self.w = QTabWidget()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;...the original tab widget is replaced. If the old widget gets garbage collected, all the tabs that had it as their parent suddenly become orphans &amp;mdash; parentless widgets that float as independent windows.&lt;/p&gt;
&lt;p&gt;Be careful not to reassign widget attributes unintentionally, especially in callbacks that might run multiple times.&lt;/p&gt;
&lt;h3&gt;Losing the parent reference&lt;/h3&gt;
&lt;p&gt;If you explicitly set a widget's parent to &lt;code&gt;None&lt;/code&gt;, it becomes a top-level window:&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;widget.setParent(None)  # This widget is now a floating window
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This sometimes happens indirectly. For example, removing a widget from a layout in certain ways can clear its parent.&lt;/p&gt;
&lt;h2 id=&quot;a-clean-approach-to-dynamic-tabs&quot;&gt;A clean approach to dynamic tabs&lt;/h2&gt;
&lt;p&gt;Here's a complete, working example that dynamically adds tabs without any floating-window issues. It demonstrates the correct way to set up a &lt;code&gt;QTabWidget&lt;/code&gt; with a &quot;+&quot; button that adds new tabs:&lt;/p&gt;
&lt;div class=&quot;code-block&quot;&gt;
&lt;span class=&quot;code-block-language code-block-python&quot;&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;import sys
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QTabWidget,
    QWidget, QVBoxLayout, QLabel
)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle(&quot;Dynamic Tabs&quot;)
        self.setFixedSize(600, 400)

        self.tabs = QTabWidget(self)
        self.tabs.currentChanged.connect(self.on_tab_changed)

        # Add an initial tab
        self.add_content_tab(&quot;Tab 1&quot;)

        # Add the &quot;+&quot; tab for creating new tabs
        self.tabs.addTab(QWidget(self.tabs), &quot;+&quot;)

        self.setCentralWidget(self.tabs)

    def on_tab_changed(self, index):
        # Check if the &quot;+&quot; tab was clicked
        if self.tabs.tabText(index) == &quot;+&quot;:
            self.add_new_tab()

    def add_new_tab(self):
        # Count existing content tabs (exclude the &quot;+&quot; tab)
        tab_count = self.tabs.count()  # includes &quot;+&quot;
        new_title = f&quot;Tab {tab_count}&quot;

        # Insert the new tab before the &quot;+&quot; tab
        new_tab = self.create_tab_content(new_title)
        insert_index = self.tabs.count() - 1
        self.tabs.insertTab(insert_index, new_tab, new_title)

        # Switch to the newly created tab (avoid retriggering)
        self.tabs.blockSignals(True)
        self.tabs.setCurrentIndex(insert_index)
        self.tabs.blockSignals(False)

    def add_content_tab(self, title):
        &quot;&quot;&quot;Add a content tab before the + tab.&quot;&quot;&quot;
        tab = self.create_tab_content(title)
        # Insert before the last tab if &quot;+&quot; exists, otherwise just add
        plus_index = None
        for i in range(self.tabs.count()):
            if self.tabs.tabText(i) == &quot;+&quot;:
                plus_index = i
                break

        if plus_index is not None:
            self.tabs.insertTab(plus_index, tab, title)
        else:
            self.tabs.addTab(tab, title)

    def create_tab_content(self, title):
        &quot;&quot;&quot;Create the widget content for a tab.&quot;&quot;&quot;
        widget = QWidget(self.tabs)  # Parent is the tab widget
        layout = QVBoxLayout(widget)
        label = QLabel(f&quot;Content for {title}&quot;, widget)
        layout.addWidget(label)
        return widget


app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;A few things to notice in this example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The main window inherits from &lt;code&gt;QMainWindow&lt;/code&gt;, and &lt;code&gt;QApplication&lt;/code&gt; is created separately.&lt;/li&gt;
&lt;li&gt;Every widget is created with an explicit parent: &lt;code&gt;QWidget(self.tabs)&lt;/code&gt;, &lt;code&gt;QLabel(text, widget)&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;blockSignals(True)&lt;/code&gt; is used when programmatically changing the current tab to prevent the &lt;code&gt;currentChanged&lt;/code&gt; &lt;a href=&quot;https://www.pythonguis.com/tutorials/pyqt6-signals-slots-events/&quot;&gt;signal&lt;/a&gt; from firing recursively.&lt;/li&gt;
&lt;li&gt;New tabs are inserted &lt;em&gt;before&lt;/em&gt; the &quot;+&quot; tab using &lt;code&gt;insertTab&lt;/code&gt;, so the &quot;+&quot; always stays at the end.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;Widget parenting is one of those things in Qt that works invisibly when everything is correct &amp;mdash; and causes confusing visual glitches the moment something is slightly off. The good news is that once you understand the pattern, the fix is almost always the same: make sure every widget has a  parent.&lt;/p&gt;
&lt;p&gt;If you're new to PyQt6, our guide to &lt;a href=&quot;https://www.pythonguis.com/tutorials/pyqt6-creating-your-first-window/&quot;&gt;creating your first window&lt;/a&gt; covers the basics of setting up a &lt;code&gt;QMainWindow&lt;/code&gt;, while the &lt;a href=&quot;https://www.pythonguis.com/tutorials/pyqt6-widgets/&quot;&gt;widgets tutorial&lt;/a&gt; walks through the most common widgets and how to use them correctly.&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, 08 Jul 2026 06:00:00 +0000</pubDate>
</item>
<item>
	<title>Brett Cannon: How to publish to PyPI using GitHub Actions securely</title>
	<guid>https://snarky.ca/how-to-publish-to-pypi-using-github-actions-securely/</guid>
	<link>https://snarky.ca/how-to-publish-to-pypi-using-github-actions-securely/</link>
	<description>&lt;p&gt;There have been several security incidents lately that involved compromising GitHub Actions workflows. This has led some to say &amp;quot;&lt;a href=&quot;https://nesbitt.io/2026/04/28/github-actions-is-the-weakest-link.html&quot;&gt;GitHub Actions is the weakest link&lt;/a&gt;&amp;quot; in publishing and to GitHub publishing a &lt;a href=&quot;https://github.blog/news-insights/product-news/whats-coming-to-our-github-actions-2026-security-roadmap/&quot;&gt;GitHub Actions security roadmap update&lt;/a&gt;. But saying it&amp;amp;aposs an issue and acknowledging the fact is one thing, but you still need to do the mitigation work &lt;strong&gt;today&lt;/strong&gt; so you are not going to be the next headline. So this post is going to outline 3 things to do so you can publish to PyPI securely when using GitHub Actions.&lt;/p&gt;&lt;p&gt;But before I go any farther, I want to make 2 things &lt;strong&gt;very&lt;/strong&gt; clear. One is this post is in no way meant to shame anyone into using GitHub Actions. For instance, I have heard people trying to shame maintainers into using GitHub Actions to use &lt;a href=&quot;https://docs.pypi.org/trusted-publishers/&quot;&gt;Trusted Publishing&lt;/a&gt;, and I think that&amp;amp;aposs wrong. Now, &lt;strong&gt;if&lt;/strong&gt; you choose to use a platform that supports Trusted Publishing, &lt;strong&gt;then&lt;/strong&gt; you should definitely use it. &lt;strong&gt;But&lt;/strong&gt; Trusted Publishing is not a reason to change your publishing workflow if the one you have is already secure. In other words, use whatever works best for you to publish &lt;strong&gt;securely&lt;/strong&gt; to PyPI, and if that&amp;amp;aposs GitHub Actions then this blog post is for you.&lt;/p&gt;&lt;p&gt;Two, the title of this post explicitly says &amp;quot;publishing&amp;quot; and not &amp;quot;building and publishing&amp;quot;. Doing builds securely is a separate concern that I am not covering. The one piece of advice I will give, though, is one the Python security developer in residence gave me: &lt;a href=&quot;https://mastodon.social/@sethmlarson/116682065542135585&quot;&gt;you should have building and publishing be separate workflows&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;With that out of the way, here are 3 steps to securing GitHub Actions for publishing to PyPI that should be relatively painless.&lt;/p&gt;&lt;h1 id=&quot;use-zizmor&quot;&gt;Use zizmor&lt;/h1&gt;&lt;p&gt;The &lt;a href=&quot;https://docs.zizmor.sh&quot;&gt;zizmor&lt;/a&gt; tool examines your GitHub Actions workflows to find things that at dubious when it comes to security. They pretty much all stem from GitHub Actions having insecure defaults in the name of convenience. There are 2 parts to using zizmor:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Make it happy&lt;/li&gt;&lt;li&gt;Set it up in CI&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;You can do those two things in whatever order you want but you need to do &lt;strong&gt;both&lt;/strong&gt; to make sure you fix any current issues you have and prevent any new issues from slipping in. Luckily both things are easy to do.&lt;/p&gt;&lt;h2 id=&quot;make-zizmor-happy&quot;&gt;Make zizmor happy&lt;/h2&gt;&lt;p&gt;To run zizmor you can do &lt;code&gt;uvx zizmor --quiet --fix .github/&lt;/code&gt; , &lt;code&gt;pipx zizmor --quiet --fix .github/&lt;/code&gt; , or however you choose to run it. That will run zizmor and fix anything that it can in a clean way. Chances are, though, there will be three things to fix by hand.&lt;/p&gt;&lt;h3 id=&quot;no-permissions-by-default&quot;&gt;No permissions by default&lt;/h3&gt;&lt;p&gt;By default, the token GitHub Actions gives to your workflow via &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; is &lt;strong&gt;way&lt;/strong&gt; too broad, so zizmor flags it. Easiest way to fix this issue is to turn off &lt;strong&gt;all&lt;/strong&gt; permissions at the global level for a workflow and then turn any permissions you need on at the job level. So put the following at the global level of your workflow file (I personally put it just before &lt;code&gt;jobs:&lt;/code&gt;):&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-YAML&quot;&gt;permissions: {}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you happen to need some specific permission, you can then &lt;a href=&quot;https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idpermissions&quot;&gt;specify it per-job&lt;/a&gt; so you scope it as tightly as possible. Or if you really need something for everything, you can still set it globally, but you at least you will be explicit about exactly what you want.&lt;/p&gt;&lt;p&gt;The reason you do this is you don&amp;amp;apost want some action to get a hold of your token that can do something as if you&amp;amp;aposre you and do something bad.&lt;/p&gt;&lt;h3 id=&quot;no-persisted-credentials-after-checkout&quot;&gt;No persisted credentials after checkout&lt;/h3&gt;&lt;p&gt;When you use the &lt;a href=&quot;https://github.com/actions/checkout&quot;&gt;checkout action&lt;/a&gt;, GitHub Actions is running Git on your behalf, complete with credentials so the &lt;code&gt;git checkout&lt;/code&gt; command works. The problem is those credentials persist passed the checkout action unless you specifically say to not keep them around. So add the following &lt;code&gt;with:&lt;/code&gt; clause to your checkout action:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-YAML&quot;&gt;  with:
    persist-credentials: false&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You do this so your credentials don&amp;amp;apost leak out to some action that will do something bad with them.&lt;/p&gt;&lt;h3 id=&quot;pin-your-actions&quot;&gt;Pin your actions&lt;/h3&gt;&lt;p&gt;When you specify an action to use in a workflow, you were probably told to use some Git tag like &lt;code&gt;uses: actions/checkout@v7&lt;/code&gt; which specifies using the &lt;code&gt;v7&lt;/code&gt; tag from the &lt;a href=&quot;https://github.com/actions/checkout&quot;&gt;https://github.com/actions/checkout&lt;/a&gt; repo. The problem with that is if that action gets compromised, an attacker can just update that tag to point to malicious code and so now &lt;em&gt;you&amp;amp;aposre&lt;/em&gt; compromised.&lt;/p&gt;&lt;p&gt;You work around this by pinning your actions to commit hashes. This might sound like a massive hassle, but there are tools that can pin all your actions for you.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://gha-update.readthedocs.io/&quot;&gt;gha-update&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;zizmor --fix --gh-token&lt;/code&gt; with a (permissionless) token&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/suzuki-shunsuke/pinact&quot;&gt;Pinact&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Those go from simplest to fanciest, but they all get the job done. I personally use gha-update as it&amp;amp;aposs quick and updates my versions along the way. But if you want to keep your current versions as-is then zizmor will do it for you, but you need to give it a token to do the updates (the token is required to avoid being throttled by GitHub). The best thing to do is to use a permissionless token, but if you&amp;amp;aposre being lazy and trust zizmor (and any tool you might be using to run it, e.g. uvx), you can get a token from &lt;code&gt;gh auth token&lt;/code&gt; (the following example is for the &lt;a href=&quot;https://fishshell.com&quot;&gt;Fish shell&lt;/a&gt;; adjust the syntax for calling &lt;code&gt;gh&lt;/code&gt; accordingly for your shell and how you prefer to call &lt;code&gt;zizmor&lt;/code&gt;):&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-fish&quot;&gt;zizmor --quiet --fix --gh-token (gh auth token) .github&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you need fancier than any of that, use Pinact.&lt;/p&gt;&lt;p&gt;You also want to require pinning not only for your workflows but any actions that use actions themselves so you&amp;amp;aposre pinned top to bottom. The easiest way to make that a requirement is to run the following command:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;gh api &amp;quot;/repos/{owner}/{repo}/actions/permissions&amp;quot; --method PUT --field enabled=true --field sha_pinning_required=true&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There&amp;amp;aposs also a way to do it via the UI:&lt;/p&gt;&lt;img src=&quot;https://storage.ghost.io/c/dc/01/dc0121d6-1790-49f9-97f9-83c5d9d1790a/content/images/2026/06/image.png&quot; class=&quot;kg-image&quot; alt=&quot;alt&quot; width=&quot;1067&quot; height=&quot;597&quot; /&gt;&lt;span&gt;Screenshot of turning on required SHA pinning in a repo under Settings - Actions - General&lt;/span&gt;&lt;h4 id=&quot;bonus-dependabot-to-keep-actions-up-to-date&quot;&gt;Bonus: Dependabot to keep actions up-to-date&lt;/h4&gt;&lt;p&gt;&lt;a href=&quot;https://docs.github.com/en/code-security/tutorials/secure-your-dependencies/dependabot-quickstart&quot;&gt;Dependabot&lt;/a&gt; will recognize your use of pins, so you can still use it to keep your actions up-to-date (if you so choose; it&amp;amp;aposs okay if you don&amp;amp;apost want to use Dependabot). The one thing I suggest is using a cooldown so you don&amp;amp;apost accidentally pick to a malicious update by adding a &lt;a href=&quot;https://docs.github.com/en/code-security/reference/supply-chain-security/dependabot-options-reference#cooldown-&quot;&gt;&lt;code&gt;cooldown&lt;/code&gt;&lt;/a&gt; of a week to your &lt;code&gt;dependabot.yml&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-YAML&quot;&gt;- package-ecosystem: github-actions
  directory: /
  schedule:
    interval: monthly
  cooldown:
    default-days: 7&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;add-zizmor-to-ci&quot;&gt;Add zizmor to CI&lt;/h2&gt;&lt;p&gt;Conveniently, &lt;a href=&quot;https://docs.zizmor.sh/integrations/#github-actions&quot;&gt;zizmor has an action&lt;/a&gt; you can set up in your repo. Using it will cause any issues found to be reported as a code scanning result under the &amp;quot;Security and quality&amp;quot; tab (which &lt;a href=&quot;https://github.com/zizmorcore/zizmor-action#advanced-security&quot;&gt;can be turned off&lt;/a&gt;).&lt;/p&gt;&lt;img src=&quot;https://storage.ghost.io/c/dc/01/dc0121d6-1790-49f9-97f9-83c5d9d1790a/content/images/2026/07/image.png&quot; class=&quot;kg-image&quot; alt=&quot;alt&quot; width=&quot;1025&quot; height=&quot;505&quot; /&gt;&lt;span&gt;Screenshot showing the &amp;quot;Code scanning&amp;quot; view under the &amp;quot;Security and quality&amp;quot; tab on GitHub&lt;/span&gt;&lt;p&gt;This means the results are &lt;strong&gt;private&lt;/strong&gt; and thus you don&amp;amp;apost have to worry about exposing anything publicly. You can also use the results as a TODO list if you would find that more motivating to have something to check off instead of getting everything working upfront. As well,  if you want to do it gradually this will give you a checklist of things to fix.&lt;/p&gt;&lt;p&gt;You can also run zizmor manually if you want in CI, but I personally just use the zizmor action in a dedicated workflow since the zizmor docs provide such a workflow configuration.&lt;/p&gt;&lt;h1 id=&quot;use-trusted-publishing&quot;&gt;Use Trusted Publishing&lt;/h1&gt;&lt;p&gt;If you&amp;amp;aposre going to use GitHub Actions to publish to PyPI, I don&amp;amp;apost see any reason not to use &lt;a href=&quot;https://docs.pypi.org/trusted-publishers/&quot;&gt;Trusted Publishing&lt;/a&gt;. It means you don&amp;amp;apost have to manage any API tokens and you can get &lt;a href=&quot;https://snarky.ca/why-pylock-toml-includes-digital-attestations/&quot;&gt;attestations&lt;/a&gt;. Basically it means you get to outsource your security concerns for how you communicate with PyPI for publishing to GitHub&amp;amp;aposs security team.&lt;/p&gt;&lt;p&gt;The one thing you should make sure to do when setting up Trusted Publishing is set up a &lt;a href=&quot;https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments/manage-environments&quot;&gt;GitHub environment&lt;/a&gt;. The Trusted Publishing docs &lt;strong&gt;strongly&lt;/strong&gt; encourage it and so do I. You can even have the environment do nothing, but doing it now at least gives you an easy option to use it for something later. But I do suggest you use environments to ...&lt;/p&gt;&lt;h1 id=&quot;require-approval-to-publish&quot;&gt;Require approval to publish&lt;/h1&gt;&lt;p&gt;The one specific thing I suggest you do with your GitHub environment is &lt;a href=&quot;https://docs.github.com/en/actions/reference/workflows-and-actions/deployments-and-environments#required-reviewers&quot;&gt;require reviewers&lt;/a&gt; to run your publishing workflow. The required reviewer can be yourself! But the key point is to require &lt;em&gt;someone&lt;/em&gt; to approve the workflow to run.&lt;/p&gt;&lt;p&gt;You might be wondering what&amp;amp;aposs the point if you trigger the release yourself? It&amp;amp;aposs to add a gate to protect against accidental running of your publishing workflow. The accident could be from you or it could be from a malicious actor who has managed to trigger the workflow. By requiring your approval, neither scenario can happen without you clicking that approval button while logged into your GitHub account. And that means someone would need to hack your GitHub account to work around it (and as mentioned above, that means you get to lean on the GitHub security team from preventing that from happening).&lt;/p&gt;&lt;p&gt;Out of everything I have listed, this is probably the most arduous as it&amp;amp;aposs a cost every time you want to do a release. But it&amp;amp;aposs one approval and you&amp;amp;aposre probably already going to be doing something to trigger the release, so you&amp;amp;aposre already online.&lt;/p&gt;&lt;p&gt;And that&amp;amp;aposs it! Those 3 steps get you a long way towards publishing securely from GitHub Actions to PyPI.&lt;/p&gt;&lt;h1 id=&quot;acknowledgments&quot;&gt;Acknowledgments&lt;/h1&gt;&lt;p&gt;Thanks to &lt;a href=&quot;https://sethmlarson.dev&quot;&gt;Seth Larson&lt;/a&gt; for providing feedback on a draft of this post and giving advice on Mastodon when I &lt;a href=&quot;https://mastodon.social/@brettcannon/116681968437045388&quot;&gt;posted about these steps&lt;/a&gt;. Thanks to &lt;a href=&quot;https://yossarian.net&quot;&gt;William Woodruff&lt;/a&gt; for creating zizmor and also giving advice on Mastodon. And thanks to everyone who participated constructively in the discussion on Mastodon.&lt;/p&gt;</description>
	<pubDate>Tue, 07 Jul 2026 20:44:52 +0000</pubDate>
</item>
<item>
	<title>PyCoder’s Weekly: Issue #742: Wagtail as Admin, Random Values, Code Quality, and More (2026-07-07)</title>
	<guid>https://pycoders.com/issues/742</guid>
	<link>https://pycoders.com/issues/742</link>
	<description>&lt;p&gt; &lt;span&gt;#742 – JULY 7, 2026&lt;/span&gt;&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/issues/742/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/16702/feed&quot; target=&quot;_blank&quot;&gt;Wagtail as Django Admin on Steroids&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; Wagtail can do pretty much everything the Django Admin can do, but includes a much more modern UI and more features. This article shows you how to use Wagtail as an Admin alternative.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16702/feed&quot; target=&quot;_blank&quot;&gt;TIM KAMANIN&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/16730/feed&quot; target=&quot;_blank&quot;&gt;Selecting Random Values in Python&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; Python&amp;rsquo;s random module provides utilities for generating pseudorandom numbers. For cryptographically-secure randomness, use the secrets module instead.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16730/feed&quot; target=&quot;_blank&quot;&gt;TREY HUNNER&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/16736/feed&quot; target=&quot;_blank&quot;&gt;Let AI Agents Into Your B2B App. Securely.&lt;/a&gt;&lt;/h3&gt; &lt;a href=&quot;https://pycoders.com/link/16736/feed&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;https://cdn.pycoders.com/012e0a1cbc39d888c748f46d3b3a4e1a&quot; alt=&quot;alt&quot; /&gt;&lt;/a&gt; &lt;p&gt; More of your users are asking to connect AI agents to your product, and you want to say yes. PropelAuth lets you give each agent scoped, revocable access, so you stay in control of what it can do. &lt;a href=&quot;https://pycoders.com/link/16736/feed&quot; target=&quot;_blank&quot;&gt;Learn more →&lt;/a&gt;&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16736/feed&quot; target=&quot;_blank&quot;&gt;PROPELAUTH&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/16715/feed&quot; target=&quot;_blank&quot;&gt;Managing and Measuring Python Code Quality&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; Master Python code quality tools like linters, formatters, type checkers, and profilers to measure, manage, and improve the code you write.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16715/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/16717/feed&quot; target=&quot;_blank&quot;&gt;Quiz: Managing and Measuring Python Code Quality&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16717/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/16731/feed&quot; target=&quot;_blank&quot;&gt;PEP 752: Package Repository Namespaces (Final)&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16731/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/16703/feed&quot; target=&quot;_blank&quot;&gt;PEP 836: JIT Go Brrr: The Path to a Supported JIT Compiler for CPython (Draft)&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16703/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/16705/feed&quot; target=&quot;_blank&quot;&gt;PyCon US 2026 Videos Are Up&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16705/feed&quot; target=&quot;_blank&quot;&gt;YOUTUBE.COM&lt;/a&gt;&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;h2&gt;Discussions&lt;/h2&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16737/feed&quot; target=&quot;_blank&quot;&gt;The Path to a Supported JIT Compiler for CPython&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16737/feed&quot; target=&quot;_blank&quot;&gt;PYTHON.ORG&lt;/a&gt;&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;h2&gt;Articles &amp;amp; Tutorials&lt;/h2&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16706/feed&quot; target=&quot;_blank&quot;&gt;Thinking About Running for the PSF Board? Let&amp;rsquo;s Talk!&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; The Python Software Foundation Board has announced two office-hour sessions dedicated to giving information on running for the PSF Board. If you&amp;rsquo;re thinking of running in the upcoming election, these sessions can help you understand the ins and outs.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16706/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/16701/feed&quot; target=&quot;_blank&quot;&gt;Celery on AWS ECS: Complete Guide&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; Running Celery on AWS ECS without losing tasks and making sure that all the work is done is not as straightforward as it may seem. Learn how to configure Celery and structure your tasks for reliable processing.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16701/feed&quot; target=&quot;_blank&quot;&gt;JAN GIACOMELLI&lt;/a&gt; • Shared by &lt;a href=&quot;https://pycoders.com/link/16718/feed&quot; target=&quot;_blank&quot;&gt;Špela Giacomelli&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/16734/feed&quot; target=&quot;_blank&quot;&gt;Learn the Agentic Coding Workflow That Actually Works on Real Projects&lt;/a&gt;&lt;/h3&gt; &lt;a href=&quot;https://pycoders.com/link/16734/feed&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;https://cdn.pycoders.com/e0075dff45b282a65ab9fa0ba4104786&quot; alt=&quot;alt&quot; /&gt;&lt;/a&gt; &lt;p&gt; 65% of Python developers are stuck using AI for small tasks that fall apart on anything real. This 2-day live course (July 11-12 via Zoom) walks you through building a complete Python app with OpenAI&amp;rsquo;s Codex, from an empty directory to a shipped project on GitHub. &lt;a href=&quot;https://pycoders.com/link/16734/feed&quot; target=&quot;_blank&quot;&gt;See the Full Curriculum →&lt;/a&gt;&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16734/feed&quot; target=&quot;_blank&quot;&gt;REAL PYTHON&lt;/a&gt;&lt;/span&gt; &lt;span&gt;sponsor&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16732/feed&quot; target=&quot;_blank&quot;&gt;Free-Threaded Python: Past, Present, and Future&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; This post summarizes a talk by core developer Thomas Wouters at PyCon US 2026 on Free-threaded Python: the attempt to remove the GIL. It describes why it is being done and what future work looks like.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16732/feed&quot; target=&quot;_blank&quot;&gt;JAKE EDGE&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/16713/feed&quot; target=&quot;_blank&quot;&gt;In Search of a New Contribution Model&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; This opinion piece from Carlton Gibson, a core Django contributor, talks about the state of contributions to OSS, how AI has made them more complicated, and how some key things are still broken.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16713/feed&quot; target=&quot;_blank&quot;&gt;CARLTON GIBSON&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/16723/feed&quot; target=&quot;_blank&quot;&gt;How to Get TIFF MetaData With Python&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; The Pillow image library gives you lots of tools for dealing with images. This article teaches you how to extract metadata from TIFF files in a few lines of Python.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16723/feed&quot; target=&quot;_blank&quot;&gt;MIKE DRISCOLL&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/16700/feed&quot; target=&quot;_blank&quot;&gt;Profile First: A 10x Faster Django Test Suite&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; Bob&amp;rsquo;s Django test suite took 30 seconds. cProfile showed 83% of it was one function: password hashing. Here&amp;rsquo;s how he found the bottleneck and the five-line fix.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16700/feed&quot; target=&quot;_blank&quot;&gt;BOB BELDERBOS&lt;/a&gt;&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16714/feed&quot; target=&quot;_blank&quot;&gt;Python 3.15 Preview: Upgraded JIT Compiler&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; Learn how the upgraded Python 3.15 JIT compiler speeds up your code with a new tracing frontend, register allocation, and in-place numeric operations.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16714/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/16729/feed&quot; target=&quot;_blank&quot;&gt;Quiz: Python 3.15 Preview: Upgraded JIT Compiler&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16729/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/16720/feed&quot; target=&quot;_blank&quot;&gt;Store Extra Data for Objects in a &lt;code&gt;WeakKeyDictionary&lt;/code&gt;&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; In several programs Adam has wanted to solve the problem of associating extra data with an object. This article outlines his latest approach.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16720/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/16719/feed&quot; target=&quot;_blank&quot;&gt;How to Get Started With the GitHub Copilot CLI&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; Learn how to install, authenticate, and use the GitHub Copilot CLI to plan, write, and review Python code from your terminal with AI agents.&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16719/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/16704/feed&quot; target=&quot;_blank&quot;&gt;Quiz: How to Get Started With the GitHub Copilot CLI&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16704/feed&quot; target=&quot;_blank&quot;&gt;REAL PYTHON&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/16709/feed&quot; target=&quot;_blank&quot;&gt;pytest-tia: Run Only the Tests Your Git Diff Actually Affects&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16709/feed&quot; target=&quot;_blank&quot;&gt;GITHUB.COM/BREADMSA&lt;/a&gt; • Shared by &lt;a href=&quot;https://pycoders.com/link/16716/feed&quot; target=&quot;_blank&quot;&gt;BreadWasEaten&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/16712/feed&quot; target=&quot;_blank&quot;&gt;Sifty: OSS Windows Maintenance CLI&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16712/feed&quot; target=&quot;_blank&quot;&gt;GITHUB.COM/VORTRIX5&lt;/a&gt; • Shared by &lt;a href=&quot;https://pycoders.com/link/16724/feed&quot; target=&quot;_blank&quot;&gt;Amine Zouaoui&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/16725/feed&quot; target=&quot;_blank&quot;&gt;purejq: A Pure-Python Implementation of &lt;code&gt;jq&lt;/code&gt;&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16725/feed&quot; target=&quot;_blank&quot;&gt;GITHUB.COM/ADAM2GO&lt;/a&gt; • Shared by adam2go&lt;/span&gt; &lt;/p&gt; &lt;/div&gt; &lt;div&gt; &lt;h3&gt;&lt;a href=&quot;https://pycoders.com/link/16707/feed&quot; target=&quot;_blank&quot;&gt;supertree: Decision Tree Visualization&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16707/feed&quot; target=&quot;_blank&quot;&gt;GITHUB.COM/MLJAR&lt;/a&gt; • Shared by &lt;a href=&quot;https://pycoders.com/link/16727/feed&quot; target=&quot;_blank&quot;&gt;Piotr Płoński&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/16733/feed&quot; target=&quot;_blank&quot;&gt;ninejs: Bringing Interactivity to Plotnine&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16733/feed&quot; target=&quot;_blank&quot;&gt;GITHUB.COM/Y-SUNFLOWER&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/16722/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; July 8, 2026&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16722/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/16711/feed&quot; target=&quot;_blank&quot;&gt;Python Atlanta&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; July 9 to July 10, 2026&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16711/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/16708/feed&quot; target=&quot;_blank&quot;&gt;PyDelhi User Group Meetup&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; July 11, 2026&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16708/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/16710/feed&quot; target=&quot;_blank&quot;&gt;DFW Pythoneers 2nd Saturday Teaching Meeting&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; July 11, 2026&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16710/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/16721/feed&quot; target=&quot;_blank&quot;&gt;EuroPython 2026&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; July 13 to July 20, 2026&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16721/feed&quot; target=&quot;_blank&quot;&gt;EUROPYTHON.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/16728/feed&quot; target=&quot;_blank&quot;&gt;SciPy 2026&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; July 13 to July 20, 2026&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16728/feed&quot; target=&quot;_blank&quot;&gt;SCIPY.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/16726/feed&quot; target=&quot;_blank&quot;&gt;EuroSciPy 2026&lt;/a&gt;&lt;/h3&gt; &lt;p&gt; July 18 to July 24, 2026&lt;br /&gt; &lt;span&gt;&lt;a href=&quot;https://pycoders.com/link/16726/feed&quot; target=&quot;_blank&quot;&gt;EUROSCIPY.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 #742.&lt;br /&gt;&lt;a href=&quot;https://pycoders.com/issues/742/feed&quot;&gt;View in Browser »&lt;/a&gt;&lt;/p&gt; &lt;img src=&quot;https://pycoders.com/issues/742/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, 07 Jul 2026 19:30:00 +0000</pubDate>
</item>
<item>
	<title>PyCharm: Best Object Detection Models for Machine Learning in 2026</title>
	<guid>https://blog.jetbrains.com/pycharm/2026/07/best-object-detection-models-for-machine-learning-in-2026/</guid>
	<link>https://blog.jetbrains.com/pycharm/2026/07/best-object-detection-models-for-machine-learning-in-2026/</link>
	<description>&lt;p&gt;Object detection powers transformative applications, from autonomous vehicles navigating city streets and security systems identifying threats in real time to retail analytics tracking inventory and medical imaging detecting tumors. But choosing the right model for your computer vision project can be challenging, especially with dozens of architectures claiming superiority across different metrics.&lt;/p&gt;



&lt;p&gt;In this guide, we’ll examine the top object detection models available in 2026, comparing their architectures, performance characteristics, and ideal use cases to help you determine which models are best suited to your applications.&amp;nbsp;&lt;/p&gt;



&lt;p&gt;Whether you&amp;#8217;re building real-time video analytics, high-precision inspection systems, or resource-constrained edge applications, you&amp;#8217;ll find clear guidance on which model best fits your requirements.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot; id=&quot;what-is-object-detection&quot;&gt;&lt;strong&gt;What is object detection?&lt;/strong&gt;&lt;/h2&gt;



&lt;p&gt;Object detection aims to &lt;strong&gt;identify&lt;/strong&gt; and &lt;strong&gt;localize&lt;/strong&gt; multiple objects within images or video frames. Unlike image classification, which only classifies the broad identity of an image, object detection identifies the objects in an image/video frame and their exact positions within it.&lt;br /&gt;In a nutshell, object detection solves &lt;strong&gt;two interdependent problems&lt;/strong&gt;:&lt;/p&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;
&lt;li&gt;&lt;strong&gt;Localizing (detecting) the objects on the image&lt;/strong&gt;, by drawing the bounding boxes for the objects on the image (it is possible that there are zero objects!). A bounding box is usually defined as a tuple (x, y, h, w), where x and y are the top-left coordinates of the bounding box rectangle, and h and w are the height and width of the bounding box, respectively.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;&lt;strong&gt;Classifying the identities of these images&lt;/strong&gt; (like a person, car, or dog).&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;This dual capability makes object detection more complex than classification alone, requiring models that can handle multiple objects of different sizes appearing anywhere in an image.&lt;/p&gt;



&lt;p&gt;As with classification tasks, a simple accuracy metric is not sufficient to assess model performance. We need metrics of two types. Firstly, &lt;strong&gt;performance metrics&lt;/strong&gt; that gauge the trade-off between incorrectly detecting objects (false positives) and not detecting objects at all in the image when they were present (false negatives). Secondly, we also need metrics to assess how long it will take our model to perform the task in question: We will call these &lt;strong&gt;compute efficiency metrics. &lt;/strong&gt;Usually, the new architectures for object detection are benchmarked on the validation partition of the &lt;a href=&quot;https://cocodataset.org/#home&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;COCO&lt;/a&gt; dataset and run on T4 NVIDIA GPU hardware.&lt;/p&gt;



&lt;p&gt;Here are the standard metrics used in the object detection community:&amp;nbsp;&lt;/p&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;
&lt;li&gt;&lt;strong&gt;Basic building block of performance metric: Intersection over union (IoU)&lt;/strong&gt; is the foundational geometric measure used to decide whether a predicted bounding box is correct. It is calculated as the area of overlap between the predicted box and the ground-truth box, divided by the area of their union – producing a score between 0 (no overlap) and 1 (perfect match). A detection is counted as a true positive only if its IoU with the nearest ground-truth box exceeds a chosen threshold (e.g. 0.5). A low IoU threshold is lenient about box placement; a high one demands tight localization.&lt;/li&gt;



&lt;li&gt;&lt;strong&gt;Performance metric: Mean average precision&lt;/strong&gt; (mAP), which evaluates detection accuracy by measuring how well predicted boxes overlap with ground truth annotations across different confidence thresholds. The most commonly cited variant, &lt;strong&gt;mAP@[50:95]&lt;/strong&gt; (also written AP50:95), averages precision over IoU thresholds from 0.50 to 0.95 in steps of 0.05, which is a stringent measure that penalizes imprecise localization as much as missed detections.
&lt;ul class=&quot;wp-block-list&quot;&gt;
&lt;li&gt;&lt;strong&gt;mAP50 vs. mAP50:95:&lt;/strong&gt; mAP50 measures detection at IoU ≥ 0.5 and scores appear higher, favoring faster models. mAP50:95 averages across IoU thresholds 0.5–0.95 – the stricter, preferred metric. For precision-critical applications (robotics, medical), it is common to optimize for mAP50:95.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;
&lt;li&gt;&lt;strong&gt;Compute efficiency metric: Frames per second&lt;/strong&gt; (FPS), which measures inference speed, determining whether a model can process video in real-time. For standard videos, real-time is defined as &amp;gt;= 30 FPS (Google or original YOLO paper) or latency &amp;lt;= 33.3ms (1/FPS * 1000). Naturally, for such applications as self-driving cars or robotics, there are higher requirements on the FPS rate, going up as high as 60–100+ FPS.&lt;/li&gt;



&lt;li&gt;&lt;strong&gt;Compute efficiency metric: Parameter count &lt;/strong&gt;is a quality of the model that influences its performance. There is a trade-off between the model&amp;#8217;s accuracy and its parameter count. That&amp;#8217;s why models are provided in different sizes of the same architecture (S, M, L, XL, etc.) to cater to various scenarios of this trade-off. This is similar to the concept of parameter count in LLMs.&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;There are a few popular choices of datasets to evaluate the performance of object detection models. As mentioned above, the standard choice for benchmarking object detection is the&lt;strong&gt; &lt;/strong&gt;&lt;a href=&quot;https://cocodataset.org/#home&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;strong&gt;COCO dataset&lt;/strong&gt;&lt;/a&gt;, containing 80 object categories across 330,000 images. Naturally, there are a lot of other datasets, specialized for certain domains, such as self-driving cars, or certain scenarios, such as the detection of objects in cluttered environments. What is important to remember is that the values of object detection metrics, IoU, and mAP depend on the dataset they were evaluated on, so mAP@[50:95]=60.1 on the COCO dataset may not be directly transferable to your custom dataset. These metrics should always be re-evaluated on your dataset to define the baseline performance of the models on it.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot; id=&quot;object-detection-algorithms-and-architecture-families&quot;&gt;&lt;strong&gt;Object detection algorithms and architecture families&lt;/strong&gt;&lt;/h2&gt;



&lt;p&gt;Object detection models fall into two different processing flows and two different architectural families.&lt;/p&gt;



&lt;h3 class=&quot;wp-block-heading&quot; id=&quot;architectures&quot;&gt;Architectures&lt;/h3&gt;



&lt;h4 class=&quot;wp-block-heading&quot; id=&quot;cnn-based&quot;&gt;&lt;strong&gt;CNN-based&lt;/strong&gt;&lt;/h4&gt;



&lt;p&gt;&lt;strong&gt;Examples: &lt;/strong&gt;Faster R-CNN, Mask R-CNN, Cascade R-CNN, YOLO&lt;/p&gt;



&lt;p&gt;CNN-based detectors rely on convolutional layers to extract local features hierarchically across the image, traditionally using predefined anchor boxes as spatial priors for localizing objects. Spatial priors are predefined assumptions about where and what size objects are likely to appear in an image, giving the model a starting point for detection rather than searching randomly.&lt;/p&gt;



&lt;h4 class=&quot;wp-block-heading&quot; id=&quot;transformer-based&quot;&gt;Transformer-based&lt;/h4&gt;



&lt;p&gt;&lt;strong&gt;Examples: &lt;/strong&gt;RF-DETR, RT-DETR, D-FINE&lt;/p&gt;



&lt;p&gt;Transformer-based detectors, inspired by advances in natural language processing, instead apply global self-attention mechanisms that allow the model to reason about relationships across the entire image simultaneously.&lt;/p&gt;



&lt;p&gt;Specifically, transformer-based detectors use learned object queries and global self-attention, where each query is trained to correspond to at most one object, unlike CNN-based detectors, which build spatial understanding locally through convolutional layers with limited receptive fields.&lt;/p&gt;



&lt;p&gt;However, in modern architectures, there exists a fusion of the two architectures: a CNN network can use self-attention modules in its architecture, such as YOLOv12 or YOLOv13, leading to cross-architectural designs.&lt;/p&gt;



&lt;h3 class=&quot;wp-block-heading&quot; id=&quot;processing-flows&quot;&gt;Processing flows&lt;/h3&gt;



&lt;h4 class=&quot;wp-block-heading&quot; id=&quot;two-stage-detectors&quot;&gt;Two-stage detectors&lt;/h4&gt;



&lt;p&gt;&lt;strong&gt;Examples:&lt;/strong&gt; Faster R-CNN, Mask R-CNN, Cascade R-CNN&lt;/p&gt;



&lt;p&gt;The network makes two sequential passes, each with a distinct job:&lt;/p&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;
&lt;li&gt;&lt;strong&gt;Stage 1&lt;/strong&gt;: Region Proposal:
&lt;ul class=&quot;wp-block-list&quot;&gt;
&lt;li&gt;Scans the image and proposes ~1000–2000 candidate regions (RoIs) that might contain objects.&lt;/li&gt;



&lt;li&gt;Doesn&amp;#8217;t care about class yet, just the fact that &amp;#8220;something interesting is here&amp;#8221;.&lt;/li&gt;



&lt;li&gt;This is the region proposal network (RPN) in classic detectors&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;



&lt;li&gt;&lt;strong&gt;Stage 2:&lt;/strong&gt; RoI classification and refinement:
&lt;ul class=&quot;wp-block-list&quot;&gt;
&lt;li&gt;Takes only the proposed regions from Stage 1.&lt;/li&gt;



&lt;li&gt;Crops/pools features for each region.&lt;/li&gt;



&lt;li&gt;Predicts the exact class and refined box coordinates for each proposal.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;



&lt;h4 class=&quot;wp-block-heading&quot; id=&quot;h-4-single-stage-detectors&quot;&gt;Single-stage detectors&lt;/h4&gt;



&lt;p&gt;&lt;strong&gt;Examples:&lt;/strong&gt; YOLO series, SSD, RetinaNet, DETR&lt;/p&gt;



&lt;p&gt;The network directly predicts class labels and bounding boxes from feature maps. It does everything in one forward pass. Usually, the following happens:&lt;/p&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;
&lt;li&gt;A dense grid of &lt;strong&gt;anchor boxes&lt;/strong&gt; (or points) is placed over the image.&lt;/li&gt;



&lt;li&gt;For each anchor, the network simultaneously predicts:
&lt;ul class=&quot;wp-block-list&quot;&gt;
&lt;li&gt;Whether there is an object there (objectness/class score).&lt;/li&gt;



&lt;li&gt;How the box should be adjusted (box regression offsets).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;



&lt;li&gt;In older versions of single-stage detectors, one needed to filter overlapping bounding boxes at the end; it was done using the non-maximum suppression (NMS) algorithm. From YOLOv10 on, using NMS is a redundant step.&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;Furthermore, modern single-stage detectors have moved away from anchor-based designs entirely, predicting box coordinates directly from grid points and pixels, eliminating the need for dataset-specific anchor tuning altogether.&lt;/p&gt;



&lt;p&gt;Historically, two-stage detectors offered better accuracy at the cost of speed, but modern single-stage detectors have largely closed this gap, achieving comparable or superior results while remaining significantly faster. Thus, we will focus on single-stage detectors only when evaluating the state-of-the-art models for practical applications.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot; id=&quot;top-object-detection-models-in-2026&quot;&gt;&lt;strong&gt;Top object detection models in 2026&lt;/strong&gt;&lt;/h2&gt;



&lt;p&gt;Two-stage pipelines (Faster R-CNN, Mask R-CNN) are no longer competitive. The current frontier is defined by single-stage NMS-free transformer architectures and models of the YOLO family. Each model below excels in a specific deployment scenario.&lt;/p&gt;



&lt;hr class=&quot;wp-block-separator has-alpha-channel-opacity&quot; /&gt;



&lt;h3 class=&quot;wp-block-heading&quot; id=&quot;rf-detr-by-roboflow-highest-accuracy&quot;&gt;RF-DETR (by Roboflow) – Highest Accuracy&lt;/h3&gt;



&lt;p&gt;&lt;em&gt;Real-Time Detection Transformer · ICLR 2026&lt;/em&gt;&lt;/p&gt;



&lt;table class=&quot;has-fixed-layout&quot;&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;strong&gt;Metric&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Value&lt;/strong&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;mAP50:95 (N)&lt;/td&gt;&lt;td&gt;48.4&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;mAP50:95 (M)&lt;/td&gt;&lt;td&gt;54.7&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Latency (N)&lt;/td&gt;&lt;td&gt;2.3 ms&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Latency (M)&amp;nbsp;&lt;/td&gt;&lt;td&gt;4.4 ms&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;mAP50:95 (2XL)&lt;/td&gt;&lt;td&gt;60.1 (COCO record)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Latency (2XL)&lt;/td&gt;&lt;td&gt;21.8 ms&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;



&lt;p&gt;The strongest real-time model available. RF-DETR uses &lt;a href=&quot;https://dinov2.metademolab.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;DINOv2&lt;/a&gt; to extract deeply rich, globally-aware feature representations of the input image, then uses deformable cross-attention in the detection head to efficiently query those features and predict bounding boxes without needing anchor boxes or NMS post-processing. The result is a model that&amp;#8217;s simultaneously more accurate on complex scenes and faster at inference than the naive combination of those components would suggest. RF-DETR is the first real-time detector to break 60 mAP on MS COCO. Designed from the ground up for fine-tuning, DINOv2 pre-training on internet-scale data gives it unmatched domain adaptability across aerial imagery, medical scans, industrial inspection, and more. It comes in four sizes: Nano, Small, Medium, Large (plus XL/2XL under a PML license).&lt;/p&gt;



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



&lt;ul class=&quot;wp-block-list&quot;&gt;
&lt;li&gt;Highest mAP of any real-time model.&lt;/li&gt;



&lt;li&gt;Exceptional domain transfer (fine-tunes fast).&lt;/li&gt;



&lt;li&gt;Best on occluded and complex scenes.&lt;/li&gt;



&lt;li&gt;Supports detection + segmentation in a single API.&lt;/li&gt;



&lt;li&gt;Apache 2.0, fully commercial-friendly.&lt;/li&gt;
&lt;/ul&gt;



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



&lt;ul class=&quot;wp-block-list&quot;&gt;
&lt;li&gt;Heavier than YOLO on edge/mobile.&lt;/li&gt;



&lt;li&gt;XL/2XL models require a PML license.&lt;/li&gt;



&lt;li&gt;Higher GPU memory vs. YOLO variants.&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;&lt;strong&gt;License:&lt;/strong&gt; Apache 2.0 (N/S/M/L) · PML 1.0 (XL/2XL)&amp;nbsp;&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href=&quot;https://github.com/roboflow/rf-detr&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/roboflow/rf-detr&lt;/a&gt;&lt;/p&gt;



&lt;hr class=&quot;wp-block-separator has-alpha-channel-opacity&quot; /&gt;



&lt;h3 class=&quot;wp-block-heading&quot; id=&quot;yolo-12-tsinghua-university-research-benchmark&quot;&gt;YOLO12 (Tsinghua University) – Research / Benchmark&lt;/h3&gt;



&lt;p&gt;&lt;em&gt;Attention-centric YOLO · NeurIPS 2025&lt;/em&gt;&lt;/p&gt;



&lt;table class=&quot;has-fixed-layout&quot;&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;strong&gt;Metric&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Value&lt;/strong&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;mAP50:95 (N)&lt;/td&gt;&lt;td&gt;40.4&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;mAP50:95 (M)&lt;/td&gt;&lt;td&gt;52.5&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Latency (N)&lt;/td&gt;&lt;td&gt;1.60 ms&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Latency (M)&amp;nbsp;&lt;/td&gt;&lt;td&gt;4.27 ms&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;License&lt;/td&gt;&lt;td&gt;AGPL-3.0&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;



&lt;p&gt;YOLO12 is the first YOLO model to place attention mechanisms at the core rather than CNNs, matching CNN-based inference speeds while gaining the global context benefits of self-attention. Key innovations: Area attention (A²) divides feature maps into regions to reduce the quadratic cost of full self-attention; Residual ELAN (R-ELAN) stabilizes training of large attention blocks; FlashAttention reduces memory bottlenecks. It is deployable on NVIDIA Jetson, NVIDIA GPUs, and macOS.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;A note on implementations.&lt;/strong&gt; YOLO12 exists in two separate codebases, and the distinction matters in practice. The original authors (Tsinghua/University at Buffalo) actively maintain their own repository at &lt;code&gt;sunsmarterjie/yolov12&lt;/code&gt;. In June 2025, they explicitly warned against using Ultralytics&amp;#8217; integration, stating it &amp;#8220;is inefficient, requires more memory, and has unstable training&amp;#8221; – issues they have fixed in their own repo. The training instability and memory criticisms often cited against YOLO12 are therefore criticisms of the Ultralytics port, not the model itself. Ultralytics&amp;#8217; recommendation to prefer YOLO26 over YOLO12 should be read with this context in mind: The comparison is partly against their own suboptimal implementation.&lt;/p&gt;



&lt;p&gt;If you use YOLO12, install from the original repository rather than via &lt;code&gt;pip install ultralytics&lt;/code&gt;.&lt;/p&gt;



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



&lt;ul class=&quot;wp-block-list&quot;&gt;
&lt;li&gt;Strong accuracy at the nano scale (beats YOLO11-N by 0.9% mAP).&lt;/li&gt;



&lt;li&gt;Long-range context via attention mechanisms: It can take into account the entire image when detecting an object, rather than a local pixel neighborhood, as in pure CNN architectures.&lt;/li&gt;



&lt;li&gt;Jetson-, Android-, and macOS-deployable.&lt;/li&gt;



&lt;li&gt;Original repo fixes memory and training stability issues present in the Ultralytics port.&lt;/li&gt;



&lt;li&gt;Actively maintained by original authors with ongoing updates (turbo variant, segmentation, and classification).&lt;/li&gt;
&lt;/ul&gt;



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



&lt;ul class=&quot;wp-block-list&quot;&gt;
&lt;li&gt;If using Ultralytics implementation:
&lt;ul class=&quot;wp-block-list&quot;&gt;
&lt;li&gt;AGPL-3.0 commercial use requires an enterprise license.&lt;/li&gt;



&lt;li&gt;Training instability and high memory on large models.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;



&lt;li&gt;If using an open-source implementation:
&lt;ul class=&quot;wp-block-list&quot;&gt;
&lt;li&gt;AGPL-3.0 commercial use requires an enterprise license.&lt;/li&gt;



&lt;li&gt;Claims to have stable training and inference in comparison with Ulitralytics implementation.&lt;/li&gt;



&lt;li&gt;Requires installing from the original repo to avoid Ultralytics port issues, resulting in slightly more setup friction.&lt;/li&gt;



&lt;li&gt;Smaller ecosystem and community support than Ultralytics-native models.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;&lt;strong&gt;License:&lt;/strong&gt; AGPL-3.0 (open-source) · Enterprise license via Ultralytics for commercial use&amp;nbsp;&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;Open-source repository:&lt;/strong&gt; &lt;a href=&quot;https://github.com/sunsmarterjie/yolov12&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/sunsmarterjie/yolov12&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;Ultralytics&lt;/strong&gt; &lt;strong&gt;repository: &lt;/strong&gt;&lt;a href=&quot;https://github.com/ultralytics/ultralytics&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/ultralytics/ultralytics&lt;/a&gt;&lt;/p&gt;



&lt;hr class=&quot;wp-block-separator has-alpha-channel-opacity&quot; /&gt;



&lt;h3 class=&quot;wp-block-heading&quot; id=&quot;yolo-26-ultralytics-best-for-edge-production&quot;&gt;YOLO26 (Ultralytics) – Best for edge / production&lt;/h3&gt;



&lt;p&gt;&lt;em&gt;Edge-first unified YOLO · September 2025&lt;/em&gt;&lt;/p&gt;



&lt;table class=&quot;has-fixed-layout&quot;&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;strong&gt;Metric&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Value&lt;/strong&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;mAP50:95 range&lt;/td&gt;&lt;td&gt;40.9–57.5&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Latency range&lt;/td&gt;&lt;td&gt;1.7–11.8 ms&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;CPU gain vs. YOLO11 (nano)&lt;/td&gt;&lt;td&gt;+43%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Unified tasks&lt;/td&gt;&lt;td&gt;5&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;



&lt;p&gt;Ultralytics&amp;#8217; flagship for 2025–2026. YOLO26 shifts focus from accuracy maximization toward deployment-oriented simplification: It removes NMS and distribution focal loss (DFL) for end-to-end inference, introduces the MuSGD optimizer for stable convergence, and adds progressive loss balancing (ProgLoss), which makes sure that the model doesn&amp;#8217;t over-optimize one objective at the expense of others, and small-target-aware label assignment (STAL), which ensures extra attention to small objects. Five tasks are solved by this one YOLO26: detection, segmentation, pose estimation, oriented bounding boxes detection, and open-vocabulary detection and segmentation. It is explicitly designed for NVIDIA Jetson Orin/Xavier, Qualcomm Snapdragon AI, and ARM CPUs. Supports INT8 and FP16 quantization, plus ONNX, TensorRT, CoreML, and TFLite export.&lt;/p&gt;



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



&lt;ul class=&quot;wp-block-list&quot;&gt;
&lt;li&gt;Best edge and mobile performance (Jetson Orin and Snapdragon).&lt;/li&gt;



&lt;li&gt;NMS-free leads to lower latency.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;43% faster CPU inference than YOLO11(N) at comparable accuracy, ideal for devices without a GPU.&lt;/li&gt;



&lt;li&gt;Five tasks in one architecture.&lt;/li&gt;



&lt;li&gt;Stable INT8/FP16 quantization.&lt;/li&gt;
&lt;/ul&gt;



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



&lt;ul class=&quot;wp-block-list&quot;&gt;
&lt;li&gt;AGPL-3.0: commercial use requires an enterprise license.&lt;/li&gt;



&lt;li&gt;Lower peak accuracy than RF-DETR XL.&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;&lt;strong&gt;License:&lt;/strong&gt; AGPL-3.0 (open-source) · Enterprise license via Ultralytics for commercial/industrial use.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href=&quot;https://github.com/ultralytics/ultralytics&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/ultralytics/ultralytics&lt;/a&gt;&lt;/p&gt;



&lt;hr class=&quot;wp-block-separator has-alpha-channel-opacity&quot; /&gt;



&lt;h2 class=&quot;wp-block-heading&quot; id=&quot;benchmark-comparison&quot;&gt;Benchmark comparison&lt;/h2&gt;



&lt;p&gt;To give a comparison between the models, here are the exact benchmark values. All scores on MS COCO val2017. Latency was measured on an NVIDIA T4 GPU.&lt;/p&gt;



&lt;table class=&quot;has-fixed-layout&quot;&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;strong&gt;Model&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;mAP50&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;mAP50:95&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Latency&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Params&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Edge-ready&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;License&lt;/strong&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;RF-DETR-N&lt;/td&gt;&lt;td&gt;67.6&lt;/td&gt;&lt;td&gt;48.4&lt;/td&gt;&lt;td&gt;2.3 ms&lt;/td&gt;&lt;td&gt;30.5 M&lt;/td&gt;&lt;td&gt;Server GPU&lt;/td&gt;&lt;td&gt;Apache 2.0&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;RF-DETR-M&lt;/td&gt;&lt;td&gt;73.6&lt;/td&gt;&lt;td&gt;54.7&lt;/td&gt;&lt;td&gt;4.4 ms&lt;/td&gt;&lt;td&gt;33.7 M&lt;/td&gt;&lt;td&gt;Server GPU&lt;/td&gt;&lt;td&gt;Apache 2.0&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;RF-DETR-2XL&lt;/td&gt;&lt;td&gt;78.5&lt;/td&gt;&lt;td&gt;60.1&lt;/td&gt;&lt;td&gt;17.2 ms&lt;/td&gt;&lt;td&gt;126.9 M&amp;nbsp;&lt;/td&gt;&lt;td&gt;Server GPU&lt;/td&gt;&lt;td&gt;PML 1.0&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;YOLO12-N&lt;/td&gt;&lt;td&gt;56.7&lt;/td&gt;&lt;td&gt;40.4&lt;/td&gt;&lt;td&gt;1.6 ms&lt;/td&gt;&lt;td&gt;2.5 M&lt;/td&gt;&lt;td&gt;ARM / Mobile / Jetson&lt;/td&gt;&lt;td&gt;AGPL-3.0&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;YOLO12-L&lt;/td&gt;&lt;td&gt;70.7&lt;/td&gt;&lt;td&gt;53.8&lt;/td&gt;&lt;td&gt;5.83 ms&lt;/td&gt;&lt;td&gt;26.5 M&lt;/td&gt;&lt;td&gt;Jetson / TensorRT&lt;/td&gt;&lt;td&gt;AGPL-3.0&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;YOLO26-N&lt;/td&gt;&lt;td&gt;—&amp;nbsp;&lt;/td&gt;&lt;td&gt;40.1&lt;/td&gt;&lt;td&gt;1.7 ms&lt;/td&gt;&lt;td&gt;2.4 M&lt;/td&gt;&lt;td&gt;ARM / Mobile / Jetson&lt;/td&gt;&lt;td&gt;AGPL-3.0&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;YOLO26-X&lt;/td&gt;&lt;td&gt;—&lt;/td&gt;&lt;td&gt;56.9&lt;/td&gt;&lt;td&gt;11.8 ms&lt;/td&gt;&lt;td&gt;55.7 M&lt;/td&gt;&lt;td&gt;Jetson / TensorRT&lt;/td&gt;&lt;td&gt;AGPL-3.0&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;



&lt;p&gt;Here is a visualization of the above results alongside additional modern object detection models for a more holistic comparison:&lt;/p&gt;


&lt;div class=&quot;wp-block-image&quot;&gt;
&lt;img src=&quot;https://blog.jetbrains.com/wp-content/uploads/2026/06/image-57.png&quot; alt=&quot;&quot; class=&quot;wp-image-717451&quot; /&gt;&lt;/div&gt;


&lt;hr class=&quot;wp-block-separator has-alpha-channel-opacity&quot; /&gt;



&lt;h2 class=&quot;wp-block-heading&quot; id=&quot;use-case-guidance&quot;&gt;Use-case guidance&lt;/h2&gt;



&lt;p&gt;&lt;strong&gt;Occluded objects:&lt;/strong&gt; RF-DETR (M/L) is the clear choice. Its DINOv2 backbone models global context across the full image, making it significantly better than CNN-based models at finding partially hidden objects.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;Small objects:&lt;/strong&gt; RF-DETR uses multi-scale feature extraction. YOLO26 also includes STAL (small-target-aware label assignment), making it competitive for small objects on edge hardware.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;Edge / mobile / Jetson:&lt;/strong&gt; YOLO26-N or YOLO12-N. YOLO26 is the Ultralytics recommendation for Jetson Orin/Xavier, Snapdragon AI, and ARM CPUs. It has 43% faster CPU inference than YOLO11n at comparable accuracy.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;Custom domain / fine-tuning:&lt;/strong&gt; RF-DETR by a significant margin. DINOv2 pre-training means it adapts to new domains (medical, aerial, and industrial) faster and with less data than any other model here.&lt;/p&gt;



&lt;hr class=&quot;wp-block-separator has-alpha-channel-opacity&quot; /&gt;



&lt;h2 class=&quot;wp-block-heading&quot; id=&quot;licensing-summary&quot;&gt;Licensing Summary&lt;/h2&gt;



&lt;table class=&quot;has-fixed-layout&quot;&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;strong&gt;Model&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;License&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Commercial use&lt;/strong&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;RF-DETR (base)&lt;/td&gt;&lt;td&gt;Apache 2.0&lt;/td&gt;&lt;td&gt;Free for all uses, including commercial products&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;RF-DETR XL/2XL&lt;/td&gt;&lt;td&gt;PML 1.0&lt;/td&gt;&lt;td&gt;Contact Roboflow for commercial licensing&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;YOLO12&lt;/td&gt;&lt;td&gt;AGPL-3.0&lt;/td&gt;&lt;td&gt;Free for open source / personal use; commercial applications require an Ultralytics Enterprise license&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;YOLO26&lt;/td&gt;&lt;td&gt;AGPL-3.0&lt;/td&gt;&lt;td&gt;Free for open source / personal use; commercial applications require an Ultralytics Enterprise license&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;



&lt;hr class=&quot;wp-block-separator has-alpha-channel-opacity&quot; /&gt;



&lt;h2 class=&quot;wp-block-heading&quot; id=&quot;quick-start-code&quot;&gt;Quick-start code&lt;/h2&gt;



&lt;h3 class=&quot;wp-block-heading&quot; id=&quot;rf-detr&quot;&gt;RF-DETR&lt;/h3&gt;



&lt;pre class=&quot;EnlighterJSRAW&quot;&gt;# Install
pip install rfdetr

# Inference
from rfdetr import RFDETRBase
model = RFDETRBase()
detections = model.predict(&quot;image.jpg&quot;)

# Fine-tune on your dataset
model.train(dataset_dir=&quot;./my_dataset&quot;, epochs=50, batch_size=4)&lt;/pre&gt;



&lt;h3 class=&quot;wp-block-heading&quot; id=&quot;h-3-yolo-26-yolo-12-via-ultralytics&quot;&gt;YOLO26 / YOLO12 (via Ultralytics)&lt;/h3&gt;



&lt;pre class=&quot;EnlighterJSRAW&quot;&gt;# Install
pip install ultralytics

# Inference — YOLO26
from ultralytics import YOLO
model = YOLO(&quot;yolo26n.pt&quot;)          # or yolo26s/m/l/x
results = model.predict(&quot;image.jpg&quot;)

# Inference — YOLO12
model = YOLO(&quot;yolo12n.pt&quot;)
results = model.predict(&quot;image.jpg&quot;)

# Export for edge (TensorRT / CoreML / ONNX)
model.export(format=&quot;engine&quot;)       # TensorRT for Jetson
model.export(format=&quot;coreml&quot;)       # Apple Silicon / iOS
model.export(format=&quot;tflite&quot;)       # Android / ARM&lt;/pre&gt;



&lt;h3 class=&quot;wp-block-heading&quot; id=&quot;h-3-yolo-12-use-original-open-source-repo-not-the-ultralytics-integration&quot;&gt;YOLO12 (use original open-source repo – not the Ultralytics integration)&lt;/h3&gt;



&lt;div class=&quot;wp-block-columns is-layout-flex wp-container-core-columns-is-layout-1 wp-block-columns-is-layout-flex&quot;&gt;
&lt;div class=&quot;wp-block-column is-layout-flow wp-block-column-is-layout-flow&quot;&gt;
&lt;pre class=&quot;EnlighterJSRAW&quot;&gt;# Install from the original authors' repo
conda create -n yolov12 python=3.11
conda activate yolov12
git clone https://github.com/sunsmarterjie/yolov12 &amp;amp;&amp;amp; cd yolov12
pip install -r requirements.txt
pip install -e .

# Inference
from ultralytics import YOLO
model = YOLO(&quot;yolov12n.pt&quot;)         # or s/m/l/x
results = model(&quot;path/to/image.jpg&quot;)
results[0].show()

# Export for edge
model.export(format=&quot;engine&quot;, half=True)   # TensorRT FP16
model.export(format=&quot;onnx&quot;)                # ONNX for broad compatibility&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;



&lt;hr class=&quot;wp-block-separator has-alpha-channel-opacity&quot; /&gt;



&lt;h2 class=&quot;wp-block-heading&quot; id=&quot;h-2-transfer-learning-and-fine-tuning&quot;&gt;Transfer learning and fine-tuning&lt;/h2&gt;



&lt;p&gt;&lt;strong&gt;RF-DETR – recommended for domain shift. &lt;/strong&gt;Thanks to a DINOv2 backbone that is pre-trained on internet-scale data, fine-tuning requires less labeled data and converges faster. Use the &lt;code&gt;rfdetr&lt;/code&gt; package with a COCO pre-trained checkpoint. Roboflow also offers a hosted fine-tuning UI.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;YOLO26 / YOLO12 – easiest pipeline.&lt;/strong&gt; Ultralytics&amp;#8217; training API is the most mature fine-tuning ecosystem. It supports YOLO-format and COCO-format datasets and has good documentation and an active community.&lt;/p&gt;



&lt;div class=&quot;wp-block-columns is-layout-flex wp-container-core-columns-is-layout-2 wp-block-columns-is-layout-flex&quot;&gt;
&lt;div class=&quot;wp-block-column is-layout-flow wp-block-column-is-layout-flow&quot;&gt;
&lt;pre class=&quot;EnlighterJSRAW&quot;&gt;# Fine-tuning YOLO26 on a custom dataset (YOLO format)
from ultralytics import YOLO

model = YOLO(&quot;yolo26m.pt&quot;)          # start from pretrained weights
model.train(
    data=&quot;custom_dataset.yaml&quot;,     # path to your dataset config
    epochs=100,
    imgsz=640,
    batch=16,
    device=0,                       # GPU index; &quot;cpu&quot; for CPU
)
metrics = model.val()               # evaluate on validation set&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;



&lt;hr class=&quot;wp-block-separator has-alpha-channel-opacity&quot; /&gt;



&lt;h2 class=&quot;wp-block-heading&quot; id=&quot;h-2-summary-choosing-the-right-model-for-your-project&quot;&gt;Summary: Choosing the right model for your project&lt;/h2&gt;



&lt;p&gt;Selecting an object detection model requires matching your specific requirements against each model&amp;#8217;s strengths. The decision framework below maps common scenarios to optimal model choices.&lt;/p&gt;



&lt;table class=&quot;has-fixed-layout&quot;&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;strong&gt;Your goal&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Best choice&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Runner-up&lt;/strong&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Highest accuracy, cloud deployment&lt;/td&gt;&lt;td&gt;RF-DETR M/XL&lt;/td&gt;&lt;td&gt;YOLO26-X&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Edge / Jetson / mobile&lt;/td&gt;&lt;td&gt;YOLO26-N/S&lt;/td&gt;&lt;td&gt;YOLO12-N&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Fine-tuning on a custom domain&lt;/td&gt;&lt;td&gt;RF-DETR&lt;/td&gt;&lt;td&gt;YOLO26&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Occluded / complex scenes&lt;/td&gt;&lt;td&gt;RF-DETR&lt;/td&gt;&lt;td&gt;YOLO26&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Research / benchmarking&lt;/td&gt;&lt;td&gt;YOLO12&lt;/td&gt;&lt;td&gt;RF-DETR&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Apache 2.0 + commercial use&lt;/td&gt;&lt;td&gt;RF-DETR (base)&lt;/td&gt;&lt;td&gt;YOLO26&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Multi-task (detect + segment + pose)&lt;/td&gt;&lt;td&gt;YOLO26&lt;/td&gt;&lt;td&gt;RF-DETR (det+seg)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;



&lt;h2 class=&quot;wp-block-heading&quot; id=&quot;h-2-get-started-with-py-charm-today&quot;&gt;Get started with PyCharm today&lt;/h2&gt;



&lt;p&gt;Selecting an object detection architecture in 2026 is a strategic decision dictated by the specific requirements of the application and the available computational budget. Whether prioritizing the record-breaking accuracy of RF-DETR for complex scenes or the unmatched efficiency of the YOLO family for edge deployment, the choice must balance mAP requirements against real-time latency constraints.&lt;/p&gt;



&lt;p&gt;The landscape of computer vision is rapidly shifting toward zero-shot detection frameworks that recognize novel objects without task-specific supervision. As foundation models increasingly integrate sophisticated image embedders like &lt;a href=&quot;https://openai.com/index/clip/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;CLIP&lt;/a&gt; or &lt;a href=&quot;https://dinov2.metademolab.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;DINOv2&lt;/a&gt; into detection pipelines, the boundaries of high-precision detection on resource-constrained hardware will continue to expand. While transformer-based architectures are developing quickly, the YOLO family’s established ecosystem ensures it remains a cornerstone for real-time production environments.&lt;/p&gt;



&lt;p&gt;To achieve the best results for your specific use case, we encourage you to experiment with the models and code samples provided in this guide. To that end, &lt;a href=&quot;https://www.jetbrains.com/pycharm/data-science/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;PyCharm&lt;/a&gt; provides the perfect ecosystem for experimentation with various open-source models via &lt;code&gt;Code -&gt; Insert HF Model&lt;/code&gt; interface. If you’d like to try this yourself, PyCharm Pro comes with a 30-day trial.&lt;/p&gt;



&lt;p&gt;For a hands-on starting point, &lt;a href=&quot;https://blog.jetbrains.com/pycharm/2026/05/build-a-live-object-detection-app-for-reachy-mini-with-tensorflow-and-pycharm/&quot;&gt;this tutorial&lt;/a&gt; shows how to build a live object detection app using TensorFlow and PyCharm Jupyter notebooks, then deploy it on a robot – covering everything from single-frame inference to a live web dashboard with annotated detections. Moreover, stay tuned for the next tutorial post, where we will discuss all three object detection models in action.&lt;/p&gt;</description>
	<pubDate>Tue, 07 Jul 2026 17:51:48 +0000</pubDate>
</item>
<item>
	<title>Ari Lamstein: This Thursday: Building Data Apps with Streamlit and Copilot</title>
	<guid>https://arilamstein.com/blog/2026/07/07/this-thursday-building-data-apps-with-streamlit-and-copilot/</guid>
	<link>https://arilamstein.com/blog/2026/07/07/this-thursday-building-data-apps-with-streamlit-and-copilot/</link>
	<description>&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot;&gt;On &lt;strong&gt;July 9&lt;/strong&gt; (9am–1pm Pacific) — this Thursday — I&amp;#8217;ll be teaching a &lt;strong&gt;4-hour live workshop&lt;/strong&gt; for O&amp;#8217;Reilly: &lt;strong&gt;Building Data Apps with Streamlit and Copilot.&lt;/strong&gt;&lt;/p&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot;&gt;This is the second time I&amp;#8217;ve run this workshop, and I&amp;#8217;ve made several improvements based on what I learned the first time.&lt;/p&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot;&gt;If you work in Python and want to turn your analyses into interactive, shareable tools, this workshop is designed for you. We&amp;#8217;ll start from a Jupyter notebook and build a complete Streamlit app that lets users explore a dataset through interactive controls, charts, and maps. Along the way, you&amp;#8217;ll also learn to use Copilot as a companion while developing software — everything from learning the library faster to improving the quality of the code you write.&lt;/p&gt;
&lt;h3 class=&quot;text-text-100 mt-2 -mb-1 text-base font-bold&quot;&gt;What we&amp;#8217;ll cover&lt;/h3&gt;
&lt;ul class=&quot;[li_&amp;]:mb-0 [li_&amp;]:mt-1 [li_&amp;]:gap-1 [&amp;:not(:last-child)_ul]:pb-1 [&amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3&quot;&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot;&gt;Structuring a Streamlit app&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot;&gt;Working with user input (select boxes, filters, etc.)&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot;&gt;Creating interactive graphics with Plotly&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot;&gt;Organizing the UI with columns and tabs&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot;&gt;Deploying your app to Streamlit Cloud&lt;/li&gt;
&lt;/ul&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot;&gt;The workshop is hands-on: you&amp;#8217;ll build the app step-by-step, and by the end you&amp;#8217;ll have a working project you can adapt to your own data.&lt;/p&gt;
&lt;h3 class=&quot;text-text-100 mt-2 -mb-1 text-base font-bold&quot;&gt;What You&amp;#8217;ll Build&lt;/h3&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot;&gt;Here&amp;#8217;s a screenshot from the app we&amp;#8217;ll build together:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://arilamstein.com/wp-content/uploads/2025/10/Screenshot-2025-10-17-at-10.40.35-AM.png&quot;&gt;&lt;img /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot;&gt;The app lets users choose a state and demographic statistic, explore how it changes over time, and view the data as a chart, map, or table.&lt;/p&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot;&gt;And while the example uses demographic data, the skills you&amp;#8217;ll learn — structuring an app, building interactive controls, and creating dynamic visualizations — apply to any Streamlit project you want to build.&lt;/p&gt;
&lt;h3 class=&quot;text-text-100 mt-2 -mb-1 text-base font-bold&quot;&gt;Who is this for?&lt;/h3&gt;
&lt;ul class=&quot;[li_&amp;]:mb-0 [li_&amp;]:mt-1 [li_&amp;]:gap-1 [&amp;:not(:last-child)_ul]:pb-1 [&amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3&quot;&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot;&gt;Data scientists and analysts who want to make their work more interactive&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot;&gt;Python users who want to build dashboards without learning web development&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot;&gt;Anyone curious about Streamlit or Copilot&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 class=&quot;text-text-100 mt-2 -mb-1 text-base font-bold&quot;&gt;How to Register&lt;/h3&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot;&gt;The workshop is hosted on O&amp;#8217;Reilly, which is a membership platform. If you&amp;#8217;re not already a member, O&amp;#8217;Reilly offers a &lt;strong&gt;free 10-day trial&lt;/strong&gt; — plenty of time to register and attend this week.&lt;/p&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot;&gt;&lt;strong&gt;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.oreilly.com/live-events/building-data-apps-with-streamlit-and-copilot/0642572306243/0642572306236/&quot;&gt;Register here&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot;&gt;Also worth knowing: &lt;strong&gt;the workshop is recorded&lt;/strong&gt;. So if July 9 doesn&amp;#8217;t work for you, it&amp;#8217;s still worth registering — you&amp;#8217;ll have access to the recording.&lt;/p&gt;
&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot;&gt;I&amp;#8217;d love to see you there.&lt;/p&gt;</description>
	<pubDate>Tue, 07 Jul 2026 16:00:39 +0000</pubDate>
</item>
<item>
	<title>Django Weblog: Django security releases issued: 6.0.7 and 5.2.16</title>
	<guid>https://www.djangoproject.com/weblog/2026/jul/07/security-releases/</guid>
	<link>https://www.djangoproject.com/weblog/2026/jul/07/security-releases/</link>
	<description>&lt;p&gt;In accordance with &lt;a href=&quot;https://docs.djangoproject.com/en/dev/internals/security/&quot;&gt;our security release policy&lt;/a&gt;,
the Django team is issuing releases for
&lt;a href=&quot;https://docs.djangoproject.com/en/dev/releases/6.0.7/&quot;&gt;Django 6.0.7&lt;/a&gt; and
&lt;a href=&quot;https://docs.djangoproject.com/en/dev/releases/5.2.16/&quot;&gt;Django 5.2.16&lt;/a&gt;.
These releases address the security issues detailed below. We encourage all
users of Django to upgrade as soon as possible.&lt;/p&gt;
&lt;h2 id=&quot;s-cve-2026-48588-potential-exposure-of-private-data-via-cached-set-cookie-response&quot;&gt;CVE-2026-48588: Potential exposure of private data via cached &lt;code&gt;Set-Cookie&lt;/code&gt; response&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;django.middleware.cache.UpdateCacheMiddleware&lt;/code&gt; and &lt;code&gt;django.views.decorators.cache.cache_page&lt;/code&gt; avoided caching responses that set a cookie while varying on &lt;code&gt;Cookie&lt;/code&gt; only when the incoming request contained no cookies at all. When the request already carried an unrelated cookie (such as a language or theme preference cookie), the protection did not apply, allowing a response that sets a session or other sensitive cookie to be stored in Django's shared cache.&lt;/p&gt;
&lt;p&gt;This issue has severity &quot;low&quot; according to the &lt;a href=&quot;https://docs.djangoproject.com/en/dev/internals/security/#security-issue-severity-levels&quot;&gt;Django security policy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thanks to Chris Whyland for the report.&lt;/p&gt;
&lt;h2 id=&quot;s-cve-2026-53877-heap-buffer-over-read-in-gdalraster&quot;&gt;CVE-2026-53877: Heap buffer over-read in &lt;code&gt;GDALRaster&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;When &lt;code&gt;django.contrib.gis.gdal.GDALRaster&lt;/code&gt; was instantiated with a bytes object representing a raster file, the &lt;code&gt;vsi_buffer&lt;/code&gt; property could over-read the allocated buffer by approximately 32 bytes. This could result in information disclosure of adjacent heap memory or, in rare cases, a segmentation fault. Only rasters stored in GDAL's virtual filesystem were affected.&lt;/p&gt;
&lt;p&gt;This issue has severity &quot;low&quot; according to the &lt;a href=&quot;https://docs.djangoproject.com/en/dev/internals/security/#security-issue-severity-levels&quot;&gt;Django security policy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thanks to Bence Nagy for the report.&lt;/p&gt;
&lt;h2 id=&quot;s-cve-2026-53878-header-injection-possibility-since-domainnamevalidator-accepted-newlines-in-input&quot;&gt;CVE-2026-53878: Header injection possibility since &lt;code&gt;DomainNameValidator&lt;/code&gt; accepted newlines in input&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;django.core.validators.DomainNameValidator&lt;/code&gt; accepted newlines in domain names. If such values were included in HTTP responses, header injection attacks were possible. Django itself wasn't vulnerable because &lt;code&gt;HttpResponse&lt;/code&gt; prohibits newlines in HTTP headers.&lt;/p&gt;
&lt;p&gt;The vulnerability only affected uses of &lt;code&gt;DomainNameValidator&lt;/code&gt; outside Django form fields, as &lt;code&gt;CharField&lt;/code&gt; strips newlines by default.&lt;/p&gt;
&lt;p&gt;This issue has severity &quot;low&quot; according to the &lt;a href=&quot;https://docs.djangoproject.com/en/dev/internals/security/#security-issue-severity-levels&quot;&gt;Django security policy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thanks to Bence Nagy for the report.&lt;/p&gt;
&lt;h2 id=&quot;s-affected-supported-versions&quot;&gt;Affected supported versions&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Django main&lt;/li&gt;
&lt;li&gt;Django 6.1 (currently at beta status)&lt;/li&gt;
&lt;li&gt;Django 6.0&lt;/li&gt;
&lt;li&gt;Django 5.2&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;s-resolution&quot;&gt;Resolution&lt;/h2&gt;
&lt;p&gt;Patches to resolve the issue have been applied to Django's
main, 6.1 (currently at beta status), 6.0, and 5.2 branches.
The patches may be obtained from the following changesets.&lt;/p&gt;
&lt;h3 id=&quot;s-cve-2026-48588-potential-exposure-of-private-data-via-cached-set-cookie-response_1&quot;&gt;CVE-2026-48588: Potential exposure of private data via cached &lt;code&gt;Set-Cookie&lt;/code&gt; response&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;On the &lt;a href=&quot;https://github.com/django/django/commit/6e365f8d01f2ba0bbd90968d76a42600fb8bc4b1&quot;&gt;main branch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;On the &lt;a href=&quot;https://github.com/django/django/commit/c2a936ab7d6048acfc341dd61e6094c2dc84782f&quot;&gt;6.1 branch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;On the &lt;a href=&quot;https://github.com/django/django/commit/64f9a2b2283fde3ec69fb0dfe441cf0f6f411ba3&quot;&gt;6.0 branch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;On the &lt;a href=&quot;https://github.com/django/django/commit/721685aa7799cc9327bd202cd1f70bd012ca95a7&quot;&gt;5.2 branch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;s-cve-2026-53877-heap-buffer-over-read-in-gdalraster_1&quot;&gt;CVE-2026-53877: Heap buffer over-read in &lt;code&gt;GDALRaster&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;On the &lt;a href=&quot;https://github.com/django/django/commit/6ca2bbe2efce21010eff48f1f36a3f621d698ed8&quot;&gt;main branch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;On the &lt;a href=&quot;https://github.com/django/django/commit/a46b417d9e441379e7f86933ec1b2fb05f63d492&quot;&gt;6.1 branch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;On the &lt;a href=&quot;https://github.com/django/django/commit/38dfbd27d7d4f4e6eaa087d7a90f2613fbf55b3a&quot;&gt;6.0 branch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;On the &lt;a href=&quot;https://github.com/django/django/commit/6c66eb8cec52b303af85c2c6e4dd00aa37654dbc&quot;&gt;5.2 branch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;s-cve-2026-53878-header-injection-possibility-since-domainnamevalidator-accepted-newlines-in-input_1&quot;&gt;CVE-2026-53878: Header injection possibility since &lt;code&gt;DomainNameValidator&lt;/code&gt; accepted newlines in input&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;On the &lt;a href=&quot;https://github.com/django/django/commit/3a720d0d8bf2529253b98968f10ca73daf6d693c&quot;&gt;main branch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;On the &lt;a href=&quot;https://github.com/django/django/commit/fe3e8a0de7fad7897fa4917013d8abd507e4d756&quot;&gt;6.1 branch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;On the &lt;a href=&quot;https://github.com/django/django/commit/a5de13f1491f1dbf2bb0ad9b91570524ebbc8acd&quot;&gt;6.0 branch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;On the &lt;a href=&quot;https://github.com/django/django/commit/d5d60ed0323cddaa0ce0237a26a3d49ac21ee05e&quot;&gt;5.2 branch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;s-the-following-releases-have-been-issued&quot;&gt;The following releases have been issued&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Django 6.0.7 (&lt;a href=&quot;https://www.djangoproject.com/download/6.0.7/tarball/&quot;&gt;tarball&lt;/a&gt; | &lt;a href=&quot;https://www.djangoproject.com/download/6.0.7/checksum/&quot;&gt;checksums&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Django 5.2.16 (&lt;a href=&quot;https://www.djangoproject.com/download/5.2.16/tarball/&quot;&gt;tarball&lt;/a&gt; | &lt;a href=&quot;https://www.djangoproject.com/download/5.2.16/checksum/&quot;&gt;checksums&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The PGP key ID used for this release is Jacob Walls: &lt;a href=&quot;https://github.com/jacobtylerwalls.gpg&quot;&gt;131403F4D16D8DC7&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;s-general-notes-regarding-security-reporting&quot;&gt;General notes regarding security reporting&lt;/h2&gt;
&lt;p&gt;As always, we ask that potential security issues be reported via private email
to &lt;code&gt;security@djangoproject.com&lt;/code&gt;, and not via Django's Trac instance, nor via
the Django Forum. Please see
&lt;a href=&quot;https://www.djangoproject.com/security/&quot;&gt;our security policies&lt;/a&gt; for further
information.&lt;/p&gt;</description>
	<pubDate>Tue, 07 Jul 2026 14:00:00 +0000</pubDate>
</item>
<item>
	<title>PyCon: Welcome, Kattni!</title>
	<guid>https://pycon.blogspot.com/2026/07/welcome-kattni.html</guid>
	<link>https://pycon.blogspot.com/2026/07/welcome-kattni.html</link>
	<description>&lt;div&gt;We are thrilled to welcome &lt;a href=&quot;https://kattstodon.com/@kattni&quot;&gt;Kattni&lt;/a&gt; as the next Co-Chair and future Chair of PyCon US! You may already know Kattni from her work on CircuitPython and BeeWare, as a conference speaker, and as the Conference Chair of PyOhio since 2024. We are truly excited for what she will bring to PyCon US and its community over the next four years, and we can't wait to share that with you. In her own words:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;div&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiBfwgQDIzQHDydQ4IE8D05wB2ReAd-LgC2hQiTVk78OZffmYvPKgUsymLOyY9SPOi0I0hNrF9yQL92pbixrf5cW3H8zQZo6pOLdW3mSSXUZYk5ReEan-me2fm5klxcK0l9AFzruBkcAVl_u9hm09ysOfs59bx-_FqDAwZpqmiDX6m9VO86wckXDw/s320/kattni.jpg&quot; /&gt;&lt;/div&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;div&gt;Hello, all! I'm Kattni, and I've been a Python community organiser for nearly a decade. I'm grateful to have spent the last three years as the PyOhio conference chair. PyOhio 2017 was the first technical conference I attended, and to now be able to guide the community that accepted me so openly as a new programmer has been a wonderful experience. I was simultaneously welcomed into the CircuitPython community, and I was able to spend the next six years returning the favor, building that community from the ground up. In 2024, I found a welcoming, supportive home in the BeeWare community, and I have since been working to create a space where others can find the same. I am greatly looking forward to the opportunity to bring my experience and energy to PyCon US.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;Everyone has different reasons for attending PyCon US; the event has a wide range of things to offer. For me, it's about the people -- seeing old friends and making new ones is by far my favorite part of the conference. I want to focus on helping the PyCon US community grow, to further cultivate the myriad perspectives, and increase the opportunities for everyone involved to have their own amazing and memorable experiences. When it comes down to it, PyCon US happens at all because of those who participate. It wouldn't exist without, at a very minimum, those who: organise it, run it, volunteer both before and during, submit to the CFP, speak, teach tutorials, present posters, sponsor, and attend.&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;br /&gt;I hope you'll join us next year in Long Beach, as I begin my journey helping make PyCon US a wonderful event. I'm incredibly excited to be working alongside Jon, and the rest of the staff, organisers, and volunteers, through the next four years. I'm looking forward to seeing everyone next year!&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;We'll have more news to share about PyCon US 2027 in the coming months. Stay tuned to the &lt;a href=&quot;https://pycon.blogspot.com/&quot;&gt;blog&lt;/a&gt; and &lt;a href=&quot;https://pycon.us19.list-manage.com/subscribe?u=5697f493c3a48994f504d4deb&amp;id=a0a3bd5663&quot;&gt;newsletter&lt;/a&gt; for updates. We look forward to welcoming you back to Long Beach next May 12 - 18.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;</description>
	<pubDate>Tue, 07 Jul 2026 11:50:07 +0000</pubDate>
</item>
<item>
	<title>Python Bytes: #487 Minimum requirements</title>
	<guid>https://pythonbytes.fm/episodes/show/487/minimum-requirements</guid>
	<link>https://pythonbytes.fm/episodes/show/487/minimum-requirements</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://github.com/bootandy/dust?featured_on=pythonbytes&quot;&amp;gt;dust&amp;lt;/a&amp;gt; -  a better du&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://hermes-agent.org/?featured_on=pythonbytes&quot;&amp;gt;Hermes Agent&amp;lt;/a&amp;gt;: The AI agent that grows with you&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/simonw/llm-coding-agent/releases/tag/0.1a0?featured_on=pythonbytes&quot;&amp;gt;llm-coding-agent 0.1a0&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=KubyT7Ttggg' style='font-weight: bold;'data-umami-event=&quot;Livestream-Past&quot; data-umami-event-episode=&quot;487&quot;&amp;gt;Watch on YouTube&amp;lt;/a&amp;gt;&amp;lt;br&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;About the show&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;Sponsored by us! Support our work through:&amp;lt;/p&amp;gt;

&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;Our &amp;lt;a href=&quot;https://training.talkpython.fm/?featured_on=pythonbytes&quot;&amp;gt;&amp;lt;strong&amp;gt;courses at Talk Python&amp;lt;/strong&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Consulting from &amp;lt;a href=&quot;https://sixfeetup.com/?featured_on=pythonbytes&quot;&amp;gt;&amp;lt;strong&amp;gt;Six Feet Up&amp;lt;/strong&amp;gt;&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;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://github.com/bootandy/dust?featured_on=pythonbytes&quot;&amp;gt;dust&amp;lt;/a&amp;gt; -&amp;lt;/strong&amp;gt; a better du&amp;lt;/p&amp;gt;

&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;code&amp;gt;du&amp;lt;/code&amp;gt; + Rust = &amp;lt;code&amp;gt;dust&amp;lt;/code&amp;gt; - a fast, visual, intuitive disk-usage CLI&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Run &amp;lt;code&amp;gt;dust&amp;lt;/code&amp;gt; and immediately see the biggest directories and files without piping through &amp;lt;code&amp;gt;sort&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;head&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;awk&amp;lt;/code&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Smart recursive output focuses on what matters instead of dumping every folder&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Colored bars show relative size and parent/child hierarchy, making “where did the space go?” obvious&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Perfect for Python projects bloated by &amp;lt;code&amp;gt;.venv&amp;lt;/code&amp;gt;, caches, Docker volumes, downloaded datasets, and local AI models&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Install via &amp;lt;code&amp;gt;brew&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;cargo install du-dust&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;conda-forge&amp;lt;/code&amp;gt;, Scoop, Snap, deb-get, or GitHub releases&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Calvin #2&amp;lt;/strong&amp;gt;: &amp;lt;a href=&quot;https://github.com/astral-sh/war/blob/main/SPEC.md?featured_on=pythonbytes&quot;&amp;gt;A Way better ARchive format for Python packaging&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;

&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;war&amp;lt;/strong&amp;gt; - new archive format spec from Astral (same team as uv/ruff), v0.0.2, still no binary encoding defined yet&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Header-Index-Store layout: header IDs the file, index maps names to store offsets, store holds compressed data&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Index uses a finite-state transducer (FST) to dedupe common path prefixes across entry names&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Supports three entry types (file, directory, link) and three compression modes (store/DEFLATE/zstd), plus an &quot;executable&quot; metadata flag&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Unpacking is atomic - writes to a temp dir, then renames into place, so a failed extract never leaves a half-unpacked directory&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Strict name-segment rules (no NUL/control chars, no leading/trailing whitespace, blocks Windows-reserved names like CON/PRN) to avoid path traversal and cross-platform footguns&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Michael #3:&amp;lt;/strong&amp;gt; &amp;lt;a href=&quot;https://hermes-agent.org/?featured_on=pythonbytes&quot;&amp;gt;Hermes Agent&amp;lt;/a&amp;gt;: The AI agent that grows with you&amp;lt;/p&amp;gt;

&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;Hermes Agent is an open-source, Python-built AI agent framework from Nous Research - think ChatGPT-style assistant, but connected to your tools, files, shell, browser, calendar, memory, and messaging apps&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;I’m using it in Discord as a long-running agent conversation, not just a one-off chatbot session&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Hermes can connect through a gateway to platforms like Discord, Telegram, Slack, WhatsApp, email, webhooks, and more - so the same assistant can follow you across surfaces&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;In my setup, I can send Hermes voice/text from Discord, keep project context across turns as threads, and ask it to actually do things: read GitHub repos, run commands, edit files, schedule calendar events, generate drafts, and verify results&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;A fun workflow: I can trigger one-shot actions from an Apple Watch shortcut - dictate a request, send it to Hermes, and have the agent execute it asynchronously&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Hermes has persistent memory, so it can remember durable preferences and facts - for example, how I like my research formatted&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;It also has “skills,” which are reusable procedures the agent can load later, so Hermes can self-improve over time instead of rediscovering the same workflow repeatedly&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;It supports scheduled jobs / cron-style automations, so it can proactively watch for releases, send summaries, run checks, or remind you about things&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;It’s provider-agnostic: OpenRouter, Anthropic, Google, xAI, local models, Nous Portal, and others&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;The big idea: Hermes turns an LLM from “a chat box I visit” into “an agent I can reach from anywhere that knows my workflows and can take real actions and learns over time.”&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Calvin #4: &amp;lt;a href=&quot;https://github.com/simonw/llm-coding-agent/releases/tag/0.1a0?featured_on=pythonbytes&quot;&amp;gt;llm-coding-agent 0.1a0&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;Simon Willison built a Claude/Codex-style coding agent on top of his &amp;lt;code&amp;gt;llm&amp;lt;/code&amp;gt; library, using an alpha of the &amp;lt;code&amp;gt;llm&amp;lt;/code&amp;gt; package plus his python-lib-template-repo&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Built almost entirely via prompted TDD - asked an agent to write a &amp;lt;a href=&quot;http://spec.md?featured_on=pythonbytes&quot;&amp;gt;spec.md&amp;lt;/a&amp;gt;, then commit + implement with red/green tests, occasionally hitting a real OpenAI key to sanity-check&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Shipped to PyPI as an alpha: &amp;lt;code&amp;gt;uvx --prerelease=allow --with llm-coding-agent llm code&amp;lt;/code&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Tool set mirrors familiar coding-agent primitives: read_file, edit_file (exact string replace + diff), write_file, list_files, search_files, execute_command&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Also exposes a Python API - &amp;lt;code&amp;gt;CodingAgent(model=&quot;gpt-5.5&quot;, root=..., approve=True).run(...)&amp;lt;/code&amp;gt; - which Simon didn't ask for but got anyway&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Demo: &amp;lt;code&amp;gt;llm code --yolo&amp;lt;/code&amp;gt; told GPT-5.5 to build a SwiftUI CLI clock; model correctly noted SwiftUI isn't really CLI-friendly and still produced an ASCII-art time display&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;Calvin:&amp;lt;/p&amp;gt;

&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;Slides, but for developers &amp;lt;a href=&quot;https://sli.dev/?featured_on=pythonbytes&quot;&amp;gt;https://sli.dev/&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Wanna reduce your token usage…. only issue is that its lossy &amp;lt;a href=&quot;https://github.com/teamchong/pxpipe?featured_on=pythonbytes&quot;&amp;gt;https://github.com/teamchong/pxpipe&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;PEP 772 - Python Packaging Council inaugural election dates set, nominations open July 28, voting September 1-15&amp;lt;/strong&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;

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

&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&quot;https://mkennedy.codes/posts/what-the-pls/?featured_on=pythonbytes&quot;&amp;gt;What the pls?&amp;lt;/a&amp;gt; revisited!&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Joke:&amp;lt;/strong&amp;gt; &amp;lt;a href=&quot;https://x.com/AlfinCodes/status/2057443209127903456?featured_on=pythonbytes&quot;&amp;gt;Min requirements for Linux&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;</description>
	<pubDate>Tue, 07 Jul 2026 08:00:00 +0000</pubDate>
</item>
<item>
	<title>The No Title® Tech Blog: Optimize Images X now supports WEBP and drag and drop</title>
	<guid>https://no-title.victordomingos.com/articles/2026/optimize-images-x_drag-and-drop</guid>
	<link>https://no-title.victordomingos.com/articles/2026/optimize-images-x_drag-and-drop</link>
	<description>&lt;p&gt;Optimize Images X, the multi-platform desktop application that helps you reduce the file size of your images on macOS, Windows and Linux, has just been updated to version 2.1.0. This new version adds drag-and-drop, conversion to more output formats, including WebP and &lt;span class=&quot;caps&quot;&gt;AVIF&lt;/span&gt;, a new image info window with a readable &lt;span class=&quot;caps&quot;&gt;EXIF&lt;/span&gt; report, and a PyInstaller spec file for building standalone&amp;nbsp;executables.&lt;/p&gt;</description>
	<pubDate>Tue, 07 Jul 2026 06:50:00 +0000</pubDate>
</item>
<item>
	<title>Seth Michael Larson: Playing the “Second Quest” of Legend of Zelda: Four Swords Adventures</title>
	<guid>https://sethmlarson.dev/legend-of-zelda-four-swords-adventures-second-quest?utm_campaign=rss</guid>
	<link>https://sethmlarson.dev/legend-of-zelda-four-swords-adventures-second-quest?utm_campaign=rss</link>
	<description>&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/The_Legend_of_Zelda:_Four_Swords_Adventures&quot;&gt;The Legend of Zelda: Four Swords Adventures&lt;/a&gt; (FSA) was released for the Nintendo
GameCube in 2004 in Japan and America and 2005 globally. The game is split into eight areas with three levels per area.
Data miners discovered that there were 8 levels that were cut late into development of the game that were
all mostly playable. These cut levels can be seen on &lt;a href=&quot;https://tcrf.net/The_Legend_of_Zelda:_Four_Swords_Adventures/Unused_Rooms&quot;&gt;The Cutting Room Floor&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In 2021 a game modding and localizing group named “&lt;a href=&quot;https://www.patreon.com/EternalDream&quot;&gt;Eternal Dream Arabization&lt;/a&gt;”
and &lt;a href=&quot;https://x.com/SSSLink64&quot;&gt;$$$Link&lt;/a&gt; created a mod for FSA which restored and made these previously unseen levels completable.
The group used &lt;a href=&quot;https://www.jaytheham.com/efsa&quot;&gt;EFSAdvent&lt;/a&gt; (&lt;a href=&quot;https://github.com/Venomalia/EFSAdvent&quot;&gt;source code&lt;/a&gt;),
a level editing tool for FSA, to make the levels completable by fixing issues
and adding Force Gems where necessary.&lt;/p&gt;

&lt;!-- more --&gt;

&lt;p&gt;The mod itself is a collection of map files to be inserted into the game ROM
and Action Replay codes that overwrite the map file loaded when entering
the final level in each respective area. Here is the full list of cut levels in order:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;River Flow (1-3)&lt;/li&gt;
&lt;li&gt;Rainy Forest (2-3)&lt;/li&gt;
&lt;li&gt;Mountain Road (3-3)&lt;/li&gt;
&lt;li&gt;Graveyard (4-3)&lt;/li&gt;
&lt;li&gt;Four Descents into the Darkness (5-3) &lt;/li&gt;
&lt;li&gt;Oasis (6-3)&lt;/li&gt;
&lt;li&gt;Through the Blizzard (7-3)&lt;/li&gt;
&lt;li&gt;Clouds Across the Wind (8-2)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This game is one of the least played titles in the Legend of Zelda series,
selling fewer than 500,000 copies globally and hasn't yet been re-released on any platform.
As far as I can tell from searching the web and YouTube there is no
footage of these cut and restored levels being played. I plan to rectify
this by playing all the levels on a modded GameCube and publishing
the recordings to YouTube. Below is the first video of me playing &quot;River Flow&quot;:&lt;/p&gt;

&lt;p&gt;&lt;center&gt;

&lt;/center&gt;&lt;/p&gt;

&lt;p&gt;I'll publish more videos as I play the rest of the levels.&lt;/p&gt;

&lt;h2&gt;How to play&lt;/h2&gt;

&lt;p&gt;If you'd like to play these cut levels yourself below is a
complete guide on how to do so using an emulator like Dolphin
and on real GameCube hardware using Swiss. To start you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A legally obtained ROM of Legend of Zelda: Four Swords Adventures.
I used FlippyDrive’s built-in &lt;a href=&quot;https://docs.flippydrive.com/backup.html&quot;&gt;Disk Backup utility&lt;/a&gt; to dump the ROMs from disks I own.&lt;/li&gt;
&lt;li&gt;An install of &lt;a href=&quot;https://www.python.org/&quot;&gt;Python&lt;/a&gt; and &lt;a href=&quot;https://pypi.org/project/pyisotools&quot;&gt;pyisotools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.patreon.com/EternalDream/posts/zelda-four-beta-60032999&quot;&gt;FSA Second Quest level files&lt;/a&gt; (&lt;code&gt;FSA_SecondQuest.7z&lt;/code&gt;). I've &lt;a href=&quot;https://github.com/sethmlarson/loz-four-swords-adventures-second-quest&quot;&gt;mirrored these
files on GitHub&lt;/a&gt; in case they are ever removed from Patreon.&lt;/li&gt;
&lt;li&gt;An emulator like Dolphin or a modded GameCube with Swiss&lt;/li&gt;
&lt;li&gt;If using a Japanese ROM of FSA+, the FSA+ English Translation Port
must be applied in addition to Second Quest level files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start by making a copy the ROM, as we'll be modifying the contents
using &lt;code&gt;pyisotools&lt;/code&gt;. Create a new directory with a Python virtual environment
(&lt;code&gt;python -m venv venv&lt;/code&gt;). Activate the virtual environment (&lt;code&gt;source venv/bin/activate&lt;/code&gt;).
Install &lt;code&gt;pyisotools&lt;/code&gt; and its dependencies using pip, I used the following lock file
to do so:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;python -m pip install \
  altgraph==0.17.5 \
  astroid==4.0.4 \
  beautifulsoup4==4.15.0 \
  bs4==0.0.2 \
  certifi==2026.6.17 \
  cffi==2.0.0 \
  chardet==7.4.3 \
  charset-normalizer==3.4.7 \
  cryptography==49.0.0 \
  dill==0.4.1 \
  dolreader==1.1.1 \
  idna==3.18 \
  isort==8.0.1 \
  mccabe==0.7.0 \
  packaging==26.2 \
  pillow==12.3.0 \
  platformdirs==4.10.0 \
  pycparser==3.0 \
  pygithub==2.9.1 \
  pyinstaller==6.21.0 \
  pyinstaller-hooks-contrib==2026.6 \
  pyisotools==2.4.7 \
  pyjwt[crypto]==2.13.0 \
  pylint==4.0.6 \
  pynacl==1.6.2 \
  pyside6==6.11.1 \
  pyside6-addons==6.11.1 \
  pyside6-essentials==6.11.1 \
  qdarkstyle==3.2.3 \
  qtpy==2.4.3 \
  requests==2.34.2 \
  shiboken6==6.11.1 \
  sortedcontainers==2.4.0 \
  soupsieve==2.8.4 \
  tomlkit==0.15.0 \
  typing-extensions==4.16.0 \
  urllib3==2.7.0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you're not interested in copy-and-pasting this command you
can full-send install using &lt;code&gt;python -m pip install pyisotools&lt;/code&gt;
and cross your fingers.&lt;/p&gt;

&lt;p&gt;From here we're going to add the modded map files to the ISO.
Use the extract (&lt;code&gt;E&lt;/code&gt;) command with &lt;code&gt;pyisotools&lt;/code&gt; to extract the ISO
to a directory:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;python -m pyisotools './The Legend of Zelda- Four Swords FOR NINTENDO GAMECUBE (v1.00).iso' E --dest .
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This will create a directory called &lt;code&gt;root/&lt;/code&gt; in the current directory
with the extracted files. Copy the files from &lt;code&gt;FSA_SecondQuest&lt;/code&gt; into
the &lt;code&gt;root/files/GC4Sword_usa/Boss&lt;/code&gt; directory:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;cp FSA_SecondQuest/boss*.arc root/files/GC4Sword_usa/Boss
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you are using the Japanese ROM for FSA, at this point you'd
also do the file replacements for the &lt;code&gt;FS+_EN_Translation_Port&lt;/code&gt;
files, too. For the English ROM this is not necessary.&lt;/p&gt;

&lt;p&gt;Now we build (&lt;code&gt;B&lt;/code&gt;) an ISO again using these new files. Make sure you've made
a copy of your ROM before doing this to avoid needing to dump the ROM
from the disk again.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;python -m pyisotools root/ B --dest './The Legend of Zelda- Four Swords FOR NINTENDO GAMECUBE (v1.00).iso'
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now we have a modified ROM that is ready to be played! To load into
the cut levels we'll need to use Action Replay cheat codes depending
on our platform, either Dolphin or Swiss.&lt;/p&gt;

&lt;h2&gt;Action Replay codes&lt;/h2&gt;

&lt;p&gt;I've tested the Action Replay codes supplied by the project, and they didn't work
as expected, so after testing I used the &lt;a href=&quot;https://tcrf.net/The_Legend_of_Zelda:_Four_Swords_Adventures/Unused_Rooms#:~:text=Cheat%20Codes&quot;&gt;Action Replay codes from
The Cutting Room Floor&lt;/a&gt; instead.
I also added the Action Replay code to the US version for unlocking all areas and levels
immediately so you can quickly visit each level. For the JP region I could not
find an associated Action Replay code, but there are &lt;a href=&quot;https://gc-saves.com/container/86&quot;&gt;100% completed
save files available&lt;/a&gt; for each region.
Remember for Dolphin you must explicitly &quot;Enable Cheats&quot; for these codes to work.&lt;/p&gt;

&lt;blockquote&gt;

Action Replay codes (US)
&lt;p&gt;All Adventure Mode Levels Unlocked (Codejunkies)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0450ECA8 FFFFFFFF
0450ED70 FFFFFFFF
0450EE38 FFFFFFFF
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replace Level 3 with Cut Sub-Stages (The Cutting Room Floor)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;04466054 3031322E
04466084 3032322E
044660B4 3033322E
044660E4 3034322E
04466114 3035322E
04466144 3036322E
04466174 3037322E
044661A4 3038322E
044811EC 31322E63
0448121C 32322E63
0448124C 33322E63
0448127C 34322E63
044812AC 35322E63
044812DC 36322E63
0448130C 37322E63
0448133C 38322E63
04481524 31325F31
04481554 32325F31
04481584 33325F31
044815B4 34325F31
044815E4 35325F31
04481614 36325F31
04481644 37325F31
04481674 38325F31
04538A14 30313200
04538A24 30323200
04538A34 30333200
04538A44 30343200
04538A54 30353200
04538A64 30363200
04538A74 30373200
04538A84 30383200
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;blockquote&gt;

Action Replay codes (JP)
&lt;p&gt;Replace Level 3 with Cut Sub-Stages (The Cutting Room Floor)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;04466054 3031322E
04466084 3032322E
044660B4 3033322E
044660E4 3034322E
04466114 3035322E
04466144 3036322E
04466174 3037322E
044661A4 3038322E
044811EC 31322E63
0448121C 32322E63
0448124C 33322E63
0448127C 34322E63
044812AC 35322E63
044812DC 36322E63
0448130C 37322E63
0448133C 38322E63
04481524 31325F31
04481554 32325F31
04481584 33325F31
044815B4 34325F31
044815E4 35325F31
04481614 36325F31
04481644 37325F31
04481674 38325F31
04538A14 30313200
04538A24 30323200
04538A34 30333200
04538A44 30343200
04538A54 30353200
04538A64 30363200
04538A74 30373200
04538A84 30383200
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;h2&gt;Swiss cheats config files&lt;/h2&gt;

&lt;p&gt;Swiss cheat codes need to be supplied in their own format.
These are the same Action Replay codes as above, but in the
Swiss cheats format. These files should be placed into the
&lt;code&gt;swiss/cheats&lt;/code&gt; folder on your microSD card (which you may
need to create) and on boot, the cheats need to be enabled
by pressing &lt;code&gt;Y&lt;/code&gt; prior to launching the game through Swiss.&lt;/p&gt;

&lt;blockquote&gt;

Swiss cheats config (US, &lt;code&gt;G4SE01.txt&lt;/code&gt;)
&lt;pre&gt;&lt;code&gt;0450ECA8 FFFFFFFF
0450ED70 FFFFFFFF
0450EE38 FFFFFFFF
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;blockquote&gt;

Swiss cheats config (JP, &lt;code&gt;G4SJ01.txt&lt;/code&gt;)
&lt;pre&gt;&lt;code&gt;0450ECA8 FFFFFFFF
0450ED70 FFFFFFFF
0450EE38 FFFFFFFF
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;
&lt;br /&gt;&lt;hr /&gt;&lt;p&gt;Thanks for reading ♥ I would love to hear your thoughts! Contact me via &lt;a href=&quot;https://mastodon.social/@sethmlarson&quot;&gt;Mastodon&lt;/a&gt;, &lt;a href=&quot;https://bsky.app/profile/sethmlarson.dev&quot;&gt;Bluesky&lt;/a&gt;, or &lt;a href=&quot;mailto:sethmichaellarson@gmail.com&quot;&gt;email&lt;/a&gt;. Browse the &lt;a href=&quot;https://sethmlarson.dev/&quot;&gt;blog archive&lt;/a&gt;. Check out my &lt;a href=&quot;https://sethmlarson.dev/blogroll&quot;&gt;blogroll&lt;/a&gt;.&lt;/p&gt;&lt;hr /&gt;&lt;br /&gt;</description>
	<pubDate>Tue, 07 Jul 2026 00:00:00 +0000</pubDate>
</item>
<item>
	<title>Rodrigo Girão Serrão: Write a coding agent from first principles: better tools</title>
	<guid>https://mathspp.com/blog/write-a-coding-agent-from-first-principles-better-tools</guid>
	<link>https://mathspp.com/blog/write-a-coding-agent-from-first-principles-better-tools</link>
	<description>&lt;img alt=&quot;&quot; src=&quot;https://mathspp.com/images/d/b/c/b/4/dbcb4f8265791ab9105d5ff06b3d49e0120c8b8f-thumbnail.webp&quot; /&gt;
                                &lt;p&gt;Improve the capabilities of your agent by providing it with better tools.&lt;/p&gt;

&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;a href=&quot;https://mathspp.com/blog/tags/python.rss#introduction&quot; class=&quot;toc-anchor after&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This tutorial builds on &lt;a href=&quot;https://mathspp.com/blog/write-a-coding-agent-from-first-principles&quot;&gt;the coding agent you implemented in the tutorial &amp;ldquo;Write a coding agent from first principles&amp;rdquo;&lt;/a&gt;.
In this tutorial, you'll take your agent and improve its capabilities by implementing the text edit and bash command tools that Anthropic provides.&lt;/p&gt;
&lt;h2 id=&quot;why-use-anthropic-s-tools&quot;&gt;Why use Anthropic's tools?&lt;a href=&quot;https://mathspp.com/blog/tags/python.rss#why-use-anthropic-s-tools&quot; class=&quot;toc-anchor after&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In the previous tutorial you implemented a coding agent that has a few tools that it can use to read, write, and execute, code.
The tool &lt;code&gt;&quot;bash&quot;&lt;/code&gt; can be used to execute arbitrary commands and the tools &lt;code&gt;&quot;read&quot;&lt;/code&gt;, &lt;code&gt;&quot;write&quot;&lt;/code&gt;, &lt;code&gt;&quot;replace&quot;&lt;/code&gt;, and &lt;code&gt;&quot;insert&quot;&lt;/code&gt;, can be used to edit files.&lt;/p&gt;
&lt;p&gt;As it turns out, these tools are so universally useful that Anthropic trained its models on specific schema definitions for these tools.
The tools still run on the client side, so you'll still get tool use blocks in the API responses, but you don't have to define the schema for the tool.
You just specify the tools by &lt;a href=&quot;https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-reference#anthropic-provided-tools&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot; class=&quot;external-link no-image&quot;&gt;their Anthropic types and names&lt;/a&gt;, and the LLMs will happily request tool uses.&lt;/p&gt;
&lt;p&gt;Anthropic trains their models on a number of useful tools but you'll focus your attention on two tools that emulate the functionality you already have:&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Text editor tool&lt;/strong&gt;: this tool replaces the four tools you defined to read, write, replace, and insert, text in text files&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bash tool&lt;/strong&gt;: this tool provides a persistent bash session that can run bash commands&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;By replacing your tools with Anthropic's, the agent will be able to make better tool calls consistently, since Anthropic trains their models on their specific tool schemas.&lt;/p&gt;
&lt;h2 id=&quot;the-native-text-editor-tool&quot;&gt;The native text editor tool&lt;a href=&quot;https://mathspp.com/blog/tags/python.rss#the-native-text-editor-tool&quot; class=&quot;toc-anchor after&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To define support for Anthropic's text editor tool you need to add it to your list of tools.
The name of the tool is &lt;code&gt;&quot;str_replace_based_edit_tool&quot;&lt;/code&gt; and its type is &lt;code&gt;&quot;text_editor_20250728&quot;&lt;/code&gt;.
(The type carries &lt;a href=&quot;https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-reference#tool-versioning&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot; class=&quot;external-link no-image&quot;&gt;a versioning suffix&lt;/a&gt; that may influence the tool's behaviour, so make sure you use the right date suffix.)&lt;/p&gt;
&lt;p&gt;Since you'll be using Anthropic's text editor tool, you can delete the functions &lt;code&gt;read&lt;/code&gt;, &lt;code&gt;write&lt;/code&gt;, &lt;code&gt;replace&lt;/code&gt;, and &lt;code&gt;insert&lt;/code&gt;, and the corresponding dictionaries that go in the list &lt;code&gt;TOOLS&lt;/code&gt;.
Instead, add the dictionary that specifies the Anthropic tool:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-py&quot;&gt;# ...
TOOLS = [
    {
        &quot;type&quot;: &quot;text_editor_20250728&quot;,
        &quot;name&quot;: &quot;str_replace_based_edit_tool&quot;,
    }
]

# Bash tool defined and added later.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For organisation purposes, you'll define the text editor tool and the bash tool in their own submodules, so create the folder &lt;code&gt;tools&lt;/code&gt; and then create the file &lt;code&gt;tools/str_replace_based_edit_tool.py&lt;/code&gt; under &lt;code&gt;src/agent&lt;/code&gt;.
In there, you'll define the code to handle the tool call.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://platform.claude.com/docs/en/agents-and-tools/tool-use/text-editor-tool&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot; class=&quot;external-link no-image&quot;&gt;text editor tool&lt;/a&gt; is a 4-in-1 tool that allows you to &lt;em&gt;view&lt;/em&gt;, &lt;em&gt;replace&lt;/em&gt;, &lt;em&gt;create&lt;/em&gt;, and &lt;em&gt;insert&lt;/em&gt;, text.
To disambiguate the action you want to do, the tool use request includes a &lt;em&gt;command&lt;/em&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-py&quot;&gt;# Example tool use dictionary:
{
  &quot;type&quot;: &quot;tool_use&quot;,
  &quot;id&quot;: &quot;toolu_01A09q90qw90lq917835lq9&quot;,
  &quot;name&quot;: &quot;str_replace_based_edit_tool&quot;,
  &quot;input&quot;: {
    &quot;command&quot;: &quot;view&quot;,  # &amp;lt;--
    # ...
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You'll use the key &lt;code&gt;&quot;command&quot;&lt;/code&gt; from the...&lt;/p&gt;</description>
	<pubDate>Mon, 06 Jul 2026 13:00:00 +0000</pubDate>
</item>
<item>
	<title>Seth Michael Larson: Mario Kart World and “seamless” media</title>
	<guid>https://sethmlarson.dev/mario-kart-world-and-seamless-media?utm_campaign=rss</guid>
	<link>https://sethmlarson.dev/mario-kart-world-and-seamless-media?utm_campaign=rss</link>
	<description>&lt;p&gt;Mario Kart World for the Nintendo Switch 2 adds a new unique
game mechanic for the series where courses that neighbor
each other in the sprawling world map can physically and thematically morph
over a short transitionary “lap”.&lt;/p&gt;

&lt;!-- more --&gt;

&lt;div class=&quot;row&quot;&gt;
&lt;div class=&quot;col-6&quot;&gt;
&lt;p&gt;These course-connecting laps are commonly called &lt;a href=&quot;https://www.mariowiki.com/Route&quot;&gt;Routes&lt;/a&gt;
or “Intermission Tracks”.
There are 202 routes connecting the 30 courses in Mario Kart
World, some of which change depending on your
direction between courses.
&lt;/p&gt;
&lt;p&gt;
This new mechanic enables
what I consider the highlight of the game: a new mode named “&lt;a href=&quot;https://www.mariowiki.com/Mario_Kart_World#Knockout_Tour&quot;&gt;Knockout Tour&lt;/a&gt;”
that is reminiscent of &lt;a href=&quot;https://horror.dreamdawn.com/?p=6589&quot;&gt;arcade racing games with time-based checkpoints&lt;/a&gt;.
&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;col-6&quot;&gt;&lt;center&gt;&lt;p&gt;
&lt;img src=&quot;https://mario.wiki.gallery/images/thumb/0/01/Mario_Kart_routes_lengths_map.png/1200px-Mario_Kart_routes_lengths_map.png&quot; /&gt;
&lt;br /&gt;&lt;small&gt;&lt;em&gt;Diagram showing which courses can transition to one another. Created by &lt;a href=&quot;https://www.reddit.com/r/mariokart/comments/1lwpetr/an_overview_of_all_routes_in_mario_kart_world_and/&quot;&gt;u/SnooHamsters6067&lt;/a&gt;
&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;&lt;/center&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;hr /&gt;

&lt;p&gt;Previously in Mario Kart,
beginning a new course meant selecting the course or Grand Prix by name in a menu.
Lakitu would provide a sweeping camera fly-over of the course, highlighting the title
and the challenges to come. The racers would
be presented and a countdown from three would begin, with varying times to hold the
throttle to boost off the starting line. After
a winner had passed the finish line the placements would be tallied and the cycle would
begin anew.&lt;/p&gt;

&lt;p&gt;Mario Kart World has done away with all of this in Grand Prix, Knockout Tour, and other rally racing formats.
There is no ceremony for each course, there is only beginning and ending play.
The courses all blur together at the edges.
This seamless approach reminded me of the many new forms of passive media participation including
short-form infinite video streams, “Auto Play”,
or algorithmically curated DJs and playlists.&lt;/p&gt;

&lt;p&gt;The effects of this “seamlessness” are similar for Mario Kart World courses and
other digital mediums. Despite playing Mario Kart World &lt;a href=&quot;https://sethmlarson.dev/nintendo-switch-play-activity-ocr&quot;&gt;for many hours&lt;/a&gt;, I can't remember many
courses by name (besides Rainbow Road). Compare that to previous Mario Kart titles, where
the courses are iconic and much easier to recall from a single screenshot.
I feel the same is true for media that is algorithmically curated for me versus
media that I've actively chosen to engage with.&lt;/p&gt;

&lt;p&gt;To be clear: the design and game mechanics of Mario Kart World are not even close
to being as negative as some of the dark patterns in media platforms today.
In Mario Kart World the mechanic is used to enable new types of play where in
digital media removing seams is used to separate you from the artists
and your peers, placing the platform as a necessary intermediary.&lt;/p&gt;

&lt;p&gt;When you're a passive
participant, there are no ceremonies.
Ceremonies are reduced because ceremonies are inflection points that might disturb you just enough to question
whether you're enjoying what you're experiencing.&lt;/p&gt;

&lt;p&gt;You don't choose an artist based
on the mood you're in. You don't need to curate a playlist ahead of time
for a longer listening session. You don't select a video
based on mutual interest with others. You won't be able to recall what
media you've experienced, who the author was, or how to follow for future
works. Is it possible to be a “fan”
of media you're experiencing without the ceremony?&lt;/p&gt;
&lt;br /&gt;&lt;hr /&gt;&lt;p&gt;Thanks for reading ♥ I would love to hear your thoughts! Contact me via &lt;a href=&quot;https://mastodon.social/@sethmlarson&quot;&gt;Mastodon&lt;/a&gt;, &lt;a href=&quot;https://bsky.app/profile/sethmlarson.dev&quot;&gt;Bluesky&lt;/a&gt;, or &lt;a href=&quot;mailto:sethmichaellarson@gmail.com&quot;&gt;email&lt;/a&gt;. Browse the &lt;a href=&quot;https://sethmlarson.dev/&quot;&gt;blog archive&lt;/a&gt;. Check out my &lt;a href=&quot;https://sethmlarson.dev/blogroll&quot;&gt;blogroll&lt;/a&gt;.&lt;/p&gt;&lt;hr /&gt;&lt;br /&gt;</description>
	<pubDate>Mon, 06 Jul 2026 00:00:00 +0000</pubDate>
</item>
<item>
	<title>Christian Ledermann: Migrate From mypy To ty And pyrefly</title>
	<guid>https://dev.to/ldrscke/migrate-from-mypy-to-ty-and-pyrefly-4p30</guid>
	<link>https://dev.to/ldrscke/migrate-from-mypy-to-ty-and-pyrefly-4p30</link>
	<description>&lt;p&gt;I wanted to migrate one of my Python packages from &lt;code&gt;mypy&lt;/code&gt; to &lt;code&gt;ty&lt;/code&gt; and &lt;code&gt;pyrefly&lt;/code&gt;. I handed this task over to Claude, and at the end I asked it to write out some guidance on how to perform it most efficiently. So what follows is &lt;strong&gt;AI-generated 'slop'&lt;/strong&gt;.&lt;/p&gt;




&lt;p&gt;This guide is not about using &lt;a href=&quot;https://github.com/cleder/fastkml&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;fastkml&lt;/code&gt;&lt;/a&gt;. It documents how &lt;code&gt;fastkml&lt;/code&gt; itself was migrated from &lt;code&gt;mypy&lt;/code&gt; to Astral's &lt;a href=&quot;https://docs.astral.sh/ty/coming-from-mypy-or-pyright/&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;ty&lt;/code&gt;&lt;/a&gt; and Meta's &lt;a href=&quot;https://pyrefly.org/en/docs/migrating-from-mypy/&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;pyrefly&lt;/code&gt;&lt;/a&gt;, so the same playbook can be replayed on other codebases with less trial and error. Keep it here because the next migration (human- or agent-driven) should start from findings, not from zero.&lt;/p&gt;

&lt;p&gt;Running two checkers instead of one is deliberate, not incidental. &lt;code&gt;ty&lt;/code&gt; and &lt;code&gt;pyrefly&lt;/code&gt; disagree with each other and with mypy often enough that running only one gives a false sense of completeness. Budget for both, and expect them to catch different subsets of the same bugs.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Get a raw error-count baseline for both tools &lt;strong&gt;before&lt;/strong&gt; touching any source. Categorize by error kind, not by file.&lt;/li&gt;
&lt;li&gt;Look for one &lt;strong&gt;systemic&lt;/strong&gt; root cause before fixing anything file-by-file. In most codebases with an optional C-extension backend (lxml, pydantic-core, orjson, etc.) there is a single architectural fix that collapses 60-90% of the noise.&lt;/li&gt;
&lt;li&gt;Fix genuine bugs the tools surface (there will be some — both tools are pickier than mypy about &lt;code&gt;Optional&lt;/code&gt;/union narrowing, positional-only stubs, and unpacking).&lt;/li&gt;
&lt;li&gt;Bulk-suppress test-file &quot;constructed-then-accessed-without-narrowing&quot; noise scoped to &lt;code&gt;tests/**&lt;/code&gt;, not case by case, and not by widening the rule to error kinds that could hide real bugs (&lt;code&gt;invalid-argument-type&lt;/code&gt; is not &lt;code&gt;unresolved-attribute&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Turn on strict presets, then promote specific rules the preset doesn't cover, and explicitly cut the ones that create disproportionate mechanical churn (ask a human before doing a 80-site &lt;code&gt;@override&lt;/code&gt; sweep).&lt;/li&gt;
&lt;li&gt;Verify: both tools clean, full test suite green (with &lt;strong&gt;and&lt;/strong&gt; without optional runtime deps installed), linter clean, and the TOML re-parses.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Phase 0 — Inventory the mypy config honestly
&lt;/h2&gt;

&lt;p&gt;Before deleting &lt;code&gt;[tool.mypy]&lt;/code&gt;, read what each flag actually bought you, because that's the strictness bar &lt;code&gt;ty&lt;/code&gt;/&lt;code&gt;pyrefly&lt;/code&gt; need to match or exceed:&lt;/p&gt;

&lt;div class=&quot;table-wrapper-paragraph&quot;&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;mypy setting&lt;/th&gt;
&lt;th&gt;Rough ty/pyrefly equivalent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;disallow_any_generics&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ty &lt;code&gt;missing-type-argument = &quot;error&quot;&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;warn_redundant_casts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ty &lt;code&gt;redundant-cast&lt;/code&gt;; pyrefly &lt;code&gt;redundant-cast&lt;/code&gt; (both exist, check current default level)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;warn_unused_ignores&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ty &lt;code&gt;unused-ignore-comment&lt;/code&gt; / &lt;code&gt;unused-type-ignore-comment&lt;/code&gt;; pyrefly &lt;code&gt;unused-ignore&lt;/code&gt; (on by default under &lt;code&gt;strict&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;warn_unreachable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;pyrefly's &lt;code&gt;strict&lt;/code&gt; preset covers this; ty has no exact analog — don't assume parity, spot-check&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;disallow_untyped_defs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Neither tool has a literal flag for this — &lt;code&gt;ty&lt;/code&gt; infers types through unannotated bodies by default (different philosophy from mypy). Don't expect a 1:1 mapping; re-derive intent instead of hunting for the same flag name.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per-module &lt;code&gt;disable_error_code&lt;/code&gt; overrides&lt;/td&gt;
&lt;td&gt;pyrefly &lt;code&gt;[[tool.pyrefly.sub-config]]&lt;/code&gt; + &lt;code&gt;matches&lt;/code&gt; glob; ty &lt;code&gt;[[tool.ty.overrides]]&lt;/code&gt; + &lt;code&gt;include&lt;/code&gt; glob&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Also inventory &lt;strong&gt;stale&lt;/strong&gt; per-module overrides — a mypy config that's been edited over years accumulates dead entries (a module path that was renamed or deleted, but the override survived). Grep for the referenced paths; don't carry dead config forward.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 1 — Install both tools and get a baseline
&lt;/h2&gt;



&lt;div class=&quot;highlight js-code-highlight&quot;&gt;
&lt;pre class=&quot;highlight shell&quot;&gt;&lt;code&gt;uv pip &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;ty pyrefly
ty check &amp;lt;src&amp;gt; &amp;lt;tests&amp;gt;
pyrefly check &amp;lt;src&amp;gt; &amp;lt;tests&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Immediately categorize, don't read line by line yet:&lt;br /&gt;
&lt;/p&gt;

&lt;div class=&quot;highlight js-code-highlight&quot;&gt;
&lt;pre class=&quot;highlight shell&quot;&gt;&lt;code&gt;ty check &amp;lt;src&amp;gt; &amp;lt;tests&amp;gt; 2&amp;gt;&amp;amp;1 | &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-oE&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'^error\[[a-zA-Z-]+\]'&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;sort&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;uniq&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;sort&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-rn&lt;/span&gt;
ty check &amp;lt;src&amp;gt; &amp;lt;tests&amp;gt; 2&amp;gt;&amp;amp;1 | &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-E&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'^\s+--&amp;gt;'&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;sed&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-E&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'s/^\s+--&amp;gt; //; s/:[0-9]+:[0-9]+$//'&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;sort&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;uniq&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;sort&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-rn&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second command (errors by file) usually reveals the systemic cause immediately: a handful of files concentrate a disproportionate share of the errors, and they're usually the ones touching an optional/duck-typed dependency.&lt;/p&gt;

&lt;p&gt;If a partial migration already exists in the repo (CI switched over but &lt;code&gt;pyproject.toml&lt;/code&gt; grew broad &lt;code&gt;ignore-missing-imports = [&quot;*&quot;]&lt;/code&gt; / blanket &lt;code&gt;missing-attribute = &quot;ignore&quot;&lt;/code&gt; sub-configs), treat that as a red flag, not a starting point. Broad suppressions accumulated during an in-progress migration usually mean someone hit friction and silenced it rather than fixed it. Re-run with those suppressions removed to see the real baseline before deciding what's worth keeping.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 2 — Find the systemic root cause first
&lt;/h2&gt;

&lt;p&gt;The highest-leverage move in this kind of migration is almost never &quot;fix errors file by file.&quot; It's finding the one architectural mismatch that both checkers are tripping over identically across dozens of call sites.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The recurring pattern&lt;/strong&gt;: a project supports an optional, richer backend (lxml over &lt;code&gt;xml.etree.ElementTree&lt;/code&gt;, &lt;code&gt;orjson&lt;/code&gt; over &lt;code&gt;json&lt;/code&gt;, &lt;code&gt;ujson&lt;/code&gt;, a C-accelerated regex engine, etc.) via a runtime try/except import, and defines either a &lt;code&gt;Protocol&lt;/code&gt; or just relies on structural duck-typing to abstract over both. mypy tolerated this for years via &lt;code&gt;ignore_missing_imports = true&lt;/code&gt;, which silently treats the untyped backend as &lt;code&gt;Any&lt;/code&gt; everywhere. Neither &lt;code&gt;ty&lt;/code&gt; nor &lt;code&gt;pyrefly&lt;/code&gt; degrade that gracefully by default — they'll either partially resolve the untyped backend's real (but incomplete) info, or fall back to typing it against whichever branch of the try/except &lt;em&gt;does&lt;/em&gt; have full stubs (usually the stdlib fallback), and then report every method/kwarg the richer backend uniquely offers as invalid.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix&lt;/strong&gt;, applied at the import site (not scattered across every call site):&lt;br /&gt;
&lt;/p&gt;

&lt;div class=&quot;highlight js-code-highlight&quot;&gt;
&lt;pre class=&quot;highlight python&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;typing&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TYPE_CHECKING&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TYPE_CHECKING&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Type-checkers see the richer backend's own stubs; the preferred
&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# backend's API is treated as a superset of the fallback's.
&lt;/span&gt;    &lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lxml&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;etree&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lxml&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;etree&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;ImportError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xml.etree.ElementTree&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;etree&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the project also defines its own &lt;code&gt;Protocol&lt;/code&gt; to abstract over both backends (e.g. &lt;code&gt;types.py: class Element(Protocol): ...&lt;/code&gt;), consider going one step further and making that Protocol &lt;strong&gt;literally alias the richer backend's real type&lt;/strong&gt; under &lt;code&gt;TYPE_CHECKING&lt;/code&gt;, falling back to the structural &lt;code&gt;Protocol&lt;/code&gt; only for runtime/non-typechecking purposes:&lt;br /&gt;
&lt;/p&gt;

&lt;div class=&quot;highlight js-code-highlight&quot;&gt;
&lt;pre class=&quot;highlight python&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TYPE_CHECKING&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lxml.etree&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_Element&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Element&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Protocol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# the original structural protocol, unchanged
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This one change collapsed roughly 150 of ~240 diagnostics in the fastkml migration, because it fixed both the &quot;backend-specific kwarg doesn't exist&quot; class of errors &lt;em&gt;and&lt;/em&gt; the &quot;structural Protocol isn't assignable to a concrete stdlib parameter type&quot; class in one shot (see pitfall below).&lt;/p&gt;

&lt;p&gt;If a stub package exists for the richer backend (&lt;code&gt;lxml-stubs&lt;/code&gt;, &lt;code&gt;types-ujson&lt;/code&gt;, etc.), add it to your typing dev-dependencies — but read the pitfalls section before assuming it's a strict improvement for &lt;em&gt;both&lt;/em&gt; checkers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pitfalls (the part worth re-reading before your second migration)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;# type: ignore[code]&lt;/code&gt; is not portable
&lt;/h3&gt;

&lt;p&gt;Neither &lt;code&gt;ty&lt;/code&gt; nor &lt;code&gt;pyrefly&lt;/code&gt; parses mypy's bracketed error-code suppression the way mypy does.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ty&lt;/code&gt; honors a &lt;strong&gt;bare&lt;/strong&gt; &lt;code&gt;# type: ignore&lt;/code&gt; (no brackets) as a blanket suppression for that line, but a bracketed &lt;code&gt;# type: ignore[some-code]&lt;/code&gt; is &lt;strong&gt;not&lt;/strong&gt; recognized as a ty-ignore at all — it's inert noise as far as ty is concerned, and the underlying error still fires.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pyrefly&lt;/code&gt; does honor &lt;code&gt;# type: ignore[...]&lt;/code&gt; by default (its &lt;code&gt;--enabled-ignores&lt;/code&gt; defaults to &lt;code&gt;type,pyrefly&lt;/code&gt;), so bracketed mypy comments mostly still work for pyrefly specifically.&lt;/li&gt;
&lt;li&gt;Both tools have their own dedicated syntax: &lt;code&gt;# ty: ignore[rule-name]&lt;/code&gt; and &lt;code&gt;# pyrefly: ignore&lt;/code&gt; / &lt;code&gt;# pyrefly: ignore[error-kind]&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Verify empirically before trusting any of this — behavior can change between tool versions:&lt;br /&gt;
&lt;/p&gt;

&lt;div class=&quot;highlight js-code-highlight&quot;&gt;
&lt;pre class=&quot;highlight python&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# type: ignore[return-value]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;ty check&lt;/code&gt; and &lt;code&gt;pyrefly check&lt;/code&gt; against a two-line repro before deciding on a suppression strategy for the whole codebase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Practical rule that worked well&lt;/strong&gt;: keep the original &lt;code&gt;# type: ignore[code]&lt;/code&gt; comment (documents intent, keeps pyrefly happy) and append &lt;code&gt;# ty: ignore[rule-name]&lt;/code&gt; on the same physical line for ty. Don't strip the mypy-era comments outright; they're free documentation of &lt;em&gt;why&lt;/em&gt; a line is exceptional.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. pyrefly's TOML keys are snake_case even though its CLI flags are kebab-case
&lt;/h3&gt;

&lt;p&gt;This is the single most time-consuming mistake to make. &lt;code&gt;pyrefly check --replace-imports-with-any 'lxml.*'&lt;/code&gt; works from the CLI. Writing the &quot;obvious&quot; TOML equivalent:&lt;br /&gt;
&lt;/p&gt;

&lt;div class=&quot;highlight js-code-highlight&quot;&gt;
&lt;pre class=&quot;highlight toml&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;[tool.pyrefly]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;replace-imports-with-any&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;lxml.*&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;   &lt;span class=&quot;c&quot;&gt;# WRONG — silently different key&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...does not raise an error from &lt;code&gt;pyrefly check&lt;/code&gt; in some code paths, but it &lt;strong&gt;does&lt;/strong&gt; hard-fail with &lt;code&gt;pyrefly dump-config&lt;/code&gt; (&lt;code&gt;unknown variant 'replace-imports-with-any'... Fatal configuration error&lt;/code&gt;), and depending on invocation order this can also break &lt;code&gt;pyrefly check&lt;/code&gt; itself later. The correct TOML key uses underscores:&lt;br /&gt;
&lt;/p&gt;

&lt;div class=&quot;highlight js-code-highlight&quot;&gt;
&lt;pre class=&quot;highlight toml&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;[tool.pyrefly]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;replace_imports_with_any&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;lxml.*&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Meanwhile, &lt;strong&gt;error-kind names&lt;/strong&gt; (used as dict keys under &lt;code&gt;[tool.pyrefly.errors]&lt;/code&gt; or inside a &lt;code&gt;rules = {...}&lt;/code&gt; table) &lt;em&gt;do&lt;/em&gt; use hyphens (&lt;code&gt;missing-override-decorator&lt;/code&gt;, &lt;code&gt;redundant-cast&lt;/code&gt;, etc.) — matching the CLI's &lt;code&gt;--error&lt;/code&gt;/&lt;code&gt;--ignore&lt;/code&gt; rule-name spelling, not the config-field spelling. There is no single consistent casing convention across the whole config surface; check &lt;code&gt;pyrefly dump-config&lt;/code&gt; after every config change, not just &lt;code&gt;pyrefly check&lt;/code&gt;, because &lt;code&gt;check&lt;/code&gt; can look clean while a nearby key is silently ignored.&lt;/p&gt;

&lt;p&gt;After any pyrefly config edit, run both &lt;code&gt;pyrefly dump-config&lt;/code&gt; (schema/parse validation) and &lt;code&gt;pyrefly check&lt;/code&gt; (behavioral validation) — one catches structural mistakes the other doesn't surface.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. pyrefly's &lt;code&gt;[[tool.pyrefly.sub-config]]&lt;/code&gt; array-of-tables is fragile against interleaving
&lt;/h3&gt;

&lt;p&gt;Pyrefly's per-path overrides use TOML's array-of-tables syntax:&lt;br /&gt;
&lt;/p&gt;

&lt;div class=&quot;highlight js-code-highlight&quot;&gt;
&lt;pre class=&quot;highlight toml&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;[[tool.pyrefly.sub-config]]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;matches&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;tests/**/*&quot;&lt;/span&gt;

&lt;span class=&quot;nn&quot;&gt;[tool.pyrefly.sub-config.errors]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;missing-attribute&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;ignore&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TOML allows other, unrelated top-level tables to appear &lt;em&gt;between&lt;/em&gt; &lt;code&gt;[[tool.pyrefly.sub-config]]&lt;/code&gt; and its paired &lt;code&gt;[tool.pyrefly.sub-config.errors]&lt;/code&gt; — the nested table still binds to the most-recently-opened array element regardless of what's interleaved. That means a &lt;code&gt;pyproject.toml&lt;/code&gt; that grew organically (auto-migration tooling appending blocks near whatever happened to be at the end of the file) can end up with three sub-config blocks scattered across 100+ lines of unrelated project/tool config, and it will &lt;em&gt;still parse&lt;/em&gt;. It becomes a landmine the moment someone (or an agent) deletes one &lt;code&gt;[[tool.pyrefly.sub-config]]&lt;/code&gt; header without also deleting its now-orphaned &lt;code&gt;[tool.pyrefly.sub-config.errors]&lt;/code&gt; block — the orphaned errors table then either binds to the wrong array element or breaks parsing entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix&lt;/strong&gt;: keep every pyrefly (and ty) config block &lt;strong&gt;contiguous&lt;/strong&gt; in one place in the file, even if that means moving it away from wherever an automated tool first inserted it. Re-parse after every edit:&lt;br /&gt;
&lt;/p&gt;

&lt;div class=&quot;highlight js-code-highlight&quot;&gt;
&lt;pre class=&quot;highlight shell&quot;&gt;&lt;code&gt;python3 &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;import tomllib; tomllib.load(open('pyproject.toml','rb')); print('OK')&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. A &lt;code&gt;Protocol&lt;/code&gt; is not assignable to a concrete class parameter
&lt;/h3&gt;

&lt;p&gt;If your duck-typing abstraction is a &lt;code&gt;Protocol&lt;/code&gt; (say, &lt;code&gt;types.Element&lt;/code&gt;) and internal code passes &lt;code&gt;Element&lt;/code&gt;-typed values into functions that are typed against the &lt;em&gt;concrete&lt;/em&gt; stdlib/third-party class (&lt;code&gt;xml.etree.ElementTree.SubElement(parent: Element[Any], ...)&lt;/code&gt;), both &lt;code&gt;ty&lt;/code&gt; and &lt;code&gt;pyrefly&lt;/code&gt; will reject it — even though the Protocol is structurally compatible at every call site. Protocol → concrete-class assignability doesn't work the way concrete → Protocol does, and a mutable/invariant attribute (&lt;code&gt;text: str&lt;/code&gt; on the Protocol vs. &lt;code&gt;text: str | None&lt;/code&gt; on the real class) makes it worse.&lt;/p&gt;

&lt;p&gt;This resolves itself for free once you apply the Phase 2 fix (alias the Protocol to the real backend's type under &lt;code&gt;TYPE_CHECKING&lt;/code&gt;) for internal, backend-facing code. Keep the original Protocol only for the codebase's genuinely-public, backend-agnostic API surface.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Registry/callback-style dispatch can't be narrowed at the signature level
&lt;/h3&gt;

&lt;p&gt;A common pattern: a generic dispatch table stores &lt;code&gt;classes: tuple[type[object], ...]&lt;/code&gt; and a matching &lt;code&gt;Protocol&lt;/code&gt; requires every registered callback to accept exactly that (necessarily wide) signature, even though any &lt;em&gt;individual&lt;/em&gt; callback only ever receives one concrete class at runtime. You cannot narrow an individual callback's parameter type to the concrete class it actually expects (&lt;code&gt;tuple[type[SpecificClass], ...]&lt;/code&gt;) — that breaks structural assignability against the wider Protocol the dispatcher requires (parameter types are contravariant; a callback that only accepts a narrower type can't stand in for one the dispatcher will call with the wider type).&lt;/p&gt;

&lt;p&gt;Fix at the call site inside the function body instead of the signature: &lt;code&gt;cast(&quot;tuple[type[SpecificClass], ...]&quot;, classes)&lt;/code&gt;, or &lt;code&gt;cls = cast(&quot;type[SpecificClass]&quot;, classes[0])&lt;/code&gt;. Keep the public signature honestly wide.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. &lt;code&gt;**heterogeneous_dict&lt;/code&gt; splats can't be validated against multi-parameter constructors
&lt;/h3&gt;



&lt;div class=&quot;highlight js-code-highlight&quot;&gt;
&lt;pre class=&quot;highlight python&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;fields&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;type_&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DataType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;int_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;SimpleField&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fields&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both checkers infer &lt;code&gt;fields: dict[str, DataType | str]&lt;/code&gt; and then check &lt;em&gt;every&lt;/em&gt; keyword argument against that whole union, rather than against each parameter's own specific type — because plain &lt;code&gt;dict[str, ...]&lt;/code&gt; splatting isn't a &lt;code&gt;TypedDict&lt;/code&gt;, so there's no per-key type information available. This isn't a narrowing bug or an inherent-bad-input case; it's a structural limitation of &lt;code&gt;**dict&lt;/code&gt; splatting itself. Either convert the dict to a &lt;code&gt;TypedDict&lt;/code&gt; (real fix, more invasive) or accept a targeted ignore comment — don't spend time trying to &quot;fix&quot; it any other way.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Community stub packages can behave differently per checker
&lt;/h3&gt;

&lt;p&gt;If you add a community-maintained stub package (&lt;code&gt;lxml-stubs&lt;/code&gt;, etc.) rather than relying on inline &lt;code&gt;py.typed&lt;/code&gt; types, expect it to have its own bugs, and expect those bugs to manifest &lt;strong&gt;differently per checker&lt;/strong&gt;. In this migration, &lt;code&gt;lxml-stubs&lt;/code&gt; declares several attributes using the legacy stub syntax &lt;code&gt;tag = ...  # type: str&lt;/code&gt; (pre-PEP 526). &lt;code&gt;ty&lt;/code&gt; tolerates this by falling back to &lt;code&gt;Unknown&lt;/code&gt; for that attribute (safe, just loses precision). &lt;code&gt;pyrefly&lt;/code&gt; mis-parses it as the attribute's type being the &lt;strong&gt;literal value&lt;/strong&gt; &lt;code&gt;Ellipsis&lt;/code&gt;, then reports every subsequent use (&lt;code&gt;.strip()&lt;/code&gt;, &lt;code&gt;.split()&lt;/code&gt;, slicing) as an error on a nonexistent &lt;code&gt;EllipsisType&lt;/code&gt; method — a wave of dozens of false positives that has nothing to do with your code.&lt;/p&gt;

&lt;p&gt;There is no fix on your side other than working around the stub bug per-checker:&lt;br /&gt;
&lt;/p&gt;

&lt;div class=&quot;highlight js-code-highlight&quot;&gt;
&lt;pre class=&quot;highlight toml&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;[tool.pyrefly]&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Force pyrefly to treat the whole (broken-for-pyrefly) stub package as Any,&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# while `ty` still gets full value from the same installed stub package.&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;replace_imports_with_any&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;lxml.*&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't assume &quot;we added the stub package&quot; is the end of the story — verify both tools independently after adding any third-party stub dependency, because &quot;more type information&quot; is not always strictly better across tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Fixing the root cause makes old workaround &lt;code&gt;cast()&lt;/code&gt;s redundant — clean them up
&lt;/h3&gt;

&lt;p&gt;Once you fix the systemic issue, re-run both tools and look specifically for &lt;code&gt;redundant-cast&lt;/code&gt; warnings. Every &lt;code&gt;cast(&quot;Element&quot;, root)&lt;/code&gt; that existed purely to placate mypy's &lt;code&gt;ignore_missing_imports&lt;/code&gt; fallback becomes genuinely unnecessary once the real type flows through correctly, and leaving it in is now dead weight (and a &lt;code&gt;ty&lt;/code&gt;/&lt;code&gt;pyrefly&lt;/code&gt; warning) rather than a workaround. This is a good automatic signal that the root-cause fix actually landed.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Some &quot;strict&quot; rules are mechanical churn, not bug-catching — decide explicitly, don't default to on
&lt;/h3&gt;

&lt;p&gt;Pyrefly's &lt;code&gt;strict&lt;/code&gt; preset (and to a lesser extent &lt;code&gt;ty&lt;/code&gt;'s optional rules) includes checks like &lt;code&gt;missing-override-decorator&lt;/code&gt; (PEP 698 &lt;code&gt;@override&lt;/code&gt;), which can flag &lt;strong&gt;dozens to low-hundreds&lt;/strong&gt; of ordinary &lt;code&gt;__repr__&lt;/code&gt;/&lt;code&gt;__init__&lt;/code&gt;/method overrides in any codebase with meaningful inheritance. This is a legitimate check with real value in large team codebases (catches silently-broken overrides after a base-class rename), but adding &lt;code&gt;@override&lt;/code&gt; everywhere is a large, purely mechanical diff disconnected from the actual &quot;fix type errors&quot; task.&lt;/p&gt;

&lt;p&gt;Don't silently turn this on or off. Surface it explicitly (to a human reviewer, or via an explicit question if you're an agent) before deciding: disable it with a documented reason, or actually do the sweep. Either is defensible; picking silently isn't.&lt;/p&gt;

&lt;h3&gt;
  
  
  10. Test-file &quot;narrowing noise&quot; is real but should not become a license to blanket-suppress everything
&lt;/h3&gt;

&lt;p&gt;The overwhelming majority of test-file errors in a mature test suite will be the same shape: construct an object, then immediately access/assign a field typed &lt;code&gt;X | None&lt;/code&gt; or &lt;code&gt;A | B | None&lt;/code&gt;, without a narrowing &lt;code&gt;assert x is not None&lt;/code&gt; — because the test &lt;em&gt;knows&lt;/em&gt; the value is set (it just set it three lines up) but the checker doesn't. This is legitimately safe to bulk-suppress scoped to the test tree:&lt;br /&gt;
&lt;/p&gt;

&lt;div class=&quot;highlight js-code-highlight&quot;&gt;
&lt;pre class=&quot;highlight toml&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;[[tool.pyrefly.sub-config]]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;matches&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;tests/**/*&quot;&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;[tool.pyrefly.sub-config.errors]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;missing-attribute&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;ignore&quot;&lt;/span&gt;

&lt;span class=&quot;nn&quot;&gt;[[tool.ty.overrides]]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;tests/**&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;rules&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;unresolved-attribute&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;ignore&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;invalid-assignment&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;ignore&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But scope the suppressed &lt;strong&gt;rule names&lt;/strong&gt; narrowly (&lt;code&gt;unresolved-attribute&lt;/code&gt;/&lt;code&gt;missing-attribute&lt;/code&gt;/the specific &lt;code&gt;invalid-assignment&lt;/code&gt; shape), not broad categories like &lt;code&gt;invalid-argument-type&lt;/code&gt;/&lt;code&gt;bad-argument-type&lt;/code&gt; — those catch genuinely wrong types passed into calls, which do happen in test code (typos, copy-paste of the wrong fixture) and are worth keeping visible. In the fastkml migration, roughly 90% of test-file errors were narrowing noise safely bulk-suppressed, and the remaining 10% surfaced one genuine test bug (a string literal assigned where an enum member was expected) plus a batch of deliberately-invalid-input tests that just needed their ignore-comment syntax migrated (see pitfall 1).&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 3 — Work file by file for what's left
&lt;/h2&gt;

&lt;p&gt;After the systemic fix and the bulk test-suppression, what remains is usually a short, tractable list (tens, not hundreds, of diagnostics). For each:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Read the surrounding code before deciding how to fix it.&lt;/strong&gt; The same &lt;code&gt;unresolved-attribute&lt;/code&gt; shape can be a real narrowing gap (add &lt;code&gt;assert x is not None&lt;/code&gt;, matching the style already used elsewhere in the file), a genuine latent bug (an off-by-one/empty-tuple case an unpacking &lt;code&gt;*args&lt;/code&gt; call didn't guard against), or a structural dispatch limitation (pitfall 5).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prefer a real fix over a cast or ignore&lt;/strong&gt; wherever one exists cheaply: correcting a wrong return-type annotation (&lt;code&gt;Optional[X]&lt;/code&gt; that never actually returns &lt;code&gt;None&lt;/code&gt;), adding &lt;code&gt;isinstance&lt;/code&gt; narrowing instead of a loose dict-dispatch, genericizing a &lt;code&gt;find&lt;/code&gt;/&lt;code&gt;find_all&lt;/code&gt;-style utility with &lt;code&gt;@overload&lt;/code&gt; + a &lt;code&gt;TypeVar&lt;/code&gt; instead of returning &lt;code&gt;object&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use &lt;code&gt;cast()&lt;/code&gt; with a one-line comment explaining *why&lt;/strong&gt;* when the limitation is structural (pitfalls 4-6), not because you're in a hurry.&lt;/li&gt;
&lt;li&gt;Re-run the checker on just that file after each fix (&lt;code&gt;ty check path/to/file.py&lt;/code&gt;) — faster feedback loop than re-running the whole tree, and it confirms the fix didn't introduce a new diagnostic in the same file.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Phase 4 — Tighten to &quot;maximum quality&quot;
&lt;/h2&gt;

&lt;p&gt;Once both tools report zero errors on the fixed baseline, raise the bar deliberately rather than assuming the default preset is already strict:&lt;br /&gt;
&lt;/p&gt;

&lt;div class=&quot;highlight js-code-highlight&quot;&gt;
&lt;pre class=&quot;highlight toml&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;[tool.pyrefly]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;preset&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;strict&quot;&lt;/span&gt;   &lt;span class=&quot;c&quot;&gt;# not &quot;legacy&quot; / &quot;default&quot; — legacy exists specifically to ease mypy migrations, it's a floor, not a target&lt;/span&gt;

&lt;span class=&quot;nn&quot;&gt;[tool.ty.rules]&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Promote rules ty ships at warn/ignore by default; discover the full list with `ty explain rule`.&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;possibly-missing-attribute&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;error&quot;&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;possibly-missing-import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;error&quot;&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;possibly-unresolved-reference&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;error&quot;&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;missing-type-argument&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;error&quot;&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;unused-ignore-comment&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;error&quot;&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;unused-type-ignore-comment&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;error&quot;&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;redundant-cast&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;error&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Discover what's available rather than guessing:&lt;br /&gt;
&lt;/p&gt;

&lt;div class=&quot;highlight js-code-highlight&quot;&gt;
&lt;pre class=&quot;highlight shell&quot;&gt;&lt;code&gt;ty explain rule                 &lt;span class=&quot;c&quot;&gt;# every rule, default level, rationale, examples&lt;/span&gt;
pyrefly check &lt;span class=&quot;nt&quot;&gt;--help&lt;/span&gt;            &lt;span class=&quot;c&quot;&gt;# --preset options, --error/--warn/--ignore, --replace-imports-with-any, etc.&lt;/span&gt;
pyrefly dump-config             &lt;span class=&quot;c&quot;&gt;# what's actually active for this project right now&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Phase 5 — Verify
&lt;/h2&gt;

&lt;p&gt;Don't call it done on &quot;the type checker is quiet.&quot; Run the full loop:&lt;br /&gt;
&lt;/p&gt;

&lt;div class=&quot;highlight js-code-highlight&quot;&gt;
&lt;pre class=&quot;highlight shell&quot;&gt;&lt;code&gt;ty check &amp;lt;src&amp;gt; &amp;lt;tests&amp;gt;
pyrefly check &amp;lt;src&amp;gt; &amp;lt;tests&amp;gt;
ruff check &lt;span class=&quot;nt&quot;&gt;--no-fix&lt;/span&gt; &amp;lt;src&amp;gt; &amp;lt;tests&amp;gt;          &lt;span class=&quot;c&quot;&gt;# type-fix edits (casts, isinstance, overloads) can introduce lint issues&lt;/span&gt;
ruff format &lt;span class=&quot;nt&quot;&gt;--check&lt;/span&gt; &amp;lt;src&amp;gt; &amp;lt;tests&amp;gt;
python &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; pytest                            &lt;span class=&quot;c&quot;&gt;# with optional runtime deps installed&lt;/span&gt;
uv pip uninstall &amp;lt;optional-dep&amp;gt;             &lt;span class=&quot;c&quot;&gt;# e.g. lxml&lt;/span&gt;
python &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; pytest &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;not &amp;lt;slow-marker&amp;gt;&quot;&lt;/span&gt;     &lt;span class=&quot;c&quot;&gt;# confirm the fallback code path still works&lt;/span&gt;
uv pip &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;.[typing,&amp;lt;optional-dep&amp;gt;]&quot;&lt;/span&gt;
python3 &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;import tomllib; tomllib.load(open('pyproject.toml','rb'))&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &quot;uninstall the optional dependency and re-run tests&quot; step matters specifically because Phase 2's fix changes how the optional backend is &lt;em&gt;typed&lt;/em&gt;, not just how it's imported — if the runtime fallback logic was touched at all while chasing type errors, this is the step that catches a broken fallback path before it ships.&lt;/p&gt;

&lt;h2&gt;
  
  
  Case study numbers (fastkml)
&lt;/h2&gt;

&lt;p&gt;For calibration on what &quot;a lot of noise, mostly one root cause&quot; looks like in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Baseline: &lt;code&gt;ty&lt;/code&gt; reported 237 diagnostics across source + tests; &lt;code&gt;pyrefly&lt;/code&gt; reported 40 errors (with an already-too-permissive config suppressing 38 more).&lt;/li&gt;
&lt;li&gt;One import-site fix (Phase 2, aliasing the duck-typed &lt;code&gt;Element&lt;/code&gt; Protocol and &lt;code&gt;etree&lt;/code&gt; module to &lt;code&gt;lxml&lt;/code&gt;'s real stubs under &lt;code&gt;TYPE_CHECKING&lt;/code&gt;) collapsed &lt;code&gt;ty&lt;/code&gt;'s source-only count from 47 to 13 in a single step.&lt;/li&gt;
&lt;li&gt;Total genuine bugs found and fixed in library source: 5 (a missed &lt;code&gt;.getroot()&lt;/code&gt; call, an unguarded empty-tuple unpack in a &lt;code&gt;zip_longest&lt;/code&gt; loop, a too-loose &lt;code&gt;type[object]&lt;/code&gt; dispatch signature, a &lt;code&gt;Self&lt;/code&gt;-in-a-list invariance issue, and one example script passing &lt;code&gt;bytes&lt;/code&gt; where &lt;code&gt;str&lt;/code&gt; was expected).&lt;/li&gt;
&lt;li&gt;Total genuine bugs found and fixed in tests: 1 (a string literal compared against an enum field).&lt;/li&gt;
&lt;li&gt;Final state: zero errors on &lt;code&gt;ty check&lt;/code&gt; and &lt;code&gt;pyrefly check&lt;/code&gt; for the CI-covered scope, with all pre-existing tests still passing, both with and without the optional &lt;code&gt;lxml&lt;/code&gt; backend installed.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you made it this far, you might be interested in the cost:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Total cost:            $40.44&lt;/li&gt;
&lt;li&gt;Total duration (API):  58m 29s&lt;/li&gt;
&lt;li&gt;Total duration (wall): 1h 48m 15s&lt;/li&gt;
&lt;li&gt;Total code changes:    635 lines added, 224 lines removed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Usage by model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;claude-haiku-4-5:  1.1k input, 39 output, 0 cache read, 0 cache write ($0.0013)&lt;/li&gt;
&lt;li&gt;claude-sonnet-5:  23.1k input, 227.0k output, 106.7m cache read, 993.5k cache write ($40.44)&lt;/li&gt;
&lt;/ul&gt;</description>
	<pubDate>Sun, 05 Jul 2026 16:29:47 +0000</pubDate>
</item>
<item>
	<title>Sebastian Pölsterl: scikit-survival 0.28.0 released</title>
	<guid>https://k-d-w.org/blog/2026/07/scikit-survival-0.28.0-released/</guid>
	<link>https://k-d-w.org/blog/2026/07/scikit-survival-0.28.0-released/</link>
	<description>&lt;p&gt;I am pleased to announce the release of &lt;a href=&quot;https://scikit-survival.readthedocs.io/en/stable/release_notes/v0.28.html#scikit-survival-0-28-0-2026-07-05&quot; target=&quot;_blank&quot;&gt;scikit-survival 0.28.0&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A highlight of this release is the support for &lt;em&gt;Polars DataFrames&lt;/em&gt; alongside pandas DataFrames via
the &lt;a href=&quot;https://narwhals-dev.github.io/narwhals/&quot; target=&quot;_blank&quot;&gt;Narwhals&lt;/a&gt; dataframe abstraction layer.
In addition, this release adds support for &lt;em&gt;scikit-learn 1.9&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;support-for-polars&quot;&gt;Support for Polars&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.pola.rs/&quot; target=&quot;_blank&quot;&gt;Polars&lt;/a&gt; is a data frame library similar to &lt;a href=&quot;https://pandas.pydata.org/&quot; target=&quot;_blank&quot;&gt;pandas&lt;/a&gt;,
but with its core written in Rust instead of Python, which often gives Polars an advantage in terms of performance.&lt;/p&gt;
&lt;p&gt;All datasets shipped with scikit-survival can now be loaded as a Polars DataFrame by specifying the
&lt;code&gt;output_type&lt;/code&gt; argument.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from sksurv.datasets import load_gbsg2
# return X as a polars DataFrame
X, y = load_gbsg2(output_type=&amp;quot;polars&amp;quot;)
X.head()
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;shape: (5, 8)
┌──────┬────────┬───────┬──────────┬────────┬─────────┬────────┬───────┐
│ age ┆ estrec ┆ horTh ┆ menostat ┆ pnodes ┆ progrec ┆ tgrade ┆ tsize │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ f64 ┆ f64 ┆ enum ┆ enum ┆ f64 ┆ f64 ┆ enum ┆ f64 │
╞══════╪════════╪═══════╪══════════╪════════╪═════════╪════════╪═══════╡
│ 70.0 ┆ 66.0 ┆ no ┆ Post ┆ 3.0 ┆ 48.0 ┆ II ┆ 21.0 │
│ 56.0 ┆ 77.0 ┆ yes ┆ Post ┆ 7.0 ┆ 61.0 ┆ II ┆ 12.0 │
│ 58.0 ┆ 271.0 ┆ yes ┆ Post ┆ 9.0 ┆ 52.0 ┆ II ┆ 35.0 │
│ 59.0 ┆ 29.0 ┆ yes ┆ Post ┆ 4.0 ┆ 60.0 ┆ II ┆ 17.0 │
│ 73.0 ┆ 65.0 ┆ no ┆ Post ┆ 1.0 ┆ 26.0 ┆ II ┆ 35.0 │
└──────┴────────┴───────┴──────────┴────────┴─────────┴────────┴───────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;transforming-dataframes&quot;&gt;Transforming DataFrames&lt;/h3&gt;
&lt;p&gt;scikit-learn enables transformers to return Polars data frames via the
&lt;a href=&quot;https://scikit-learn.org/stable/modules/df_output_transform.html#df-output-transform&quot; target=&quot;_blank&quot;&gt;set_output API&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from sklearn import set_config
from sklearn.preprocessing import StandardScaler
set_config(transform_output=&amp;quot;polars&amp;quot;)
# standarize the columns of a polars DataFrame
X_standarized = StandardScaler().fit_transform(
X.select(&amp;quot;age&amp;quot;, &amp;quot;tsize&amp;quot;)
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;scikit-surival has two transformers that now accept polars and pandas data frames as input:
&lt;a href=&quot;https://scikit-survival.readthedocs.io/en/v0.28.0/api/generated/sksurv.kernels.ClinicalKernelTransform.html&quot; target=&quot;_blank&quot;&gt;ClinicalKernelTransform&lt;/a&gt;, and
&lt;a href=&quot;https://scikit-survival.readthedocs.io/en/v0.28.0/api/generated/sksurv.preprocessing.OneHotEncoder.html&quot; target=&quot;_blank&quot;&gt;OneHotEncoder&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&quot;clinicalkerneltransform&quot;&gt;ClinicalKernelTransform&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;ClinicalKernelTransform&lt;/code&gt; computes a kernel matrix, so the output will be a numpy array, as before.
With scikit-survival 0.28.0, it is aware of the
&lt;a href=&quot;https://docs.pola.rs/user-guide/concepts/data-types-and-structures/#appendix-full-data-types-table&quot; target=&quot;_blank&quot;&gt;polars colum types&lt;/a&gt;
&lt;code&gt;String&lt;/code&gt;, &lt;code&gt;Categorical&lt;/code&gt;, &lt;code&gt;Enum&lt;/code&gt; and &lt;code&gt;Object&lt;/code&gt;.
However, polars does not have a concept similar to &lt;a href=&quot;https://pandas.pydata.org/docs/user_guide/categorical.html#sorting-and-order&quot; target=&quot;_blank&quot;&gt;ordered categories&lt;/a&gt; in pandas: &lt;code&gt;pd.Categorical([…], ordered=True)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;When computing the clinical kernel for a polars data frame, you can specify the order
of categories with the &lt;code&gt;ordinal_categories&lt;/code&gt; argument, otherwise all non-numeric columns
will be treated as &lt;em&gt;nominal&lt;/em&gt; columns, where values have no specific order
(e.g. the column &lt;code&gt;horTh&lt;/code&gt; with values &amp;ldquo;yes&amp;rdquo; and &amp;ldquo;no&amp;rdquo; in the example below).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from sksurv.kernels import ClinicalKernelTransform
K = ClinicalKernelTransform(
ordinal_categories={&amp;quot;tgrade&amp;quot;: [&amp;quot;I&amp;quot;, &amp;quot;II&amp;quot;, &amp;quot;III&amp;quot;]},
).fit_transform(
X.select(&amp;quot;age&amp;quot;, &amp;quot;horTh&amp;quot;, &amp;quot;tgrade&amp;quot;)
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;onehotencoder&quot;&gt;OneHotEncoder&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;OneHotEncoder&lt;/code&gt; encodes the string-type columns to numeric columns.
It automatically returns a data frame of the same type as the input:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from sksurv.preprocessing import OneHotEncoder
X_onehot = OneHotEncoder().fit_transform(X.select(&amp;quot;horTh&amp;quot;, &amp;quot;tgrade&amp;quot;))
X_onehot.head()
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;shape: (5, 3)
┌───────────┬───────────┬────────────┐
│ horTh=yes ┆ tgrade=II ┆ tgrade=III │
│ --- ┆ --- ┆ --- │
│ f64 ┆ f64 ┆ f64 │
╞═══════════╪═══════════╪════════════╡
│ 0.0 ┆ 1.0 ┆ 0.0 │
│ 1.0 ┆ 1.0 ┆ 0.0 │
│ 1.0 ┆ 1.0 ┆ 0.0 │
│ 1.0 ┆ 1.0 ┆ 0.0 │
│ 0.0 ┆ 1.0 ┆ 0.0 │
└───────────┴───────────┴────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;fitting-survival-models&quot;&gt;Fitting survival models&lt;/h3&gt;
&lt;p&gt;Finally, you can pass a polars data frame to the fit and predict functions of any estimator.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from sklearn.pipeline import make_pipeline
from sksurv.linear_model import CoxPHSurvivalAnalysis
pipe = make_pipeline(
OneHotEncoder(), StandardScaler(), CoxPHSurvivalAnalysis()
)
pipe.fit(X[:500], y[:500])
risk_scores = pipe.predict(X[500:])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that this only works for &lt;em&gt;eager&lt;/em&gt; data frames, &lt;em&gt;lazy&lt;/em&gt; data frames will give an error.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;X_lazy = X.lazy()
pipe.fit(X_lazy[:500], y[:500])
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-python-traceback&quot;&gt;Traceback (most recent call last):
File &amp;quot;example.py&amp;quot;, line 21, in &amp;lt;module&amp;gt;
pipe.fit(X_lazy[:500], y[:500])
File &amp;quot;…/site-packages/sklearn/base.py&amp;quot;, line 1403, in wrapper
return fit_method(estimator, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
…
File &amp;quot;…/site-packages/sksurv/_dataframe/_input.py&amp;quot;, line 96, in ensure_eager_dataframe
_reject_polars_lazyframe(obj)
File &amp;quot;…/site-packages/sksurv/_dataframe/_input.py&amp;quot;, line 84, in _reject_polars_lazyframe
raise TypeError(_LAZYFRAME_NOT_SUPPORTED_MSG)
TypeError: polars.LazyFrame is not supported; call .collect() before passing to scikit-survival.
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;new-contributors&quot;&gt;New Contributors&lt;/h2&gt;
&lt;p&gt;A big shoutout to our two new contributors:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/cakedev0&quot; target=&quot;_blank&quot;&gt;cakedev0&lt;/a&gt; for adding support for scikit-learn 1.9&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/55Kamiryo&quot; target=&quot;_blank&quot;&gt;55Kamiryo&lt;/a&gt; for adding support for Polars data frames.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;updated-dependencies&quot;&gt;Updated Dependencies&lt;/h2&gt;
&lt;p&gt;With this release, the minimum supported version are:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Package&lt;/th&gt;&lt;th&gt;Minimum Version&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;narwhals&lt;/td&gt;&lt;td&gt;2.0.1&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;scikit-learn&lt;/td&gt;&lt;td&gt;1.9.0&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;install&quot;&gt;Install&lt;/h2&gt;
&lt;p&gt;scikit-survival is available for Linux, macOS, and Windows
and can be installed either&lt;/p&gt;
&lt;p&gt;via pip:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pip install scikit-survival
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or via conda&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt; conda install -c conda-forge scikit-survival
&lt;/code&gt;&lt;/pre&gt;</description>
	<pubDate>Sun, 05 Jul 2026 16:22:05 +0000</pubDate>
</item>
<item>
	<title>Core Dispatch: Core Dispatch #7</title>
	<guid>https://coredispatch.xyz/editions/7</guid>
	<link>https://coredispatch.xyz/editions/7</link>
	<description>&lt;p&gt;Welcome back to Core Dispatch! This edition covers June 19 through July 5, 2026.
Python 3.15.0 beta 3 landed on June 23, and beta 4 is up next on July 18, with
3.13.15 and 3.14.7 following on August 4.&lt;/p&gt;
&lt;p&gt;Last edition, we covered the Steering Council&amp;#x27;s request that the experimental
JIT chart its future through a Standards Track PEP. A new PEP has now been
drafted and discussion has started: &lt;a href=&quot;https://peps.python.org/pep-0836/&quot;&gt;PEP 836&lt;/a&gt;,
&amp;quot;JIT Go Brrr: The Path to a Supported JIT Compiler for CPython,&amp;quot; sketches what
moving the JIT from experiment to supported feature could involve, from
performance expectations to interop and tooling compatibility.&lt;/p&gt;
&lt;p&gt;PyCon US 2026 talk recordings have also started landing, and we&amp;#x27;ve pulled a few
from the core team into Core Team Musings below: Pablo Galindo Salgado and László
Kiss Kollár on Python 3.15&amp;#x27;s new &lt;a href=&quot;https://youtu.be/f1x4X83CDSA&quot;&gt;Tachyon sampling profiler&lt;/a&gt;,
Thomas Wouters on the &lt;a href=&quot;https://youtu.be/PZu6LyiZVbM&quot;&gt;past, present, and future of free-threaded Python&lt;/a&gt;,
and Emma Smith on &lt;a href=&quot;https://youtu.be/42kibVnUHYE&quot;&gt;Rust for CPython&lt;/a&gt;. Not everything
is up yet, so expect more picks as the rest are published.&lt;/p&gt;
&lt;p&gt;On the packaging side, the inaugural
&lt;a href=&quot;https://blog.python.org/2026/06/2026-packaging-council-election-dates/&quot;&gt;Packaging Council election dates&lt;/a&gt;
are out. And looking ahead, &lt;a href=&quot;https://ep2026.europython.eu/&quot;&gt;EuroPython 2026&lt;/a&gt;
kicks off July 13, so expect a wave of talks and Language Summit coverage to
pull from in a future edition.&lt;/p&gt;
&lt;p&gt;As always, if you maintain a package or just like living on the edge, give the
latest 3.15 beta a spin and &lt;a href=&quot;https://github.com/python/cpython/issues&quot;&gt;file any issues&lt;/a&gt;
you find.&lt;/p&gt;
&lt;h3&gt;Upcoming Releases&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://peps.python.org/pep-0790/&quot;&gt;Python 3.15.0 beta 4&lt;/a&gt; — Jul 18&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://peps.python.org/pep-0719/&quot;&gt;Python 3.13.15&lt;/a&gt; — Aug 04&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://peps.python.org/pep-0745/&quot;&gt;Python 3.14.7&lt;/a&gt; — Aug 04&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Official News&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.python.org/2026/06/2026-packaging-council-election-dates/&quot;&gt;Packaging Council Inaugural Election Dates&lt;/a&gt; — By Pradyun Gedam&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.python.org/2026/06/mitigated-api-bypass-for-download-metadata-python-dot-org/&quot;&gt;Mitigated API authentication bypass for python.org download metadata&lt;/a&gt; — By Seth Larson&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.python.org/2026/06/python-3150-beta-3/&quot;&gt;Python 3.15.0 beta 3 is here!&lt;/a&gt; — By Hugo van Kemenade&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pyfound.blogspot.com/2026/07/thinking-about-running-for-psf-board.html&quot;&gt;Thinking about running for the PSF Board? Let’s talk!&lt;/a&gt; — By Marie Nordin&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;PEP Updates&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://peps.python.org/pep-0752/&quot;&gt;PEP 752: Implicit namespaces for package repositories&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://peps.python.org/pep-0810/&quot;&gt;PEP 810: Explicit lazy imports&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://peps.python.org/pep-0799/&quot;&gt;PEP 799: A dedicated &lt;code&gt;profiling&lt;/code&gt; package for organizing Python profiling tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://peps.python.org/pep-0836/&quot;&gt;PEP 836: JIT Go Brrr: The Path to a Supported JIT Compiler for CPython&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Merged PRs&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/python/cpython/pull/134627&quot;&gt;Add &lt;code&gt;remove()&lt;/code&gt; and &lt;code&gt;repack()&lt;/code&gt; to &lt;code&gt;ZipFile&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/python/cpython/pull/143929&quot;&gt;Normalize all line endings (CR, CRLF, and LF) in &lt;code&gt;configparser&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/python/cpython/pull/153007&quot;&gt;Fix abrupt closing of empty comment in &lt;code&gt;HTMLParser&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/python/cpython/pull/150741&quot;&gt;Limit trailer lines and interim responses read by &lt;code&gt;http.client&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/python/cpython/pull/151559&quot;&gt;Fix symlink escape via &lt;code&gt;tarfile&lt;/code&gt; hardlink-extraction fallback&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/python/cpython/pull/150917&quot;&gt;Implement &lt;code&gt;BytesIO.peek()&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/python/cpython/pull/150877&quot;&gt;Fix &lt;code&gt;SyntaxError&lt;/code&gt; message for &lt;code&gt;from x lazy import y&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/python/cpython/pull/150906&quot;&gt;Improve &lt;code&gt;SyntaxError&lt;/code&gt; message for &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; and &lt;code&gt;||&lt;/code&gt; operators&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Discussion&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://discuss.python.org/t/pep-835-shorthand-syntax-for-annotated-type-metadata/107814&quot;&gt;PEP 835: Shorthand Syntax for Annotated Type Metadata&lt;/a&gt; — 🔥 122 new replies · 3.1k views&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://discuss.python.org/t/pep-832-virtual-environment-discovery/106998&quot;&gt;PEP 832: Virtual Environment Discovery&lt;/a&gt; — 🔥 21 new replies · 8.3k views&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://discuss.python.org/t/pep-836-jit-go-brrr-the-path-to-a-supported-jit-compiler-for-cpython/108010&quot;&gt;PEP 836: JIT Go Brrr: The Path to a Supported JIT Compiler for CPython&lt;/a&gt; — 🆕 🔥 15 new replies · 975 views&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://discuss.python.org/t/pep-822-dedented-multiline-string-d-string/105519&quot;&gt;PEP 822: Dedented Multiline String (d-string)&lt;/a&gt; — 2 new replies · 6.9k views&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Core Dev Musings&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://fidget-spinner.github.io/posts/ultra-fast-tracing.html&quot;&gt;Python 3.15’s Ultra-Low Overhead Interpreter Profiling Mode&lt;/a&gt; — By Ken Jin&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://vstinner.github.io/pep-814-add-frozendict-builtin-type.html&quot;&gt;PEP 814: Add frozendict built-in type&lt;/a&gt; — By Victor Stinner&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://youtu.be/f1x4X83CDSA&quot;&gt;Tachyon: Python 3.15&amp;#x27;s sampling profiler&lt;/a&gt; — By Pablo Galindo Salgado &amp;amp; László Kiss Kollár&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://youtu.be/PZu6LyiZVbM&quot;&gt;Free-threaded Python: past, present and future&lt;/a&gt; — By Thomas Wouters&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://youtu.be/42kibVnUHYE&quot;&gt;Rust for CPython: Making Python Safer and More Robust for Everyone&lt;/a&gt; — By Emma Smith&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Upcoming CFPs &amp;amp; Conferences&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ep2026.europython.eu/&quot;&gt;EuroPython 2026&lt;/a&gt; — Jul 13&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.scipy2026.scipy.org/&quot;&gt;SciPy 2026&lt;/a&gt; — Jul 13&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://euroscipy.org/&quot;&gt;EuroSciPy 2026&lt;/a&gt; — Jul 18&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pycon.am/&quot;&gt;PyData PyCon Armenia 2026&lt;/a&gt; — Jul 24&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pythondeadlin.es&quot;&gt;PyOhio 2026&lt;/a&gt; — Jul 25&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.pycon.fr/2026/en/&quot;&gt;📋 PyCon France 2026 Deadline&lt;/a&gt; — Aug 01&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://2026.pycon.ie/&quot;&gt;📋 PyCon Ireland 2026 Deadline&lt;/a&gt; — Aug 01&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pybay.org/&quot;&gt;📋 PyBay 2026 Deadline&lt;/a&gt; — Aug 01&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://2026.ploneconf.org/&quot;&gt;📋 Plone Conference 2026 Deadline&lt;/a&gt; — Aug 01&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pydata.org/global2026/&quot;&gt;📋 PyData Global 2026 Workshop Deadline&lt;/a&gt; — Aug 04&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Credits&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/savannahostrowski&quot;&gt;Savannah Ostrowski&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/hugovk&quot;&gt;Hugo van Kemenade&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
	<pubDate>Sun, 05 Jul 2026 00:00:00 +0000</pubDate>
</item>
<item>
	<title>PyCon Ireland: Update on PyCon Ireland 2026: Venue Change and Extended CFP Deadline</title>
	<guid>https://main--pycon-ireland-2026.netlify.app/blog/venue-change/</guid>
	<link>https://main--pycon-ireland-2026.netlify.app/blog/venue-change/</link>
	<description>&lt;p&gt;Our booked venue, &lt;strong&gt;Trinity College Dublin&lt;/strong&gt;, has informed us that it can no longer host the conference. This decision was made by the venue, and it means we can no longer confirm &lt;strong&gt;17 October 2026&lt;/strong&gt; as our event date.&lt;/p&gt;
&lt;p&gt;We know this is disappointing, especially for everyone who was planning to submit a talk, book travel, or simply mark the date in their calendar. We&amp;rsquo;re disappointed too, but we want to be transparent with the community as soon as we have news, rather than staying quiet while we sort things out.&lt;/p&gt;
&lt;h2 id=&quot;what-this-means-right-now&quot;&gt;What This Means Right Now&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The Call for Proposals deadline has been extended to 30 August 2026.&lt;/strong&gt; We don&amp;rsquo;t want the venue search to hold back speakers who want to share their Python knowledge, so submissions remain open, you don&amp;rsquo;t need to wait for the new venue and date to submit. Submit your proposal on our &lt;a href=&quot;https://sessionize.com/pycon-ireland-2026/&quot;&gt;Sessionize page&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The conference date of 17 October 2026 no longer stands.&lt;/strong&gt; We will announce a new date once we have one.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;We are actively looking for a new venue&lt;/strong&gt; in the Dublin area that can accommodate the conference. As soon as a venue and date are confirmed, we will update the website and announce it through our usual channels.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;If you already submitted a talk, don&amp;rsquo;t worry, your proposal is safe.&lt;/strong&gt; We will let speakers know the new date and venue as soon as they&amp;rsquo;re confirmed, so you can make your own arrangements in good time.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;what-stays-the-same&quot;&gt;What Stays the Same&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PyCon Ireland 2026 is still happening.&lt;/strong&gt; We are treating this as a reschedule, not a cancellation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Our commitment to the community remains unchanged&lt;/strong&gt;, we are as enthusiastic as ever to showcase your talks, from first-time speakers to seasoned practitioners.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The team has already begun the process of securing a new venue.&lt;/strong&gt; We will share updates as soon as there is something concrete to report.&lt;/li&gt;
&lt;li&gt;We still welcome proposals from first-time and experienced speakers alike, across the full range of Python topics, in both full-talk (30 minutes) and lightning-talk (5 minutes) formats. See the &lt;a href=&quot;https://2026.pycon.ie/cfp/&quot;&gt;full CFP page&lt;/a&gt; for submission guidelines.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;how-to-stay-updated&quot;&gt;How to Stay Updated&lt;/h2&gt;
&lt;p&gt;The fastest way to hear about the new venue and date is to follow us on &lt;a href=&quot;https://mastodon.ie/@Python_Ireland&quot;&gt;Mastodon&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/pythonireland&quot;&gt;X&lt;/a&gt;, or &lt;a href=&quot;https://www.linkedin.com/company/python-ireland&quot;&gt;LinkedIn&lt;/a&gt;. We will also post updates here on the blog.&lt;/p&gt;
&lt;p&gt;Thank you for your patience and continued support of Python Ireland. We look forward to sharing better news with you soon, and we can&amp;rsquo;t wait to read your proposals.&lt;/p&gt;</description>
	<pubDate>Sat, 04 Jul 2026 00:00:00 +0000</pubDate>
</item>
<item>
	<title>Bob Belderbos: One Core, Two Interfaces, No Rewrites</title>
	<guid>https://belderbos.dev/blog/two-interfaces-one-core/</guid>
	<link>https://belderbos.dev/blog/two-interfaces-one-core/</link>
	<description>&lt;p&gt;When building applications, I always build the core first, then the interfaces. It was no different with &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://askthecanon.com&quot;&gt;Ask the Canon&lt;/a&gt;: a &lt;code&gt;uv run main.py ask &quot;...&quot;&lt;/code&gt; CLI for quick iteration and validation, then the web app for MVP. Search, ranking, citations, all using the same engine.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;
&lt;p&gt;Ask the Canon's core is a handful of pure functions in one module. Both interfaces are thin wrappers. This is the second post in a series on how it's built. &lt;a href=&quot;https://belderbos.dev/blog/semantic-search-without-a-vector-database/&quot;&gt;The first one&lt;/a&gt; was about the retrieval engine. This one is about the wider architecture.&lt;/p&gt;
&lt;h2 id=&quot;functional-core-two-interfaces&quot;&gt;Functional core, two interfaces&lt;/h2&gt;
&lt;p&gt;I just have one module with pure functions, clear contracts, and no hidden state:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; embed&lt;/span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;(texts: list[&lt;/span&gt;&lt;span class=&quot;z-support&quot;&gt;str&lt;/span&gt;&lt;span&gt;]) -&amp;gt; np.ndarray:&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt; ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; load_library&lt;/span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;(book_ids&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt;None&lt;/span&gt;&lt;span&gt;) -&amp;gt; tuple[list[Passage], np.ndarray]:&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt; ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; search_passages&lt;/span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;(query, passages, vectors, ...) -&amp;gt; list[tuple[&lt;/span&gt;&lt;span class=&quot;z-support&quot;&gt;int&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-support&quot;&gt; float&lt;/span&gt;&lt;span&gt;]]:&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt; ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; reflow&lt;/span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;(text:&lt;/span&gt;&lt;span class=&quot;z-support&quot;&gt; str&lt;/span&gt;&lt;span&gt;) -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-support&quot;&gt; str&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt; ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;load_library&lt;/code&gt; reads the cached &lt;code&gt;.npy&lt;/code&gt; files off disk and hands back a list of &lt;code&gt;Passage&lt;/code&gt; tuples plus the stacked matrix.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;search_passages&lt;/code&gt; takes those two and a query and returns ranked &lt;code&gt;(index, score)&lt;/code&gt; pairs.&lt;/p&gt;
&lt;p&gt;The web layer consumes the core functions, no re-implementation:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;from&lt;/span&gt;&lt;span&gt; main&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; import&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    embed,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Passage,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    humanize_author,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    load_library,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    reflow,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    search_passages,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The CLI's &lt;code&gt;ask()&lt;/code&gt; and the web app's &lt;code&gt;/api/ask&lt;/code&gt; share the same spine: load the library, call &lt;code&gt;search_passages&lt;/code&gt;, walk the ranked &lt;code&gt;(index, score)&lt;/code&gt; pairs. From there each does its own thing. The CLI prints &lt;code&gt;rich&lt;/code&gt; panels and offers an interactive deep-read; the web app serializes to &lt;code&gt;Match&lt;/code&gt; JSON and logs a bit of analytics on the way out.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;ranking&lt;/em&gt; decision, what comes back and in what order, is shared. Everything downstream is presentation, which is exactly where a CLI and a web app &lt;em&gt;should&lt;/em&gt; differ.&lt;/p&gt;
&lt;p&gt;We do the same in our &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://pythonagenticai.com&quot;&gt;agentic AI program&lt;/a&gt;: one core engine, three interfaces (CLI, Telegram, API / web dashboard).&lt;/p&gt;
&lt;h2 id=&quot;i-needed-caching&quot;&gt;I needed caching&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;load_library&lt;/code&gt; is not cheap. It walks &lt;code&gt;books/&lt;/code&gt;, reads a JSON file and an &lt;code&gt;.npy&lt;/code&gt; file per book, and stacks 80k vectors into one matrix with &lt;code&gt;np.vstack&lt;/code&gt;. You don't want to pay that overhead on every HTTP request!&lt;/p&gt;
&lt;p&gt;In the CLI that's a non-issue: the process loads once and exits. On the web side, it's one decorator away:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;from&lt;/span&gt;&lt;span&gt; functools&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; import&lt;/span&gt;&lt;span&gt; cache&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;@cache&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; library&lt;/span&gt;&lt;span&gt;() -&amp;gt; tuple[list[Passage], np.ndarray]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;/span&gt;&lt;span&gt; load_library()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;@cache&lt;/code&gt; turns the first call into the real load and every call after into a dictionary lookup, much faster.&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;@app.get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;/api/ask&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; ask&lt;/span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;(q:&lt;/span&gt;&lt;span class=&quot;z-support&quot;&gt; str&lt;/span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;, k:&lt;/span&gt;&lt;span class=&quot;z-support&quot;&gt; int&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt; 5&lt;/span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;, per_book:&lt;/span&gt;&lt;span class=&quot;z-support&quot;&gt; int&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt; 2&lt;/span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;, floor:&lt;/span&gt;&lt;span class=&quot;z-support&quot;&gt; float&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.6&lt;/span&gt;&lt;span&gt;) -&amp;gt; list[Match]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    passages, vectors&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;/span&gt;&lt;span&gt; library()&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;  # cached&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;    ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;pre-warm-on-startup-not-on-the-first-visitor&quot;&gt;Pre-warm on startup, not on the first visitor&lt;/h2&gt;
&lt;p&gt;There's a subtlety &lt;code&gt;@cache&lt;/code&gt; doesn't solve on its own. If the &lt;em&gt;first&lt;/em&gt; request is what triggers &lt;code&gt;library()&lt;/code&gt; (&quot;wakes up PyTorch&quot;), then the first real visitor pays that tax. App restarts are rare, but making the first visitor wait still isn't acceptable.&lt;/p&gt;
&lt;p&gt;FastAPI's &lt;code&gt;lifespan&lt;/code&gt; offers a nice fix for this: do it as soon as the app starts, before the first request:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;@asynccontextmanager&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;async def&lt;/span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; lifespan&lt;/span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;(app: FastAPI):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    init_db()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    logger.info(&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;Pre-warming vector library and loading models into RAM...&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    _&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;/span&gt;&lt;span&gt; library()&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;          # fills the @cache with the stacked matrix&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    _&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;/span&gt;&lt;span&gt; embed([&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;warmup&amp;quot;&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;   # forces PyTorch to wake up and allocate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    logger.info(&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;Ready for traffic.&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    yield&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;app&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;/span&gt;&lt;span&gt; FastAPI(&lt;/span&gt;&lt;span class=&quot;z-variable&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;classics&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-variable&quot;&gt; lifespan&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;/span&gt;&lt;span&gt;lifespan)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;I left a log line to watch the startup time. I also added some comments for possible collaborators and my future self.&lt;/li&gt;
&lt;li&gt;I use &lt;code&gt;_&lt;/code&gt; as a throwaway variable to make it clear the return value is ignored.&lt;/li&gt;
&lt;li&gt;You can put shutdown logic after &lt;code&gt;yield&lt;/code&gt;, similar to how pytest fixtures work. Clean.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By the time the first request lands, both are warm.&lt;/p&gt;
&lt;h2 id=&quot;lazy-loading&quot;&gt;Lazy loading&lt;/h2&gt;
&lt;p&gt;I am a proponent of imports at the top, but lazy loading is a serious performance consideration. It's coming in 3.15:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Lazy imports defer the loading and execution of a module until the first time the imported name is used, in contrast to ‘normal’ imports, which eagerly load and execute a module at the point of the import statement. - &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://peps.python.org/pep-0810/&quot;&gt;PEP 810 – Explicit lazy imports&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That's the automatic version, landing in 3.15. Here I do it by hand: defer the model import into the function that needs it:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;@cache&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; _model&lt;/span&gt;&lt;span&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    import&lt;/span&gt;&lt;span&gt; sentence_transformers&lt;/span&gt;&lt;span class=&quot;z-keyword&quot;&gt; as&lt;/span&gt;&lt;span&gt; st&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;  # lazy, so the offline env vars take effect first&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;/span&gt;&lt;span&gt; st.SentenceTransformer(&lt;/span&gt;&lt;span class=&quot;z-constant&quot;&gt;EMBED_MODEL&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So the model loads once, and only if something actually calls &lt;code&gt;_model()&lt;/code&gt;. &lt;code&gt;@cache&lt;/code&gt; hands back the same instance every time after.&lt;/p&gt;
&lt;p&gt;The &quot;offline env vars&quot; part refers to the second reason I need the import here. At the top of the module I have:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;os.environ.setdefault(&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;HF_HUB_OFFLINE&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;1&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;os.environ.setdefault(&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;TRANSFORMERS_OFFLINE&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;1&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;os.environ.setdefault(&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;TQDM_DISABLE&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;1&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hugging Face reads &lt;code&gt;HF_HUB_OFFLINE&lt;/code&gt; &lt;em&gt;at import time&lt;/em&gt;. Import &lt;code&gt;sentence-transformers&lt;/code&gt; before those are set and it will try to reach out to the internet, which is not what I want because I have the data and model cached locally. Set them first and the model stays fully offline, no surprise network calls.&lt;/p&gt;
&lt;h2 id=&quot;functions-vs-classes&quot;&gt;Functions vs classes&lt;/h2&gt;
&lt;p&gt;None of this needs a class. The core is functions over plain data (&lt;code&gt;Passage&lt;/code&gt; and &lt;code&gt;Chunk&lt;/code&gt; are &lt;code&gt;NamedTuple&lt;/code&gt;s), the only state is a memoized function, and the two interfaces are thin adapters that share common behavior.&lt;/p&gt;
&lt;p&gt;That's the payoff. When I want a third interface tomorrow (e.g. a scheduled job or a different API), it imports the same functions and gets the same behavior for free.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Next up in part 3: the small post-processing tricks that make the results actually good, no bigger model required.&lt;/p&gt;</description>
	<pubDate>Sat, 04 Jul 2026 00:00:00 +0000</pubDate>
</item>
<item>
	<title>Armin Ronacher: Better Models: Worse Tools</title>
	<guid>https://lucumr.pocoo.org/2026/7/4/better-models-worse-tools/</guid>
	<link>https://lucumr.pocoo.org/2026/7/4/better-models-worse-tools/</link>
	<description>&lt;p&gt;A very strange &lt;a href=&quot;https://github.com/earendil-works/pi/issues/6278&quot;&gt;Pi issue&lt;/a&gt;
sent me down a rabbit hole over the last two days.  The short version is that
newer Claude models sometimes call Pi&amp;#8217;s edit tool with extra, invented fields in
the nested &lt;code&gt;edits[]&lt;/code&gt; array.  And not Haiku or some small model: Opus 4.8.  The
edit itself is usually correct but the arguments do not match the schema as
the model invents made-up keys and Pi thus rejects the tool call and asks to
try again.&lt;/p&gt;
&lt;p&gt;That alone is not too surprising as models emit malformed tool calls sometimes.
Particularly small ones.  What surprised me is that this is getting worse with
newer Anthropic models as both Opus 4.8 and Sonnet 5 show it but none of the
older models.  In other words, the SOTA models of the family are worse at this
specific tool schema than their older siblings.&lt;/p&gt;
&lt;p&gt;In case you are curious about Fable: I intentionally did not test it because I
was not sure if the classifiers they are running might downgrade me to Opus
silently.&lt;/p&gt;
&lt;h2&gt;Tool Calls Are Text&lt;/h2&gt;
&lt;p&gt;If you have not spent too much time looking at LLM tool calling internals, the
important thing to understand is that tool calls are not magic and use some
rather crude in-band signalling.  The model receives a transcript, a system
prompt and a list of available tools.  The server munches that into a large
prompt with special marker tokens.  Because the model was trained and
reinforced on examples of that format, at some point during generation it emits
something that the API or client interprets as &amp;#8220;call this tool with these
arguments&amp;#8221;.&lt;/p&gt;
&lt;p&gt;For a file edit tool, the intended invocation payload might say something like
this:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;quot;path&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;some/file.py&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;quot;edits&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;quot;oldText&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;text to replace&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;      &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;quot;newText&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;replacement text&amp;quot;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A harness then validates the arguments, performs the edit, and feeds the result
back into the model.  If validation fails, the model sees an error and usually
tries again.&lt;/p&gt;
&lt;p&gt;How exactly that formatting happens is not known for the Anthropic models, but
some people have gotten out &amp;#8220;ANTML&amp;#8221; markers and they at times do leak also into
public communications.  To the best of my knowledge, the call above would come
out serialized like this from the model:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;antml:function_calls&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;antml:invoke&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;edit&amp;quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;antml:parameter&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;path&amp;quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;some/file.py&lt;span class=&quot;nt&quot;&gt;&amp;lt;/antml:parameter&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;antml:parameter&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;quot;edits&amp;quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
[
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;{
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&amp;quot;oldText&amp;quot;:&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&amp;quot;text&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;to&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;replace&amp;quot;,
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&amp;quot;newText&amp;quot;:&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&amp;quot;replacement&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;text&amp;quot;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;}
]
&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;/antml:parameter&amp;gt;&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;/antml:invoke&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/antml:function_calls&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;An important thing to note here is that this thing, while looking like XML, is
not really XML.  It&amp;#8217;s just a thing they found convenient to tokenize and train
on.  The other thing to note is that a basic top-level string parameter appears
in-line whereas an array of objects is implemented via JSON serialization.
While I&amp;#8217;m not &lt;em&gt;entirely sure&lt;/em&gt; that this is how it works, there are some
indications that this is not too far off.  This will become relevant later.&lt;/p&gt;
&lt;p&gt;There are two very different ways to make the model produce a structure like
this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You can &lt;em&gt;ask&lt;/em&gt; the model to produce valid JSON matching a schema and then
validate it afterwards.&lt;/li&gt;
&lt;li&gt;You can constrain the sampler so that invalid JSON, or even invalid schema
shapes, cannot be sampled in the first place.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The second approach is what people usually refer to as grammar-aware or
constrained decoding.  The sampler masks out tokens that would violate the
grammar.  If the model is currently inside a JSON object and the schema says
only &lt;code&gt;oldText&lt;/code&gt; and &lt;code&gt;newText&lt;/code&gt; are allowed, the sampler can prevent it from
emitting &lt;code&gt;&amp;quot;in_file&amp;quot;&lt;/code&gt; or &lt;code&gt;&amp;quot;type&amp;quot;&lt;/code&gt;.  Grammar-aware decoding can be used both to
constrain something to be syntactically valid JSON and also to enforce specific
enum values or keys.&lt;/p&gt;
&lt;p&gt;Without any form of constraints the model is merely following a learned
convention.&lt;/p&gt;
&lt;h2&gt;The Failure&lt;/h2&gt;
&lt;p&gt;Pi&amp;#8217;s edit tool supports multiple exact string replacements in one call.  That is
why the arguments contain an &lt;code&gt;edits&lt;/code&gt; array.  In the failing cases the model
produces entries like this:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;quot;oldText&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;quot;newText&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;quot;requireUnique&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;or this:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;quot;oldText&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;quot;newText&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;quot;oldText2&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;quot;newText2&amp;quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Across repeated trials I saw a whole zoo of invented trailing keys: &lt;code&gt;type&lt;/code&gt;,
&lt;code&gt;id&lt;/code&gt;, &lt;code&gt;kind&lt;/code&gt;, &lt;code&gt;unique&lt;/code&gt;, &lt;code&gt;requireUnique&lt;/code&gt;, &lt;code&gt;matchCase&lt;/code&gt;, &lt;code&gt;in_file&lt;/code&gt;,
&lt;code&gt;forceMatchCount&lt;/code&gt;, &lt;code&gt;children&lt;/code&gt;, &lt;code&gt;notes&lt;/code&gt;, &lt;code&gt;cost&lt;/code&gt;, &lt;code&gt;oldText2&lt;/code&gt;, &lt;code&gt;newText2&lt;/code&gt;,
&lt;code&gt;oldText_2&lt;/code&gt;, &lt;code&gt;newText_2&lt;/code&gt;, and even an &lt;code&gt;event.0.additionalProperties&lt;/code&gt; key inside
the edit object itself.&lt;/p&gt;
&lt;p&gt;The most annoying part is that the actual &lt;code&gt;oldText&lt;/code&gt; and &lt;code&gt;newText&lt;/code&gt; payloads were
byte-correct in the invalid calls I inspected.  The model had in fact produced
the right invocation but then added nonsense at the end of the object.&lt;/p&gt;
&lt;p&gt;The failure is also heavily context-dependent.  A fresh single-turn prompt like
&amp;#8220;edit this file&amp;#8221; did not reproduce it at all for me.  An agentic history where the
model had read files, diagnosed a problem and then composed a multi-line edit
could reproduce it.  And more annoyingly, not all transcripts will show that behavior.
In fact, I needed &lt;a href=&quot;https://github.com/pasky&quot;&gt;Petr Baudis&lt;/a&gt;&amp;#8216;s transcripts to
reproduce this for me at all!  In that user&amp;#8217;s session continuing the session
caused Opus 4.8 to fail around 20% of the time.  Stripping thinking blocks from
history reduced the failure rate by half.  Turning on strict tool invocation
eliminated it in my runs.&lt;/p&gt;
&lt;h2&gt;Why It&amp;#8217;s Getting Worse&lt;/h2&gt;
&lt;p&gt;My strongest hypothesis is that this is not random deterioration but a training
artifact.&lt;/p&gt;
&lt;p&gt;When older Anthropic models were trained, they were trained on some tools (some of
which were documented).  But that training did not yet have a user-shipped
harness like Claude Code as the obvious target.  Modern Anthropic models are
most likely different because their post-training includes Claude Code or a
harness that looks very similar.  The model learns what a successful tool call
looks like in that environment.  It also learns what mistakes are tolerated by that environment.&lt;/p&gt;
&lt;p&gt;Claude Code&amp;#8217;s own tools are comparatively flat.  The ordinary edit tool is not
Pi&amp;#8217;s nested &lt;code&gt;edits[]&lt;/code&gt; shape; it is closer to &lt;code&gt;file_path&lt;/code&gt;, &lt;code&gt;old_string&lt;/code&gt;,
&lt;code&gt;new_string&lt;/code&gt;, and an optional flag (&lt;code&gt;replace_all&lt;/code&gt;).  Looking at Claude Code&amp;#8217;s
client is very instructive: it contains retry paths for malformed tool use,
parameter aliases, type coercions, Unicode repairs and filtering of unknown
keys.  In other words, Anthropic&amp;#8217;s own client appears to expect and accept a
fair amount of slop and repairs it, mostly silently.&lt;/p&gt;
&lt;p&gt;If reinforcement learning happens in a harness like that, or a simulation of
one, then slightly malformed tool calls can still complete the task and receive
reward.  The harness fully absorbs the error and there is little gradient
against inventing an alias, adding a stray field or using a nearby parameter
name.&lt;/p&gt;
&lt;p&gt;Worse, the model may become very strongly adapted to the canonical Claude Code
edit tool shape.  A different harness can present a tool with the same semantic
intent but a different schema.  Such a tool can increasingly be
off-distribution.  The better-trained model might actually fight you harder
because its prior is stronger.&lt;/p&gt;
&lt;p&gt;This is not too surprising, but it is a change from how this was a few months ago.
When Opus 4.5 launched, it adapted to other edit tools exceptionally well.  In
fact, I was pretty convinced that we&amp;#8217;re on a good path where the models are
more likely to adapt to any sort of tool shape that comes around for as long
as the instructions are good.&lt;/p&gt;
&lt;p&gt;Now I&amp;#8217;m somewhat worried about the track we&amp;#8217;re on here.  Alternative tool
schemas might not just be unfamiliar.  They might be implicitly punished by
post-training that optimizes for one particular, forgiving tool ecology.  And
that ecology is not documented.  While there is a &lt;a href=&quot;https://platform.claude.com/docs/en/agents-and-tools/tool-use/text-editor-tool&quot;&gt;text editor
tool&lt;/a&gt;
that is documented, you will see that this format is in fact not followed by
Claude Code.  What Claude Code does internally (which is a closed-source
harness) is hidden from you.&lt;/p&gt;
&lt;h2&gt;The Slop Harness&lt;/h2&gt;
&lt;p&gt;Claude Code is obviously closed-source but we can look at the minified code and
get some idea of what it does.  And honestly, it&amp;#8217;s very forgiving of incoming
data.&lt;/p&gt;
&lt;p&gt;For a start, Claude Code checks the model&amp;#8217;s visible text for leaked &lt;code&gt;&amp;lt;invoke&lt;/code&gt;
markup.  It also emits some telemetry when that happens and then it has its
own state machine to retry such bad calls by pushing back to the model.&lt;/p&gt;
&lt;p&gt;It has explicit Unicode escape repair which fixes broken &lt;code&gt;\uXXXX&lt;/code&gt; sequences and
lone surrogates in string values.  It also has per-tool aliases for parameters.
For instance, &lt;code&gt;Edit&lt;/code&gt; accepts &lt;code&gt;old_str&lt;/code&gt; (presumably from the times when the models
were trained on the officially documented text editor tool), the newer &lt;code&gt;old_string&lt;/code&gt;
from the schema, &lt;code&gt;new_str&lt;/code&gt;/&lt;code&gt;new_string&lt;/code&gt;, &lt;code&gt;path&lt;/code&gt; as an alias for &lt;code&gt;file_path&lt;/code&gt;, and some more.&lt;/p&gt;
&lt;p&gt;It also silently filters out unexpected keys and it does not use &lt;code&gt;strict&lt;/code&gt; mode
either.  The issue with &lt;code&gt;strict&lt;/code&gt; mode is that Anthropic applies complexity
limits to the tool definitions that cause API requests to fail, so presumably
that&amp;#8217;s why Claude Code does not attempt to use it.&lt;/p&gt;
&lt;h2&gt;Strictness&lt;/h2&gt;
&lt;p&gt;Will this problem be with us in other harnesses too?  One huge issue with
Anthropic is that the models are completely closed, and so is the harness.
Codex models are also closed, but at least the harness is not.  We also have
&lt;a href=&quot;https://github.com/openai/gpt-oss&quot;&gt;gpt-oss&lt;/a&gt; which is at least a bit
interesting.  The models are explicitly trained to use OpenAI&amp;#8217;s
&lt;a href=&quot;https://github.com/openai/harmony&quot;&gt;harmony&lt;/a&gt; response format and there is
a lot of documentation that at least tells us how OpenAI people think about
this.&lt;/p&gt;
&lt;p&gt;Harmony makes channels and tool-call content types part of the prompt format.  A
function call can look like this:&lt;/p&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&amp;lt;|start|&amp;gt;assistant&amp;lt;|channel|&amp;gt;commentary to=functions.get_weather
&amp;lt;|constrain|&amp;gt;json&amp;lt;|message|&amp;gt;{&amp;quot;location&amp;quot;:&amp;quot;San Francisco&amp;quot;}&amp;lt;|call|&amp;gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The important bit is &lt;code&gt;&amp;lt;|constrain|&amp;gt;json&lt;/code&gt;.  The model can express in-band that
this message body is JSON, and an inference stack can use that boundary to
switch into JSON-constrained sampling for the body of the tool call.  Presumably
a bit of this also happens in Anthropic&amp;#8217;s models, at least in &lt;code&gt;strict&lt;/code&gt; mode
I would imagine.&lt;/p&gt;
&lt;p&gt;The marker in harmony helps the sampler to detect when it needs to sample with a
specific grammar, and because it is part of the transcript, it makes that rather
easy to do.  For hosted GPT models, there is also an option to provide a
&lt;a href=&quot;https://lark-parser.readthedocs.io/en/latest/grammar.html&quot;&gt;LARK&lt;/a&gt; grammar for
custom tools that need to adhere to something like this.&lt;/p&gt;
&lt;p&gt;Anthropic appears different from that, though maybe not entirely.  If an array
of objects is represented as JSON, as it appears to be, then the model has to
write JSON inside the tool parameter.  There is probably basic
grammar-constrained sampling going on, and that may partly explain the extra
keys.  For a nested array parameter, that JSON includes escaped multi-line file
content inside string literals, inside one tag.  The unexpected,
made-up keys appear exactly at the highest-entropy point of that task: after
closing a several-hundred-token escaped &lt;code&gt;newText&lt;/code&gt; string, where the model must
decide &lt;code&gt;}&lt;/code&gt; vs &lt;code&gt;, &amp;quot;...&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Opus 4.8 and Sonnet 5 seem to have much stronger priors about what an edit tool
call should look like and that prior appears to be Claude Code&amp;#8217;s edit schema: a
flat old/new string pair, plus the optional &lt;code&gt;replace_all&lt;/code&gt; flag.  My guess is
that Opus has learned that an edit operation may have one extra optional field,
but under Pi&amp;#8217;s nested &lt;code&gt;oldText&lt;/code&gt;/&lt;code&gt;newText&lt;/code&gt; shape it has no trained name for that
field.  So it samples a plausible name fresh each time, which is why the
failures produce dozens of random keys rather than one stable alias.&lt;/p&gt;
&lt;p&gt;As &lt;code&gt;strict&lt;/code&gt; mode in Anthropic appears to fix this, I presume that on the server
side they are refusing to sample a key that is not permitted by the JSON schema
structure.  That would also explain why they have limits to the complexity of
the tool definitions when strict mode is enabled.&lt;/p&gt;
&lt;p&gt;So far, the Codex models I tested did not show this type of regression.  I tested
all available ones except 5.6, which I do not have access to yet.&lt;/p&gt;
&lt;h2&gt;What This Means For Harnesses&lt;/h2&gt;
&lt;p&gt;The uncomfortable lesson is that tool schemas are not neutral, at least not on
Anthropic models.  We like to pretend that a schema is an abstract contract and
the model is a general reasoner that will follow it, but that might no longer be
the case for some of the tools.&lt;/p&gt;
&lt;p&gt;Tool schemas are somewhere in the distribution and some shapes are close to
what the model saw during post-training and some are far away.  Some are easy for
the provider&amp;#8217;s hidden encoding (e.g. top-level attributes in ANTML), whereas some
require the model to write large escaped JSON objects inside nested arrays after
long multiline strings.  The model may be smart enough to understand the schema
and still be bad at sampling the exact shape under pressure.&lt;/p&gt;
&lt;p&gt;If this type of model behavior continues, I wonder what the implications for
harnesses are.  Obviously one could turn on &lt;code&gt;strict&lt;/code&gt; sampling in
Anthropic and the problem should go away.  On the other hand, that the model
has this behavior shows the impact that reinforcement learning has on them.
Fighting that prior is probably futile if you want to get the best model performance.&lt;/p&gt;
&lt;p&gt;Right now the reality is that Claude Code is not open source and we cannot
really know what they are doing in their RL environments either.  We cannot assume
Claude-Code-trained behavior will transfer cleanly to your tools unless they are
a close match.  The more post-training happens inside one dominant harness, the
more every other harness will have to inherit its quirks.&lt;/p&gt;
&lt;p&gt;I used to be more skeptical of strict grammar-constrained tool invocation
because constrained decoding can have quality tradeoffs.  I still think that can
be true in general, but this bug moved my priors significantly.  If the newest
models get better at solving the task while getting worse at faithfully emitting
an alternative tool schema, then the harness needs stronger guarantees
somewhere.&lt;/p&gt;
&lt;p&gt;If you want to find out more, or you want to discuss this, consider reading the
&lt;a href=&quot;https://github.com/earendil-works/pi/issues/6278&quot;&gt;issue on the Pi tracker&lt;/a&gt;.&lt;/p&gt;</description>
	<pubDate>Sat, 04 Jul 2026 00:00:00 +0000</pubDate>
</item>
<item>
	<title>Mycli: Release v2.0.0</title>
	<guid>https://www.mycli.net/v2.0.0.html</guid>
	<link>https://www.mycli.net/v2.0.0.html</link>
	<description>&lt;p&gt;&lt;code&gt;mycli&lt;/code&gt; is a command line interface for MySQL which includes
auto-completion and syntax highlighting.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.mycli.net/install&quot;&gt;Read the install instructions&lt;/a&gt; to find out how to get the latest version.&lt;/p&gt;
&lt;p&gt;Mycli v2.0.0 has &lt;a href=&quot;https://github.com/dbcli/mycli/blob/v2.0.0/changelog.md#breaking-changes&quot;&gt;breaking changes&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Major features added in recent months include&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;powerful fuzzy history search with &lt;a href=&quot;https://github.com/junegunn/fzf&quot;&gt;fzf&lt;/a&gt; (thanks &lt;a href=&quot;https://github.com/lazmond3&quot;&gt;lazmond3&lt;/a&gt;!)&lt;/li&gt;
&lt;li&gt;integrated …&lt;/li&gt;&lt;/ul&gt;</description>
	<pubDate>Fri, 03 Jul 2026 07:00:00 +0000</pubDate>
</item>

</channel>
</rss>
