Planet Python
Last update: October 20, 2026 07:48 PM UTC
October 20, 2026
PyCoder’s Weekly
Issue #757: Django Migrations, Functional Programming, MVC, and More (2026-10-20)
#757 – OCTOBER 20, 2026
View in Browser »
Django ORM, Migrations and makemigrations
This tutorial builds a small coffee and tea store one migration at a time, then breaks it on purpose. You’ll backfill a required field, roll a migration back, untangle two developers’ migrations and fix a slow page with Django 6.1’s new fetch modes.
MY-SPACE.IO • Shared by Mirza Ishraq Yeahia
Functional Programming in Python: When and How to Use It
See how Python supports functional programming, what the paradigm actually is, and where it fits in the code you write day to day.
REAL PYTHON
MCP Auth That Passes the Security Review
When your customers connect AI agents to your product, their security team will have questions. PropelAuth has the answers built in: role-gated scopes, enterprise SSO through Okta and Entra ID, phishing-resistant consent screens, and an audit log of every agent connection. Learn More →
PROPELAUTH sponsor
Exploring the MVC Pattern in Python
Learn how the Model-View-Controller pattern organizes Python web apps, then build a small Flask app using models, views, and controllers.
REAL PYTHON course
Claude Code Course: Stop Copy-Pasting From ChatGPT
Build a complete Python CLI app from an empty directory with Claude Code, in two live 4-hour sessions on October 24–25. You’ll plan features, run tests, manage git, and debug AI-generated code.
REAL PYTHON
Articles & Tutorials
PyTorch Year in Review: OpenTeams’ Contributions in 2026
OpenTeams’ PyTorch team reviews their 2026 contributions, in a year when PyTorch received 58% more pull requests than the same period of 2025, a growing share of them written with AI coding agents. Highlights include a redesign of the symbolic interpreter in torch.compile, day 1 compiler support for Python 3.15, a new operator, and a build backend re-write!
ANDREW JAMES • Shared by Andrew James
Bayesian Optimization for Efficient Hyperparameter Search
Instead of testing every hyperparameter combination, Bayesian optimization uses results from earlier trials to decide what to try next. This hands-on tutorial builds it step by step with scikit-learn’s GaussianProcessRegressor and a custom Expected Improvement function, plotting the model’s predictions and uncertainty along with each point the search picks.
KHUYEN TRAN • Shared by Khuyen Tran
Pendulum and the Most Cursed + Operator in All of Python
Pendulum, a popular Python datetime library, has a + operator that inspects the call stack to decide which answer to give you. The post explains how it ended up there, and why the design behind it, a “drop-in” subclass of datetime, still matters for your code now that the hack itself has been patched.
ARIE BOVENBERG • Shared by Arie Bovenberg
Checking Email Reputation, and Allowing Outlook Again
Two years ago, PyPI prohibited Outlook email domains in the registration of new accounts due to a large malware campaign. With an update to how email reputation is calculated, addresses from outlook.com and hotmail.com can once more be used.
MIKE FIEDLER
Why Parquet Dominates Modern Data Engineering
How Parquet’s columnar layout, encodings, zstd compression, and row group statistics make it smaller and faster to read than CSV, measured on NYC taxi trips data with Polars, DuckDB, and PyArrow.
ORAL ERSOY DOKUMACI • Shared by Oral Ersoy Dokumaci
Gemini 4 Argon Draws a Python Reading a Book
Explore Gemini 4 Argon’s Python code, turtle drawing, and responses to small tasks that check modern syntax, release knowledge, focused edits, and made-up functions.
REAL PYTHON
Python Project Structure: How to Choose the Right Layout
Learn how to structure Python projects: pick between flat and src/ layouts, organize your code, and apply the patterns to CLI, web, and GUI apps.
REAL PYTHON
Docling vs LlamaParse: How to Extract PDF Tables in Python
Learn how to extract tables from a PDF in Python with Docling and LlamaParse, and compare local parsing against a cloud API on speed and cost.
REAL PYTHON
Strings and Character Data in Python
Work with Python strings using a rich set of operators and functions: string literals, the str() function, and the methods you’ll use most.
REAL PYTHON
How to Use Python: Taking Your First Steps
Take your first steps with Python. Install it, run your first program, and work with variables, data types, loops, functions, and errors.
REAL PYTHON course
11 Beginner Tips for Learning Python Programming
Python tips for beginners: the learning strategies and habits that take you from your first script to code you’re proud of.
REAL PYTHON
Projects & Code
Events
Weekly Real Python Office Hours Q&A (Virtual)
October 21, 2026
REALPYTHON.COM
Swiss Python Summit 2026
October 22 to October 24, 2026
PYTHON-SUMMIT.CH
PyCon Panamá 2026
October 22 to October 24, 2026
PYCON.PA
Django Girls Ho 2026
October 22 to October 24, 2026
DJANGOGIRLS.ORG
PyHo 2026
October 23 to October 25, 2026
PYTHONHO.COM
PyBeach 2026
October 24 to October 25, 2026
PYBEACH.ORG
FastAPI Conf 2026
October 28 to October 29, 2026
FASTAPICONF.COM
PyConFR 2026
October 29 to November 2, 2026
PYCON.FR
Happy Pythoning!
This was PyCoder’s Weekly Issue #757.
View in Browser »
[ Subscribe to 🐍 PyCoder’s Weekly 💌 – Get the best Python news, articles, and tutorials delivered to your inbox once a week >> Click here to learn more ]
Python Bytes
#501 A Nod to Sir-Mix-Alot
<strong>Topics covered in this episode:</strong><br> <ul> <li><strong><a href="https://blog.jupyter.org/posts/2026/notebooks-on-demand-nod-a-jupyterlab-extension-for-a/?featured_on=pythonbytes">Nod: pause your Python program and open a notebook at any line</a></strong></li> <li><strong><a href="https://pola.rs/posts/release-polars-2/?featured_on=pythonbytes">Polars 2.0 ships: streaming engine by default, spill-to-disk, and a row-order gotcha</a></strong></li> <li><strong><a href="https://github.com/noripyt/django-cachalot?featured_on=pythonbytes">django-cachalot caches your ORM queries and invalidates them for you</a></strong></li> <li><strong><a href="https://noumenal.es/stack-report/towards-async-transactions-in-django/?featured_on=pythonbytes">Towards async transactions in Django</a></strong></li> <li><strong>Extras</strong></li> <li><strong>Joke</strong></li> </ul><a href='https://www.youtube.com/watch?v=TGDze5W1EZ8' style='font-weight: bold;' data-umami-event="Livestream-Past" data-umami-event-episode="501">Watch on YouTube</a><br> <p><strong>About the show</strong></p> <p>Sponsored by us! Support our work through:</p> <ul> <li>Our <a href="https://training.talkpython.fm/?featured_on=pythonbytes"><strong>courses at Talk Python</strong></a></li> <li>Consulting from <a href="https://sixfeetup.com/?featured_on=pythonbytes"><strong>Six Feet Up</strong></a> <strong>Connect with the hosts</strong></li> <li>Michael: <a href="https://fosstodon.org/@mkennedy">Mastodon</a> / <a href="https://bsky.app/profile/mkennedy.codes?featured_on=pythonbytes">BlueSky</a> / <a href="https://x.com/mkennedy?featured_on=pythonbytes">X</a> / <a href="https://www.linkedin.com/in/mkennedy/?featured_on=pythonbytes">LinkedIn</a></li> <li>Calvin: <a href="https://sixfeetup.social/@calvin?featured_on=pythonbytes">Mastodon</a> / <a href="https://bsky.app/profile/calvinhp.com?featured_on=pythonbytes">BlueSky</a> / <a href="https://x.com/calvinhp?featured_on=pythonbytes">X</a> / <a href="https://www.linkedin.com/in/calvinhp/?featured_on=pythonbytes">LinkedIn</a></li> <li>Show: <a href="https://fosstodon.org/@pythonbytes">Mastodon</a> / <a href="https://bsky.app/profile/pythonbytes.fm">BlueSky</a> / <a href="https://x.com/PythonBytes?featured_on=pythonbytes">X</a></li> <li>Join us on YouTube at <a href="https://pythonbytes.fm/stream/live"><strong>pythonbytes.fm/live</strong></a> to be part of the audience. Usually <strong>Tuesday at 7am PT</strong>. Older video versions available there too.</li> <li>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 <a href="https://pythonbytes.fm/friends-of-the-show">our friends of the show list</a>, we'll never share it.</li> </ul> <p><strong>Michael #1:</strong> <a href="https://blog.jupyter.org/posts/2026/notebooks-on-demand-nod-a-jupyterlab-extension-for-a/?featured_on=pythonbytes">Nod: pause your Python program and open a notebook at any line</a></p> <p>Nod (Notebooks-on-Demand) is a JupyterLab extension by Eric Rawn that drops a notebook into a running Python program. Add a <code>notebook()</code> call anywhere, run the program with <code>nod</code>, and execution pauses there with a notebook containing all variables at that point in the call stack. Edits go back to the source file with a button, with cells converted to code and markdown to comments via Jupytext.</p> <ul> <li>It answers the classic 'this would be easier in a notebook' debugging moment without copying snippets out to files.</li> <li>Like a breakpoint with a notebook inside: the left panel walks the call stack and notebook state switches with it</li> <li>Install is pip install nodpy plus nod --install-kernel, then run with nod python -m myfile</li> <li>The round trip back to source is the differentiator; markdown cells become comments, cells become newlines, and notebooks are disposable since the source file recreates them</li> <li>nodLog() stashes variable values across calls to explore later; the article also pitches notebook() for stepping through AI-generated code line by line</li> <li>Early stage: repo from December 2025 with 16 stars and essentially one contributor, so expect rough edges</li> </ul> <p><strong>Calvin #2: <a href="https://pola.rs/posts/release-polars-2/?featured_on=pythonbytes">Polars 2.0 ships: streaming engine by default, spill-to-disk, and a row-order gotcha</a></strong></p> <ul> <li>Polars 2.0 released Oct 6, 2026 (Ritchie Vink). The post says it's "not intended to be a big feature release", but it bundles a few big changes.</li> <li>LazyFrame.collect now defaults to the streaming engine, with large memory and speed gains on most queries.</li> <li>Breaking: streaming doesn't guarantee row order for join, group_by and unpivot. Use maintain_order=True if order matters. This is why it's a major version.</li> <li>Out-of-core (spill-to-disk) is on by default. It spills at about 80% of RAM ("may need tuning") with a 64 GB default disk budget. It covers sort, window functions and many expressions. Joins and group-bys are planned, not shipped.</li> <li>The new native Map dtype (Arrow MapType) replaces the old List(Struct(key, value)) reading. It comes with map.get, map.contains_key, map.len, map.keys and map.values.</li> <li>Stricter by design: errors should surface up front and implicit handling of data mismatches is opt-in. collect_schema() catches schema mismatches without reading data.</li> <li>SQL is now a first-class interface, with join reordering, better common-subplan elimination, and dynamic predicates/bloom filters.</li> <li>Benchmarks are self-run TPC-H/TPC-DS derived numbers against DuckDB 1.5.6 and 2.0 alpha, and DataFusion 54. They are not official TPC results.</li> <li>Polars was fastest on all but one benchmark. At 192 threads its constant overhead hurts small data (SF10 TPC-DS is 1.8x slower), and a fix is hoped for in the next release.</li> <li>Roadmap, not released: out-of-core joins and group-bys, better scaling at high core counts, Polars Cloud, and early GeoPolars work.</li> <li>Upgrade guide: https://docs.pola.rs/releases/upgrade/2/</li> </ul> <p><strong>Michael #3:</strong> <a href="https://github.com/noripyt/django-cachalot?featured_on=pythonbytes">django-cachalot caches your ORM queries and invalidates them for you</a></p> <p>django-cachalot is a Django package that caches ORM queries and invalidates them automatically when the underlying data changes. Install it, add one line to INSTALLED_APPS, and read queries run through your cache backend with no code changes. The trick is that it caches whole tables, so any write flushes that table's cached queries, making it a strong fit for read-heavy sites and a poor one for frequently updated tables. It supports Django 4.2 through 6.1 and Python 3.8 to 3.14.</p> <ul> <li>ORM caching that normally takes careful engineering drops in with pip install and one settings line, invalidation included.</li> <li><strong>Near-100x on joins:</strong> the README reports nearly 100x speedups for initial deployments heavy on select_related and prefetch_related.</li> <li>It caches the entire table, so a hot table with frequent writes invalidates constantly; docs suggest staying under 50 modifications per minute and mixing in cache-machine for hot paths.</li> <li>Setup is two lines, plus caveats: sync clocks across servers sharing one cache, and run a <a href="http://manage.py/?featured_on=pythonbytes">manage.py</a> command after restoring a database outside Django.</li> </ul> <p><strong>Calvin #4: <a href="https://noumenal.es/stack-report/towards-async-transactions-in-django/?featured_on=pythonbytes">Towards async transactions in Django</a></strong></p> <ul> <li>Carlton Gibson, The Stack Report, Oct 9, 2026. It's about getting async with transaction.atomic() into Django, hoped for 6.2 and not a commitment. Status check: both PRs are still open as of Oct 19-20, with no approvals and no milestone.</li> <li>The problem: asyncio tasks share one thread and so one DB connection. A slow transaction that rolls back also discards a sibling task's quick write. Today transactions go through sync_to_async, so everything runs serially on one thread.</li> <li>Design: async with transaction.atomic() opens a nested ThreadSensitiveContext, which gets its own worker thread and connection. On exit the connection is cleaned up and control returns to the request's connection. Siblings keep using the request's connection in autocommit, so their writes survive a rollback.</li> <li>Only the task that opened the block may nest further atomic() blocks. Any other task raises TransactionManagementError, and this is enforced.</li> <li>Subtasks inside a transaction are discouraged, since the connection is serial and parallel queries gain little. If you need them, use structured concurrency (TaskGroup or an awaited gather()) so they finish before the block exits. That rule is not enforced yet, and Gibson wants Django to catch late queries before 6.2.</li> <li>Caveats: each async transaction uses an extra connection for its duration, so pools may need tuning. The advice to disable CONN_MAX_AGE for async still applies.</li> <li>Origin: the Django on the Med sprint in Pescara (hosted by Paolo Melchiorre). An expert panel on Day 1 included Lily Acorn, Simon Charette, Jacob Walls, and Charlie Denton and Sam Searles-Bryant of Kraken Tech (authors of django-subatomic, which is being integrated into Django). Caleb Hattingh then investigated the design and opened both PRs.</li> <li>PRs: Django #22074 ("Fixed #33882 -- async with transaction.atomic") has review comments from four people, with the latest commit on Oct 19 adding a test for a late child-task query. asgiref #596 (ThreadSensitiveContext changes) had Gibson's review on Oct 19 and follow-up edits on Oct 20.</li> </ul> <p><strong>Extras</strong></p> <p>Calvin:</p> <ul> <li>Jazzband is Shutting Down (announced in March), but there are alternatives like https://django-commons.org/</li> <li>Treemd — nice TUI for viewing Markdown https://github.com/Epistates/treemd Michael:</li> <li>Talk Python <a href="https://talkpython.fm/community?featured_on=pythonbytes">Membership</a></li> <li>Talk Python <a href="https://talkpython.fm/guides?featured_on=pythonbytes">Visual topic guides</a></li> <li>Quick Follow Up to Episode #499 where we shared PEP 824 None coalescing operator, we also have <a href="https://peps.python.org/pep-0823/?featured_on=pythonbytes">PEP 823</a> (None Aware access operators) the successor to PEP 505. - via Nick Muoh and <a href="https://social.tchncs.de/@rafaelwo/117415996506597741?featured_on=pythonbytes">Rafael</a></li> <li><a href="https://github.com/pola-rs/polars/issues/27955?featured_on=pythonbytes">Free Threaded Polars, but staled on PyPI</a>?</li> </ul> <p><strong>Joke:</strong> <a href="https://www.youtube.com/watch?v=3TNpOD6bov8">LGTM (Looks Good to Me) - Claude Opus 5.5 music video</a></p>
Rodrigo Girão Serrão
DX improvements in Python 3.15
Learn about the top 3 developer experience improvements from Python 3.15
This article covers the top 3 improvements made to the developer experience of Python 3.15 users:
- improved error messages
- more colours
- default UTF-8 encoding
15 days of Python 3.15
To celebrate the release of Python 3.15, over the next 15 business days I'll be writing about a new 3.15 feature every day. Explained clearly and with examples so you don't have to sift through the changelog.
Subscribe to receive this free email series:
If you want, you can also check the schedule of the upcoming emails.
Improved error messages
Over the past few years, lots of effort has been poured into improving Python's error messages. Python 3.15 is no exception, as it introduces three improved types of error messages.
Errors in accessing nested attributes
If you get an AttributeError exception when accessing a name of an object, but that object has another member that has the correct name, Python suggests it:
class Inner:
def __init__(self):
self.value = 42
class Outer:
def __init__(self):
self.inner = Inner()
o = Outer()
o.value
# AttributeError: 'Outer' object has no attribute 'value'.
# Did you mean '.inner.value' instead of '.value'?
Incidentally, while Python 3.15 was in alpha, I found and reported a bug with this feature that was fixed in just a couple of hours!
Checking methods from popular programming languages
If you get an AttributeError when trying to access a method from a built-in type, Python starts by checking if that's a typo.
For example, list.pp will ask if you meant to type list.pop.
Now, if there's no “obvious” typo, Python will also check a static table of common...
Mariatta
Python Core Sprint 2026: Day 1
This week I’m in San Francisco for the annual Python Core Sprint. This year the sprint is hosted by Brandt Bucher from OpenAI, at OpenAI’s office, and runs Monday to Friday, October 19 to 23. I’m only here for the first three days. On Thursday I fly out to Seattle for SeaGL, where I’m one of the keynote speakers :)
I could not make it to last year’s sprint at Arm in Cambridge, UK, and it is unlikely I’ll be able to make it to next year’s European sprint either, so I’m glad this one is on the West Coast and within reach. The sprint is one of the very few times in a year when this many Python core team members are in the same room, focused on nothing but Python. It is still one of my favorite Python events of the year. Thank you to Brandt and OpenAI for hosting us!
Python Software Foundation
Wikis: Relaunched!
In February 2026, the Python Software Foundation (PSF) made the PSF, Python, and Jython wikis read-only due to excessive automated traffic and security concerns on outdated infrastructure. This sudden change was disruptive to the many community members who rely on and contribute to these wikis. We are pleased to share that we have stood up new wikis through GitHub Pages that address the automated traffic and security concerns. The new wikis will have a more modern front-end, as well as a permissions-based content management system (CMS) for community members to make updates.
Find the new wikis here:
More details and context
Excessive automated traffic
With the old wiki structure, we received a large volume of alerts at all hours, each serious enough to page and wake our on-call infrastructure staff, which is very limited (just one person currently, although this will be changing soon!). The quantity of scraping overloaded our servers, causing outages, which happened often enough that these issues required a significant amount of effort for the PSF staff to manage. On review back in February, that traffic turned out to be almost entirely AI bots that were trying to scrape the entire internet.
Security
Additionally, some portions of the wikis were still built with Python 2. Python 2 reached end of life on January 1, 2020, and no longer receives upstream security fixes. Moving to a static site makes it very fast and reduces security risks, while also having the benefit of a more modern user interface.
Impact
The PSF acknowledges that retiring the old wikis without notice was frustrating to the overall community and especially so to those community members who have spent a lot of time contributing to the wikis over the years. The abrupt nature of this change did not reflect the care and respect that our contributors deserve, and we recognize the frustration this caused. We built the new wikis with the guiding principle of how to best serve the community, and we hope they will continue to be a valuable community resource, and hopefully improve on the experience. The new CMS means you can even create pull requests in your code editor if you prefer!
Platform
GitHub was chosen for the home of the new wikis as this is where CPython, python.org, and other properties the PSF hosts currently live. The hope is to continue to streamline the PSF's hosting and stewardship of services to simplify for both PSF staff and the community folks who contribute to them.
How to Contribute
Previously, the Python and Jython wikis were user-editable by invitation of an existing editor and the PSF wiki was permissions based. Moving forward, all three of the wikis will be permissions based via the GitHub repository they are hosted on, and changes can be requested through pull requests by anyone in the community.
We realize that moving to GitHub, with an entirely permissions based process, is a big change from the previous form. As stewards of Python's web presence, we've opted to provide a more modern interface, relying on GitHub, the platform that already powers CPython development. GitHub allows us to distribute granular control, review changes through pull requests, and maintain the uptime this service deserves. Also, since the site is now generated statically, it removes any worry about AI bots overwhelming the hosts that the wikis were previously hosted on.
Please review the repository's README file to learn more about contributing. We welcome the community to come join in on updating, cleaning up, and adding pages to the new wiki! We will happily grant write access to the Python wiki to contributors who are known stewards of the previous wiki, and look forward to bringing on more contributors in the future.
PyCon Ireland
Schedule for PyCon Ireland 2026
We are delighted to announce the schedule for PyCon Ireland 2026, to be held at Chartered Accountants House in Dublin on Saturday 21 November. You can choose from more than thirty talks across three tracks, at least four workshops, and two panels, as well as keynotes and lightning talks, given by dozens of speakers.
We’ll have lots of sessions on AI and Machine Learning, including
- agentic AI, coding agents, multi-agent workflows, and agent evaluation
- Bayesian modeling
- benchmarking ML models
- data architecture for AI
- forecasting
- managing parallel pipelines
- MCP servers and knowledge graphs
- ML monitoring
- mutation testing
- neural networks
- reliable machine learning
- searching with agents
- teaching robots
- tensor regression
- workflows
But it’s not just AI. There will also be sessions on
- asynchronous I/O
- learning in public
- natural language processing
- Python packaging
- quantum programming
- route optimisation
- scientific computing
- secure coding
- software archaeology
- supply chain security
- testing
- vector search
- web scraping
We had more than 170 submissions that needed to be evaluated and arranged. We thank everyone who took the time to make a submission. We had difficult choices to make when drawing up the schedule.
Tickets
If you’ve been waiting for the schedule to appear before buying your ticket, wait no more! Tickets are being sold now. The General ticket costs €59, the Student ticket is only €35, and the Corporate ticket is €159. With these all-inclusive tickets, as well as access to all the sessions and networking with the speakers and other attendees, you get a light breakfast, a sandwich lunch, and morning and afternoon refreshment breaks.
We do expect to make some further additions to the schedule, so check regularly for updates.
October 19, 2026
Rodrigo Girão Serrão
Startup configuration files
Learn how to use startup configuration files to run package entry points when the Python interpreter starts up.
A startup configuration file is a file with the extension .start.
Starting from Python 3.15, the Python interpreter uses startup configuration files to run package entry points when the interpreter starts up.
Here is an example of a simple startup configuration file that installs error-links when the Python interpreter starts:
error_links:install
The entry point error_links:install from the above points to the function install from the module error_links.
In general, an entry point resembles an import:
- the import path
- followed by a colon
- followed by the callable that gets called
For the entry point error_links:install, the equivalent import would be from error_links import install.
The module site and some historical context
The module site, from the standard library, is imported automatically when the Python interpreter starts up.
When imported, the module site does a couple of things.
Among them, it adds some paths to the module search path and then checks them for configuration paths.
In Python 3.14 or earlier, configuration files were .pth files.
.pth files can be used to extend sys.path by listing directories.
Alongside that, and for backwards compatibility reasons, .pth files could also include import lines.
import lines in a .pth represent a security risk, so PEP 829 introduced a safer way of running custom hooks during Python interpreter start up.
import lines in .pth files are now deprecated and Python 3.15+ also looks for .start files to run the listed entry points.
Python 3.15 brings much more to the table
From the two new built-ins frozendict and sentinel, to lazy imports, package startup configuration files, typing improvements, or more colour everywhere, Python 3.15 has a lot more to offer.
If you want to learn more about what's new in Python 3.15, take a look at the “15 days of Python 3.15” series I'm running.
For 15 days, I'll explain a new Python 3.15 feature every day.
Get the free 3.15 email series:
Django Weblog
Quarterly contributor outreach
Hi Django community, thank you all for continuing to contribute to Django.
The Steering Council is reaching out to bring people's attention to a few areas. We have major features we'd like to test and review for 6.2, smaller changes that could use attention and then various New Feature ideas we'd like community input on.
Major features
These PRs are things we'd like to refine, test and get merged in. We're looking for people to test the features themselves and review the PRs. Please see our guidelines on reviewing PRs.
CompositeField(multi-column subqueries)- Only needs testing, not review.
DEFAULT_PK_FIELDsetting (e.g. UUID primary keys)- Move models between apps
Sage Abdullah has a great Django London talk on how to test your project against Django's main branch. Please note that you would need to change the branch to point to the PR’s specific branch.
Other features
These are other PRs that we thought would be cool to have included in 6.2. They are in various states of refinement. We're looking for people to test and review the PRs. They are smaller in nature, so it may be easier to get involved here.
- Adding
QuerySet.executed_queries - Partitioned cookies
- Support for the HTTP QUERY method
- Improve CSRF debug messaging behind proxies
New Features
This section is bringing attention to the New Features queue. Ideas are in various stages and require different types of input. With over 150 ideas so far, we'll try to provide direction for those who have limited time to participate.
Ideas needing technical vetting [medium+ effort]
These are ideas the community would like integrated, but we need contributor opinion on. The links are to proposals that could use vetting.
- Ability to customize model Meta and migration generation
- Implement modern CSRF protection
- Add timeout API for tasks
- Subresource integrity (SRI)
- Adding import maps to Django
Ideas needing community input [low effort]
We encourage people to review the entire New Feature queue and use the emoji reactions. Please use the 😕 emoji to register a "saw this, but have no opinion". This helps us understand what has been reviewed by the community. We have selected a few from the list that we'd like people to weigh in on:
- Better hooks to customize
PasswordResetFormemail sending - Add PR help in Working with Git GitHub contribution documentation
- Service URL parsing in core (e.g. database and cache URLs)
- Standardise Task execution order
- Update
AuthenticationMiddlewareto delegate to Authentication Backends - Support sending to Unicode email addresses via SMTP
Thank you
Django is built by the community and every bit helps move Django forward. We encourage everyone to feel empowered to participate. If you aren't sure where to start or have questions, ask on the Django Forum or in the Django Discord. We're happy to help you find your way in.
- The Django Steering Council
Hugo van Kemenade
Releasing Python 3.15.0
Prologue #
Like last year, I livetooted the release of Python 3.15.0. Here it is in blogpost form!
Two weeks #
Two weeks until Python 3.15.0 final!
#Python
#Python315
Fri, Sep 18, 2026, 00:30 EEST
One week #
One week until Python 3.15.0 final!!
What are you most looking forward to?
#Python
#Python315
Tue, Sep 22, 2026, 21:17 EEST
Python 3.15.0 release day! #
First blocker, we’re getting 503s on docs.python.org and buildbot.python.org, the latter of which is needed to check the tier 1 and 2 buildbots are passing, needed for a release.
It’s a certificate issue from an incompatible pyOpenSSL upgrade. We’re waiting for the US to wake up and poke it with a stick.
#Python
#Python315
Thu, Oct 1, 2026, 14:19 EEST
Red #
The US woke up and they’re both back up!
Now, there’s one tier 1 failure to check.
#Python
#Python315
#release
Thu, Oct 1, 2026, 14:22 EEST
Flaky #
RuntimeError: join took too long: <Process name='Process-2107' pid=2290504 parent=2276730 started daemon>
Looks like a flaky test, and python/cpython#157184 has a fix to increase a timeout. Will try restarting for now.
#Python
#Python315
#release
Thu, Oct 1, 2026, 14:25 EEST
Green #
That passed, the buildbots are back to green ✅️
Next, issues/PRs with release-blocker label.
One is a straightforward docs update, I marked as blocker so I don’t forget, to change future tense to present.
Then three last-minute lazy import blockers that need a closer look.
#Python
#Python315
#release
Thu, Oct 1, 2026, 15:11 EEST
Traditional surprise rc3 #
Following recent tradition, I’m going to add a surprise third release candidate!
It makes sense to include the last-minute lazy-import fixes in 3.15.0 final.
It also makes sense to give time to test them properly, so today’s planned 3.15.0 final is postponed, and I’m going to release a 3.15.0rc3 tomorrow, assuming everything is in place.
And then 3.15.0 final will follow about week later. Exact date to be announced.
We’re nearly there! 🤞
#Python
#Python315
#release
Fri, Oct 2, 2026, 00:49 EEST
Missing backport #
The three lazy blockers are finally merged into main, two have been backported to
3.15, but wait, the third failed,
because it depends on some tests not yet backported.
Okay, backport created, merge conflict fixed, CI is now spinning, and automerge enabled. That’ll merge in about 15 mins, then we can update it into the last blocker PR, that should merge about 30 mins later, and rc3 can begin!
#Python
#Python315
#release
Fri, Oct 2, 2026, 20:58 EEST
rc3 #
Get yer rc3 here!
You know the drill: please test and report bugs!
Library maintainers, join the Cool Testers’ Club 😎 and add 3.15 to your test matrix.
Python 3.15.0 final planned for 2026-10-09.
#Python
#Python315
#release
Sat, Oct 3, 2026, 00:14 EEST
Python 3.15.0 release day (second attempt)! #
✅️ Release date updated in What’s New in Python 3.15
✅️ No release blockers
✅️ No deferred blockers
✅️ Buildbots all green
#Python
#Python315
#release
Fri, Oct 9, 2026, 13:03 EEST
Cooldowns #
Pydantic finally added support for Python 3.15 yesterday, and aiohttp on Monday, so I could use Python 3.15 rc3 to run the release scripts and release Python 3.15.0 final!
But… I’m going to respect our seven-day cooldown, and stick with 3.14.
You also use cooldowns for your dependencies, right?
#Python
#Python315
#release
Fri, Oct 9, 2026, 13:12 EEST
blurb bug #
Pushing the big launch button:
.venv/bin/python run_release.py --repository ../cpython --ssh-user hugovk --release 3.15.0
… and immediately ran into a bug with our changelog tool blurb, because we’ve had no
new NEWS fragments since rc3 :)
This needs a fix in blurb, a new PyPI release, and then the docs lockfile regenerated and backported to 3.15.
Fix PR: python/blurb#92
#Python
#Python315
#release
Fri, Oct 9, 2026, 13:43 EEST
blurb fix #
OK, merged and released (really easy using Trusted Publishing), lockfile updated and backported.
Restarted the release and that’s better now.
A temporary branch and tag has been pushed to my fork, and I need to verify the release commit does not contain any unexpected changes.
All looks good. Next to start the installer and artifact builds.
#Python
#Python315
#release
Fri, Oct 9, 2026, 14:21 EEST
Installers #
The release artifact build failed, need to bump blurb in that lockfile too.
Like this: python/release-tools#451
Now restarted that workflow with the existing CPython tag, no need for retag.
This builds source+docs tarballs and Android+iOS artifacts. Takes ~30 mins.
Ned Deily has started the macOS installer builds. And I’ve started the Windows installer builds, takes ~2 hours.
#Python
#Python315
#release
Fri, Oct 9, 2026, 14:56 EEST
SBOM #
In the meantime, the GitHub Actions build has finished, SBOMs created and some things uploaded.
The Windows process has paused a couple of times, awaiting button clicks to approve signing of binaries. The whole release team gets emails about this, so no sneaking out signed releases. 🥷
#Python
#Python315
#release
Fri, Oct 9, 2026, 16:04 EEST
Nearly there #
Windows complete, tags pushed upstream, Sigstore signing done, adding files to python.org… nearly there…
#Python
#Python315
#release
Fri, Oct 9, 2026, 17:11 EEST
Out now #
🐍🚀 Out now: Python 3.15.0 final!
💤 Lazy imports!
🧊 frozendict builtin!
💂 sentinel builtin!
📉 Tachyon
profiler!
🖼️ Frame pointers everywhere!
🛅 Unpacking in comprehensions!
🗣️
UTF-8 as the default encoding!
🛞 Stable ABI for free-threading!
🆕 Package
startup config files!
⌨️ TypedDict & TypeForm!
🐎 Faster JIT!
🎨 More
colour!
🚌 & more!
Huge thanks to the Sovereign Tech Agency supporting my work as release manager and enabling another smooth release!
#Python
#Python315
#CPython
#release
Fri, Oct 9, 2026, 17:23 EEST
It’s not over yet! #
I updated PEP 790, which also updates the PEPs API, which is used to point the docs at /3/ to 3.15, kicked off a docs build: docs.python.org/3/
Unpin one toot and pin another to my profile.
(3.14 might not be the newest, but we can still celebrate with pie!)
And then unlock the 3.15 branch so the core team can start merging 185 PRs that didn’t get in during RC.
#Python
#Python315
#release
Fri, Oct 9, 2026, 18:27 EEST
Also #
✅️ update 3.15 label
✅️ rebuild devguide
to update: devguide.python.org/versions/
✅️
unlock 3.15 branch
And I think that’s it!
Hey, @savannah, you’re up next with the first 3.16 alpha on Tuesday! 🚀
#Python
#Python315
#Python316
#release
Fri, Oct 9, 2026, 18:58 EEST
Python Software Foundation
Announcing Python Software Foundation Fellow Members for Q3 2026! 🎉
The PSF is pleased to announce its third batch of PSF Fellows for 2026. Let us welcome the new PSF Fellows for Q3! The following people continue to do amazing things for the Python community:
Aldo Caneo
Donghee Na
Paul Moore
Takayuki Shimizukawa
Thank you for your continued contributions. We have added you to our Fellows Roster.
The above members help support the Python ecosystem by being phenomenal leaders, sustaining the growth of the Python scientific community, maintaining virtual Python communities, maintaining Python libraries, creating educational material, organizing Python events and conferences, starting Python communities in local regions, and overall being great mentors in our community. Each of them continues to help make Python more accessible around the world. To learn more about the new Fellow members, check out their links above.
Let's continue recognizing Pythonistas all over the world for their impact on our community. The criteria for Fellow members is available on our PSF Fellow Membership page. If you would like to nominate someone to be a PSF Fellow, please send a description of their Python accomplishments and their email address to psf-fellow at python.org. We are accepting nominations for Quarter 4 of 2026 through November 20th, 2026.
Are you a PSF Fellow and want to help the Work Group review nominations? Contact us at psf-fellow at python.org.
LernerPython blog, from Reuven Lerner
Pandas date range selection: Slicing a datetime index with .loc
Want to select a chunk of time from a Python Pandas data frame?
Make the datetime column the index, then use .loc and a slice to choose dates:
(
df
.set_index('tpep_pickup_datetime')
.sort_index()
.loc['2025-11-10':'2025-11-20']
['trip_distance']
.mean()
)
The post Pandas date range selection: Slicing a datetime index with .loc appeared first on LernerPython.
Peter Hoffmann
Home Server Ops with a Read-Only Agent and pyinfra
My home server is a small mini PC running Debian. It holds the file shares, Time Machine backups, Gitea, a music server and a few small apps of my own. Like most home servers it grew by hand over the years: a command here, a config edit there, notes somewhere else. Its exact state was mostly in my head, and not reliably.
Over two days I moved it to a setup where a coding agent does most of the work, but can't change the server on its own. This setup has proven to be extremely reliable and helpful: the server now has a declarative state, and I can review every change the agent proposes before it is applied.
The idea
The setup is built on two rules:
- The agent gets read-only access. It can SSH into the server as my normal
user and look at everything: systemd units, logs, disk usage, config files,
cron jobs.
sudorequires a password, which the agent doesn't have. So in practice it can explore, but not change anything. - Every change is code. The agent never runs ad-hoc commands to fix something. It writes or changes a pyinfra deploy in a git repo. pyinfra is "Ansible in Python": you describe the desired state with Python operations, it compares that state with the server and only applies the difference.
The sudo password is the boundary between the two. I run the dry run and the real deploy in my own terminal, see the diff and type the password. No special user, no extra tooling.

The repo
miniserver/
├── CLAUDE.md # rules for the agent
├── SERVER.md # current state of the server, written by the agent
├── inventory.py # the server, resolved via ~/.ssh/config
├── justfile # plan, deploy, ping, fact, ...
├── deploys/ # one pyinfra deploy per topic
│ ├── base.py # packages, time sync, unattended-upgrades
│ ├── backup.py
│ ├── proxy.py
│ ├── ...
│ └── all.py # includes everything in order
└── files/ # scripts and systemd units deployed to the server
CLAUDE.md holds the rules the agent follows in every session. The important
part is short:
- Read-only exploration is fine: ssh in and run commands that only read state.
- Never modify the server without explicit permission.
- Prefer pyinfra over ad-hoc commands for changes. Always do a dry run first
(`just plan [deploys/<file>.py]`) and review the plan before the real run.
- `files/` holds the scripts/units deployed to the server; edit them here,
never directly on the server.
SERVER.md is the inventory the agent wrote in the first session by exploring
the server: OS, hardware, disks, users, services, backups. It gets updated at
the end of every session, so the next session starts from a description instead
of from scratch.
A deploy
A deploy is plain Python. This one manages a single setting in Gitea's
app.ini, restarts Gitea only if the setting changed, and checks the result:
from pyinfra.operations import files, server, systemd
APP_INI = "/home/git/gitea/custom/conf/app.ini"
ROOT_URL = "http://gitea.local/"
root_url = files.line(
name=f"app.ini: ROOT_URL = {ROOT_URL}",
path=APP_INI,
line=r"^ROOT_URL *=.*$",
replace=f"ROOT_URL = {ROOT_URL}",
_sudo=True,
)
systemd.service(
name="Restart gitea after config change",
service="gitea",
restarted=True,
_if=root_url.did_change,
_sudo=True,
)
server.shell(
name="Verify Gitea",
commands=["systemctl is-active gitea"],
_sudo=True,
)
Deploys are idempotent: running them again changes nothing. That makes it safe
to run all.py at any time to check whether the server still matches the repo.
The workflow
A typical change looks like this:
- I describe what I want, e.g. "every app should get a name like
gitea.localinstead of a port". - The agent explores the server read-only, writes the deploy (here: Caddy as reverse proxy plus mDNS aliases) and tells me what will change.
- I run
just plan deploys/proxy.py, a pyinfra dry run with diff, and review it. - I run
just deploy deploys/proxy.py. - The agent verifies the result over SSH, updates
SERVER.mdand commits.
The agent does the tedious parts (reading man pages, finding config locations, writing systemd units, checking logs), while I keep control over what actually changes. And the git history doubles as an operations log.
What came out of it
In two days the repo grew to about 40 commits: a full system upgrade, the printer moved over from an old Raspberry Pi, Time Machine backups for the Macs, push notifications for failed jobs, a reverse proxy with local names for all apps, and two of my own Python apps deployed as pinned releases.
Mistakes still happened, but they ended up in CLAUDE.md as lessons learned
for the next session. A few examples:
apt autoremoveafter the upgrade removed about 250 packages, includingsmartmontools. pyinfra's dry run can't show what apt will remove, so now the rule is to checkapt-get -s autoremovefirst.- A backup of an unmounted mount point "succeeds" with an empty snapshot. The backup script now refuses an empty source or a missing destination.
OnFailure=on a unit withRestart=alwaysfires on every restart, which means a flood of notifications.
One caveat: read-only doesn't mean the agent sees nothing sensitive. It can read everything my user can read. For a home server I'm fine with that, but it's worth keeping in mind before you do the same with a machine that holds other people's data.
October 17, 2026
The Python Coding Stack
2. Who actually does the work, the model or the software?
In the first Agents Unpacked conversation, Monty and I explored what changes when an AI system can choose actions rather than just offer advice. But saying that an agent organises a folder of photographs leaves an important question unanswered: which part actually opens the folder?
In this conversation, we return to the photographs and follow the exchanges between the model, the tools, and the software connecting them. Here, “model” means the large language model (LLM), rather than the whole agent. We’ll look at who requests an action, who carries it out, and how the result gets back to the model.
Who's Monty? Monty is my personal AI agent. Read the introduction to this series.
In conversation • Chapter 2
Monty:
Suppose you ask an AI assistant:
“Organise these photographs into folders by year, but leave the originals untouched.”
Before anything can be organised, something needs to find out which photographs are there. The model might produce a request along these lines:
“List the files in the photographs folder.”
That sounds like an action. But there’s a distinction worth pausing over: producing a request to list files is not the same as listing them. Something has to receive that request, access the folder, and return the list.
When you picture an AI assistant doing this, Stephen, do you imagine the model itself opening the folder, or a separate piece of software acting on its request?
Stephen:
I hadn't thought of that yet. So, the model is the LLM, which is often in the cloud, such as the OpenAI or Anthropic LLMs. They can't really access the files on my computer. So it can't be the model. There are local models, but I think they can't access files on my computer directly, either, as their job is to predict the most likely continuation of a conversation, right? So, my final answer is: not the LLM.
Monty:
Yes. The LLM itself doesn’t open the folder. And your point about local models is important: running a model on your computer doesn’t, by itself, give it access to your files.
One small refinement: an LLM generates a continuation a piece at a time, using probabilities. It doesn’t necessarily choose the most likely continuation at every step. And that continuation can be a request for an action, rather than a reply addressed to you.
Back to our photographs. The model produces a request to list the files. The surrounding software receives that request and, if its access rules allow it, runs a file-listing tool. That tool actually reads the folder.
Now we have a list of filenames. The software sends that result back to the model, so it can use the information when deciding what to request next.
Suppose the list contains filenames such as IMG_1042.jpg and IMG_1043.jpg. Would that give the model enough information to organise the photographs by year?
Stephen:
No, the model only knows the name of the file. It would need the metadata for the photos, which may contain the year. Let me guess. It needs to ask the software for that data, since the LLM can't access it directly. Is that right?
Monty:
Exactly. The model identifies what information is missing and requests it. The software carries out that request.
It might request: “Read the date-taken metadata from these photographs.” If a suitable tool is available and access is allowed, the software runs it and sends the results back to the model.
Notice how the first result shapes the next action: filenames weren’t enough, so the model asks for dates. If some dates are missing, it will need to decide how to handle that gap rather than invent a year.
There’s one connection we haven’t explained yet, though. We’ve described the model as “asking the software”, almost as if it were speaking to a person. How does the software recognise an action request rather than ordinary conversational text? That’s where a tool call comes in.
Stephen:
May I ask a question before we discuss tools? I understand that the LLM is the system that keeps a conversation and a 'thinking' process going. But when you say the "software", what do you mean? It's not the software that's in the OpenAI or Anthropic cloud, say, that's running the LLMs, right?
Monty:
By “the software”, I mean the application that uses the model and connects it to things it can do, rather than the software that runs the model’s calculations. I’ve been rather vague about that distinction.
Imagine a photo-organising application running on your computer. It could:
Send your request to a model hosted by OpenAI or Anthropic.
Receive the model’s request to inspect the photographs.
Read the files locally, then send the relevant information back to the model.
In that arrangement, the model runs in the cloud, while the application handling your files runs on your computer.
But that’s one possible arrangement, not a rule. The surrounding application could run in the cloud, locally, or partly in each place. A cloud application would still need an authorised connection to your computer, or uploaded copies, to work with those photographs. Being alongside the model doesn’t give it access to your files.
And one adjustment to your opening description: the model generates each response or reasoning step, but the application usually keeps the conversation record and supplies the relevant history when it calls the model again. The model isn’t necessarily sitting there continuously keeping the conversation alive between messages.
Stephen:
Is this software you refer to what's sometimes called a harness?
Monty:
Yes. “Harness” is a common name for the surrounding software we’ve been describing. People use the term somewhat differently, so it doesn’t identify one fixed design.
In our photograph example, the harness handles the practical connections: it sends information to the model, receives its requests, arranges for tools to run, and returns their results. It also manages the process between model calls, so another step can happen without you having to prompt each one.
It may also enforce access restrictions, ask for approval, or stop the process after a limit. Those protections have to be implemented. Calling something a harness doesn’t guarantee that it includes them.
So the useful distinction is: the model proposes what to do next, while the harness manages how that request is handled. The tool is what actually performs the requested operation, such as reading a photograph’s metadata.
Stephen:
So, the model is the 'brain' that does the 'thinking'. The tools are the 'muscles' that get things done. And the harness is what keeps everything connected. It's the coordinator making sure information flows across all the parts, including the human using the system. Would that be an accurate analogy?
Monty:
Yes, that’s a useful analogy. The model suggests what to do, the tools carry out operations, and the harness connects and coordinates the parts, including passing messages to and from you.
I’d put two qualifications around it.
First, “brain” and “thinking” are useful shorthand here, not a claim that the model thinks like a human or is conscious.
Second, the harness isn’t just the wiring. It also contains programmed rules governing the process. The model might request that a photograph be deleted, but the harness could reject that request because the application only permits reading and copying files. That restriction would need to be enforced in the software, not merely included in an instruction asking the model to behave.
And tools needn’t be mindless muscles. A tool could use another AI model, for example to analyse a photograph. The analogy helps distinguish their roles, but it doesn’t dictate how each part is built.
Stephen:
Got it. I derailed you earlier with my question. Please go on.
Monty:
Your question filled in a missing piece. Now we can make “the model asks the software” more precise.
Before the model can request an operation, it needs to be told what tools are available, what they do, and what information each requires. For our photographs, one tool might read date-taken metadata and require a filename.
Rather than producing an ordinary sentence addressed to you, the model can produce a tool call: a structured request that the harness can recognise. In plain English, its contents might be:
Tool: Read photo metadata
File:IMG_1042.jpg
The harness uses that information to invoke the tool. The tool reads the file and returns a result, perhaps:
Date taken: 14 August 2023
The harness then supplies that result to the model on its next turn.
The tool call is the request, not the execution. It can be rejected, fail, or return incomplete information. Here, though, the model now has a date it can use to choose the next step.
Does that clarify how a request to the software differs from a sentence in our conversation?
Stephen:
Yes, I understand now. So the user asks the harness a question. The harness is the software that's managing the whole process. The harness passes this question to the model. The model may either respond with a text message or ask for a tool. If it asks for a tool, the harness decides whether to run that tool. If it runs a tool, such as a folder-reading tool or a photo-metadata tool, it will then pass the information that the tool returns to the model again. And this dance continues until the model has an answer for the user.
Monty:
Yes, that captures the flow well. That repeated exchange between model, harness, and tools is often called an agent loop. The model can choose its next request using the information returned from the previous one.
Here’s that dance in a diagram. Each component appears once, and the numbered arrows trace one possible journey between them.
At 1, you send your request to the harness. At 2, the harness supplies it to the model, along with other relevant information.
The model returns a tool request at 3. If the request is allowed, the harness runs the tool at 4. The result comes back to the harness at 5, and the harness supplies it to the model at 6. Those exchanges can repeat as the model uses each result to choose what to request next.
At 7, the model produces a reply, which the harness delivers to you at 8. That’s one possible route, not a compulsory sequence: the model can also reply without requesting a tool. And the outline groups the agent’s parts by their role, not by where they run.
Now, back to your reponse. I’d refine your last sentence slightly: the dance continues until the model produces a final response or the harness stops the process, perhaps because of an error, a limit, or a need for your approval.
And having an answer isn’t necessarily the same as completing the task. For our photographs, “I’ve organised them” is a claim. We’d want evidence that the copies actually exist in the right year folders and that the originals remain untouched.
Let’s follow one photograph a little further. The metadata says it was taken in 2023. There’s a tool for moving files and another for copying them. Given your original request, which should the model choose, and what should it check afterwards?
Stephen:
So, I just checked what our original prompt was: “Organise these photographs into folders by year, but leave the originals untouched.” So, I would expect the model to ask the harness to use the tool that copies a file. It may also need to ask for a tool that creates a new folder, which may be needed for organising the photos.
Monty:
Exactly. Copying fits the request. Moving would remove the files from their original locations. And you’ve spotted a possible prerequisite: the year folder needs to exist before the copy can go into it.
Depending on how the tools are built, the model might request folder creation separately, or the copy tool might create missing folders itself. The model needs the tool’s description to know which.
Afterwards, it should ask for checks that the copy exists in the 2023 folder, matches the original, and that the original is still present and unchanged. A tool reporting “copy succeeded” is useful evidence, but it doesn’t establish all of those things on its own.
Now suppose the next photograph has no date-taken metadata. What should the model do with that one?
Stephen:
There are several options, and I guess the answer depends on what sort of behaviour the agent was given. By default, I'd assume it will inform the user that it can't file this photo as it has no date. If the agent was instructed to be proactive and work hard on finding a solution, it might try harder, perhaps by comparing it to other photos. However, any estimate should be clearly flagged as such.
Monty:
Yes. The key distinction is between finding more evidence and making a guess. The model could request other information, but “be proactive” shouldn’t turn an uncertain year into an apparently known one.
Comparing photographs might provide a clue. A photo without a date could show the same gathering as a dated photograph. That supports an estimate, not a confirmed date. Even a file’s creation date may reflect when it was copied onto the computer rather than when the photograph was taken.
Depending on the agreed behaviour, the agent could ask you, leave that photograph unresolved, or put a copy in an “Unknown year” folder and explain why.
Notice how our three parts remain distinct here: the model chooses what to investigate next, the harness handles its requests, and the tools obtain the information. The model then has to judge what that information actually supports. More tool use doesn’t automatically mean a more reliable conclusion.
Stephen:
How does the model know which tools it can request?
Monty:
The harness supplies descriptions of the available tools when it calls the model. The model doesn’t automatically discover what’s installed on your computer.
Think of it as a menu. Each entry explains:
The tool’s name.
What it does and when it’s useful.
What information a request must include.
For our photograph example, an entry might say:
Read photo metadata: Reads information stored inside a photograph, including its date taken, if present. Requires the file’s location.
The model uses that description to choose a tool and construct a request in the required format. It doesn’t need to know how the tool’s internal code reads the metadata, just what the tool offers and how to request it.
The harness may supply a small, fixed menu, or provide a way to discover additional tools as needed. Being shown a tool doesn’t guarantee permission to use it on every file, though. The software still needs to check the particular request against whatever restrictions are implemented.
Stephen:
Got it.
Monty:
There’s one more useful case: what if there isn’t a ready-made tool for the job?
Suppose there’s no dedicated photo-metadata tool, but the system has a tool that can run programs. The model might write a small program to read the dates, then request that the execution tool run it.
That still preserves our distinction:
The model writes the program.
The execution tool runs it, subject to the software’s restrictions.
The results return to the model, which decides what to do next.
Generating code doesn’t give the model direct access to your photographs. Without something to execute that code, it’s just text.
So if an assistant shows you a program and says, “This will organise your photographs,” has it actually organised them?
Stephen:
The agent can create the program by using the model, and it can run it if it has the tool to run programs. So, it has the capability to organise the photos. However, something can go wrong in the process. So it would need to check its results, too.
Monty:
Exactly. You’ve separated what the system can do, whether it has done it, and whether it did it correctly.
In my example, merely showing you the program doesn’t establish that it has been run. The system still needs to run it, then inspect the results. A program can also run without reporting an error and still put photographs in the wrong folders.
That brings us back to our central question: who actually does the work?
It’s a joint effort. The model interprets the request, selects actions, and judges returned information. The tools perform operations. The harness manages their exchanges and applies whatever controls have been implemented.
So when we call something “an AI agent”, we’re generally talking about the working system as a whole, not just the LLM. The model is an important component, but it isn’t the entire agent.
Stephen:
Got it.
Monty:
Let’s pause there. We started with “the AI organises my photographs” and unpacked who does what: the model chooses actions, the tools perform them, and the harness manages the process. None of those parts alone is the whole agent.
What we’ve unpacked in this chapter
When we say an AI agent organises a folder of photographs, we’re describing the work of a whole system. The model can choose what to request next, but it doesn’t open folders or copy files by itself. The harness connects its requests to tools and returns their results, giving the model information it can use to continue.
The key points:
The model selects actions and interprets returned information. The tools perform operations, and the harness manages the exchanges and applies whatever controls have been implemented.
A tool call is a request, not an action already completed. The request can be refused, fail, or return incomplete information.
Tool results can shape the next request. That repeated exchange is the agent loop, which can also pause or stop before the task is finished.
The harness supplies descriptions of available tools. Being shown a tool doesn’t guarantee permission to use it on every file.
Writing a program, running it, and checking its results are separate steps. Neither generated code nor a confident reply proves that the photographs were organised correctly.
Next, we’ll look more closely at how a model’s words become requests that software can act on.
<< Previous Post: 1. What makes an AI agent different from a chatbot?
>> Next Post: Coming soon…
EuroPython
Humans of EuroPython: Emma Cooke
This week in our Humans of EuroPython series, we’d love to shine a light on Emma Cooke, a member of the Accessibility Team, and the ways she helped shape the EuroPython 2026 conference.
Emma’s unique perspective took our approach to accessibility to a whole new level, helping us progress towards our goal of making EuroPython a place for everyone. We’re extremely grateful for that.

EP: If you had to sum up your volunteering experience in just three words, what would they be?
Energising. Welcoming. Fulfilling.
EP: Before you volunteered, had you already been to EuroPython as an attendee, or did volunteering mark your very first encounter with it?
I’d been as an attendee in Prague 2024, then as a ‘pocket of sun’ volunteer in 2025, and 2026 in Kraków was my first year volunteering on the organising committee.
EP: What did your main volunteer role involve, and what would a typical day look like for you?
As part of the accessibility team, I wanted to focus on making the conference as inclusive and as accessible as possible. I looked after things like checking the venue was physically accessible for those with mobility issues, having quiet rooms and stim toys available to help neurodivergent attendees, responding to individual requests for assistance, childcare, dietary requirements, and just generally ensuring that folks at the conference could get the support they needed.
EP: What are you most proud of achieving as part of your volunteer work?
I am proud of introducing the Hidden Disabilities Sunflower scheme to EuroPython this year. The Sunflower is a tool that helps people with non-visible disabilities signal that they may need a little extra time or patience, without having to explain themselves. As a sunflower wearer myself, it is nice to see the sunflower supported in more places, and to raise awareness of the initiative.
EP: Has volunteering changed how you experience conferences generally, even ones you&aposre just attending now?
Volunteering has massively changed my perspective! I have so much appreciation for the work that goes on behind the scenes. This is what got me interested in volunteering in the first place - the first time I attended EuroPython I was just in awe of the dedication of the volunteer team, and I wanted to know more about how it all came together.
EP: If you were encouraging someone to join the EuroPython organising team, what would you say are the top three reasons to do it?
The people. This community draws in people who genuinely want to make things better for everyone, and being part of the organising team gives you a chance to work with those people.
The impact you can have. The conference simply doesn’t happen without volunteers.
You&aposll gain a perspective you can&apost get as an attendee.
EP: Thank you so much for your contribution, Emma!
Sign up for EuroPython Blog
The official blog of everything & anything EuroPython! EuroPython 2026 13-19 July, Kraków
No spam. Unsubscribe anytime.
Talk Python Blog
Introducing Talk Python Membership
For years, I’ve wanted to offer something premium for podcast listeners. The obvious option was an ad-free feed. That never sat right with me. No ads is not much of a bonus. So over the last few months, I’ve been building something that goes beyond the podcast and beyond the courses. Today I’m thrilled to announce Talk Python Membership.
In short, membership gets you community, live tech meetups, episode previews, and yes an ad-free feed.
Talk Python to Me
#566: State of the art in Python 2026
Which of your Python tools would you still pick if you started fresh today? For a long time, the answer was easy. The stack felt settled. AI has reopened almost every one of those choices. Some tools got faster. Others got replaced outright. On this episode, a panel takes stock of the state of the art in Python for 2026. Calvin Hendryx-Parker is CTO and co-founder of Six Feet Up. Pat Decker works in DevOps at Hibu. Reuven Lerner has trained Python developers for 15 years. Sydney Runkle does product management for open source at LangChain. One of them barely opens an editor anymore. Listen in to hear the surprising replacement.<br/> <br/> <strong>Episode sponsors</strong><br/> <br/> <a href='https://talkpython.fm/training'>Talk Python Courses</a><br> <a href='https://talkpython.fm/devopsbook'>Python in Production</a><br/> <br/> <h2 class="links-heading mb-4">Links from the show</h2> <div><strong>Guests</strong><br/> <strong>Pat Decker</strong><br/> <strong>Reuven Lerner</strong>: <a href="https://github.com/reuven?featured_on=talkpython" target="_blank" >github.com</a><br/> <strong>Calvin Hendryx-Parker</strong>: <a href="https://www.linkedin.com/in/calvinhp?featured_on=talkpython" target="_blank" >linkedin.com</a><br/> <strong>Sydney Runkle</strong>: <a href="https://github.com/sydney-runkle?featured_on=talkpython" target="_blank" >github.com</a><br/> <br/> <strong>Talk Python Membership</strong>: <a href="https://talkpython.fm/community" target="_blank" >talkpython.fm/community</a><br/> <br/> <strong>This Reddit post</strong>: <a href="https://www.reddit.com/r/Python/comments/1wkziub/state_of_the_art_in_python_2026/?featured_on=talkpython" target="_blank" >www.reddit.com</a><br/> <strong>PSF+Jetbrains survey</strong>: <a href="https://blog.jetbrains.com/pycharm/2025/08/the-state-of-python-2025/?featured_on=talkpython" target="_blank" >blog.jetbrains.com</a><br/> <strong>vscode</strong>: <a href="https://code.visualstudio.com?featured_on=talkpython" target="_blank" >code.visualstudio.com</a><br/> <strong>pycharm</strong>: <a href="https://www.jetbrains.com/pycharm/?featured_on=talkpython" target="_blank" >www.jetbrains.com</a><br/> <strong>cmux</strong>: <a href="https://cmux.com/?featured_on=talkpython" target="_blank" >cmux.com</a><br/> <strong>warp.dev</strong>: <a href="https://www.warp.dev?featured_on=talkpython" target="_blank" >www.warp.dev</a><br/> <strong>herdr</strong>: <a href="https://herdr.dev/?featured_on=talkpython" target="_blank" >herdr.dev</a><br/> <strong>Royal TS</strong>: <a href="https://apps.microsoft.com/detail/xp99wvbk3knn3s?hl=en-US&gl=US&featured_on=talkpython" target="_blank" >apps.microsoft.com</a><br/> <strong>Tailscale</strong>: <a href="https://tailscale.com/?featured_on=talkpython" target="_blank" >tailscale.com</a><br/> <strong>uv</strong>: <a href="https://docs.astral.sh/uv/?featured_on=talkpython" target="_blank" >docs.astral.sh</a><br/> <strong>FastAPI</strong>: <a href="https://fastapi.tiangolo.com/?featured_on=talkpython" target="_blank" >fastapi.tiangolo.com</a><br/> <strong>Litestar</strong>: <a href="https://litestar.dev/?featured_on=talkpython" target="_blank" >litestar.dev</a><br/> <strong>Django</strong>: <a href="https://www.djangoproject.com?featured_on=talkpython" target="_blank" >www.djangoproject.com</a><br/> <strong>httpx2</strong>: <a href="https://github.com/pydantic/httpx2?featured_on=talkpython" target="_blank" >github.com</a><br/> <strong>ty</strong>: <a href="https://github.com/astral-sh/ty?featured_on=talkpython" target="_blank" >github.com</a><br/> <strong>pyrefly</strong>: <a href="https://pyrefly.org/?featured_on=talkpython" target="_blank" >pyrefly.org</a><br/> <strong>postgres</strong>: <a href="https://www.postgresql.org/?featured_on=talkpython" target="_blank" >www.postgresql.org</a><br/> <strong>sqlite</strong>: <a href="https://www.sqlite.org/?featured_on=talkpython" target="_blank" >www.sqlite.org</a><br/> <strong>duckdb</strong>: <a href="https://duckdb.org/?featured_on=talkpython" target="_blank" >duckdb.org</a><br/> <strong>Hetzner</strong>: <a href="https://www.hetzner.com/?featured_on=talkpython" target="_blank" >www.hetzner.com</a><br/> <strong>DigitalOcean</strong>: <a href="https://www.digitalocean.com/?featured_on=talkpython" target="_blank" >www.digitalocean.com</a><br/> <strong>AWS</strong>: <a href="https://aws.amazon.com/?featured_on=talkpython" target="_blank" >aws.amazon.com</a><br/> <strong>Great Docs</strong>: <a href="https://posit-dev.github.io/great-docs/?featured_on=talkpython" target="_blank" >posit-dev.github.io</a><br/> <strong>Zensical</strong>: <a href="https://zensical.org/?featured_on=talkpython" target="_blank" >zensical.org</a><br/> <strong>Vue.js</strong>: <a href="http://Vue.js?featured_on=talkpython" target="_blank" >Vue.js</a><br/> <strong>AgentsView</strong>: <a href="https://www.agentsview.io?featured_on=talkpython" target="_blank" >www.agentsview.io</a><br/> <strong>LangChain Blog</strong>: <a href="https://www.langchain.com/blog?featured_on=talkpython" target="_blank" >www.langchain.com</a><br/> <strong>Umami Example of Great Docs</strong>: <a href="https://mkennedy.codes/docs/umami-python/?featured_on=talkpython" target="_blank" >mkennedy.codes</a><br/> <strong>Mintlify</strong>: <a href="https://www.mintlify.com/?featured_on=talkpython" target="_blank" >www.mintlify.com</a><br/> <strong>DuckLake</strong>: <a href="https://ducklake.select/2025/05/27/ducklake-01/?featured_on=talkpython" target="_blank" >ducklake.select</a><br/> <strong>Robyn Web Framework</strong>: <a href="https://robyn.tech?featured_on=talkpython" target="_blank" >robyn.tech</a><br/> <strong>Django Ninja</strong>: <a href="https://django-ninja.dev?featured_on=talkpython" target="_blank" >django-ninja.dev</a><br/> <strong>Talk Python CLI</strong>: <a href="https://talkpython.fm/ai-integration" target="_blank" >talkpython.fm</a><br/> <strong>KEdit</strong>: <a href="https://www.kedit.com?featured_on=talkpython" target="_blank" >www.kedit.com</a><br/> <strong>Kitty</strong>: <a href="https://sw.kovidgoyal.net/kitty/?featured_on=talkpython" target="_blank" >sw.kovidgoyal.net</a><br/> <br/> <strong>Watch this episode on YouTube</strong>: <a href="https://www.youtube.com/watch?v=Rzn1Gf89N2Q" target="_blank" >youtube.com</a><br/> <strong>Episode #566 deep-dive</strong>: <a href="https://talkpython.fm/episodes/show/566/state-of-the-art-in-python-2026#takeaways-anchor" target="_blank" >talkpython.fm/566</a><br/> <strong>Episode transcripts</strong>: <a href="https://talkpython.fm/episodes/transcript/566/state-of-the-art-in-python-2026" target="_blank" >talkpython.fm</a><br/> <br/> <strong>Theme Song: Developer Rap</strong><br/> <strong>🥁 Served in a Flask 🎸</strong>: <a href="https://talkpython.fm/flasksong" target="_blank" >talkpython.fm/flasksong</a><br/> <br/> <strong>---== Don't be a stranger ==---</strong><br/> <strong>YouTube</strong>: <a href="https://talkpython.fm/youtube" target="_blank" ><i class="fa-brands fa-youtube"></i> youtube.com/@talkpython</a><br/> <br/> <strong>Bluesky</strong>: <a href="https://bsky.app/profile/talkpython.fm" target="_blank" >@talkpython.fm</a><br/> <strong>Mastodon</strong>: <a href="https://fosstodon.org/web/@talkpython" target="_blank" ><i class="fa-brands fa-mastodon"></i> @talkpython@fosstodon.org</a><br/> <strong>X.com</strong>: <a href="https://x.com/talkpython" target="_blank" ><i class="fa-brands fa-twitter"></i> @talkpython</a><br/> <br/> <strong>Michael on Bluesky</strong>: <a href="https://bsky.app/profile/mkennedy.codes?featured_on=talkpython" target="_blank" >@mkennedy.codes</a><br/> <strong>Michael on Mastodon</strong>: <a href="https://fosstodon.org/web/@mkennedy" target="_blank" ><i class="fa-brands fa-mastodon"></i> @mkennedy@fosstodon.org</a><br/> <strong>Michael on X.com</strong>: <a href="https://x.com/mkennedy?featured_on=talkpython" target="_blank" ><i class="fa-brands fa-twitter"></i> @mkennedy</a><br/></div>
LernerPython blog, from Reuven Lerner
Pandas Grouper NaNs: Why outlier dates create empty groups
Got NaNs when using Grouper on a Python Pandas data frame?
groupby+Grouper chops up the datetime values, from the earliest to the latest, by the specified granularity.
If even one datetime is super early/late, you’ll get tons of NaNs.
dropna is often helpful here.
The post Pandas Grouper NaNs: Why outlier dates create empty groups appeared first on LernerPython.
October 16, 2026
LernerPython blog, from Reuven Lerner
Pandas Grouper: Group datetime columns by custom time periods
Want to change the granularity when grouping on a Python Pandas data frame’s datetime column?
Use Grouper.
For example: Here we get the mean trip_distance per 14-day period:
df.groupby(pd.Grouper(key='tpep_pickup_datetime', freq='14D'))['trip_distance'].mean()
The post Pandas Grouper: Group datetime columns by custom time periods appeared first on LernerPython.
October 15, 2026
Artem Golubin
Python's binary wheels and supply chain attacks
Python is known for fast packages despite being a slow language. The fastest Python packages are written in compiled languages such as C, C++, or Rust, with Python acting only as a glue.
Python users usually don't even notice that, because PyPI (Python's package index) provides
precompiled binaries.
They are called wheels, and when you run pip install, it prefers them over the source distribution.
For example, each release of my packages ships up to 90 different wheels.
You need a separate binary wheel for each Python version, platform, and architecture
combination. The regular distribution only contains the source code.
Without Python wheels, the distribution would be much harder. When wheels are not available, a user has to compile the code when installing a package. That requires the correct compiler and system dependencies.
Since I've spent quite a while analyzing malicious packages on PyPI, this got me thinking about wheels.[......]
Rodrigo Girão Serrão
Unpacking inside comprehensions
Learn how to use the unpacking operator inside list comprehensions.
Starting with Python 3.15, you can use the asterisk * to unpack inside list comprehensions:
nested = [[0, 1, 2], [3, 4], [], [5, 6, 7]]
flattened = [*sublist for sublist in nested]
print(flattened) # [0, 1, 2, 3, 4, 5, 6, 7]
This new unpacking feature also works with set comprehensions, dictionary comprehensions, and generator expressions. This article explains the syntax, the alternatives in Python 3.14 or earlier, and shows real world example applications.
15 days of Python 3.15
To celebrate the release of Python 3.15, over the next 15 business days I'll be writing about a new 3.15 feature every day. Explained clearly and with examples so you don't have to sift through the changelog.
Subscribe to receive this free email series:
If you want, you can also check the schedule of the upcoming emails.
Unpacking inside a comprehension
If you have a nested list of lists, you can flatten them out by unpacking inside a comprehension:
nested = [[0, 1, 2], [3, 4], [], [5, 6, 7]]
flattened = [*sublist for sublist in nested]
print(flattened) # [0, 1, 2, 3, 4, 5, 6, 7]
Before the unpacking syntax in 3.15, you'd have to write a nested list comprehension:
nested = [[0, 1, 2], [3, 4], [], [5, 6, 7]]
flattened = [
element
for sublist in nested
for element in sublist
]
print(flattened) # [0, 1, 2, 3, 4, 5, 6, 7]
The structure of the nested list comprehension mirrors the structure of the equivalent nested for loop:
nested = [[0, 1, 2], [3, 4], [], [5, 6, 7]]
for sublist in nested:...
Bob Belderbos
My bookkeeping runs on code now. AI only reads the PDFs.
I paid for a popular bookkeeping tool and it worked well. But the UI kept getting slower, and I wasn't using its reporting, so I had no real view of my numbers.
So I did what I usually do: I scoped a solution, built the smallest thing that worked and tested it.
Result: about 2K lines of Python, 45 tests, my own invoice scanner with a human in the loop, and a couple of make commands to close the month and regenerate an Excel report (synced to Drive) for my accountant.
What closing a month of bookkeeping involves
I'm an autónomo (freelancer) in Spain. Each month I issue invoices to clients, collect supplier invoices in a mix of currencies, and hand the close to my asesor (accountant).
On paper the paid tool covered all of that, and maybe a higher tier had the report I wanted. But I live in the terminal, and a command-line workflow beats a nicer dashboard. It's the same lesson I teach clients: build around how people work, not around how a vendor decided things should work.
Before writing any code, I mapped the workflow and split the steps into rules and interpretation.
Only one step needed AI
It's tempting to let AI loose on everything, but the map showed only one step where an LLM added value: extracting fields from supplier PDFs with varying layouts. I use Claude by default, but the model call is one function, so swapping in a local model is a small change.
Everything else is arithmetic (numbering, VAT, IRPF, totals, FX), which belongs in plain code with tests.
For example, the code converts currencies at the ECB rate via frankfurter.dev, and keeps the original amount and rate in the description: (USD 20.00 @ 0.88511 ECB 2026-10-01).
Most of the work in adding AI was the control layer around it. The model proposes fields. Python computes, converts, assigns ids and validates. I fix and approve. Tests and logs record what happened. See also: the control layer is the product.
The AI proposes, I append
Nothing touches the ledger until I press a (append):

Each proposed row gets the next EXP-YYYY-NNN id and the extracted fields, with VAT and gross computed by code. Append is blocked while validation errors exist:
choice = input(" [a]ppend / [e]dit / [s]kip / [q]uit: ").strip().lower()
if choice == "a":
if _errors(exp):
print(" fix the errors first (edit), or skip.")
continue
return exp
Extraction misses things, like a receipt without a printed tax ID or a description too vague for my asesor. The edit step catches those:

On approval, the PDF is renamed to EXP-YYYY-NNN_Supplier.pdf. That rename is the whole state machine, no database needed. The rename happens before the row is written. If it crashes in between, the next run proposes that PDF again under the same id, so nothing is lost or recorded twice.
I also fenced off the AI that helped me write the code: reads and edits to my issued invoices and invoice ledger are denied, and every shell command requires my approval.
Boring by design
I keep most of the system immutable (a habit Rust is reinforcing). The CSV files are the source of truth for invoices and expenses. Issued invoice PDFs are immutable (overwriting one needs --force). Excel is regenerated, never edited.
Every total in the workbook is a formula pointing back to its rows, not a pasted number, so my asesor can click any figure and see where it came from without asking me.
The month close is easy now: drop the PDFs in a folder, make scan, make report (which validates first).
When my asesor confirmed mid-build that the VAT on my expenses wasn't deductible in my case, it had to move into cost. The tests pinned the totals, so it was one commit.
There is one tradeoff: I now need to maintain this system. There's no vendor support, the FX lookup needs a network connection, and tax rules might change. For a one-person business with tech skills, that's worth it: I control the process, I dropped a subscription, and the month close is faster.

The transferable part is the order of operations:
- Map the workflow.
- Give the model the one step that needs judgment.
- Code the rules.
- Put a human at the critical decision points.
- Make the results traceable.
If a process in your business is slow, error-prone or lives in one person's head, that's what my Scope First Audit maps out.
P.S. The a/e/s/q prompt treats every field the same, so I extracted the scanner into invoice-scanner. It asks only about low-confidence fields, shows the source text and logs corrections. More on that in an upcoming post.
October 14, 2026
Rodrigo Girão Serrão
frozendict built-in
Learn how to use the new frozendict built-in to create and manipulate immutable dictionaries.
Python 3.15 introduced a new built-in data type: the frozendict. A frozendict is an immutable dictionary that can be created with the built-in of the same name:
version_info = frozendict(
{"major": 3, "minor": 15, "patch": 0}
)
The dictionary literal syntax using the curly braces {} creates a regular dictionary, so you must always use the built-in frozendict to create a new frozen dictionary.
In this article you'll learn about the characteristics of this immutable dictionary like its hashability, along with how to use this in conjunction with dict in typing contexts.
15 days of Python 3.15
To celebrate the release of Python 3.15, over the next 15 business days I'll be writing about a new 3.15 feature every day. Explained clearly and with examples so you don't have to sift through the changelog.
Subscribe to receive this free email series:
If you want, you can also check the schedule of the upcoming emails.
Creation and usage
You create immutable dictionaries with the built-in frozendict and you can access key/value pairs with the regular dictionary syntax:
version_info = frozendict({"major": 3, "minor": 15, "patch": 0})
print(version_info["major"]) # 3
You can also iterate through a frozendict's keys, values, or items:
for name, number in version_info.items():
print(name, number)
"""
major 3
minor 15
patch 0
"""
If you don't have to support Python 3.14 or earlier, you should use frozendict whenever you're creating or returning a dictionary that isn't supposed to be explicitly and clearly used as mutable.
Immutability of a frozendict
The main premise of frozendict is that it's immutable:
version_info = frozendict({"major":...
PyPy
PyPy v8.0.1 release
PyPy v8.0.1: release of python 2.7, 3.11, and 3.12 beta released 2026-09-14
The PyPy team is proud to release version 8.0.1 of PyPy after the previous
release on Sept 25, 2026. Some problems with buffer memory leaks were fixed,
and problems around the new strategy to tie RPython objects to non-managed C
PyObject objects were also fixed. The stdlib for 3.11 and 3.12 was updated,
as was the vendored libexpat (to 2.8.5). Internally, we now use pytest3.10
rather than 2.9.2.
The release includes three different interpreters:
PyPy2.7, supporting the syntax and the features of Python 2.7 including the stdlib for CPython 2.7.18+ (the
+is for backported security updates)PyPy3.11, supporting the syntax and the features of Python 3.11, including the stdlib for CPython 3.11.17. Barring security issues, this will be the last release to support 3.11.
PyPy3.12, supporting the syntax and features of Python 3.12, including the stdlib for CPython 3.12.15.
The interpreters are based on much the same codebase, thus the triple release.
We recommend updating. You can find links to download the releases here:
We would like to thank our donors for the continued support of the PyPy project. If PyPy is not quite good enough for your needs, we are available for direct consulting work. If PyPy is helping you out, we would love to hear about it and encourage submissions to our blog via a pull request to https://github.com/pypy/pypy.org
We would also like to thank our contributors and encourage new people to join the project. PyPy has many layers and we need help with all of them: bug fixes, PyPy and RPython documentation improvements, or general help with making RPython's JIT even better.
If you are a python library maintainer and use C-extensions, please consider making a CFFI version of your library that would be performant on PyPy. Failing that, PyPy will soon support the cp312-abi3 tag for limited ABI wheels. In any case, cibuildwheel supports building wheels for PyPy.
What is PyPy?
PyPy is a Python interpreter, a drop-in replacement for CPython. It's fast (PyPy and CPython performance comparison) due to its integrated tracing JIT compiler.
We also welcome developers of other dynamic languages to see what RPython can do for them.
We provide binary builds for:
x86 machines on most common operating systems (Linux 32/64 bits, Mac OS 64 bits, Windows 64 bits)
64-bit ARM machines running Linux (
aarch64) and macos (macos_arm64).
PyPy supports Windows 32-bit, Linux PPC64 big- and little-endian, Linux ARM 32 bit, RISC-V RV64IMAFD Linux, and s390x Linux but does not release binaries. Please reach out to us if you wish to sponsor binary releases for those platforms. Downstream packagers provide binary builds for debian, Fedora, conda, OpenBSD, FreeBSD, Gentoo, and more.
What else is new?
For more information about the release, see the full changelog.
Please update, and continue to help us make pypy better.
Cheers, The PyPy Team
Python Bytes
#500 Episode 500!
<strong>Topics covered in this episode:</strong><br> <ul> <li><strong>A quick chat with Brian looking back</strong></li> <li><strong>Extras</strong></li> <li><strong>Joke</strong></li> </ul><a href='https://www.youtube.com/watch?v=dwFj3L7kR74' style='font-weight: bold;' data-umami-event="Livestream-Past" data-umami-event-episode="500">Watch on YouTube</a><br> <p><strong>About the show</strong></p> <p><strong>Brian #1: A quick chat with Brian looking back</strong></p> <p><strong>Nick #2</strong>: <a href="https://www.youtube.com/watch?v=dW-ktsvhQQI">Jev and the Case for Boring ML</a></p> <p>Vincent Warmerdam from marimo benchmarked <a href="https://typesafe.ai/blog/introducing-system-one-models-and-jev?featured_on=pythonbytes">Jev, a new "System 1"</a> decision model from TypeSafe AI. <a href="https://www.youtube.com/watch?v=dW-ktsvhQQI">In his video</a> It handles classification without the latency or token cost of generative LLMs, which raises a practical question for data teams: where does this fit alongside traditional machine learning?</p> <ul> <li>He tested Jev against classical machine learning models to see if its speed and accuracy hold up on real-world datasets. </li> <li>General LLMs work well for reasoning, but generating full token streams just to assign a category adds unnecessary latency and cloud cost. </li> <li>Jev avoids generation entirely by evaluating the input and returning only the decision, which makes it much faster and cheaper than standard chat APIs. </li> <li>It consistently outperformed eight-shot sentence transformer setups like SetFit out of the box, especially on datasets with dozens of target categories. </li> <li>Traditional models like logistic regression still matter because they run in single-digit milliseconds on basic CPUs, cost nothing per inference, and keep private data within your own network. </li> <li>You cannot fine-tune Jev directly, so standard models remain the better choice when your data contains internal company acronyms, product names, or domain slang. </li> <li>The most practical approach is using Jev to bootstrap initial labels or audit bad training data, then training a small model you can own and run locally.</li> </ul> <p><strong>Brett #3</strong>: <a href="https://pyfound.blogspot.com/2026/09/announcing-2026-python-packaging.html?featured_on=pythonbytes">Announcing the 2026 Python Packaging Council Election Results!</a></p> <p>The inaugural Python packaging council (PPC) has been elected and started meeting!</p> <ul> <li>Introduced via <a href="https://peps.python.org/pep-0772/?featured_on=pythonbytes">PEP 772</a> </li> <li>5 members elected <ul> <li>Top 2 vote-getters get two-year terms </li> <li>Other 3 get one-year terms </li> <li>All members will get two-year terms going forward, staggering the elections </li> </ul></li> <li>There have been 4 meetings far as well as a meeting with the SC <ul> <li><a href="https://discuss.python.org/t/notes-from-python-packaging-councils-first-meeting/109203?featured_on=pythonbytes">Initial meeting notes</a> </li> </ul></li> <li>As the inaugural packaging council still working out basic logistics like how to take notes </li> <li>Also working out what the packaging council should do as PEP 772 explicitly says <a href="http://packaging.python.org?featured_on=pythonbytes">packaging.python.org</a> falls under the PPC, but otherwise tries not to dictate what the PPC is in charge of</li> </ul> <p><strong>Calvin #4</strong>: <a href="https://lemire.me/blog/2026/10/12/parsing-json-at-tens-of-gigabytes-per-second-in-python/?featured_on=pythonbytes">Parsing JSON at tens of gigabytes per second in Python</a></p> <ul> <li>Daniel Lemire (Oct 12) benchmarks fastsimdjson 0.6.0, a Python binding for simdjson on PyPI, repo at simdjson/fastpysimdjson. Its map_many function runs a callback per JSON line across N threads. </li> <li>Workload: Eight hours of GH Archive data, 7.6 GB and 2.2M events. The task counts event types and looks up the target repo's language for each opened PR, and it only touches a few fields per event. </li> <li>Lazy trick: With lazy=True, the whole event is parsed but Python objects are created only for the fields you access. The idea comes from Tyler Kennedy's pysimdjson. </li> <li>Single core: stdlib json gets 0.18 GB/s (about 42 s for the whole set), orjson 0.42, msgspec 1.29, fastsimdjson lazy 2.13 (3.6 s). </li> <li>64 cores: stdlib json gets 3.17 GB/s, msgspec 24.6, fastsimdjson eager 29.8, and fastsimdjson lazy 65.7. That's the full 7.6 GB in about 0.12 s, roughly 31x its own single-core speed. </li> <li>Requires free-threading: It ran on Python 3.14.6 free-threaded (3.14t) on a 64-core dual-Xeon box. </li> <li>Grain of salt: <ul> <li>It's one machine and one dataset. </li> <li>It's an embarrassingly parallel job with per-thread counters. </li> <li>The lazy win only shows up when you read a few fields from big objects. </li> <li>msgspec was handed a schema declaring only the needed fields. </li> <li>The post itself doesn't say whether this generalizes.</li> </ul></li> </ul> <p><strong>Michael #5</strong>: <a href="https://blog.miguelgrinberg.com/post/how-fast-is-python-3-15?featured_on=pythonbytes">Grinberg's annual benchmark says 3.15 is flat, but the JIT finally pays off</a></p> <p>Miguel Grinberg ran his annual release-eve benchmark, comparing Python 3.15.0rc3 against every interpreter back to 3.10, plus PyPy, Node.js, and Rust, using his usual two CPU-bound programs: recursive Fibonacci and bubble sort. The standard 3.15 interpreter lands within noise of 3.14 on his workloads, while the experimental JIT build is roughly 20 to 28 percent faster and finally beats the standard interpreter, which has not happened before. The free-threaded build holds up well in the 4-thread tests. He is candid that the workloads are just Fibonacci and bubble sort, and the official release lands days after his measurements.</p> <ul> <li>It is an independent, workload-based counterpoint to the official 7-8 percent JIT geometric-mean claim, from a series with real trend data back to 3.10. </li> <li>He did the same exercise for 3.12, 3.13, and 3.14, so there is a multi-year trend line to draw on. </li> <li>Methodology is two hand-picked CPU-bound programs, recursive Fibonacci plus bubble sort, not a pyperformance-style suite, which cuts both ways. </li> <li>PyPy 3.12 still runs circles around every CPython in the single-threaded tests, and Rust is about 77x faster on Fibonacci. </li> <li>The JIT gain is real in these numbers, but the JIT stays experimental, so he will not use it in production yet. </li> <li>Free-threaded fibo at 4 threads ran about 4.5x faster than the standard interpreter with the GIL. </li> <li>His verdict: not worth rushing an upgrade for speed alone, though lazy imports may tempt production projects.</li> </ul> <p><strong>Carol #6</strong>: Transparent and trusted AI Our Way - <a href="https://satyrn.ai/?featured_on=pythonbytes">Satyrn AI</a></p> <p>Paul Everitt, Georgi Ker and I were discussing Python, its community and AI back in March at the JetBrains PyTV event. Python is the foundational language for data science and AI tools. We wanted something that reflects the Python community's strengths: language and learning.</p> <ul> <li>Mission: <a href="https://satyrn.ai/mission/?featured_on=pythonbytes">https://satyrn.ai/mission/</a> </li> <li>Python 3.15 trained model is on Hugging Face </li> <li>After Paul's Europython keynote, he asked for folks to join the community (think of it as a collective of individuals putting their talents together </li> <li>We used some of the organizational tips from Jupyter and JupyterHub's early days on scaling community and emphasizing leveling up individuals </li> <li>Paul will be keynoting in Africa and sharing progress made by a team of dedicated volunteers </li> <li>Putting AI tools and knowledge in the hands of Pythonistas makes good sense and over time will benefit people (A theme that I have spoken about since <a href="https://speakerdeck.com/willingc/pycon-2023-keynote?featured_on=pythonbytes">PyCon 2023</a> and most recently <a href="https://speakerdeck.com/willingc/press-start?featured_on=pythonbytes">PyCon JP 2026</a></li> </ul> <p><strong>Extras</strong></p> <p>Michael: </p> <ul> <li><a href="https://getartcraft.com/apps?featured_on=pythonbytes">Adobe Creative Suite was mostly cloned in Rust</a> <ul> <li>ArtCraft released seven free, open-source desktop apps that mirror the Adobe lineup: PhotoCraft for Photoshop, VectorCraft, FilmCraft, LightCraft, PrintCraft, EffectCraft, and DesignCraft. Everything is written from scratch in Rust with WebAssembly builds for the browser, and developer Brandon Thomas says the apps are AI-built clean-room reimplementations, a claim Ars Technica covered alongside the predictable skepticism. <ul> <li>The clean-room story matters: reimplementing Adobe from public specs and behavior with AI is legally defensible, and building the apps for agents to drive, not just humans, is a genuinely different design bet. </li> <li>PhotoCraft already shows real depth: layers, masks, adjustment layers, layer styles, and real PSD support, with over 1,700 tests and dual CPU and GPU compositors tested against each other. </li> <li>The agent story: an MCP server and JSON control protocol let an agent headless-edit documents or drive the live app, useful for bulk edits and first drafts. </li> <li>Tradeoffs are real: reviewers call most of the apps super early alpha, and trade dress claims from Adobe remain a live legal risk even for clean-room code. </li> <li>Sustainability matters: the founder says token sales from their ArtCraft AI model fund the work so it does not become an orphaned small-team project. </li> <li>What happens when open-source alternatives that once took years of community work can be built by a model in weeks? </li> </ul></li> <li>Reminds me of <a href="https://pythonbytes.fm/episodes/show/473/a-clean-room-rewrite">the chardet story</a> we covered awhile ago. The reason I put this topic here is I think it encapsulates a lot of what's happening in the industry for devs now. Building software has changed, creating is easy - sustaining is hard, owning an idea is more fleeting than before, Rust obviously, MCP and CLIs. It's just an interesting bridge from the first 500 episodes and the next 500 likely.</li> </ul></li> </ul> <p>Calvin:</p> <ul> <li><a href="https://www.llmfit.org/?featured_on=pythonbytes">https://www.llmfit.org/)</a> see what models can run on your hardware! </li> <li>uv 0.13.0 makes Python 3.15 the default!</li> </ul> <p>Nick:</p> <ul> <li>Black Python Devs: <ul> <li><a href="https://blackpythondevs.org/student-ambassador-program/100-campus-tour-campaign/?featured_on=pythonbytes">https://blackpythondevs.org/student-ambassador-program/100-campus-tour-campaign/</a></li> </ul></li> </ul> <p>Brett:</p> <ul> <li><a href="https://blog.python.org/2026/10/python-3150-final-is-here/?featured_on=pythonbytes">Python 3.15.0 final is out</a>!</li> </ul> <p><strong>Joke</strong>:</p> <ul> <li>Exceptional by Derrick Chambers <ul> <li><strong>Interviewer</strong>: It says here on your résumé that you’re an “exceptional Python developer.” Can you prove it? </li> <li><strong>Me</strong>: Sure. Run any of my code. It raises exceptions a lot of the time.</li> </ul></li> </ul>
LernerPython blog, from Reuven Lerner
Pandas groupby on datetime parts with the dt accessor
If your Python Pandas data frame has a datetime column, you can group on a portion of that via the “dt” accessor.
For example, let’s get mean trip_distance for each hour of the day:
df.groupby(df['tpep_pickup_datetime'].dt.hour)['trip_distance'].mean()
The post Pandas groupby on datetime parts with the dt accessor appeared first on LernerPython.


