skip to navigation
skip to content

Planet Python

Last update: August 19, 2026 07:48 PM UTC

August 19, 2026


Talk Python to Me

#559: 12 Things You Should (and Shouldn't) Do in AWS

Your site is down. It's 3am. Is it a bug, a bill, or a breach? You can't tell yet, and everyone is watching you find out. Matt Lea has spent fifteen years being the person companies call when an outage is costing them real money per hour, and his whole argument is that everything you'd want in that moment gets decided months earlier, on ordinary afternoons, when someone chose the convenient thing. We walk his top twelve dos and don'ts in AWS - infrastructure as code, IAM roles instead of access keys, private subnets, no wildcards, no public buckets - and I push on which of them actually matter if you're one person on a small VPS. Then we get to Cloud War Games, where Matt breaks things on purpose so your team's first real incident isn't their first incident. Let's get into it.<br/> <br/> <strong>Episode sponsors</strong><br/> <br/> <a href='https://talkpython.fm/sentry'>Sentry Error Monitoring, Code talkpython26</a><br> <a href='https://talkpython.fm/course-certifications'>Talk Python Courses</a><br> <a href='https://talkpython.fm/training'>Talk Python Courses</a><br/> <br/> <h2 class="links-heading mb-4">Links from the show</h2> <div><strong>Guest</strong><br/> <strong>Matt Lea</strong>: <a href="https://www.linkedin.com/in/schematical/?featured_on=talkpython" target="_blank" >linkedin.com</a><br/> <br/> <strong>Talk Python Certificates</strong>: <a href="https://training.talkpython.fm/certificates" target="_blank" >training.talkpython.fm/certificates</a><br/> <br/> <strong>Schematical</strong>: <a href="https://schematical.com?featured_on=talkpython" target="_blank" >schematical.com</a><br/> <strong>CloudWarGames.com</strong>: <a href="https://cloudwargames.com?featured_on=talkpython" target="_blank" >cloudwargames.com</a><br/> <strong>Zero to Hero on AWS Security</strong>: <a href="https://www.oreilly.com/videos/zero-to-hero/0642572107789/?featured_on=talkpython" target="_blank" >www.oreilly.com</a><br/> <strong>Repo</strong>: <a href="https://github.com/schematical/sc-terraform/tree/main/_course?featured_on=talkpython" target="_blank" >github.com</a><br/> <strong>Custom Wheel Offset</strong>: <a href="https://customwheeloffset.com?featured_on=talkpython" target="_blank" >customwheeloffset.com</a><br/> <strong>2012 TechCrunch Disrupt Hackathon</strong>: <a href="http://techcrunch.com/2012/09/09/meet-the-disrupt-sf-2012-hackathon-winners-livebolt-takes-grand-prize-auctopus-and-heatdata-are-runners-up/?featured_on=talkpython" target="_blank" >techcrunch.com</a><br/> <strong>tech comics</strong>: <a href="https://schematical.com/comics?featured_on=talkpython" target="_blank" >schematical.com</a><br/> <strong>shhgit</strong>: <a href="https://github.com/eth0izzle/shhgit?featured_on=talkpython" target="_blank" >github.com</a><br/> <strong>Zero Trust in 200ms: Implementing Identity-Per-Transaction</strong>: <a href="https://us.pycon.org/2026/schedule/presentation/140/?featured_on=talkpython" target="_blank" >us.pycon.org</a><br/> <strong>Coolify</strong>: <a href="https://coolify.io/?featured_on=talkpython" target="_blank" >coolify.io</a><br/> <strong>returned to full GA Nov 2025</strong>: <a href="https://aws.amazon.com/blogs/devops/aws-codecommit-returns-to-general-availability?featured_on=talkpython" target="_blank" >aws.amazon.com</a><br/> <strong>Signed URLs/cookies</strong>: <a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-overview.html?featured_on=talkpython" target="_blank" >docs.aws.amazon.com</a><br/> <strong>Cloudflare</strong>: <a href="https://www.cloudflare.com/?featured_on=talkpython" target="_blank" >www.cloudflare.com</a><br/> <strong>Bunny Shield</strong>: <a href="https://bunny.net/shield/?featured_on=talkpython" target="_blank" >bunny.net</a><br/> <strong>Cloud War Games One</strong>: <a href="https://www.youtube.com/watch?v=91z1pn7fEjM" target="_blank" >www.youtube.com</a><br/> <strong>Cloud War Games Two</strong>: <a href="https://www.youtube.com/watch?v=xHXjiTi4nCA" target="_blank" >www.youtube.com</a><br/> <strong>LinkedIn</strong>: <a href="https://linkedin.com/in/schematical?featured_on=talkpython" target="_blank" >linkedin.com</a><br/> <strong>YouTube</strong>: <a href="https://youtube.com/schematical" target="_blank" >youtube.com</a><br/> <strong>KnocKnoc</strong>: <a href="https://knocknoc.io?featured_on=talkpython" target="_blank" >knocknoc.io</a><br/> <br/> <strong>Watch this episode on YouTube</strong>: <a href="https://www.youtube.com/watch?v=NTedSITJyRo" target="_blank" >youtube.com</a><br/> <strong>Episode #559 deep-dive</strong>: <a href="https://talkpython.fm/episodes/show/559/12-things-you-should-and-shouldnt-do-in-aws#takeaways-anchor" target="_blank" >talkpython.fm/559</a><br/> <strong>Episode transcripts</strong>: <a href="https://talkpython.fm/episodes/transcript/559/12-things-you-should-and-shouldnt-do-in-aws" 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>

August 19, 2026 06:57 PM UTC


Anarcat

The people vs the AI overlords

Previously in this series: The Four Horsemen of the LLM Apocalypse.

In a post to oss-security, my (Debian) co-developer Russ Allbery stated that "open source software [OSS] is coming face to face with a motivation crisis that has been building for a long time". His point is essentially that large language models (LLMs1) are making the existing OSS community crisis worse. For him, it's the flood of code reviews, but he argues that varies according to people's desires, for others it's security issues and so on.

I think Russ is right, but I would argue there's something much bigger than our open communities going on here, and it's about the entire field of computing. This pressure is on all of us, regardless of whether we work on open source software or not.

How people use models

People using LLMs in their workflow have radically changed how programming works, even for people who claim to avoid vibe-coding. And I'm sorry to single out one poor maintainer here: it's not you, Brian, you're just one example among many. But this is typical use of those models nowadays:

Once it’s done, I’ll use /code-review and let Claude spawn sub-agents to do a full review of the new code. This usually finds some problems, even problems that the “main” Claude instance didn’t find during its validation. I usually keep running /code-review again and again after finding and fixing issues, until there aren’t any left.

Think about what that means for a minute. This is automation built to fire up dozens of agents crunching at a problem for minutes if not hours of GPU compute time, in parallel. This is essentially a couple of shelves in a datacenter rack, totally maxed out on power and cooling, abstracted behind a cute little /code-review command.

The author, here, is rightly concerned that "Anthropic could pull the rug out and require API pricing", which is perhaps a code word for "charging something closer to actual costs". Brian also pays lip service to environmental and societal costs but those are largely abstracted away, so let's keep that conversation aside here as well, as we have discussed it before anyways.

But clearly, this way of working has an (externalized) cost, to say the least.

Paying for non-free tools

For decades my work has been focused on free and open source software. I've long stopped using proprietary operating systems like Windows or Mac, and even before that switch, I was mostly using free software on those platforms, partly out of principle, but also because I was too poor. So the tools of my trade are free, and I build free tools with them.

It feels like we're going backwards: when I was in school, a millennia ago, my classmates didn't have access to a compiler and were wondering how they would scrape the money to buy a compiler like Borland's or Microsoft's. I had a compiler built into my operating system (FreeBSD at the time), so that wasn't a problem for me. For them, it was a significant expense, but at least those expenses (or more shady sourcing of programs) were a one-shot deal.

Fast forward 30 years, and software is rented: you pay monthly for Adobe's Photoshop and Microsoft's office suite just like you pay for Netflix, Disney+ or Spotify2. And now you need to add dozens (if not hundreds of dollars) of monthly credits to access LLMs on top of that.

So, now we have to pay to get anything done? This is peak enshitification of our job: first they steal our work to train their models, and then they sell it back to us at a profit.

Attacking the engineers

AI is coming for our jobs, as engineers, if not everyone, according to the narrative. For a while now, our job market has deteriorated: less jobs, for less pay. Lots of skilled engineers looking for work and finding crap jobs then still looking while working.

This is not by accident.3 We engineers have a lot of power, it is not organized, but that's just a couple of unions away (easy!). Tech overlords know this, so they are attacking our profession, directly, by forcing us to train and use models that they can control.

Even in environments where programmers are not forced to use LLMs, the mere pressure of other people's LLM-generated work is huge. One can be forced to review LLM outputs, or just peer pressured you into producing more.

We're now supposed to accelerate delivery, because models can presumably do things so much better and faster. With supply chain security becoming such a large vector that we now have worms crawling around developers accounts on NPM, increasing the delivery cadence seems like a really bad idea.4

The LLM hype is part of the larger wave of cyberwar against workers, against water, against the Earth, against all the people. This is not a matter of individually "adapting to the reality" or personal choice, but a political, social, hard problem we need to address collectively.

Previously in this series: The Four Horsemen of the LLM Apocalypse.


  1. I again prefer the term LLM to "AI" because models do not possess intelligence. I did use it in the title because click baiting is apparently important, but I stopped short of calling this one "Rage Against the Machines" because that would be the title of every blog post I have ever made.
  2. Yes, I know that Visual Studio is kind of free now, but I wouldn't be surprised if they turn that into a rental as well, because why not.
  3. Beyond sabotaging the job market, Sam Altman event wants to sell "intelligence as a utility" something that is just a really bad idea but especially shows how megalomaniac those people are.
  4. This brings back memories of another era, walking us back decades in terms of computer security.

August 19, 2026 02:14 PM UTC


PyCharm

What’s Fixed and Improved in PyCharm 2026.2

Across the PyCharm 2026.2 release line, we shipped 263 fixes and improvements. Many improve Python code insight directly, with more precise type inference, fewer false positives, smarter completion and imports, and more reliable refactoring. Here are some of the smaller changes you’re likely to notice in everyday Python development.


SQLAlchemy 2.0 support

SQLAlchemy has been a long-standing source of false positives – enough that several duplicate tickets have accumulated over the years. This release resolves a batch of them for the 2.0 style.

String forward-references inside Mapped[...] resolve correctly:

posts: Mapped[list["Post"]] = relationship(back_populates="author")

# "Post" now resolves to the model class

PyCharm also correctly infers the mapped type returned by Session.get(), instead of treating the result as the model class itself:

report = session.get(Report, report_id)

reveal_type(report)  # was: type[Report] | None   now: Report | None

Modern hybrid_property setters written as @name.inplace.setter are recognized, so assigning to the property no longer produces a warning. Model class attributes defined via mixins are picked up again, too, clearing the old unexpected argument reports on model constructors.

(PY-78816, PY-65142, PY-59732, PY-51906, PY-28762)


Code insight and type inference

Control-flow narrowing and “unreachable code”

Several false This code is unreachable reports and instances of lost narrowing across loops have been fixed. The common issue: flow analysis either gave up or over-eagerly narrowed to Never in branches it should have kept alive.

isinstance on a numeric union no longer kills the else branch:

def foo(y: int | float) -> None:

    if isinstance(y, float):

        pass

    else:

        print(y)  # was flagged unreachable, y inferred as Never

Narrowing also survives a while loop, so re-narrowing an optional attribute inside the loop body no longer reports a bogus has no attribute error.

(PY-83206, PY-83354, PY-88265)

Strings inside type annotations

A string used as metadata inside Annotated[...] – a Pydantic discriminator field name, for instance – is no longer parsed as a forward reference and flagged as unresolved.

(PY-48749, PY-82245)

Iterable unpacking and star expressions

PyCharm’s analysis of tuple and star unpacking could lose type information and fall back to Any. Unpacking a starred value into a tuple lost its element types, *-expansion collapsed to Any, and several genuine errors went unreported. Starred expressions preserve their element types:

def a() -> tuple[int, int]:

    return 2, 3

def b() -> tuple[int, int, int]:

    return (1, *a())  # no more bogus "Expected tuple[int, int, int]"

(PY-12592, PY-27205, PY-43585, PY-90219)

Augmented assignment

A cluster of false positives came from augmented assignments being misanalyzed. A simple /= on an int produced the wrong type:

foo = 5

foo /= 2

reveal_type(foo)  # was: int   now: float | int

(PY-80622)

Self and constructor return types

Self binds correctly through classmethod parameters typed as type[Self]:

class A:

    @classmethod

    def bar(cls, y: type[Self]) -> Self: ...

x = A.bar(A)      # was a spurious "Expected type[A], got type[A]"

reveal_type(x)    # was: Any   now: A

Construction also respects __new__, __init__, and metaclass __call__. When __new__ returns something other than an instance, that’s the constructed type – even when an __init__ is present. The same fix covers explicitly parameterized calls like MyClass[int]() and __new__ assigned as a class attribute.

(PY-89296, PY-77611, PY-88644, PY-89571)

Enum members: Literal types for .value and .name

Reading an enum member’s .value or .name yields a precise Literal instead of a widened str or int, so assignments to Literal[...] target type-check. This matches mypy’s inference:

from enum import Enum

from typing import Literal

class E(Enum):

    a = "a"

b: Literal["a"] = E.a.value   # was: Expected 'Literal["a"]', got 'str'

n: Literal["a"] = E.a.name    # .name is a Literal too

(PY-61028, PY-79198)

Parameter types inferred from decorators

When a decorator constrains the callable it accepts, the decorated function’s parameters are inferred from that constraint instead of falling back to Any:

from typing import Callable

def d(fn: Callable[[int], str]): ...

@d

def f(a):

    reveal_type(a)   # was: Any   now: int

(PY-79204)

Also fixed


Completion and auto-import

Smarter auto-import 

Auto-import is now noticeably less noisy. Previously, if a module was already imported, PyCharm would offer to add a second, redundant import instead of qualifying through the one you already had. The quick-fix – and the completion popup – prefer to reuse the existing import.

Given pkg/src.py containing MyClass, and a file that already imports the module, Alt+Enter produces this:

from pkg import src  # no longer flagged as unused

src.MyClass

instead of adding from pkg.src import MyClass. The same reuse logic applies to plain import pkg.src, and to the auto-import completion on a second Ctrl+Space.

Nested classes can be auto-imported too, which is something PyCharm didn’t previously support:

# mod.py

class Outer:

    class Inner:

        pass

# main.py – Alt+Enter on Inner now offers "Import Outer from mod"

from mod import Outer

value = Outer.Inner()

(PY-87970, PY-87971, PY-87972, PY-88009, PY-88016)

Completion for unittest.mock.patch() targets

Patching by string target previously offered no code assistance, so dotted paths had to be entered manually. The string argument to mock.patch(...) gets code completion for modules, classes, and their attributes, and it no longer suggests the invalid as keyword mid-path:

from unittest import mock

# sample.py defines: class Foo: my_attr = 42

with mock.patch("sample.Foo.my_attr", 14):

    ...

# completion now offers `sample`, `Foo`, and `my_attr`

(PY-89189, PY-89191, PY-89192)

Typed signatures when overriding built-in methods

Completing an override of a dunder or built-in method fills in the full annotated signature – and auto-imports the types it needs – instead of bare parameters:

from types import TracebackType

class A:

    def __exit__(self, exc_type: type[BaseException] | None,

                 exc_val: BaseException | None,

                 exc_tb: TracebackType | None): ...

# was: def __exit__(self, exc_type, exc_val, exc_tb):

(PY-79218)


Editor and inspections

Type inlay hints

Inferred type arguments are shown inline at the call site, so you can see what a generic resolved to without hovering over it:

class A[T]:

    def __init__(self, t: T): ...

A[int](1)     # [int] shown as an inlay hint

Type names rendered inside inlay hints – return types and solved arguments alike – are also clickable, so you can jump straight to a type’s definition from the hint.

(PY-90411, PY-90293)

f-string format-spec validation

PyCharm already validated the str.format() mini-language. Those checks apply to f-strings too, and PyCharm flags formatting a type that doesn’t implement __format__:

data = 1

f"{data:.2f}"   # ok

f"{data:.2q}"   # now flagged: unsupported format spec

class A: ...

f"{A():d}"      # now flagged: A doesn't support the 'd' format

(PY-51322, PY-89760)


Refactoring

The Rename refactoring also updates references to a module when the module itself is renamed. Previously, the renaming left importing sites pointing at the old name:

# rename provider/provider_module.py → some_module.py

from ..provider import provider_module  # this reference is updated too

(PY-53274)

The Refactor | Field action is now Attribute, and the documentation says “instance attributes” to match Python terminology (PY-85828).


Conclusion

Taken together, these changes make PyCharm’s understanding of Python more precise and predictable: fewer false positives, better type inference, smarter completion, and less time spent working around cases where the IDE gets valid code wrong.

Many of these improvements started with real-world examples reported by users. If PyCharm still misunderstands a typing pattern, framework API, or other valid Python code in your project, let us know in YouTrack – a small reproducer can help us turn that friction into the next fix.

Try PyCharm 2026.2 and let us know which improvements make the biggest difference for your workflow.

Thank you for using PyCharm!

August 19, 2026 01:54 PM UTC


Python Software Foundation

How AWS Powers PyPI and the PSF

Written by Jacob Coffee, Director of Engineering at the Python Software Foundation

Working on infrastructure at the Python Software Foundation (PSF) as the Director of Engineering is a broad job with many hats. Python turns up everywhere. It's in healthcare systems and government agencies, in research labs and classrooms, in one-person side projects and in infrastructure at companies with six-figure headcounts. Somebody is using it to analyze a genome right now and somebody else is using it to automate a spreadsheet they hate arranging by hand. That range has always been the interesting part of the PSF's work, and it's only broadened in the last couple of years as AI continues to pull people from many disciplines into writing Python.

At the PSF we support and maintain the infrastructure behind PyPI, Python.org, PyCon US, CPython, and a growing number of community services. The majority of the PSF's infrastructure runs on AWS, and nearly all of that bill is covered by credits from the AWS Open Source Credits Program.

The shape of it

PyPI takes over six billion requests a day and about thirteen billion counting file downloads. Package egress runs around 10 petabytes a day.

Almost none of that reaches AWS. Fastly serves it at the edge through their Fast Forward program at a hit ratio just under 99%, so only about one request in seventy makes it to origin. That caching is the core reason our AWS bill is not measured in the millions of dollars a year.

What does reach us is the part that is not cached, including things like user uploads, logins, and account management. That work runs on:

What changed this year

Our AWS spend is up about 69% comparing July 2026 to July 2025, and 40% across the trailing twelve months. That's new.

For context on how new: last October, Ee Durbin, our former Director of Infrastructure, noted that PSF's AWS credit usage had grown 25% over eight years while daily requests went from millions to billions. Eight years of holding that line. 2026 is the first year it broke.

Some of it is simply more of everything. Compute, search, and load balancing all climbed from 30% to 80%, which is the profile of more people and machines using the service. There are certainly more of both. A lot more Python being written, a lot more agents installing packages on someone's behalf, and a lot more CI runs from all the projects those people and agents are creating. Package repositories were designed around the assumption that a human decides to run pip install some number of times a day. That assumption is well past its expiration.

What we do about it

At the PSF, we take the credits seriously, which means spending real engineering time on utilizing what we have in an optimal way so our usage doesn't increase. Being frugal with the credits entrusted to us is vital.

When a widely used GitHub Action makes requests it doesn't need to make, fixing the Action beats anything we can do on our side. As an example, the PSF pushed for exactly that in astral-sh/setup-uv, which is now live and will shave off usage as people upgrade.

On the PSF side, the EKS migration is the big one. Once we're there, autoscaling lets capacity track demand instead of sitting provisioned for peak at 3 AM (San Francisco time) on a Sunday.

There's just one little (read: major) constraint: Ee left the PSF earlier this year, and I'm currently the only person here working full time on infrastructure. Every item on that list is real and every one is slower than it should be. The PSF is hiring, and that hiring is possible in part because we aren't spending the same money on servers.

That said, hiring has a ceiling set by funding, and PyPI should be considered a supply chain risk until the PSF is able to hire more engineering staff. There is a very large amount of the software industry that sits on top of our services. Feature development, a rising volume of security reports, and day to day maintenance are currently being carried by one and a half full-time employees and one full-time employee on PyPI support requests. If your company installs from PyPI, ask internally about what your organization would be willing to fund. That question can be worth much more coming from inside a company than from the PSF. If you are interested in securing a service agreement with PyPI, please fill out our survey.

What the funding actually bought

AWS has backed PyPI two different ways, and both are worth outlining.

A very exciting investment was made in 2023, when AWS became PyPI's inaugural Security Sponsor, putting $144,000 into creating the PyPI Safety & Security Engineer role. That role is now funded by Alpha-Omega, and it's the reason malware comes down off PyPI in hours instead of whenever a volunteer has a spare moment. This investment also followed a pattern AWS had already established, having helped fund the rewrite, internationalization, and 2FA support for PyPI.

The credits do something quieter. They hold the infrastructure bill near zero, which means the PSF's general fund goes to employees, PyCon US, and other operational costs instead of servers. That's what pays for support staff handling the account recovery and project ownership requests that arrive every single day, and what makes it possible to ship things like Trusted Publishing, digital attestations, and organization accounts, instead of just keeping the lights on.

PyPI is free to use and will stay free to use at reasonable levels. Core publishing and installing stay free, permanently. What we are doing is building out real benefits for Organization accounts, and looking at sensible rate limits for the heaviest consumers at the top of the curve.

Which is exactly why in-kind support isn't just a line item to us. A sponsor choosing not to renew would mean an emergency migration or tens of thousands of dollars a month, either of which comes straight out of the work above. AWS has renewed every year since 2018, and the predictability of that is worth as much as the amount. The same goes for Fastly, Google Cloud, Datadog, Sentry, Depot, and PagerDuty, who carry other parts of this mountain of infrastructure.

How you can help

Four things, in order of how much they matter:

  1. Cache your installs. If your CI pulls from PyPI on every run with a cold cache, you're a meaningful part of the graph. The PSF uses Docker cache mounts and pip's cache on our own builds, and npm and apt caching too, because it's faster for us and cheaper for everyone. Free package repositories aren't a limitless resource, and the fix is usually about six lines of config.
  2. Sign up for a PyPI Organization if your company publishes to PyPI. Recurring revenue from Organizations is the most sustainable funding base we have, and Community organizations remain free. Look forward to announcements about future features for PyPI organization accounts soon.
  3. Interested in a PyPI service agreement? Enhanced options like annual bulk seat purchases, higher project size limits, and prioritized support are available. The revenue helps fund the broader PyPI ecosystem while giving your organization benefits at scale. Fill out our service agreement interest survey and we'll follow up.
  4. Ask your infrastructure vendors for multi-year commitments with open source foundations. Annual renewal cycles carry real risk for projects like ours. The five-year agreement the PSF signed with Fastly in 2024 is a sustainable model, and we'd like more of them.

The open letter the PSF co-signed last year called this a critical inflection point rather than a crisis. A year on, with our first real break in eight years of flat infrastructure costs, that still reads about right.

A big thanks to Mila Zhou and the AWS Open Source team, who have made the credits process about as painless as an annual funding renewal can be, and to everyone at AWS who's kept this program going for so long. PyPI would look very different without it.

This blog was recently edited to more accurately reflect numbers presented and work done.

August 19, 2026 11:20 AM UTC


Python GUIs

Creating PySide6 UI without .ui / Qt Designer — Build your entire GUI in pure Python code, no .ui files required

Is it possible to write a PySide6 application without using .ui files or Qt Designer? How can I create widgets and lay out a window entirely in Python code?

Yes, absolutely. While Qt Designer is a helpful visual tool for building interfaces, you never need to use it. You can create every part of your GUI — windows, buttons, labels, layouts, and more — directly in Python. Many developers prefer working this way because it keeps everything in one place and gives you full control over your interface.

In this tutorial, we'll walk through building a PySide6 interface entirely in code, starting from a basic window and gradually adding widgets and layouts.

A minimal PySide6 window

Every PySide6 application needs two things: a QApplication instance, and at least one widget to display. Here's the simplest possible window:

python
import sys
from PySide6.QtWidgets import QApplication, QWidget

app = QApplication(sys.argv)

window = QWidget()
window.setWindowTitle("My App")
window.show()

app.exec()

Run this and you'll see an empty window with the title "My App". QWidget is the base class for all UI elements in Qt, and on its own it gives you a blank window to work with.

Adding a single widget

Let's add a button to the window. You can create any widget and place it inside your window by passing the window as the widget's parent:

python
import sys
from PySide6.QtWidgets import QApplication, QPushButton, QWidget

app = QApplication(sys.argv)

window = QWidget()
window.setWindowTitle("My App")

button = QPushButton("Click me!", window)

window.show()

app.exec()

The QPushButton is created with two arguments: the text to display, and the parent widget (window). This tells Qt that the button belongs inside the window.

This works, but the button just sits at position (0, 0) in the top-left corner. To arrange multiple widgets neatly, you'll want to use a layout.

Using layouts to arrange widgets

Layouts handle the positioning and resizing of widgets for you. Qt provides several layout types, including QVBoxLayout (vertical), QHBoxLayout (horizontal), and QGridLayout (grid-based).

Here's how to stack a few widgets vertically:

python
import sys
from PySide6.QtWidgets import (
    QApplication, QLabel, QLineEdit, QPushButton,
    QVBoxLayout, QWidget,
)

app = QApplication(sys.argv)

window = QWidget()
window.setWindowTitle("My App")

layout = QVBoxLayout()
layout.addWidget(QLabel("Enter your name:"))
layout.addWidget(QLineEdit())
layout.addWidget(QPushButton("Submit"))

window.setLayout(layout)
window.show()

app.exec()

Here we create a QVBoxLayout, add three widgets to it, and then apply the layout to the window with setLayout(). The layout takes care of stacking the label, text input, and button from top to bottom, and it will resize them sensibly if the user resizes the window.

Subclassing QWidget for your own windows

As your interface grows, putting everything at the top level of your script becomes hard to manage. The standard approach is to create your own window class by subclassing QWidget (or QMainWindow for more complex apps). This lets you set up the interface inside the __init__ method:

python
import sys
from PySide6.QtWidgets import (
    QApplication, QLabel, QLineEdit, QPushButton,
    QVBoxLayout, QWidget,
)


class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("My App")

        layout = QVBoxLayout()

        self.label = QLabel("Enter your name:")
        layout.addWidget(self.label)

        self.name_input = QLineEdit()
        layout.addWidget(self.name_input)

        self.submit_button = QPushButton("Submit")
        layout.addWidget(self.submit_button)

        self.setLayout(layout)


app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()

By storing the widgets as attributes on self, you can refer to them later — for example, to read the text from the input field or to connect a button click to a function.

Connecting signals to make it interactive

Widgets in Qt communicate using signals and slots. When something happens — like a button being clicked — the widget emits a signal. You can connect that signal to any Python function to respond to the event.

Let's make the Submit button print a greeting:

python
import sys
from PySide6.QtWidgets import (
    QApplication, QLabel, QLineEdit, QPushButton,
    QVBoxLayout, QWidget,
)


class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("My App")

        layout = QVBoxLayout()

        self.label = QLabel("Enter your name:")
        layout.addWidget(self.label)

        self.name_input = QLineEdit()
        layout.addWidget(self.name_input)

        self.submit_button = QPushButton("Submit")
        self.submit_button.clicked.connect(self.greet)
        layout.addWidget(self.submit_button)

        self.greeting_label = QLabel("")
        layout.addWidget(self.greeting_label)

        self.setLayout(layout)

    def greet(self):
        name = self.name_input.text()
        self.greeting_label.setText(f"Hello, {name}!")


app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()

The line self.submit_button.clicked.connect(self.greet) tells Qt: "When this button is clicked, call the greet method." Inside greet, we read the text from the input and update the greeting label.

Using QMainWindow for menus and status bars

If your application needs a menu bar, toolbars, or a status bar, you can subclass QMainWindow instead of QWidget. QMainWindow provides a built-in structure with these elements ready to use.

With QMainWindow, you place your content inside a central widget rather than setting a layout directly on the window:

python
import sys
from PySide6.QtWidgets import (
    QApplication, QLabel, QLineEdit, QMainWindow,
    QPushButton, QVBoxLayout, QWidget,
)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("My App")

        # Create a central widget and set it
        central_widget = QWidget()
        self.setCentralWidget(central_widget)

        layout = QVBoxLayout()
        central_widget.setLayout(layout)

        self.label = QLabel("Enter your name:")
        layout.addWidget(self.label)

        self.name_input = QLineEdit()
        layout.addWidget(self.name_input)

        self.submit_button = QPushButton("Submit")
        self.submit_button.clicked.connect(self.greet)
        layout.addWidget(self.submit_button)

        self.greeting_label = QLabel("")
        layout.addWidget(self.greeting_label)

        # Add a status bar message
        self.statusBar().showMessage("Ready")

    def greet(self):
        name = self.name_input.text()
        self.greeting_label.setText(f"Hello, {name}!")
        self.statusBar().showMessage(f"Greeted {name}")


app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()

The pattern is the same — create widgets, add them to a layout, connect signals — but now you also get a status bar at the bottom of the window for free.

Nesting layouts for complex interfaces

You can combine layouts to create more sophisticated designs. For example, you might have a horizontal layout inside a vertical layout to place two buttons side by side:

python
import sys
from PySide6.QtWidgets import (
    QApplication, QHBoxLayout, QLabel, QLineEdit,
    QPushButton, QVBoxLayout, QWidget,
)


class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("My App")

        outer_layout = QVBoxLayout()

        self.label = QLabel("Enter your name:")
        outer_layout.addWidget(self.label)

        self.name_input = QLineEdit()
        outer_layout.addWidget(self.name_input)

        # A horizontal row of buttons
        button_layout = QHBoxLayout()
        self.submit_button = QPushButton("Submit")
        self.submit_button.clicked.connect(self.greet)
        button_layout.addWidget(self.submit_button)

        self.clear_button = QPushButton("Clear")
        self.clear_button.clicked.connect(self.clear)
        button_layout.addWidget(self.clear_button)

        outer_layout.addLayout(button_layout)

        self.greeting_label = QLabel("")
        outer_layout.addWidget(self.greeting_label)

        self.setLayout(outer_layout)

    def greet(self):
        name = self.name_input.text()
        self.greeting_label.setText(f"Hello, {name}!")

    def clear(self):
        self.name_input.clear()
        self.greeting_label.clear()


app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()

Notice that we use addLayout() (not addWidget()) to nest one layout inside another. You can combine QVBoxLayout, QHBoxLayout, and QGridLayout in any arrangement to build whatever interface you need.

Complete working example

Here's the final version bringing everything together — a QMainWindow with nested layouts, interactive widgets and a status bar.

python
import sys
from PySide6.QtWidgets import (
    QApplication, QHBoxLayout, QLabel, QLineEdit,
    QMainWindow, QPushButton, QVBoxLayout, QWidget,
)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Greeter App")
        self.setMinimumWidth(300)

        central_widget = QWidget()
        self.setCentralWidget(central_widget)

        outer_layout = QVBoxLayout()
        central_widget.setLayout(outer_layout)

        self.label = QLabel("Enter your name:")
        outer_layout.addWidget(self.label)

        self.name_input = QLineEdit()
        self.name_input.setPlaceholderText("Your name here...")
        outer_layout.addWidget(self.name_input)

        button_layout = QHBoxLayout()

        self.submit_button = QPushButton("Submit")
        self.submit_button.clicked.connect(self.greet)
        button_layout.addWidget(self.submit_button)

        self.clear_button = QPushButton("Clear")
        self.clear_button.clicked.connect(self.clear)
        button_layout.addWidget(self.clear_button)

        outer_layout.addLayout(button_layout)

        self.greeting_label = QLabel("")
        outer_layout.addWidget(self.greeting_label)

        self.statusBar().showMessage("Ready")

    def greet(self):
        name = self.name_input.text()
        if name:
            self.greeting_label.setText(f"Hello, {name}!")
            self.statusBar().showMessage(f"Greeted {name}")
        else:
            self.greeting_label.setText("Please enter a name.")
            self.statusBar().showMessage("No name entered")

    def clear(self):
        self.name_input.clear()
        self.greeting_label.clear()
        self.statusBar().showMessage("Cleared")


app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()

Copy, paste, and run this file. You'll get a fully functional window with a text input, two buttons, a dynamic greeting label, and a status bar — all created without Qt Designer or any .ui files.

Everything you can do in Qt Designer, you can do in code. As your applications grow, you might find that building the interface programmatically makes it easier to manage, debug, and keep in version control alongside the rest of your Python code. To explore more of the available widgets, take a look at the PySide6 widgets tutorial.

For an in-depth guide to building Python GUIs with PySide6 see my book, Create GUI Applications with Python & Qt6.

August 19, 2026 06:00 AM UTC


Armin Ronacher

What Is Reasoning

A few weeks ago a paper was shared that showed how to extract reasoning traces from closed-weight models. Together with online discussions about tricking models into leaking them, it made me investigate it more out of curiosity. Twitter seems full of half-truths and confusion about how this works, so perhaps this helps some to understand what is happening.

Hiding Traces

Reasoning traces are usually hidden from us. We have lamented this, but mostly have to accept it. Open-weight models thankfully reveal them, and from their behavior you can see that their traces can be long and confusing. This is probably a good reason to separate them from what is normally shown to users.

At minimum, UIs need to detect them. The industry has done a good job at making reasoning traces sound special and exotic, but they really are just text: the model is trained to emit its thinking into a scratchpad as part of its response, before its final answer.

GPT-OSS’s Harmony response format makes this easy to see:

<|channel|>analysis<|message|>
I need to work this out ...
<|end|><|start|>assistant<|channel|>final<|message|>
The answer is ...
<|return|>

The markers are special tokens, but the reasoning between them uses “the same text” as the final answer (just that GPT chain-of-thought text sounds really funny). When the model samples the analysis channel token, a parser routes the following text into a separate stream exposed through the Responses API. For closed models, presumably a simple model redacts and summarizes it.

Reasoning Effort

How much budget goes to reasoning? Earlier APIs exposed reasoning token budgets, making it seem like a property of the sampling process. In reality, reasoning effort is baked into the system prompt. GPT-OSS puts this into the system prompt:

Reasoning: low

That’s it. Training produces the resulting behavior, such as emitting the token sequence that switches to the analysis channel. This also explains why changing the effort invalidates the KV cache. I think closed GPT models call reasoning effort “juice,” since you can ask most models how much juice they have.

In DwarfStar for DeepSeek with max reasoning this is added to the system prompt:

Reasoning Effort: Absolute maximum with no shortcuts permitted.
You MUST be very thorough in your thinking and comprehensively decompose the
problem to resolve the root cause, rigorously stress-testing your logic against
all potential paths, edge cases, and adversarial scenarios.

Don’t Think

The destination of reasoning tokens is therefore a learned convention: the model is trained to keep scratch work out of the final channel. Trick it into thinking it is in that channel and it may leak tokens. We have even seen older models, when thinking is disabled, reason into the bash tool and echo their thoughts to /dev/null.

So in some sense the only “special” behavior for some models is not to think. That at times is done by “mechanically” removing the model’s usual ways to think. In DwarfStar, disabled thinking uses the prefill </think>, while enabled thinking uses <think>, which are the tokens that close and start thinking. GPT-OSS doesn’t prefill but lets the model decide either way on its own.

But presumably, some inference APIs prefill the opening token when reasoning is enabled, so the model never samples it itself and might prevent the sampling of the reasoning token when disabled since it can be trivially detected. This may explain why a custom think tool can trick models into putting some reasoning where it should not go — but only when native reasoning is disabled.

Fun fact: this blog post triggered safey checks

Hilariously enough I was unable to use GPT 5.6 terra for spell and grammar checking on this blog post because of safety filters. Had to switch to Kimi.

GPT-5.6-terra refusing to spell-check this blog post

August 19, 2026 12:00 AM UTC

August 18, 2026


PyCoder’s Weekly

Issue #748: Line Breaks, OpenCode, Segfaults, and More (2026-08-18)

#748 – AUGUST 18, 2026
View in Browser »

The PyCoder’s Weekly Logo


Breaking Up (Lines) Is Hard to Do

Here’s a seemingly simple question: given a chunk of multi-line text, how do you split it and return an array? Unicode makes everything harder than it might first seem.
B-LIST.ORG

Coding With OpenCode: AI-Assisted Python

Learn how to use OpenCode for AI-assisted Python coding, using a free Gemini API key to analyze and refactor code right in your terminal.
REAL PYTHON course

Quiz: Coding With OpenCode: AI-Assisted Python

REAL PYTHON

The Top Open-Source Code Reviewer on Code-Review-Bench

PR-AF places #2 of 42 overall on Martian’s Code-Review-Bench, ahead of CodeRabbit, Copilot, and Devin. Roughly 3x more valid findings than the commercial tools, at ~10x lower cost per review. Verified findings only. Apache 2.0, self-hosted, runs on any open or closed model. Star & Deploy →
AGENTFIELD sponsor

Two Lines of Python That Segfault the Interpreter

Two lines of plain Python segfaulted every CPython with lazy annotations: PEP 749 gave SET_ADD an operand that user code can rebind.
TIMOFEI IVANKOV ‱ Shared by Timofei Ivankov

Packaging Council Election Candidates for 2026!

PYTHON.ORG

Announcing the PSF Board Candidates for 2026!

PYTHON SOFTWARE FOUNDATION

Django Is Moving to an Annual Release Cycle

DJANGO SOFTWARE FOUNDATION

PEP 844: public and private Builtins (Draft)

PYTHON.ORG

Python 3.12.14, 3.11.16 and 3.10.21 Released

PYTHON.ORG

Django Rest Framework Release 3.18.0

GITHUB.COM/ENCODE

Articles & Tutorials

OAuth 2.0 in Python: The Authorization Code Flow Explained

OAuth’s authorization code flow, explained from the four roles up: the redirect, the code-for-token exchange, state, PKCE, and refresh tokens, with a complete runnable Python script and a table of the OAuth errors you’ll actually hit.
ENDPOINT51.COM ‱ Shared by Simon O’Connor

The Problem With pandas Isn’t Performance

This opinion piece argues that pandas’ biggest everyday cost is often cognitive overhead rather than execution time and explores whether a backend-independent data-analysis DSL could reduce it
NEAL HUGHES ‱ Shared by Neal Hughes

🐍 Object-Oriented Python, Taught Live

The Object-Oriented Python live cohort begins August 24. Five 2-hour Zoom sessions Mon to Fri build one growing application end to end, with OOP features introduced as the code starts needing them: classes, the data model, inheritance vs composition, properties, dataclasses. Learn more →
REAL PYTHON sponsor

Security of Everything at PyCon 2026

This year’s PyCon US added a dedicated security track. Talk Python interviews some of the key folks from the center of it: Juanita Gomez, Mike Fiedler, and Seth Michael Larson.
TALK PYTHON podcast

The HTML Representation of the Index API Is Now Frozen

‘PyPI has adopted PEP 833, which “freezes” the HTML representation of the index API, which is also sometimes called the “simple API” or the “simple repository API.”’
PYPI.ORG

How to Integrate OpenTelemetry With a FastAPI App

Learn how to integrate OpenTelemetry with a FastAPI app in Python to instrument endpoints, export traces, add custom spans, and correlate logs.
REAL PYTHON

Quiz: How to Integrate OpenTelemetry With a FastAPI App

REAL PYTHON

Qt Designer and Python: Build Your GUI Applications Faster

Learn how to use Qt Designer to build Python GUIs with PyQt6. Create your main windows and dialogs visually, then load them in your app.
REAL PYTHON

Admin Form Injection

Learn about a hidden Django admin feature for passing values between pop-up windows on the web.
TIM SCHILLING

Caller-Specific Coverage

An idea for a new feature for coverage measurement: per-caller coverage
NED BATCHELDER

Projects & Code

pytest-leak-finder: Find Leaking Test Before the One That Fails

GITHUB.COM/MGAITAN

num2words: Convert Numbers to Words. 42 –> Forty-Two

GITHUB.COM/SAVOIRFAIRELINUX

marimo-book: Static Site Generator for marimo Notebooks

GITHUB.COM/LJCHANG

pyrig: Aautomate Python Project Setup

GITHUB.COM/WINIPEDIA ‱ Shared by Winipedia

cog: Embedded Graph Database for Python

GITHUB.COM/ARUN1729

Events

Weekly Real Python Office Hours Q&A (Virtual)

August 19, 2026
REALPYTHON.COM

PyCon Ghana 2026

August 20 to August 23, 2026
PYCON.ORG

PyCon Latam 2026

August 20 to August 24, 2026
PYLATAM.ORG

PyLadies: Getting Started With Tabular Foundation Models With TabPFN

August 20, 2026
MEETUP.COM

PyData Bristol Meetup

August 20, 2026
MEETUP.COM

OSS Security Meetup: Mike Fiedler, PyPI Safety and Security Engineer

Wednesday, August 26
LUMA.COM ‱ Shared by Mila Zhou

PyCon JP 2026

August 21 to August 24, 2026
PYCON.JP

DjangoCon US 2026

August 24 to August 29, 2026
DJANGOCON.US

PyCon AU 2026

August 26 to August 31, 2026
PYCON.ORG.AU

PyCon PL 2026

August 27 to August 31, 2026
PYCON.ORG

PyCon Kenya 2026

August 28 to August 30, 2026
PYCON.KE

PyCon Togo 2026

August 28 to August 30, 2026
PYTOGO.ORG


Happy Pythoning!
This was PyCoder’s Weekly Issue #748.
View in Browser »

alt

[ 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 ]

August 18, 2026 07:30 PM UTC


Python Bytes

#492 Codeberg Puts Head in Sand

<strong>Topics covered in this episode:</strong><br> <ul> <li><strong>Python 3.12.14, 3.11.16, 3.10.21 - security releases</strong></li> <li><strong><a href="https://lucumr.pocoo.org/2026/7/24/codeberg-divides/?featured_on=pythonbytes">Codeberg’s AI-code ban tests its role as a GitHub alternative</a></strong></li> <li><strong>Brett Cannon: what's missing for reproducible builds on PyPI</strong></li> <li><strong><ul> <li>nothing records the source code a distribution came from. <code>direct_url.json</code> captures it when you install from a repo or archive, so the fix is putting the same info in sdist/wheel metadata.</li> </ul></strong></li> <li><strong><ul> <li>recording the build tools. Wheels can already do this via PEP 770 SBOMs in <code>.dist-info/sboms/</code> - sdists can't, since they're a tarball plus a precalculated <code>PKG-INFO</code> with nowhere to hang extra metadata. Either "don't use sdists" or an sdist v2.</li> </ul></strong></li> <li><strong>Extra extra extra, hear all about it</strong></li> <li><strong>Extras</strong></li> <li><strong>Joke</strong></li> </ul><a href='https://www.youtube.com/watch?v=5wBExYOSx0A' style='font-weight: bold;'data-umami-event="Livestream-Past" data-umami-event-episode="492">Watch on YouTube</a><br> <p>Sponsored by <strong>Logfire from Pydantic</strong> <a href="https://pythonbytes.fm/logfire">pythonbytes.fm/logfire</a></p> <p>This episode is brought to you by Pydantic Logfire. It's observability for AI apps from the team behind Pydantic - agents, LLMs, APIs, database, and infrastructure in a single trace, queried with Postgres-compatible SQL. Your coding agent can query it too, through their MCP server. I'll tell you more later.</p> <p><strong>Connect with the hosts</strong></p> <ul> <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> </ul> <p>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.</p> <p><strong>Calvin #1: Python 3.12.14, 3.11.16, 3.10.21 - security releases</strong></p> <p>https://blog.python.org/2026/08/python-31214-31116-31021/</p> <ul> <li>Source-only security releases for the three branches now in security-fix-only mode; release team blamed the European solar eclipse for the timing.</li> <li><code>tarfile</code> hardening. Multiple path-traversal bypasses of the <code>data</code> filter closed, including a symlink escape that bypassed the CVE-2025-4330 fix; <code>extract()</code> now applies the filter to link targets too.</li> <li>Four fresh CVEs: CVE-2026-2297 (<code>SourcelessFileLoader</code> not using <code>io.open_code()</code> for <code>.pyc</code>), CVE-2026-4224 (expat crash on deeply nested content models), CVE-2026-3644 (control chars in <code>http.cookies.Morsel</code>), plus the completed CVE-2021-4189 fix in <code>ftplib.ftpcp</code>.</li> <li>Quadratic-complexity DoS cleanup across the stdlib: <code>HTMLParser</code>, <code>configparser</code> regexes, <code>unicodedata.normalize()</code>, <code>csv.Sniffer.sniff()</code>, and ElementTree XPath index predicates.</li> <li>Header/injection fixes: CR/LF rejected in <code>HTTPConnection.set_tunnel()</code>, control chars blocked in <code>wsgiref.handlers</code> status, and <code>webbrowser</code> now rejects leading dashes (plus a <code>%action</code> prefix bypass).</li> <li><code>http.client</code> now caps chunked trailer lines and 1xx interim responses at 100 each - a hostile server could previously hang the client forever despite a socket timeout.</li> <li>Memory-safety odds and ends: stale pointers in lzma/bz2/zlib decompressors after <code>MemoryError</code>, a bz2 stack overflow on reuse-after-error, and bundled libexpat bumped to 2.8.3. If you're still on 3.10, 3.11, or 3.12 - and you extract tarballs from anywhere you don't fully control - this one's not optional.</li> </ul> <p><strong>Michael #2: <a href="https://lucumr.pocoo.org/2026/7/24/codeberg-divides/?featured_on=pythonbytes">Codeberg’s AI-code ban tests its role as a GitHub alternative</a></strong></p> <ul> <li>Armin’s article “<a href="https://lucumr.pocoo.org/2026/7/24/codeberg-divides/?featured_on=pythonbytes">Codeberg Divides</a>”</li> <li>Armin Ronacher argues that Codeberg’s new terms, which prohibit projects mostly written with generative AI, create a vague and difficult-to-enforce boundary. His larger concern is that a democratically governed host can still be unpredictable or ideologically narrow, weakening Codeberg’s potential as a broad European alternative to GitHub. <ul> <li>The strongest question for Python developers is whether repository hosting should judge legal open source by how code was produced, or focus on behavior and resource abuse.</li> <li>“Mostly generated” is hard to measure in modern codebases where developers mix handwritten code, completions, agents, and generated refactors.</li> <li>Ronacher suggests clearer alternatives: ban all LLM involvement, or target autonomous repository spam, abusive resource use, and low-quality generated contributions directly.</li> <li>Codeberg is free to choose a values-driven community, but that may conflict with being predictable, neutral infrastructure and a serious GitHub competitor.</li> <li>Worth discussing: can open-source communities set meaningful AI boundaries without driving maintainers and projects into opposing camps?</li> </ul></li> <li>Very first search for these <a href="https://xn--gckvb8fzb.com/i-regret-migrating-to-codeberg/?featured_on=pythonbytes">terms lands on this page</a>. <ul> <li><em>Codeberg looked like a viable alternative. 
 Unfortunately, the latest update to its terms of service seems to mark a first step in changing one part I moved there for, namely the “freedom” part.</em></li> </ul></li> </ul> <p><strong>Sponsor</strong>: <a href="https://pythonbytes.fm/logfire">Logfire from Pydantic</a></p> <p>Your AI agent failed at 2am. Was it the model? A tool call? The database? Most observability tools can't tell you, because they only see part of your stack. Pydantic Logfire sees all of it. One trace across your agents, LLMs, APIs, and database. Down to the infrastructure: services, Kubernetes, and hosts. It's built on OpenTelemetry, with SDKs for Python, TypeScript, and Rust, and it works with any OTel-compatible language. Every prompt, token count, and cost, right next to your vector searches and API calls. You query everything with Postgres-compatible SQL. And so can your coding agent, through the Logfire MCP server. Stop guessing. Read the trace. Pydantic Logfire. AI, it's still just engineering. Visit <a href="http://pythonbytes.fm/logfire">pythonbytes.fm/logfire</a> today and sign up today. Get 10M records free every month, no card required. You can even click “Onboard with your coding agent” to copy a prompt to have claude or codex integrate Logfire into your app. Thanks to Pydantic for supporting the show.</p> <p><strong>Calvin #3: Brett Cannon: what's missing for reproducible builds on PyPI</strong></p> <ul> <li>Framing came out of his 2026 Python Packaging Council nomination - the secure-supply-chain gap he found is that Python has no defined way to do reproducible builds at all.</li> <li>Design goal is zero friction: producers uploading to PyPI shouldn't have to do anything. The work lands on build backends and installers.</li> <li>Gap #1: nothing records the source code a distribution came from. <code>direct_url.json</code> captures it when you install from a repo or archive, so the fix is putting the same info in sdist/wheel metadata.</li> <li>Gap #2: recording the build tools. Wheels can already do this via PEP 770 SBOMs in <code>.dist-info/sboms/</code> - sdists can't, since they're a tarball plus a precalculated <code>PKG-INFO</code> with nowhere to hang extra metadata. Either "don't use sdists" or an sdist v2.</li> <li>The replay mechanism already exists: <code>[build-system]</code> in <code>pyproject.toml</code> is a defined entry point, so if backends recorded their own environment, you could reinstall and re-run the build.</li> <li>Payoff idea: trusted third parties report successful reproductions back to PyPI, which displays "independently reproduced by X" - surfaced in the index API so installers could prefer reproduced files.</li> <li>Explicitly framed as a perk, not a requirement - roughly SLSA build level 1, no shaming projects that don't opt in. Verbal kicker option: "And don't think pure-Python wheels are off the hook. Something built that wheel, and if that something was compromised, so is your wheel. SolarWinds was a build-process attack."</li> </ul> <p><strong>Michael #4: Extra extra extra, hear all about it</strong></p> <ul> <li><a href="https://docs.python.org/release/3.14.7/whatsnew/changelog.html?featured_on=pythonbytes">Python 3.14.7</a></li> <li>Upgraded the MCP servers to 2026-07-28 v2 protocols (<a href="https://talkpython.fm/ai-integration?featured_on=pythonbytes">talk python</a>, <a href="https://pythonbytes.fm/ai-integration">python bytes</a>)</li> <li>Got agentsview running <a href="https://www.agentsview.io/pg-sync/?featured_on=pythonbytes">synced via postgres</a></li> <li>Talk Python courses, <a href="https://training.talkpython.fm/team-trial?featured_on=pythonbytes">teams trial offering</a></li> <li>Talk Python courses, <a href="https://training.talkpython.fm/government?featured_on=pythonbytes">government procurement offering</a></li> <li><a href="https://www.audible.com/pd/Lean-TDD-Audiobook/B0HDV6DM5S?eac_link=kbTXxf8oGNDB&ref=web_search_eac_asin_1&eac_selected_type=asin&eac_selected=B0HDV6DM5S&qid=VQkJQUTSmK&eac_id=141-5188421-6797858_VQkJQUTSmK&sr=1-1&featured_on=pythonbytes">Lean TDD audio book is out</a></li> </ul> <p><strong>Extras</strong></p> <p>Calvin:</p> <ul> <li><strong>uv now prefers post-quantum key exchange -</strong> https://github.com/astral-sh/uv/releases/tag/0.12.4</li> </ul> <p><strong>Joke: <a href="https://turnoff.us/geek/beware-of-dog/?featured_on=pythonbytes">Beware of dog</a></strong></p>

August 18, 2026 06:07 PM UTC


Bob Belderbos

Guardrails Protect Your Codebase. What Protects Your Judgment?

Cal Newport's On AI Coding and Its Discontents lands on an uncomfortable point: the speed AI gives you is paid for with the thinking that made us good engineers in the first place.

Read code instead of writing it and you get passive recognition where you used to have an active model of the code.

The vendors sell one answer to this: more AI. The nostalgic answer is the opposite: go back to writing everything by hand. I am searching for a midway.

No right answer, but at least a direction to pursue

When I posted about this recently, I offered 3 options: write more code manually, double down on AI, or find some middle with more friction. I got some valuable comments from developers picking the middle. And that's where I've landed too.

The more I read and think about it, the more the middle stops looking like a compromise. It seems the way forward, but only if you specify what friction to keep.

Skill atrophy isn't caused by using AI per se. It's caused by delegating the part that builds your internal model.

I've argued that the friction is where the learning lives, and nothing about agents changes that. What changes is that it's now easier to bypass friction and it's on us to keep it in.

It helps to separate two problems here, because AI creates both and they aren't the same. One is skill atrophy: stop doing the thinking and your mental model fades. The other is correctness: AI writes plausible code that can be subtly wrong.

Let's look at each in turn.

Do you 'own' the code?

A sharp reply came from someone on Bluesky:

agents draft, but re-derive the hard path before merge. If you can't explain a weird branch without re-reading the whole file, you rewrote too little.

Not all code is created equal. The boundary conditions, error paths, and testing assumptions are where we as engineers pull our weight.

For every diff, can you explain the change? What are the edge cases and risks?

In practice I find that this gets easier when you scope the agent to narrow, well-defined tasks instead of blanket features. Small tasks keep you in the loop, stop the agent from drifting, and prevent giant code reviews nobody wants to read.

I also like how Corey Schafer summarized how he keeps the friction:

Push the rest into the system

Those four habits are all manual. They keep your judgment in the loop, but they run on your attention, and attention is finite. You can't re-read every branch on every diff forever. So whatever those habits can't cover by hand, you push into the system and let it enforce the rest automatically.

A commenter on Threads put it well:

the real fix is correctness by design, stronger types, compile-time assertions, more "faff in the way." We never fully adopted that stuff because it takes patience humans run out of. AI doesn't. It learns your constraints once, then works inside them on every diff after.

And on Fosstodon:

I feel like all these systems have too many surprises, and making invalid states illegal is the best defensive alternative to not using them, which would be the first choice. Once you get guarantees, if you can type faster with gen ai than your fingers, maybe thats worth the squeeze depending on your morals.

That's why Rust is so appealing to me. Making invalid states unrepresentable and the agent has to comply with this. I discussed this here.

Python gives you less strict guarantees, but we can approximate it. Your type checker (ty, pyrefly, mypy) catches the class of type errors the model would otherwise slip past you. Pydantic models validate at the boundary, so bad data fails loudly instead of drifting three functions deep. And well-thought out test cases can challenge plausible looking code. And for AI generated tests you can use mutation testing (an article on this soon here ...)

So balancing responsible AI use has two sides. Re-derive the decisions that matter, which keeps your judgment sharp. And encode stricter rules to keep your agent accountable to higher standards. One protects your mental model, the other protects the code base. Both protect peace of mind.

As said before: the tooling / way of working has changed, but AI doesn't change what software engineering is; we can automate some of the boring parts, but the majority of the work (including the code-producing part) is a lot of back-and-forth and applying experience and judgment.

Keep reading


Newport's worry is right: outsourced thinking atrophies. The escape isn't more or less AI. It's identifying, every time you build software, the small set of decisions you refuse to let the model make for you.

Can you explain each change? Do you truly own it? Did you keep making the hard decisions yourself? And are strict guardrails in place to protect the codebase when AI writes more of the code?

Reach out to me if you want to chat about this. It feels a bit like the wild west right now, and nobody has final answers. I'm really keen to hear how you're navigating the fast pace of change our industry is going through.

August 18, 2026 12:00 AM UTC


Seth Michael Larson

When str.lower() is a security vulnerability in Python

Some internet standards only support ASCII characters, but the world uses much more than the Latin alphabet. Thus, a mapping from Unicode to ASCII for use in domain names is required.

NamePrep was part of that solution, defined in RFC 3491 as a profile of StringPrep, and is crucially a component of Internationalizing Domain Names in Applications (IDNA), also known as “IDNA 2003”. The StringPrep algorithm is defined in RFC 3454. IDNA 2003 has been obsoleted by IDNA 2008 defined in RFC 5890, 5891, 5892, and 5893.

Python supports IDNA 2003 through the idna codec (str.encode('idna')) and IDNA 2008 is supported by the idna package on the Python package Index. Python's implementation of StringPrep is implemented in the stringprep module in the standard library. In general, you should be using the idna package (IDNA 2008) and not .encode("idna") (IDNA 2003), but sometimes you do need the older behavior.

md5-240e0a066fbd28891db9e76043276e9f

StringPrep defines the “case folding” step (case folding is approximately “how to lowercase/uppercase a codepoint”) in Section 3.2, enabling case-insensitive comparisons of strings, by mapping all characters through mapping tables B.2 and B.3. B.2 is effectively str.lower(), lowercasing all characters according to Unicode rules and B.3 contains the exceptions. The Python code implementing this (and assuming B.3 table is captured correctly) is the following code below:

md5-4f979c536cfd56d696221cec539cf01c

And that might seem fine... and the title probably gave it away already. The str.lower() call in this function is a vulnerability!

Why? Because str uses whatever Unicode data that the particular Python interpreter is shipped with, you can figure out what Unicode version your Python interpreter uses by accessing unicodedata.unidata_version:

md5-4546488f0cbcfe00d7f3a1769cb9a0b9

There's also a database of Unicode 3.2.0 data available on every version of Python (unicodedata.ucd_3_2_0) specifically for the StringPrep and IDNA algorithms:

md5-56b84a927fc32234b9d0f638c01c98c7

This is important! StringPrep depends on this specific version of Unicode to operate consistently, the B.2 and B.3 tables in RFC 3454 are essentially Unicode 3.2.0 case-folding rules encoded into a table. So we need to use Unicode 3.2.0 case-folding rules, not newer Unicode case-folding rules. This is why calling str.lower() represents a difference in the implementation and the specification, and therefore a vulnerability:

md5-a56c9c32b9ebeff41b4204e7dd201499

The fix was to create new exceptions so that str.lower() would behave as if it was using Unicode 3.2.0 for only particular function. So, we go through each Unicode codepoint and record when the behavior of str.lower() is different when comparing the Unicode version shipped with Python and Unicode 3.2.0. And that's all, now IDNA 2003 is consistent with the specification.

Thanks to Bitshift for reporting the vulnerability, Stan Ulbrych for co-developing the remediation, and Marc-Andre Lemburg and Petr Viktorin for reviewing the remediation. See CVE-2026-17084 for more details.

md5-530571d20754d44d4ee009954881e467



Thanks for reading ♄ I would love to hear your thoughts! Contact me via Mastodon, Bluesky, or email. Browse the blog archive. Check out my blogroll.



August 18, 2026 12:00 AM UTC

August 17, 2026


The Python Coding Stack

Back From Holidays ‱ SOLID Ideas, Agents, Rust

It feels good to be back from holiday. Feeling refreshed, mostly.

The to-do list is longer, the inbox is noisier, and home has thoughtfully provided its own backlog: a replacement car, a new school uniform, and a garden that has staged a minor coup.

I may need another holiday already.

And that’s before even talking about programming and the revolution it is currently going through. I’m a Python educator. Somewhat unusually for Python educators, I’ve been a full-time Python educator for over a decade.

But AI is changing how people learn and what people need to learn. Like many others, I find myself fascinated by what I can now do with AI, while also aware that it is changing the economics of work that has supported me for years.

It’s time to find out how anti-fragile I am. Over the coming weeks and months, I’ll write about using AI, I’ll share my thoughts about programming in 2026 and beyond, and I’ll tell you what I think about how learning and teaching are changing.

So I am going to explore this in public: how we learn, teach, write, and assess code in an AI-shaped world.

Here is some signposting for what else you will read on these pages in the coming months:

Subscribe now

So there is plenty on the horizon here at The Python Coding Stack: plenty of Python, and plenty of Python-adjacent stuff, too. And some other stuff, too. I hope you will join me for the journey.

August 17, 2026 03:14 PM UTC


Brian Okken

Lean TDD Book Launch

Hey all. I’m super excited to announce the launch of my new book.

It’s Lean TDD: TDD Without the Waste.

That link in the last sentence is a link to leantdd.com.

There you’ll find links to all the places you can read or listen to it.

Right now, it’s available at:

In the next few months, it will get onto other platforms. I’ve already had requests for Kobo, and just raw files sold through my site, and I’d also like to get it onto library platforms.

August 17, 2026 12:00 AM UTC

August 16, 2026


Brett Cannon

What's missing to have reproducible builds on PyPI

While writing the section of my 2026 Python Packaging Council (PPC) nomination on secure supply chain, I realized that one thing related to having a secure supply chain that we lack is a defined way to perform reproducible builds. The reason I like the idea of making reproducible builds work is that I think it can be done in such a way as to not require any work on the part of the producer of a distribution (which is a technical term for sdists or wheels, i.e., the people who upload stuff to PyPI), and thus make reproducible builds very low-friction for people to opt into supporting.

Why you should care

In terms of secure supply chain, reproducible builds can let independent 3rd parties verify that the bits in a distribution match what&aposs expected based on the source code the distribution was made from. That lets you potentially detect if anyone tampered with the code during the build process. As well, there&aposs a side-effect that from having to record the software involved in the build process means you can more easily detect if some known, compromised tool was used even if you don&apost do everything necessary to verify the bits are exactly the same. And this isn&apost some hypothetical benefit: SolarWinds was compromised due to malicious code being injected into the build process.

And don&apost let the word "build" fool you into thinking that pure Python wheels aren&apost vulnerable. There&aposs a build backend that was used to make that wheel, and if that build back-end was compromised then it may inject some malicious code into the wheel. So there isn&apost some area in Python packaging that gets to ignore these risks.

What&aposs missing from the specs

Where&aposs the source code?

So what do we need in Python packaging to make reproducible builds possible? First, we need to record what source code was used. This isn&apost recorded in sdists or wheels, but if you install directly from a source repository or an archive then it&aposs recorded in a direct_url.json file by your installer. If we were to record the same information in sdists and wheels (such as in the metadata), then we would know the location of the source code used to make the distribution.

What software was used to make the distribution?

But the next tricky bit is recording all the tools used to create the distribution. For wheels, we have support for recording software bill of materials (SBOMs). And as PEP 770, which added that support, says (disclaimer: I was the PEP delegate), SBOMs can be used to record the build tools used to create a distribution. And if you record all the software used to build a distribution, you can hopefully reproduce the exact same bits and show nothing was tampered with.

Unfortunately, sdists don&apost have a similar mechanism to record SBOMs. Since sdists are usually just a tarball with a PKG-INFO file that contains some precalculated metadata, there isn&apost a place to put any other metadata file. So we either have to shrug and say, "don&apost use sdists if you want reproducible builds," or we will have to come up with some sdist v2 format that allows for more structured data.

Who records what software was used?

Now, you might be wondering how you would even go about reproducing a build even if you do have all of this information. Luckily, since we have the [build-system] table in pyproject.toml, we have a defined entry point into the build back-end that produced the distribution. That means once we have all the software required to run the build backend recorded, we can replay the build process by installing the same things and calling the build backend as defined by [build-system]. And if build backends took care of recording what&aposs installed in the environment they are running in, theoretically we could record all the software used for the build back-end and store it in SBOMs today without distribution producers having to do extra work (but it does mean work for the pip maintainers).

Surfacing reproducibility on PyPI

Assuming all of this comes to pass and we record the where the source code is that went into a distribution and the software used to make the distribution, how do we make it useful to people? Does every person who cares about having a secure supply chain have to rebuild everything they use themselves? Is there some way for even people who don&apost care about this stuff to benefit?

One possible way is if there were trusted verifiers who could tell PyPI when they successfully reproduced a distribution. Since various enterprises are going to be doing this anyway, they could feed that information back to PyPI so they can visibly say for a distribution file, "this file was independently reproduced by <name of trusted party>". That means users get to know a distribution is what the creator expected, and the verifier gets a bit of recognition for helping the community out. This could even be surfaced in the index API so you could have installers prefer reproduced distributions.

This should be done in a way not to shame anyone who happens not to use a build backend that can create a distribution that can be reproduced. This should always be viewed as a perk and not a requirement. It&aposs like getting to say your project meets build level 1 of SLSA (which all of this would meet); some people like getting to say that, but it isn&apost a knock against anyone who chooses not to care.

Acknowledgments

Thanks to Seth Larson for listening to my idea and also checking over this blog post.

August 16, 2026 03:36 AM UTC

August 15, 2026


Juri Pakaste

TIL: Typed do in Swift

TL;DR: Swift has do throws(MyError). It's helpful.

This is one of those "I can't believe I had missed that" things.

I've been pretty enthusiastic about adopting typed throws in Swift. If you need to process certain kinds of errors, it just makes sense to me. However, it was always a bit painful. You have this:

do {
    // throwing code here
} catch {
    // error is precisely typed here, assuming
    // the block above throws just one type
}


 and all is fine and good, except that the "throwing code" doesn't need to grow particularly complex before Swift decides to widen the type to any Error and your catch block starts producing compilation errors.

When I ran into this, I'd use catch let error as MyError. Fine. Except it isn't, because now your error-handling code is incomplete: you have to add an unreachable catch-all block, and you're unhappy with the language and your life choices.

I did page through the enhancement proposal when it passed through Swift Evolution, but I just never noticed that it doesn't end there. What you do in this situation is this:

do throws(MyError) {
    // throwing code here
} catch {
    // now the compiler doesn't get confused and
    // all is well-typed unicorns and sunshine
}

Now you know too.

August 15, 2026 11:30 AM UTC

August 13, 2026


PyCharm

Open weight models are having a moment, driven by control, choice, and cost. Hybrid and local AI are now getting serious looks, so JetBrains teamed up with DeepLearning.AI on a free AI Coding Workflows: Hybrid to Local course that covers the ideas and options.

The course is now available and uses PyCharm and its AI Chat. Here’s a peek into the course.

Claude Code: Subagents and cheaper models

We start the course with, well, not-local. Instead, we use what you already know – Claude Code and its Anthropic models – to introduce some of the techniques and “levers” that help bring choice, control, and even cost reduction. (Yes, I wrote emdashes.)

We did a previous course on Spec-Driven Development (SDD) so of course, we wanted to start there. Smaller models struggle with big, open-ended “vibe coding.” Dividing and bounding the work keeps smaller models on track. Important note: this course’s example app is really basic. You might say “that’s too easy.” But that’s part of the takeaway: big brain models can do the upfront work, forming right-sized steps for smaller models.

We then illustrate this division with a Claude Code subagent. The main chat prompt implements each roadmap phase in a fresh subagent, to better manage context. This then gives the payoff: a cheaper model for the implementer. Use a “big brain” (Opus) for main conversation thinking and a “little brain” (Haiku) for implementation.

Each lesson finishes with metrics about the change in tokens, turns, cost, and estimated wall time. Which brings us to the main course goal: learning the ideas instead of the specifics, which change weekly.

New agent, inference, and model

That covers the four levers:


The course then introduces choice and control:


We first move to OpenCode, running in PyCharm. JetBrains wants our IDEs to be open platforms for agents and models. This makes the move from Claude Code to OpenCode straightforward: it’s the same UI. We add OpenRouter (a paid step), connect it to OpenCode, and choose DeepSeek as a model.

Next we repeat our sequence: all in one chat, then context isolation using a subagent. But this time, with a different agent and model.

We finish by making a dedicated implementer subagent in Markdown. This gives quite a number of levers of control: in the frontmatter for mandatory controls, and in the subagent body for “persuasion” guidance. Most importantly, we have the implementer use the smaller DeepSeek v4 Flash model as the “little brain.”

Compared to the Claude Code version, the metrics were, unsurprisingly, a lot cheaper.

Hybrid and Local

Now for the main attraction: for routine development, can we do some – or even all – of the work locally?

We start with a lesson on setting up local AI: LM Studio as the inference server and Gemma 4 12B as the local model, targeting a 32 GB laptop.

We then configure the implementer subagent to use this local Gemma 4 model, promoting DeepSeek v4 Flash from last lesson’s “little brain” up to “big brain.” The results? Quite good, as it turns out.

Then the big test: fully local, with Qwen 3.5 27B as the “big brain.” The results: better than expected, showing that guardrails help.

How did hybrid and local do? Both of these lessons finish with a review of the metrics. That’s one of the big course takeaways: look at the evidence. You can see how small models struggle, and see the effect of helping them succeed.

Hybrid and Local AI Are Heating Up

Much thanks to DeepLearning.AI both for working with us again and for pushing to get this out fast. This topic is now red-hot in the news: Sovereign AI, privacy and security, and of course cost. The innovations are coming really fast and it is important to have a gentle introduction to the fundamentals.

We’ll do more updates here on Local AI for control, choice, and cost. Most of all, we at PyCharm believe in the human-in-the-loop. Stay tuned for more on this.

August 13, 2026 01:41 PM UTC


Python Software Foundation

Announcing the Packaging Council Election Candidates for 2026!

What an exciting list! Please take a look at who is running for the inaugural Python Packaging Council (PPC) on the Nominees page. This election has 17 nominees for 5 open seats on the council.

Election Overview

This inaugural election fills all five seats on the PPC. The two candidates receiving the highest number of votes shall be designated Cohort A with a two year term, and the three candidates receiving the next highest number of votes shall be designated Cohort B with a one year term.

In future elections, each cohort will be elected for a full two-year term in alternating years, so that roughly half of the PPC turns over each cycle.

Election Timeline

Not sure what UTC is for you locally? Check this UTC time converter!

Reminder: Affirm your intention to vote

If you wish to vote in this election, you must affirm your intention to vote no later than Tuesday, August 25th, 2:00 pm UTC, to participate in this election.

Every PSF Voting Member (Supporting, Contributing, and Fellow) needs to affirm their membership to vote in this election. Find more information, including step-by-step instructions on voting affirmation, in our "Affirm Your PSF Membership Voting Status" blog post.

If you run into any issues, or have questions about your membership, please contact pc-elections@python.org.

Voting: What to expect

If you are a voting member of the PSF that affirmed your intention to participate in this election, you will receive an email from "OpaVote Voting Link <noreply@opavote.com>" with your ballot -- the subject line will read "Python Packaging Council Election 2026" on September 1st. If you don't receive a ballot as expected, please first check your spam folder for a message from "noreply@opavote.com".

If you don't see anything, please get in touch by emailing pc-elections@python.org so we can look into your account and make sure we have the most up-to-date email for you.


August 13, 2026 10:13 AM UTC


PyCharm

Hybrid and Local AI course at DeepLearning.AI

August 13, 2026 10:01 AM UTC


Python Software Foundation

Announcing the PSF Board Candidates for 2026!

What an exciting list! Please take a look at the 18 candidates running for the PSF Board this year on the Nominees page. This year there are 4 seats open on the PSF Board. You can see who is currently on the board on the PSF Officers & Directors page. (Cheuk Ting Ho, Christopher Neugebauer, Denny Perez, and Georgi Ker are at the end of their current terms.)

Nomination and supporting statements are the clearest way to see how each candidate actually thinks: their priorities, experience, and vision for the PSF. Rather than just voting on name recognition, we encourage you to read each candidate's nomination and supporting statements. PSF Board members shape real decisions about budget, programs, and the direction of the PSF. Reading these statements helps ensure your vote reflects what the candidates would actually do in the role. 

Board Election Timeline:

Not sure what UTC is for you locally? Check this UTC time converter.

Reminder to affirm your intention to vote!

If you wish to vote in this year’s election, you must affirm your intention to vote no later than Tuesday, August 25th, 2:00 pm UTC, to participate in this year’s election. This year’s Board Election vote begins Tuesday, September 1st, 2:00 pm UTC, and closes on Tuesday, September 15th, 2:00 pm UTC. 

Every PSF Voting Member (Supporting, Contributing, and Fellow) needs to affirm their membership to vote in this year’s election. You should have received an email from "psf@psfmember.org <Python Software Foundation>" with the subject "[Action Required] Affirm your PSF Membership voting intention for 2026 PSF Board Election" that contains information on how to affirm your voting status. 

Per a recent Bylaw change that allows for simplifying the voter affirmation process by treating past voting activity as intent to continue voting, if you voted last year, you will automatically be added to the 2026 voter roll. Please note: If you removed or changed your email on psfmember.org, you may not automatically be added to this year's voter roll. 

Find more information, including step-by-step instructions on voting affirmation, in our  ‘Affirm Your PSF Membership Voting Status” blog post. If you run into any issues, or have questions about your membership, please contact psf-elections@pyfound.org.

Voting: what to expect

If you are a voting member of the PSF that affirmed your intention to participate in this year’s election, you will receive an email from “OpaVote Voting Link <noreply@opavote.com>” with your ballot, the subject line will read “Python Software Foundation Board of Directors Election 2026” on September 1st. If you don’t receive a ballot as expected, please first check your spam folder for a message from “noreply@opavote.com”. If you don’t see anything get in touch by emailing psf-elections@pyfound.org so we can look into your account and make sure we have the most up-to-date email for you.

If you have questions about your membership status or the election, please email psf-elections@pyfound.org. You are welcome to join the discussion about the PSF Board election on the Python Discuss forum.

August 13, 2026 09:59 AM UTC


Wingware

Wing Python IDE Version 12.0.2 - August 13, 2026

Wing Python IDE version 12.0.2 has been released. This release adds a native ARM64 Windows version of Wing, improves remote development on Windows, streamlines Claude Code setup, and improves performance and responsiveness, particularly when working with very large projects and on Windows. It also reduces the size of the analysis cache database by about 20% and fixes a number of bugs. See the change log for details.

Wing 12 integrates the Claude Code AI coding agent directly into the IDE, with a new Claude Code tool, a Tasks tool for planning and reviewing AI agent work, and a set of MCP servers that give the agent access to Wing's source code analysis, unit testing, debugger, and code review features. See the list of Wing 12 features below for details.

Wing 12 Screen Shot

Downloads

After installing Wing 12, be sure to Check for Updates in Wing's Help menu so that you have the latest hot fixes.

Wing 12 -- the full Python IDE, available as Wing Pro (for agentic development) or Wing Classic (for manual development) depending on your license, with a free 30-day trial of Wing Pro.

Wing 101 v. 12 -- a simplified free Python IDE for teaching beginning programmers.

Wing 11 and earlier versions are not affected by installation of Wing 12 and may be installed and used independently. However, project files for Wing 11 and earlier are converted when opened by Wing 12 and should be saved under a new name, since Wing 12 projects cannot be opened by older versions of Wing.

New in Wing 12

AI Coding Agent Integration with Claude Code

Wing 12 adds a Claude Code tool that integrates the Claude Code AI coding agent with the IDE. Set Up for Claude Code in the Project menu configures the active project for AI agent development.

A set of MCP (Model Context Protocol) servers gives Claude Code access to Wing's source code analysis, testing, and debugger functionality, so the agent can more efficiently navigate and understand your code, write, run, and fix unit tests, and use the debugger to diagnose difficult runtime errors. In our benchmarks, giving Claude Code access to Wing's MCP servers made agent-driven coding tasks both faster and cheaper.

Tasks Tool

The new Tasks tool lets you plan, queue, execute, review, and audit the history of AI agent development tasks, making it easier to supervise and inspect the agent's work before committing it to revision control.

FIX Features and Write Tests

Wing 12 adds AI agent driven FIX features that hand the current debugger bug, failing unit tests, or code warnings to Claude Code for resolution. New Write Tests items in the Testing and editor context menus prompt the agent to write unit tests for selected code.

Code Actions

Wing 12 also adds AI Code Actions, accessed from the FIX icon in the editor toolbar, that operate on selected code or the enclosing scope. Built-in actions include explaining code, reviewing it for quality or security risks, fixing code warnings, optimizing for performance, and updating comments and docstrings. The action list is user-extensible, so you can add your own prompts for tasks you run often.

Pseudo-Terminal for OS Commands and Debug I/O

The OS Commands and Debug I/O tools now default to using a pseudo-terminal that implements full ANSI terminal emulation, so you can run and debug programs that use color output, cursor positioning, or full-screen TUIs.

Redesigned OS Commands Capability

The OS Commands tool has been replaced with configurable OS Commands in the Tools menu. Each OS Command acts like its own tool, for use in any tool or editor split.

Tools in Editor Splits and Reorganized Tools Menu

Tools can now also be added or dragged to editor splits, allowing for much more flexible workspace layout. The Tools menu has been reorganized into related groups, with less-used and legacy tools in an Other sub-menu, so more commonly used tools are easier to find.

Test Discovery and Preferences Search

Wing 12 adds automatic test file discovery and discovery of individual unit tests within files, so you usually don't need to specify test file patterns or add test files individually. The Preferences dialog now supports text search and back/forward navigation.

Other Minor Features and Improvements

Wing 12 also includes many other improvements, including:

  • IDE build for ARM64 Windows
  • Improved performance and responsiveness
  • Improved remote agent installation and remote development
  • Significantly faster source code analysis
  • Prompts for SSH passphrases and HTTPS credentials when needed during VCS operations
  • Faster detection of externally modified files, with reduced CPU load
  • Saving and restoring of tool console scrollback across project close and reopen
  • Clickable OSC 8 hyperlinks in the OS Commands and Debug I/O tools
  • A preference to select the ssh or plink.exe SSH implementation
  • A notice on the next startup when Wing's previous session ended in an unexpected crash

Wing 12 also makes a number of other bug fixes and usability improvements.

Product Line Changes

Wing 12 simplifies the product line. The Commercial / Non-Commercial use distinction has been replaced by two feature-based product tiers:

  • Wing Pro -- the full-featured Python IDE including AI agent development tools
  • Wing Classic -- the complete traditional Python IDE for hands-on development, with no AI agent features

Anyone may purchase either tier for any purpose. Existing Commercial and Non-Commercial Use licenses both become Wing Pro. Customers who don't need the AI agent features may move to Wing Classic at renewal time, or any time sooner by contacting support@wingware.com.

Wing Personal has been discontinued. Existing Wing Personal users may continue to use Personal 11.x indefinitely, switch to free Wing 101, or purchase a Wing Classic license. See Pricing for details.

Changes and Incompatibilities

The single-LLM-query AI features originally introduced in Wing 11 (the AI Coder and AI Chat tools) are considered legacy in Wing 12 and hidden from the user interface by default. They remain available in projects that already use them and can be re-enabled with Project Properties > AI in Project Properties or in the Projects > AI preferences.

See Wing's Claude Code Agent Integration for Wing 12's AI agent approach.

If you have questions, please don't hesitate to contact us at support@wingware.com.

August 13, 2026 01:00 AM UTC


Trey Hunner

Reorganizing Python's sys module

The sys module is the primary junk drawer of the Python standard library.

A good junk drawer holds miscellaneous items that don’t have another sensible home.

I often think of utils modules as “junk drawer” modules. I believe that both utils modules and junk drawers have their purpose, but junk drawers can get out of hand.

As I recently noted in my talk about pathlib, I see both the sys module and the os module as junk drawer modules. They serve a similar purpose to utils modules, but they have a different name.

In this post, I wonder: what would Python’s sys be like if it was designed today, from scratch?

The overview: 9 new sys submodules

As of Python 3.15, the sys module has 115-121 attributes (depending on whether you’re in the REPL and whether an exception has occurred), and 63 of those are functions.

All of those names need permanent homes.

How can we reorganize a utils-style module that’s grown quite large?

Give it submodules!

No, really… this isn’t the worst solution. Django’s utils package isn’t so bad.

So we could split sys into these 9 submodules:

You may notice some similarities to other modules in the Python standard library. That’s a hint that it might be worth moving some of these utilities into other parts of the standard library. But moving functionality between top-level modules is a much bigger change, so let’s set that idea aside.

For now, let’s take a closer look at our tentatively re-organized sys package.

The sys package and its submodules

A couple of these 9 submodules have only a handful of attributes, a couple have over 20, and the rest fall somewhere in the middle.

sys.cli

This submodule would handle command-line arguments and program control:

sys.imports

Everything related to imports and modules:

sys.io

The standard I/O streams:

sys.repl

Hooks and settings for Python’s interactive prompt:

sys.interpreter

Information about the Python build, the Python installation, and the operating system:

sys.memory

Memory management tools used for profiling, optimization, and debugging:

sys.exceptions

Tools for accessing and handling exceptions:

sys.profile

Profiling, tracing, auditing, and other runtime introspection:

sys.runtime

Settings that control interpreter runtime behavior:

Could this actually be done?

When I first pondered this experiment last year, this was a very hypothetical thought experiment that I assumed could never be done. I still mostly feel the same way.

There are a few big problems with such a refactoring:

  1. What would the transition period look like for the huge amount of code that currently uses existing sys features?
  2. Would backwards compatibility be maintained forever? If so, would this cause more confusion than it’s worth?
  3. How would code that monkey patches attributes like sys.stdout work?

I thought the third question was the biggest roadblock, but I now think it’s the first 2 questions.

Those first 2 questions are big questions and I haven’t thoroughly thought through the upsides and downsides of such a refactoring.

That third question is a technical one, but I think I can answer it… but the answer is messy.

The magic of module-level __setattr__

When Python users want to capture all output from their program to a file, they reassign sys.stdout to an in-memory file-like object. That’s how the contextlib.redirect_stdout helper works, and many testing tools use the same technique.

This monkey patching of sys.stdout is somewhat common, which poses a bit of a problem for us.

Imagine that stdout actually lived in a sys.io submodule. If sys.stdout and sys.io.stdout were two separate attributes, code that assigned to one would be invisible to code that read from the other.

We need a way to synchronize reads and writes of the old flat sys namespace to forward them to the newly nested namespace within the right submodule.

Python has supported customizing module-level attribute reads since Python 3.7, thanks to module-level __getattr__ functions (PEP 562). But Python doesn’t support module-level __setattr__ functions (that idea was proposed in PEP 726 and rejected).

Although… every Python module is an instance of ModuleType, and Python allows changing the class of a module object. If we swap in a ModuleType subclass, we can define whatever __getattr__ and __setattr__ behavior we’d like.

This trick is demonstrated in a proof-of-concept newsys package.

The newsys package reimplements sys as a package with 9 submodules. As a proof of concept, this module simply proxies to the sys module. All reads and writes of newsys.io.stdout or newsys.stdout proxy to the original sys.stdout (since that’s what everything else still uses under the hood in the existing Python interpreter).

What would the transition look like?

If this transition was ever actually done, I imagine it might look something like this:

  1. Add the submodules, with every old flat name still working via forwarding
  2. Update the documentation to nudge folks toward the new names
  3. Soft deprecate the flat names someday (or maybe never)
  4. (Likely never) hard deprecate the flat names

There’s a tiny bit of precedent for sys submodules: sys.monitoring (added in Python 3.12) is an actual module that lives under sys. But since sys isn’t a package, import sys.monitoring doesn’t work, as noted at the top of the sys.monitoring documentation page.

But, I doubt this will ever be done. Python isn’t known for reorganizing modules just to clean things up (outside of the big Python 2/3 split).

Removing names like sys.path and sys.argv would break a huge amount of code… and I can imagine Python tutorials and long-time Python users dragging their feet on re-learning “the new way”. After all… why re-learn something when the old version already works and isn’t going anywhere?

If this was ever done, the flat names might need to keep working forever, and permanent aliases might cause more confusion than such a reorganization is worth.

A thought experiment, not a proposal

I’m not seriously proposing that we actually reorganize sys… at least not seriously enough to draft a PEP.

But I do think there’s a practical takeaway here for our own code. When a utils module grows out of hand, submodules can help. And if other code relies on the old flat names, a module-level __getattr__ function (or that hacky __class__ trick) can keep the old names working while you reorganize.

I doubt sys will ever change, but I had fun imagining a version of Python where it did.

August 13, 2026 12:30 AM UTC


Python Insider

Announcing the Packaging Council Election Candidates for 2026!

Announcing the 17 nominees for the inaugural Python Packaging Council election, and how to vote in the election.

August 13, 2026 12:00 AM UTC

August 12, 2026


Django Weblog

DSF Office Hours

The DSF Board hosts open office hours every Wednesday at 6:00 PM UTC (check your local time). Anyone in the Django community is welcome to drop in. You do not need an agenda or an invitation. Video call details are on the DSF Office Hours page.

We have been running these since October 2024, and right now we have two things we would especially like to talk with you about.

Who shows up

On any given Wednesday, you might find DSF Board members, Steering Council members, Django Fellows, working group members, and community members who are curious about joining a working group. There is no membership requirement. Anybody from the community can join, and often does.

We recently published a call for applicants for a Django Executive Director. If you are considering applying, or you are still deciding whether it is the right fit, come to office hours and ask us anything: what the job actually looks like, what we expect in the first year, how the search works. We would rather answer your questions directly than have you guess from a job posting.

If this is the first you are hearing about the search, please help us spread the word. The best candidate may be someone who has not thought to look.

Fundraising to support it

Hiring an Executive Director is why we raised our 2026 fundraising goal from $300,000 to $500,000, which needs about $16,000 per month in additional recurring support. We have made real progress and are working to close the rest.

If your company uses Django, you can help through corporate sponsorship, a direct donation, or GitHub Sponsors. Most of these are a small lift for a company that already depends on Django. If you want fundraising materials to bring to your leadership team, or help picking the option that fits, come to office hours, and we will get you what you need.

Everything else

Office hours cover plenty beyond that: what our working groups are up to and how to join one, projects the Foundation is working on, and whatever you have been wondering about how the DSF operates. In the weeks before a board meeting, we use the time to gather feedback on what we are about to discuss. If you want the board to hear something, this is a direct line.

One thing office hours are not: a general Django support channel. It is not the place to debug your code or market a product. For coding help, the Django Forum will get you faster answers.

Office hours are the most direct way to keep up with the Foundation, but they are not the only one. We wrote up all the other places we post and where the conversation happens if Wednesdays do not work for you.

Otherwise, put a Wednesday on your calendar and say hello.

August 12, 2026 05:25 PM UTC


PyCharm

What’s New in PyCharm 2026.2.1

This PyCharm release is a big one for anyone building with AI. Your agents can now roll up their sleeves inside your Jupyter notebooks – working against a live kernel instead of firing off disconnected scripts. And they finally know which Python to use, so packages land in the right environment every time.

We’re also welcoming marimo notebooks into the IDE and introducing changes to bundled plugins to keep PyCharm fast and focused.

Release highlights

Jupyter notebook skill for AI agents

Let AI agents such as Claude Code and Codex create, edit, and run .ipynb notebooks via PyCharm’s notebook model and a live kernel, so variables, models, and data persist across cells instead of disappearing when the agent shells out. For you, this means more reliable notebook and ML work – with fewer tokens used. To start, just open the AI chat and ask the agent to work in your notebook.

Agent environment coordinator

Tired of AI agents installing packages into the wrong Python environment? This new skill gives the agent your project’s configured interpreter and tool – uv, Poetry, pip in a venv, or conda – so commands target the right environment, not a system one. If none exists, it can set one up via PyCharm, and the agent decides how to use the information. To start, ask the agent to run or install something in your project.

marimo notebooks in PyCharm [third-party plugin]

You can now open, edit, and run marimo notebooks directly in PyCharm with the new plugin developed by the marimo team. 

Work with reactive cells and interactive UI elements in a dedicated notebook without leaving your IDE. Because marimo notebooks are stored as Python files, they are Git-friendly, executable as scripts, and easy to integrate into your existing Python projects.

Changes to bundled plugins in 2026.2

As part of ongoing maintenance, we are unbundling and deprecating low-usage plugins, including Data Wrangler, Hugging Face, and Google Colab support. You can continue to install compatible versions from JetBrains Marketplace, but these plugins are no longer bundled or actively maintained by the PyCharm team. A more focused set of bundled plugins means a leaner codebase, helping us keep PyCharm fast and responsive and invest our effort where it has the most impact.

Redesigned Python Packages tool window

Redesigned Python Packages tool window in PyCharm

Clearer type checking

Get clearer, more actionable type messages:

Clearer type checking in PyCharm

Bug fixes

Download PyCharm

All of these updates are available in PyCharm 2026.2.1. Update right from the IDE or the Toolbox App, or download the latest version to try everything out on your own projects. As always, we’d love to hear your feedback.

August 12, 2026 12:05 PM UTC

We Stopped AI Agents From Installing Into the Wrong Python: Task Success Rates Jumped to 95%+

AI agents are supposed to save you time. Ask one to install a dependency or run your project, though, and it often does the opposite: It installs into the wrong Python, ignores the uv or virtual environment your project uses, and hands back a broken setup for you to fix yourself.

PyCharm’s new Agent Environment Coordinator skill fixes this, and this blog post shows just how helpful it proves to be.

AGENT ENVIRONMENT COORDINATOR The agent stopped guessing Python. Average task success 68% -> 98% Baseline With skill 28 Python tasks 6 AI models No system Python pollution

We tested six AI models using 28 different Python programming tasks. Without access to the project’s real environment, they solved 68% of the tasks on average. After we gave them access, their average success rate shot up to 98% – and they didn’t even modify the system Python.

If you’re currently using AI agents in your Python projects, read on to see how the Agent Environment Coordinator can improve their performance.

When the agent could see the project’s environment, it stopped failing

When using the Agent Environment Coordinator skill, each agent, regardless of the model, was able to complete far more of the 28 tasks. (See the Methodology section below for details on what the tasks entailed.) Here is the share of successfully completed tasks for each model, comparing the baseline to running with the skill in PyCharm:

MODEL BASELINE WITH SKILL Claude Sonnet 4.6 36% 96% Claude Sonnet 5 73% 100% Claude Opus 4.8 67% 100% Claude Opus 5.0 94% 98% Codex / GPT-5.5 62% 95% Codex / GPT-5.6 80% 100%

Every model improved, with the weakest baseline improving the most.

Why we built this

LLMs almost never use a project’s dedicated virtual environment. They fall back to a system interpreter, ignoring the fact that there may be several system interpreters and real projects often have more complex, multi-interpreter setups already configured in PyCharm that the agent has no way to see.

For example, pip install httpx runs against the wrong Python, the package installs globally, the script fails, and the environment is polluted.

PyCharm already knows which interpreter belongs to your project and which tool manages it. The agent just couldn’t ask – so we gave it a way.

How it works

The Agent Environment Coordinator lets the agent ask PyCharm two things. get_python_environment returns the correct interpreter for the file or module in question – the path plus the tool behind it (uv, Poetry, pip + venv, conda). If no environment exists yet, configure_python_interpreter sets one up by reusing PyCharm’s existing configuration mechanism – the same one that offers to create a .venv – so the new interpreter also becomes visible in the IDE.

The important part is what the skill doesn’t do. It returns information; it never intercepts or rewrites the command. The agent asks which Python to use, gets an accurate answer, and decides whether and how to use it to write the command itself. We hand it the missing context using existing mechanisms in PyCharm – we don’t let it take the wheel.

The payoff is practical: The agent works with your project setup out of the box. You don’t need to coach it through prompts about which environment to use, or clean up wrong installs afterward.

Methodology

We built a dataset of 28 tasks covering everyday Python-environment work, like running tests, installing a library, listing dependencies, resolving a version conflict, and so forth.

Each task ultimately required the agent to pick the correct interpreter to execute a command. The eval also reduced the reward when the agent polluted the system environment, so a high score reflects a clean run, not just a passing one.

We ran the full set three times per model, with and without the skill, using Harbor, and averaged the results.

Results

Success rates climbed across the board – Sonnet 5 improved from 73% to 100%, Opus 5 from 94% to 100%, and Codex/GPT-5.6 from 80% to 100%. 

Two things stand out in addition to this numerical jump: 

Want to try it?

Open the AI chat in PyCharm 2026.2.1 and ask your agent to install a package or run something in your project – it’ll reach for the right interpreter on its own.

The Agent Environment Coordinator is one of PyCharm’s bundled skills. You can browse and manage all of them right in the IDE, expand the built-in library with external registries like public GitHub repositories, or import skills you’ve already set up for Claude Code or Codex.

August 12, 2026 12:01 PM UTC

We Gave AI Agents a Live Jupyter Kernel in PyCharm

If you’ve handed notebook work to an AI agent, you know how it tends to go: More often than not, it corrupts your .ipynb, loses your trained model the moment the run finishes, or burns budget sitting idle through a long job while you watch.

To solve this, we’re introducing a brand-new Jupyter skill. Built directly into PyCharm, it lets your AI agent work inside a live Jupyter kernel instead of handing the job to a subprocess and losing your progress. This one change means state persists across cells, the .ipynb isn’t corrupted, and long jobs wait until execution is completed instead of constantly checking and wasting precious tokens.

JUPYTER SKILL FOR PYCHARM A live kernel made Opus cheaper than the shell. 12% cheaper Claude Opus 5 across 12 ML tasks Kernel USD 59.09 Shell USD 67.06 12 ML tasks 98% cache reads State persists across cells

For Opus, the kernel ran cheaper than the shell

We tested the efficiency of the Jupyter skill by comparing the performance of agents when solving twelve different machine learning problems. We compared three different modes: strictly using bash, strictly using the kernel via the Jupyter skill, and a mixture of both.

While the agent was able to solve all twelve tasks in every mode, there was a difference in how much each mode spent. For Claude Opus 5, working through the kernel cost 59.09 USD versus 67.06 USD through the shell – about 12% cheaper.

MODE COST INPUT TOKENS CACHE READS Kernel (skill) Shell (baseline) USD 59.09 USD 67.06 72.7M 36.3M 98% 82%

Here’s the counterintuitive part: The kernel used more tokens, yet cost less. That’s because it keeps the prompt cache warm. 98% of its input was cache reads, versus 82% for the shell – and cache reads incur only 1/12 of the cost of creating a fresh cache.

Why we built this

Notebooks are where coding agents tend to fall apart. Most AI tools treat an .ipynb like a plain text file: They hand-edit the JSON (and corrupt it), and then run code by running a subprocess. The moment an agent starts the subprocess, the kernel state – the trained model, the loaded dataframe, and every import – lives in the child process, and vanishes when that process exits. The agent can’t inspect it, checkpoint it, or reuse it. Output is buffered until the run ends, so progress is invisible, and long training jobs get babysat – blind until the connection times out.

We asked the obvious question: What if the agent operated a live Jupyter kernel through the IDE?

So we built our new Jupyter skill, which exposes PyCharm’s own notebook intelligence – its notebook model and live-kernel control – to the agent. It does this through a single MCP wrapper, execute_tool, which covers the core notebook operations, including creating, editing, and reading notebooks; running cells; waiting on long runs; probing a running kernel; and controlling its lifecycle. The skill tells the agent when and how to use them.

How it works

The agent:

Methodology

We used twelve tasks from the MLGym machine-learning benchmark – classification, regression, and reinforcement-learning problems, each of which requires the agent to load data, train, evaluate, and save a result. We ran them across Claude Opus 5 and OpenAI’s GPT-5.6 models, Sol and Terra, through Codex. We compared three modes: through the kernel only, through the kernel plus the shell, and through the shell alone. As these benchmark tasks expose test labels to the agent, we treat cost – not accuracy – as the reliable signal.

One caveat, for transparency: An audit found that one of the twelve tasks, Titanic, was contaminated – the agent could peek at the test set, and each agent used this to select the best model to present as the final solution. Titanic is a well-known, easy task for LLMs, and the issue appeared consistently across all three modes, so it doesn’t skew the comparison. The pattern holds even with Titanic removed – the kernel still ran 10% cheaper than the shell for Opus (56.34 USD versus 62.65 USD).

Results

The cost win is model- and task-dependent. It was clearest for Claude Opus on long, stateful jobs, while the shell came out cheaper on short tasks and for the Codex models – which already use the cache efficiently, so there the skill earns its place on workflow, not cost.

Where it still falls short

Two things are worth keeping in mind:

The skill removes the mechanical waste, but doesn’t turn a weak approach into a strong one.

Want to try it?

Open the AI chat in PyCharm 2026.2.1 and ask your agent to work in a notebook – create one, load a dataset, or kick off a training run. The agent will operate the kernel directly instead of running commands in the shell.

You can also browse and manage skills directly from the IDE, expand the built-in library with external registries like public GitHub repositories, or let PyCharm import skills you’ve already set up for Claude Code or Codex.

August 12, 2026 12:00 PM UTC