skip to navigation
skip to content

Planet Python

Last update: July 15, 2026 01:48 PM UTC

July 15, 2026


Django Weblog

Supporting the Triptych Project

The Django Steering Council — in its role as the DSF's arm for technical governance — has provided a Letter of Collaboration in support of a funding application by Carson Gross and Alex Petros to advance the Triptych Project: three proposals to make HTML itself more expressive, in every browser, by default.

Here's why, and how you can help.

HTML over the wire, and Django

The last few years have seen a move back towards serving multipage applications, with server-rendered templates. The HTMX library has probably had the biggest impact in the Django space, but Unpoly, Turbo, and others are part of the same story: send HTML over the wire, let the browser do what browsers do, and skip the client-side application layer where you don't really need it. It's a simpler model of the web — and it's one that speaks to Django's heart.

This isn't a movement Django has watched from the sidelines. Template partials, added in Django 6.0, were directly inspired by the patterns these libraries make natural.

The Triptych Project

The Triptych Project takes the core insights from HTMX (and the related libraries) and proposes them for the HTML standard itself. Three small additions:

  1. PUT, PATCH, and DELETE methods for forms — completing HTML's HTTP vocabulary.
  2. Button actions — buttons that make HTTP requests without a wrapping form. This is the current focus.
  3. Partial page replacement — links, forms, and buttons that target part of the DOM.

Together these aim to make it possible to build far more of the web with plain HTML — no JavaScript dependency, no library, nothing to ship or maintain.

Button actions

The current proposal (WHATWG #12330, full proposal) adds the action and method attributes to <button>. The canonical example is logout. Today there's no semantic way to write a logout button; you have to wrap it in a form:

<form action=/logout method=POST>
  <button>Logout</button>
</form>

Every Django developer has written this kind of thing. With button actions we could write the simpler single line:

<button action=/logout method=POST>Logout</button>

This isn't abstract for us. The Django admin's submit row holds multiple buttons, and a link disguised as a button:

<div class="submit-row">
  <input type="submit" value="Save" class="default" name="_save">
  <input type="submit" value="Save and add another" name="_addanother">
  <input type="submit" value="Save and continue editing" name="_continue">
  <a href="/admin/auth/user/.../delete/" class="deletelink">Delete</a>
</div>

Here, all the save inputs lead to the same action URL from the wrapping form. The view then branches on the submitted name value. That, of course, works, but we can imagine simpler, more flexible ideas being enabled via the Multi-Action Pages examples in the proposal.

The disguised submit link leads to the deletion confirmation page, where we then submit a form to confirm the action. That's the correct behaviour, but the markup confuses the intent: this isn't (really) a navigation to a new page, it's the first step of an action — deleting the object. The proposal's discussion of Buttons vs Links describes situations we come up against writing applications regularly.

The goal here is simpler patterns that will help us write better markup.

Why we're supporting this

The Django Software Foundation's mission includes a commitment to "advance the state of the art in Web development". Standards work is that in its purest form: an improvement to HTML lands for everyone, in every framework, in every browser, indefinitely.

It's also slow, painstaking work — specification, implementer engagement, web platform tests — that needs sustained attention. Carson and Alex are applying for funds so that people can devote real time to it. Our Letter of Collaboration is a concrete contribution to that application.

How you can help

If your company builds on Django, or indeed any other framework — with HTMX, Unpoly, Turbo, or plain HTML forms — this work benefits you directly. Carson and Alex are seeking non-binding letters of support on official letterhead for the funding application. Details and contacts are on the Triptych Project site.

Individually, do read the proposals, weigh in constructively on the WHATWG issues, and spread the word.

A simpler web is a better web. We're glad to support work that moves HTML in that direction.

July 15, 2026 11:00 AM UTC

July 14, 2026


PyCoder’s Weekly

Issue #743: Stacks & Queues, Django F-Expressions, MCP Clients, and More (2026-07-14)

#743 – JULY 14, 2026
View in Browser »

The PyCoder’s Weekly Logo


Stacks and Queues in Python

This post shows you how to use a Python list for stack operations (last-in, first-out) and a deque from the collections module for queue operations (first-in, first-out).
TREY HUNNER

Nifty Django Feature: F Expressions

Django’s F-Expression provides a way of querying fields from the ORM. They’re particularly handy to traverse relationships in more complex queries.
TIM SCHILLING

Secure Your Code, Wherever, or However You Write It

alt

AI coding agents have blind spots, they reproduce patterns but struggle with security context. AURI by Endor Labs plugs into your editor via MCP, catching flaws, signaling exposed secrets, and spotting malicious dependencies. Ship secure by default. Try AURI Free →
ENDOR LABS sponsor

Testing MCP Servers With a Python MCP Client

Learn how to build a Python MCP client that tests MCP servers from your terminal. List their tools, prompts, and resources, then call each one.
REAL PYTHON course

Quiz: Testing MCP Servers With a Python MCP Client

REAL PYTHON

PEP 797: Shared Object Proxies (Rejected)

PYTHON.ORG

Django Security Releases Issued: 6.0.7 and 5.2.16

DJANGO SOFTWARE FOUNDATION

Articles & Tutorials

Constructing and Judging Modern Agentic Workflows

How can you improve your LLM agent systems through specification enrichment? What are the advantages of having an LLM act as a judge within an agent system? This week on the show, Senior IEEE Member and Quality Engineer Suneet Malhotra joins us to discuss building and evaluating agentic architecture.
REAL PYTHON podcast

What for x in y Hides From You

An explanation of how Python’s for x in y syntax is a thin wrapper around the iterator protocol: iter(...), next(...), and StopIteration. Using examples from Memphis, a Python interpreter written in Rust, it shows how this design makes lists, ranges, and generators feel unified rather than magical.
TYLER GREEN • Shared by Tyler Green

Stop stitching 5 different systems together for your agents.

alt

Dev teams spend weeks fitting together vector DBs, graph DBs, relational stores, filesystem primitives and optimizing cache. How about everything via a single API? P90 sub-200ms recall - the fastest graph database to unlock true agent memory, knowledge graphs and user-personalization. Click Here to Try HydraDB Out for Free
HYDRADB sponsor

Building a Fast HTML Toolkit in C for Python

turbohtml began as a patch to speed up html.escape and html.unescape in CPython. When the core team declined to maintain SIMD in the standard library, it became a third party library instead. This post is its story.
BERNÁT GÁBOR • Shared by Bernát Gábor

How to Publish to PyPI Using GitHub Actions Securely

If you’re using GitHub Actions to publish your Python libraries, this article is for you. Learn what are the best practices to ensure the process is secure and what tools you can use to validate it.
BRETT CANNON

Python 3.15’s Ultra-Low Overhead Interpreter Profiling Mode

Ken is one of the key contributors to the experimental JIT. This post talks about how Python 3.15’s interpreter profiling mode is helping them figure out what is working with the JIT and what isn’t.
KEN JIN

PEP 814: Add Frozendict Built-in Type

Victor has been involved in multiple attempts to add a frozen dict type to Python. His latest PEP has been accepted and frozen dictionaries will be added to Python 3.15. This post is his story.
VICTOR STINNER

How to Clean Messy CSV Files With Python

This introductory article shows you how to do data cleaning on CSV files using pandas, including dealing with duplicate rows, missing values, mixed date formats, and more.
ABID ALI AWAN,

PSF News: Security, Elections, and PyCon US 2026

This post is the monthly news round up of all things PSF. It covers a re-cap of PyCon US, several security fixes, updates from the PSF board, and more.
PYTHON SOFTWARE FOUNDATION

How to Use GitHub

Learn how to use GitHub step by step to create a remote repository, push your local Python project, and collaborate with others using GitHub Issues.
REAL PYTHON

Quiz: How to Use GitHub

REAL PYTHON

Projects & Code

pyStrich: 1D and 2D Barcode Generator Library

GITHUB.COM/MMULQUEEN • Shared by Michael Mulqueen

Snakie: Cross-Platform MicroPython IDE

GITHUB.COM/KEVINMCALEER

Notion2Pandas: Import Notion Databases Into pandas

GitLab.com
GITLAB.COM/JAEGER87

envgap: Find Gaps Between .env, Shell Env, and Python Code

GITHUB.COM/PINAK-DATTA • Shared by Pinak Datta

CLI-based Text-to-Speech Tool

GITHUB.COM/REALPACIFIC • Shared by Prashant Barahi

Events

Weekly Real Python Office Hours Q&A (Virtual)

July 15, 2026
REALPYTHON.COM

PyData Bristol Meetup

July 16, 2026
MEETUP.COM

PyLadies Dublin

July 16, 2026
PYLADIES.COM

DjangoGirls Tamale 2026

July 17 to July 19, 2026
DJANGOGIRLS.ORG

EuroSciPy 2026

July 18 to July 24, 2026
EUROSCIPY.ORG

PyData PyCon Armenia 2026

July 24 to July 26, 2026
PYCON.AM


Happy Pythoning!
This was PyCoder’s Weekly Issue #743.
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 ]

July 14, 2026 07:30 PM UTC


Peter Bengtsson

How to use a list/tuple/array in Django with a raw SQL cursor

This does not work:


from django.db import connection

list_of_values = [1, 2, 3]
with connection.cursor() as cursor:
    cursor.execute("""
        SELECT *
        FROM my_model_table
        WHERE some_value IN %s
    """, [
        tuple(list_of_values),
    ])
    results = cursor.fetchall()

It will give you:

django.db.utils.ProgrammingError: syntax error at or near "'(1,2,3)'"
LINE 4:         WHERE id IN '(1,2,3)'

It used to work with psycopg v2. Now, in psycopg v3, you have to use the ANY operator. See "You cannot use IN %s with a tuple"

This will work:


from django.db import connection

list_of_values = [1, 2, 3]
with connection.cursor() as cursor:
    cursor.execute(
        """
        SELECT *
        FROM my_model_table
        WHERE some_value = ANY(%s)
    """,
        [
            list_of_values,
        ],
    )
    results = cursor.fetchall()

Note the ANY(%s), and instead of a list that has a tuple, it's a list that has a list.

What About a List of Strings

Consider...


from django.db import connection

-list_of_values = [1, 2, 3]
+list_of_values = ['foo', 'bar', 'fiz']
with connection.cursor() as cursor:
    cursor.execute(
        """
        SELECT *
        FROM my_model_table
        WHERE some_value = ANY(%s)
    """,
        [
            list_of_values,
        ],
    )
    results = cursor.fetchall()

That will result in:

django.db.utils.DataError: invalid input syntax for type integer: "foo"
LINE 4:         WHERE some_value = ANY('{foo,bar,fiz}')

My solution was to rewrite the SQL string itself and treat each value as a parameter each. In other words, the SQL string, before being sent to cursor.execute(...) will contain something like this:


AND (
  some_value = % OR
  some_value = % OR
  some_value = % OR
  some_value = % OR
  -- ...etc...
  some_value = %
)

This will work and is safe:


from django.db import connection

list_of_values = ["foo", "bar", "fiz"]
with connection.cursor() as cursor:
    cursor.execute(
        f"""
        SELECT *
        FROM my_model_table
        WHERE ({" OR ".join(["some_value = %s" for _ in list_of_values])})
    """,
        list_of_values,
    )
    results = cursor.fetchall()

July 14, 2026 01:14 PM UTC


Mike Driscoll

Python 101 3rd Edition Kickstarter Launch

Learn Python with a Book Designed for Beginners

Hello! I’m Michael Driscoll, and I’m excited to announce the third edition of Python 101.

For years, Python 101 has helped readers take their first steps into programming. Whether you’re a complete beginner, a student, or someone looking to learn Python for work or personal projects, the goal has always been the same: make learning Python approachable, practical, and enjoyable.

Python has continued to evolve, and it’s time for the book to evolve with it. This Kickstarter will help fund the writing, editing, production, and publication of the fully updated third edition.

Why a Third Edition?

Python has changed significantly since the previous edition. New language features, updated best practices, and improvements throughout the ecosystem make this the right time for a comprehensive update.

The third edition will include:

The goal isn’t simply to revise the previous edition—it’s to create the best beginner-focused version of Python 101 yet.

Who Is This Book For?

This book is designed for:

No prior programming knowledge is required.

Why Kickstarter?

Kickstarter allows readers to directly support the creation of the book while helping ensure it receives the time and attention needed for a high-quality release.

Funding will help cover:

Most importantly, it allows me to focus on creating the strongest edition possible.

Rewards

Depending on your pledge level, rewards may include:

Project updates will be shared throughout the process so backers can follow the book’s progress.

Thank You

Whether you back this project or simply share it with others, your support means a great deal.

Writing technical books is a collaborative effort between authors and readers. Your encouragement has helped make previous editions successful, and I’m excited to bring an updated Python 101 to the next generation of Python learners.

Thank you for helping make the third edition possible.

Back the project

The post Python 101 3rd Edition Kickstarter Launch appeared first on Mouse Vs Python.

July 14, 2026 01:05 PM UTC


PyPodcats

Trailer: Episode 12 With Juanita Gomez

A preview of our chat with Juanita Gomez. Watch the full episode on July 17, 2026A preview of our chat with Juanita Gomez. Watch the full episode on July 17, 2026

Sneak Peek of our chat with Juanita Gomez, hosted by Cheuk Ting Ho and Mariatta Wijaya.

Juanita is a Ph.D. candidate in Computer Science at UC Santa Cruz, where her research focuses on improving the security of scientific open source software. She is a former developer of the Spyder IDE, one of the community managers for the Scientific Python project, and part of the organizing committee for the SciPy conference.

In this episode, Juanita shares how she went from making music videos on YouTube to creating friendlier documentation for Spyder, her research on open source security, practical security tips for maintainers, and her journey navigating imposter syndrome as a woman and Latina in tech. Plus: The X Factor, Shakira, and singing on stage at SciPy.

Full episode is coming on July 17th, 2026! Subscribe to our podcast now!

July 14, 2026 01:00 PM UTC


Python Bytes

#488 tau - it's 2pi and it writes code

<strong>Topics covered in this episode:</strong><br> <ul> <li><strong>The trusted-publishing debate: how to do it right vs. why you shouldn't trust it</strong></li> <li><strong><a href="https://link.mail.beehiiv.com/ss/c/u001.nhis-D9RLtwtIum5I0BjhZ22Z4E_bpBKQN-16RenQ3ws2TZPhzEfwjZMSGiGAQx3p407RO5QUnbYLEGjQnorGAGD5oCA5TGTjsy6RbCrTYDXJN8BUDTRz-W0ZLYlAKrwzGPByECjf7lwVCl3nZRCGXvvhp8TvBLNfJRgnY-wJ9ktogLFr2YqqQyXhwmgzpa-6HsPhylQ_9BWTK3teRtSwrRXrLfxrVWwQXEG6Zbqaz5nRGc6xNV7pMdWvEGpfoBMUwbGl0aGZ40ZX_cYbR-sgKDJdl0v95gGqtcySNXfNgokF54k8Xao40CrloFhcFtX/4s6/5odqdEikSeKZEjId8lTcSg/h29/h001.6m_9ZXKKfEi-Sx1OcsN8EInQX8eEjhrHaS4h_Yo0T7o?featured_on=pythonbytes">JupyterLab 4.6 and Notebook 7.6 are out!</a></strong></li> <li><strong><a href="https://twotimespi.dev/?featured_on=pythonbytes">Tau</a> – new small, readable terminal coding agent</strong></li> <li><strong><a href="https://docs.djangoproject.com/en/6.0/topics/tasks/?featured_on=pythonbytes">Django Tasks and Django 6.1</a></strong></li> <li><strong>Extras</strong></li> <li><strong>Joke</strong></li> </ul><a href='https://www.youtube.com/watch?v=lJpOzcVTlho' style='font-weight: bold;'data-umami-event="Livestream-Past" data-umami-event-episode="488">Watch on YouTube</a><br> <p><strong>About the show</strong></p> <p>Sponsored by us! Support our work through:</p> <ul> <li>Our <a href="https://training.talkpython.fm/?featured_on=pythonbytes"><strong>courses at Talk Python</strong></a></li> <li>Consulting from <a href="https://sixfeetup.com/?featured_on=pythonbytes"><strong>Six Feet Up</strong></a></li> </ul> <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> </ul> <p>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.</p> <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:</strong> The trusted-publishing debate: how to do it right vs. why you shouldn't trust it</p> <p>https://snarky.ca/how-to-publish-to-pypi-using-github-actions-securely/ (Brett Cannon) and https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing (W<strong>illiam Woodruff</strong>)</p> <ul> <li>Trusted Publishing (PyPI's OIDC-based auth scheme, also now used by npm, RubyGems, <a href="http://crates.io?featured_on=pythonbytes">crates.io</a>, NuGet) replaces long-lived API tokens with short-lived, auto-scoped credentials tied to CI/CD machine identity.</li> <li>Yossarian's post: it's purely an <em>authentication</em> mechanism between a machine identity and a package — it says nothing about package safety or quality. PyPI deliberately avoids any "verified/trusted" badge for it, unlike its verified-URL checkmarks.</li> <li>Same logic applies to PyPI attestations: anyone can sign with any machine identity they control, so an attestation's presence isn't itself a trust signal.</li> <li>Bottom line from that post: don't confuse "trusted" (machine-to-machine) with "trustworthy" (human judgment about the package).</li> <li><a href="http://Snarky.ca?featured_on=pythonbytes">Snarky.ca</a>'s companion piece is more practical: given GitHub Actions compromises in the news, the real fix is 3 concrete steps — run zizmor to lock down workflow permissions/checkout credentials and pin actions to commit hashes, adopt Trusted Publishing to eliminate stored PyPI tokens, and require manual approval via a GitHub environment before any publish job runs.</li> <li>Takeaway for listeners: Trusted Publishing is good hygiene for <em>how</em> you authenticate to PyPI, but it's not a substitute for securing your CI pipeline itself — or for actually vetting the packages you install.</li> </ul> <p><strong>Michael #2: <a href="https://link.mail.beehiiv.com/ss/c/u001.nhis-D9RLtwtIum5I0BjhZ22Z4E_bpBKQN-16RenQ3ws2TZPhzEfwjZMSGiGAQx3p407RO5QUnbYLEGjQnorGAGD5oCA5TGTjsy6RbCrTYDXJN8BUDTRz-W0ZLYlAKrwzGPByECjf7lwVCl3nZRCGXvvhp8TvBLNfJRgnY-wJ9ktogLFr2YqqQyXhwmgzpa-6HsPhylQ_9BWTK3teRtSwrRXrLfxrVWwQXEG6Zbqaz5nRGc6xNV7pMdWvEGpfoBMUwbGl0aGZ40ZX_cYbR-sgKDJdl0v95gGqtcySNXfNgokF54k8Xao40CrloFhcFtX/4s6/5odqdEikSeKZEjId8lTcSg/h29/h001.6m_9ZXKKfEi-Sx1OcsN8EInQX8eEjhrHaS4h_Yo0T7o?featured_on=pythonbytes">JupyterLab 4.6 and Notebook 7.6 are out!</a></strong></p> <p>Michał Krassowski's rundown - a chunky minor release: 68 features, 97 bug fixes, 95 contributors, one of the biggest ever.</p> <ul> <li><strong>Scratchpad console</strong> (Notebook 7.6 headliner) - a console next to your notebook sharing its kernel, for throwaway experiments. Ctrl+B.</li> <li><strong>Jump to last-edited cell</strong> - new commands hop through recently edited cells.</li> <li><strong>File browser glow-up</strong> - Date Created column, editable breadcrumbs with Tab-completion, and Open in Terminal.</li> <li><strong>Debugger</strong> - sources open in the main area, floating step/continue overlay, live kernel-sources filter.</li> <li><strong>Custom layouts (Lab)</strong> - activity bar top/bottom, draggable panels, four-way tab splits, per-panel Ctrl+scroll zoom.</li> <li><strong>~5x faster extension builds</strong> - webpack → Rspack, and <code>jupyter-builder</code> means no full Lab install needed to build extensions.</li> <li><strong>Keyboard/a11y</strong> - add shortcuts from the UI (no JSON), Find &amp; Replace in Edit menu (Ctrl+H).</li> </ul> <p><strong>Calvin #3: <a href="https://twotimespi.dev/?featured_on=pythonbytes">Tau</a></strong> – new small, readable terminal coding agent</p> <ul> <li><strong>Tau</strong> – new small, readable terminal coding agent (Python 3.12+), built as both a working tool and a teaching project for how coding agents work under the hood</li> <li>Install via <code>uv tool install tau-ai</code>, <code>pipx</code>, or <code>pip</code>; ships a <code>tau</code> CLI</li> <li>Three-layer architecture: <code>tau_ai</code> (provider-neutral model layer) → <code>tau_agent</code> (reusable "brain": messages, tools, events, loop) → <code>tau_coding</code> (CLI/TUI, file &amp; shell tools, sessions)</li> <li>Supports OpenAI, Anthropic, OpenAI Codex, OpenRouter, Hugging Face, and custom/local OpenAI-compatible endpoints</li> <li>Built-in tools (read/write/edit/bash), durable JSONL sessions with resume/branching, project instructions via <code>AGENTS.md</code>, and context compaction</li> <li>Core harness is UI-agnostic — same brain can power the TUI, print mode, or a custom frontend — usable as a standalone library too</li> </ul> <p><strong>Michael #4: <a href="https://docs.djangoproject.com/en/6.0/topics/tasks/?featured_on=pythonbytes">Django Tasks and Django 6.1</a></strong></p> <ul> <li><strong>Django 6.0 finally ships first-party background tasks</strong> (<code>django.tasks</code>) - out of Jake Howard's DEP 14, accepted May 2024, after two decades of everyone bolting on Celery/RQ/Huey.</li> <li><strong>It's an API, not a worker.</strong> Django handles task definition, validation, queuing, and result storage - it does not execute them. You bring the backend.</li> <li><strong>The default backend traps people.</strong> <code>ImmediateBackend</code> runs tasks inline on the request thread and blocks until done - so out of the box <code>.enqueue()</code> backgrounds nothing (a 5-second task means a 5-second response). The other built-in, <code>DummyBackend</code>, runs nothing at all. Both are dev/test only.</li> <li><strong>Nice API otherwise:</strong> slap <code>@task</code> on a function, call <code>.enqueue()</code>, get back a <code>TaskResult</code> you look up later by id - with async twins like <code>aenqueue()</code>. Gotcha: args and return values must survive a JSON round-trip, so a tuple sneakily comes back as a list.</li> <li><strong>The community local backend to know: <code>django-tasks-local</code></strong> by Chris Beaven (SmileyChris). A <code>ThreadPoolExecutor</code> backend that gives real background threads with zero infrastructure - no Redis, no Celery, no database - plus a <code>ProcessPoolBackend</code> for CPU-bound work → <a href="http://github.com/lincolnloop/django-tasks-local?featured_on=pythonbytes">github.com/lincolnloop/django-tasks-local</a></li> <li><strong>Its catch:</strong> results live in memory, so pending tasks vanish on restart or deploy. Great for dev and low-traffic production; for persistence, drop to Jake Howard's <code>django-tasks</code> (DatabaseBackend + worker command).</li> </ul> <p><strong>Extras</strong></p> <p>Calvin:</p> <ul> <li><a href="https://hugovk.dev/blog/2026/fixing-dict/?featured_on=pythonbytes">Fixing the dictionary with Python 3.14</a> — Hugo van Kemenade stumbled on - and got fixed - a markup bug in the OED's own citation of a 1706 use of the pi symbol.</li> </ul> <p>Michael:</p> <ul> <li><a href="https://bunny.net/blog/were-making-bunny-dns-free/?featured_on=pythonbytes">Bunny DNS is now free</a></li> </ul> <p><strong>Jokes:</strong></p> <ul> <li><strong>What's the object-oriented way to become wealthy?</strong> Inheritance</li> <li><strong>To understand what recursion is...</strong> You must first understand what recursion is</li> <li><strong>3 SQL statements walk into a NoSQL bar.</strong> Soon, they walk out They couldn't find a table.</li> </ul>

July 14, 2026 08:00 AM UTC

July 13, 2026


Django Weblog

Explore the DjangoCon US 2026 Speaker Lineup and Reserve Your Spot

DjangoCon US 2026 is just around the corner, and now is the perfect time to start planning your conference experience.

Our speaker lineup is now available, featuring talks from Django contributors, maintainers, educators, and community members covering everything from web development and APIs to deployment, security, testing, AI, and the future of the Django ecosystem.

Whether you're attending your first DjangoCon US or returning to reconnect with friends and colleagues, you'll find opportunities to learn, share ideas, and meet people from across the global Django community.

Beyond the talks, your conference registration includes access to tutorials, Open Spaces, community sprints, hallway conversations, and social events that make DjangoCon US a unique experience.

If you haven't registered yet, there's still time to join us in Chicago, August 24–28.

Register for DjangoCon US 2026: https://2026.djangocon.us

Browse the speaker lineup: https://2026.djangocon.us/news/announcing-lineup/

We'll be sharing more updates in the coming weeks, including the full conference schedule, travel reminders, and everything you need to make the most of your time at DjangoCon US.

We look forward to seeing you in Chicago this August!

July 13, 2026 10:51 PM UTC


Hugo van Kemenade

Security: line goes up

Like many other projects, CPython is experiencing a huge increase in security reports.

CVEs per year #

Last month, PSF Security Developer-in-Residence Seth Larson posted a chart of CVEs per year, showing a large increase in 2026:

2007-2024 all below 20 per year. 2025 and year-to-date 2026 had just over 20, with 2026 extrapolated to around 65.

But this only represents the output of security work, and doesn’t show all the work dealing with incoming reports. Many are closed and dealt with as non-security bug reports instead; many are closed as neither security nor bug reports.

Let’s reveal some of this unseen work by the Python Security Response Team (PSRT).

GHSAs by month #

Here are the number of incoming GitHub Security Advisories (GHSA) reports created since July 2024:

Chart of new security reports. Single digits or zero per month from 2024, increasing to around 40 in 2026.

GHSAs by year #

Here is the same thing by year, and remembering we’re only halfway through 2026:

18 in 2024, 41 in 2025, 175 so far in 2026.

Email reports by month #

We’ve only fairly recently been encouraging new reports be made via GHSA. Before this, they were usually made by email. The next chart is the number of email discussions (or threads) and participants by month:

Number of discussions and participants per month follow each other closely. Single digits from 2014, around 20 by 2019, 40 by 2021 and 2022, a dip to 15 for 2023 to 2025, up to 50 for 2026 so far.

Thanks #

Big thanks to Seth for all his work as Security Developer-in-Residence: helping shepherd all these reports, developing a security policy to improve the quality of incoming reports and help us assess them, and defining PSRT membership and responsibilities via PEP 811 to build an active team. All this would be much harder without his guidance! And thanks to Alpha-Omega for sponsoring his position at the PSF.

July 13, 2026 08:44 PM UTC


Talk Python to Me

#555: Marimo Pair - A Canvas for Agent + Developers Collaboration

Coding agents have gotten really good at one kind of work. You scope a feature, edit some files, run the tests, ship it. It all happens on disk. But that is not how data work feels. You load something, you look at it, you run a cell, you watch how it responds, and you decide the next move from whatever is sitting in memory. And until now, your agent couldn't see any of that. It only saw the files. Never the live state. <br/> <br/> This episode, that wall comes down. marimo pair drops a coding agent right inside a running notebook, with full access to every variable Python is holding in memory. The notebook becomes a shared canvas. You point, it runs the code. You tell it to zoom in on the Picasso paintings, and the chart just updates. No MCP tools to wire up, no schema to describe. Just Python, and an agent that can finally see what you see. Trevor Manz is back to walk us through 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/training'>Talk Python Courses</a><br/> <br/> <h2 class="links-heading mb-4">Links from the show</h2> <div><strong>marimo pair</strong>: <a href="https://marimo.io/pair?featured_on=talkpython" target="_blank" >marimo.io/pair</a><br/> <br/> <strong>Course transcripts announcement</strong>: <a href="https://talkpython.fm/blog/posts/announcing-german-subtitles-on-courses/" target="_blank" >talkpython.fm/blog</a><br/> <br/> <strong>anywidget: Jupyter Widgets made easy</strong>: <a href="https://talkpython.fm/episodes/show/530/anywidget-jupyter-widgets-made-easy" target="_blank" >talkpython.fm</a><br/> <strong>marimo</strong>: <a href="https://marimo.io/?featured_on=talkpython" target="_blank" >marimo.io</a><br/> <strong>blog</strong>: <a href="https://marimo.io/blog/marimo-pair?featured_on=talkpython" target="_blank" >marimo.io</a><br/> <strong>GitHub</strong>: <a href="https://github.com/marimo-team/marimo-pair?featured_on=talkpython" target="_blank" >github.com</a><br/> <strong>given this</strong>: <a href="https://martinalderson.com/posts/wall-street-lost-285-billion-because-of-13-markdown-files/?featured_on=talkpython" target="_blank" >martinalderson.com</a><br/> <strong>llms.txt</strong>: <a href="https://talkpython.fm/llms.txt" target="_blank" >talkpython.fm</a><br/> <strong>mcp</strong>: <a href="https://talkpython.fm/ai-integration" target="_blank" >talkpython.fm</a><br/> <strong>cli</strong>: <a href="https://talkpython.fm/blog/posts/talk-python-now-has-a-cli/" target="_blank" >talkpython.fm</a><br/> <strong>open issues</strong>: <a href="https://github.com/marimo-team/marimo-pair/issues?featured_on=talkpython" target="_blank" >github.com</a><br/> <strong>Discord</strong>: <a href="https://marimo.io/discord?featured_on=talkpython" target="_blank" >marimo.io</a><br/> <strong>Marimo Pair</strong>: <a href="https://marimo.io/pair?featured_on=talkpython" target="_blank" >marimo.io</a><br/> <strong>OpenCode</strong>: <a href="https://opencode.ai?featured_on=talkpython" target="_blank" >opencode.ai</a><br/> <strong>AI Tooling for Software Engineers in 2026</strong>: <a href="https://newsletter.pragmaticengineer.com/p/ai-tooling-2026?featured_on=talkpython" target="_blank" >newsletter.pragmaticengineer.com</a><br/> <br/> <strong>Watch this episode on YouTube</strong>: <a href="https://www.youtube.com/watch?v=6LAQnnW-gTY" target="_blank" >youtube.com</a><br/> <strong>Episode #555 deep-dive</strong>: <a href="https://talkpython.fm/episodes/show/555/marimo-pair-a-canvas-for-agent-developers-collaboration#takeaways-anchor" target="_blank" >talkpython.fm/555</a><br/> <strong>Episode transcripts</strong>: <a href="https://talkpython.fm/episodes/transcript/555/marimo-pair-a-canvas-for-agent-developers-collaboration" 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>

July 13, 2026 05:07 PM UTC


Rodrigo Girão Serrão

itertools cheatsheet

Cheatsheet with visual diagrams that explain how the iterables from itertools work.

This cheatsheet contains diagrams that explain how the iterables from the module itertools work in a visual way.

Download this cheatsheet

A4 itertools cheatsheet shown in light and dark themes.

A4 itertools cheatsheet shown in light and dark themes.

Download this cheatsheet

July 13, 2026 02:28 PM UTC


Mike C. Fletcher

PyVRML97 2.3.4b1

Continuing on with the Open Source work. PyVRML97 2.3.4b1 is almost all build and CI process updates. There are a few minor fixes for modern Python's where bool can't be used as a list index and a change for NumPy 2.x array comparison failures. This beta is mostly just so that we can pull it from OpenGLContext's alpha when it's released.

July 13, 2026 01:53 AM UTC


Bob Belderbos

Learning New Skills in the AI Era (vBrownBag)

I joined the vBrownBag podcast with Damian to talk about how to actually learn a new language or skill when an agent can write the code before you finish typing the prompt.

Keep the friction in

The thread running through the whole conversation is friction. Agents are close to slot machines: a bit of dopamine, the path of least resistance, and suddenly you are delegating the thinking, not just the typing. The weeks where I hand off the most are the weeks I come out least happy with my own skills.

Drake meme: rejecting outsourcing your thinking to the model, preferring to read it yourself and ask where you get stuck

So I keep deliberate friction in the loop. I built coding platforms for Python and Rust with no AI in them, on purpose, so you still write the code in the browser without assistance.

When you are learning something, you have to go through the cycles at least once before you let an agent do it for you.

That is also why I can lean on agents more in Python (20 years of programming in, I can smell-test the output) than in a language I am still new to.

The litmus test is simple: how well do I understand the thing I am shipping?

AI to explain, not AI to do

AI is remarkable at explaining a specific concept. It is dangerous as a crutch for deeper understanding. The distinction I keep drawing: use it to explain, not to do the work you signed up to learn.

We got into where the silent errors hide. Reviewed code can look completely plausible and still be only 70% right, because you never went deep enough to feel the wrong part (I also discussed this recently on complexity.fm).

On a recent project the app worked and returned good results, but it was silently never searching the second half of every chunk (see here).

That is the failure mode I see most with students shipping AI-built code, which is why I keep coming back to rubber-stamping AI PRs as the real risk.

Learn by building, with tests as the guide

When people ask how to learn Rust (or anything) without losing ownership, the shape is always the same:

In the Rust cohort we build a JSON parser this way: tokenizer first, then bindings with PyO3, then benchmarking. Several students beat the C parser on performance (see here and here).

This only happens because they owned every line instead of having an agent generate it.

Watch the full conversation:

Watch on YouTube

The line I keep repeating: AI is an accelerator, not a compass. Start with your own thinking, then let it help, and keep a high enough bar that you never accept the first draft.

Keep reading

Thanks Damian / vBrownBag for having me on. If you want to stay technical without outsourcing the thinking, that is exactly what we work on in the Rust and agentic AI cohorts.

July 13, 2026 12:00 AM UTC


Armin Ronacher

The Tower Keeps Rising

I feel that some vibecoded software changes somewhat randomly and unexpectedly. That made me think about Bruegel’s “The Tower of Babel” which shows an already quite chaotic depiction of the Tower of Babel. The story is usually told as one about pride and ambition and ultimately why people no longer speak the same language. But it is also a story about the unity that makes technological progress work.

The text begins with a technology upgrade:

And they said one to another, Go to, let us make brick, and burn them thoroughly. And they had brick for stone, and slime had they for morter.

They use it for a civilizational project:

let us build us a city and a tower, whose top may reach unto heaven

But when God assesses the situation the bricks are not what concern him:

the people is one, and they have all one language, […] and now nothing will be restrained from them.1

The source of their power is coordination. They share a language and with that shared language they can combine their work into something no one of them could build alone. God does not take away the bricks or their knowledge of how to make them. He takes away their ability to understand one another, and construction stops.

There is the appealing idea that AI-assisted programming means better tools which lets us build more ambitious software. That is certainly true at the level of the individual and without doubt a developer with an agent will be dramatically more capable of changing a codebase. But large software projects have never been limited only by how quickly an individual can produce code. They are limited by how well people can coordinate their understanding of the system they are changing.

The shared language of a software project is not English or Python but it is the common understanding of what its concepts mean, where the boundaries are, which invariants matter, who owns what, and why the system has the shape it does. This language is rarely written down in one place. It lives partly in documentation and code, but also in code review, conversations, arguments, and the experience of having to explain a change to somebody else.

Before agents, some of this shared understanding was maintained by friction. If I wanted to change your storage layer, I usually had to read your code, ask you questions, and perhaps coordinate with another team whose service depended on it. This was slow, and much of that slowness was waste but not all of it was. Some of it was the process by which your understanding became mine, and by which both of us discovered whether we still agreed about how the system worked. This friction synchronizes people.

Agents remove much of that friction. I can ask an agent to add OAuth, you can ask one to add caching, and somebody else can ask one to rebuild the database from first principles and make the UI pink. Each change can be reasonable in isolation. The code can compile, the tests can pass, and the explanations can be generated on demand. None of us necessarily has to talk to the others, or even acquire the part of the shared model that the change once would have forced us to learn.

As I said many times before: agents do not feel pain, only humans do. Agents now let us act in parts of the system where we would previously have needed other people and in code bases where the people would have revolved.

When I look at some vibecoded scaled-up projects the codebases become Babel not because nobody can communicate, but because nobody needs to. Every developer has a tireless translator that can explain a corner of the tower and make whatever local alteration they ask of it. The changes keep landing, even as the architectural language that would let the humans reason about them together disappears.

But it’s not the biblical story. At Babel, the loss of common language stops construction whereas in AI-assisted engineering, construction can continue after shared understanding has already collapsed. The lack of an immediate failure is what makes it curious and a bit disorienting. The tower does not fall, and so we do not notice what was lost. It just keeps rising.

July 13, 2026 12:00 AM UTC

July 12, 2026


Christian Ledermann

Buzzword Bingo: An Experiment in Spec-Driven AI Development

This is a submission for Weekend Challenge: Passion Edition

What I Built

I built Buzzword Bingo, a multiplayer bingo game for conferences, webinars and meetings where players mark off the inevitable buzzwords as they appear.

The application allows someone to create a game, share a link with participants, and let everyone play along on their own unique bingo board. The first player to complete a row, column or diagonal wins.

Under the hood, though, the game itself was almost secondary.

The real goal was to answer a question I had been wondering about for a while:

How far can I push Claude with specification-driven development while still achieving reliable type coverage and maintaining the coding standards I expect from a production Python project?

The project became an experiment in AI-assisted software engineering, strict typing, and how much guidance modern coding agents actually need to produce maintainable software.

Demo

There is no live demo, but you can have a look at the screenshots taken by playwright during testing

Code

Repository:

bsbingo GitHub repository

How I Built It

Specification Driven Development

The project followed a specification-driven approach using Speckit.

Rather than iterating directly in code, I created specifications describing what the system should do and allowed Claude to implement them.

A big accelerator for the project was using scaf for the initial bootstrap. Rather than spending the first few hours wiring together repository structure, CI, containerization, infrastructure, and developer tooling, I started from a production-oriented foundation and focused on shaping it to match my own preferences. Having Kubernetes manifests, Terraform, deployment pipelines, and modern Python tooling available from day one made it much easier to concentrate on the actual experiment: how far specification-driven development and AI coding agents could take the application.

I ended up needing three major specifications:

  1. Project scaffolding
  1. Backend implementation
  1. Frontend implementation

Django Without the JavaScript Framework

The application uses:

HTMX turned out to be an excellent fit for this type of application.

Most interactions consist of:

No client-side state management was required.

Capability URLs

One design decision I particularly liked was using capability URLs instead of authentication.

Each board receives a unique UUID:

/board/5b97b663-1f2f-4e54-8d2f-f45f3272f870/

Possession of the URL grants access to that board.

This removes the need for:

For a lightweight conference game this felt like the right trade-off.

Going All-In On Type Safety

I care a lot about clean code and strong typing in Python, so I decided to push the type system as far as possible.

Instead of relying on a single type checker, I combined:

This was paired with a strict ruff configuration with almost every rule enabled.

One of the goals of the experiment was to see whether Claude could operate effectively within these constraints.

What Worked

This instruction worked well:

Prefer precise, narrow types (Enum, NewType, TypedDict, dataclasses with Final or Literal fields) over Any, untyped dict or list, or stringly-typed values. Illegal states should be unrepresentable in the type system rather than guarded against only at runtime.

Once Claude had a few examples to follow, it started producing significantly better type annotations and more expressive domain models.

Pre-commit hooks proved to be the first line of defence, catching issues before they ever reached CI. Linters, formatters, and all three type checkers ran automatically on every commit, providing rapid feedback and keeping the codebase consistent throughout the experiment.
To avoid spending time hand-crafting the configuration, I used pc-init to generate a strict .pre-commit-config.yaml tailored for modern Python projects. This ensured that formatting, linting, and type checking became part of the development workflow rather than an afterthought.

What Didn't Work

Claude struggled with this instruction:

All Python code MUST be fully type-annotated; untyped function signatures and untyped module-level values are not permitted.

Instead of fixing missing annotations, it occasionally attempted to disable checks in pyproject.toml.

Some manual intervention and code review were required to steer it back towards the desired standards.

The experience reinforced an observation I've made repeatedly with coding agents:

Agents optimize for making the error disappear, not necessarily for preserving your engineering constraints.

If you care about those constraints, you still need strong feedback loops.

Type Checker Observations

Running all three type checkers together was still faster than a single mypy run.

Interestingly, they complemented each other rather than duplicating effort:

The newer type-checking ecosystem is still catching up with mypy in terms of documentation and examples, so reaching the level of strictness I wanted involved a fair amount of experimentation.

Prize Categories

Not submitting for any specific prize category.

The real prize was finding out how far AI-assisted, specification-driven development can be pushed before human review becomes the limiting factor. 😉️

July 12, 2026 09:23 PM UTC


Mike C. Fletcher

PyOpenGL 4.0.0a1

I've been trying to make some time for Open Source projects again. I've been using LLMs for much of the coding because the vast bulk of it at this point is just grunt work. First up is PyOpenGL. The tests the LLM produced turned up a bunch of bugs in the core that have lain dormant for years because the endpoints weren't getting used. The LLM tests are not particularly fun or interesting, but they did a pretty good job of finding wrapping errors. They also exercised GLES and EGL enough to make it far more reasonable to actually use those two interfaces.

Shout out to glfw python library for working cleanly on the Wayland only environment. Definitely helped to find the hidden GLX dependencies we had throughout the Linux platform implementation. One of the biggest ones there was the GLUT library. The other thing that came out was the GLE library being legacy (compatibility) OpenGL.

PyOpenGL 4.0.0a1 is classified as a major release mostly because of the abandonment of old Pythons (<3.9) and old Numpy (<2). Other than that there's mostly just bug-fixes that came from the new test suites.

GLU
* gluUnProject4 missing arguments
* gluNewQuadric/gluQuadricCallback fix the callback mechanism to work like Nurbs code
* gluTessVertex/gluTessBeginPolygon and combine callback, original object return fixes
* gluGetNurbsProperty added, allocates the output
* gluNurbsCallbackData(EXT) argtype fix

glGet Sizes
* sizing tables regenerated based on results from size probing, lots of incorrect sizes fixed; note that these fixes are constrained to extensions I happen to have access to on my platforms
* fix the code generator's constant generation
* glGetPolygonStipple fixed size output
* glGetCompressedTexImage glGetTexImageCompressed was ignoring level and using an ARB constant

Wrappers
* remove double wrapping on glGetHistogramParameter{f,i}vEXT, glGenVertexArrays, glDrawBuffersEXT (which was also mis-named glDrawBuffers)
* glHistogram double wrapped as well, which was crashing vertex_array_object on import which was then causing higher level code to treat the extension as unsupported

64-bit Integer Arrays
* GL_INT64 / GL_UNSIGNED_INT64 new array types for all of the array handlers

No-Numpy Operation
* ctypesarrays zeros/ones handler
* a few spots where GLchar arrays were needed as return types
* gl(Get)ProgramNamedParameter*NV input size fix
* glGetActiveAttribARB optional bufSize parameter added
* ARB.vertex_shader allow passign in size parameter
* allow passing a ctypes char_p as shader-text

GLES
* images module for GLES
* friendly wrappers mimicing the GL ones for lots of endpoints
* glGetString/glGetStringi restype fix
* Normalising of GLES extension names to the GL_* form (same as GL)

General Bug Fixes
* Large constant wrapping fix
* Caching of extension/version data per-context
* Core/version extension handles cases where "VERSION" is not the *first* token
* ShaderProgram.retrieve() fix for unpacking glGetProgramBinary
* input-or-output converter for args that can be either
* ArrayDatatype.get_ffi_argtype etc PyPy specific mechanisms for array interactions

Logging
* make the log decorator more type-check friendly

Packaging
* License declaration fixes for more modern packaging tools

There shouldn't be many significant regressions, as almost everything is a correctness fix, but there's a lot of new code, particularly for the GLES improvements. The alpha is up now for those who want to test the changes against their codebase, but this is an alpha release, so there may be more significant code changes as we move toward a 4.0.0 final release.

There's still some work to do on the OpenGLContext release, but the teaser image above should give you an idea where it's going. It's a direct render of the Khronos sample asset A Beautiful Game

July 12, 2026 08:55 PM UTC

July 11, 2026


Python⇒Speed

6× faster binary search: from compiled code to mechanical sympathy

How do you speed up computational Python code? A common, and useful, starting point is:

  1. Pick a good algorithm.
  2. Use a compiled language to write a Python extension.
  3. Maybe add parallelism so you can use multiple CPU cores.

But what if you need more speed? Consider the following real problem, one of the steps in scikit-learn’s gradient histogram boosting algorithm:

scikit-learn implements this by splitting up the full range of float values into 255 buckets, creating a sorted array of bucket boundaries, and then using binary search to choose the appropriate bucket for each value. The binary search is implemented in a compiled language, and it can run in parallel on multiple cores.

Recently, as part of my work at Quansight, and inspired by two posts by Paul Khuong, I sped up this implementation significantly. How? By making sure the code wasn’t fighting against the CPU.

In this article I’m going to walk you through that speed-up, demonstrated on a simplified example. Then I’m going to demonstrate a series of additional optimizations, with the final version running 6× faster than the original one.

It’s worth knowing that I will be speeding through mentions of many different low-level hardware topics: instruction-level parallelism, branch (mis)prediction, memory caches, SIMD, and more. This is only one article, it can only briefly introduce you to what’s possible, it can’t function as an in-depth tutorial. So I’ll talk about how you can learn more about these topics at the end of the article.

Read more...

July 11, 2026 12:00 AM UTC

July 10, 2026


Mike Driscoll

An Intro to Spiel – Creating Presentations in Your Terminal with Python

Have you ever wanted to create a presentation in your computer’s terminal? While this is an uncommon need, a clever open source developer has provided a solution to this problem! The project is called Spiel, and while it is currently archived, the idea is pretty cool. Spiel uses the Rich package to create the slides for your presentation. Note: while the GitHub page doesn’t explain why the project is archived, it appears to use a very old version of Textual which cannot be upgraded.

Let’s spend a little time learning how this all works.

Installing Spiel

According to the Spiel GitHub page, you can try Spiel without even installing it if you have docker installed. Here’s how to try Spiel:

$ docker run -it --rm ghcr.io/joshkarpel/spiel

However, for the purposes of this tutorial, you really should install Spiel. To do that, you will be using pip. Open up your terminal and run the following:

pip install spiel

Feel free to create a Python virtual environment first if you don’t want to install Spiel into your global Python packages.

Once you have Spiel installed, you can check that it is working by running the Spiel demo, like this:

spiel demo present

If that works, you are good to go!

Creating Your Presentation

The documentation gives a good example of how to create a one-slide presentation. Here’s their example:

from rich.console import RenderableType

from spiel import Deck, present

deck = Deck(name="Your Deck Name")


@deck.slide(title="Slide 1 Title")
def slide_1() -> RenderableType:
    return "Your content here!"


if __name__ == "__main__":
    present(__file__)

According to the documentation, there are two ways to add slides:

Here is a more complete example that creates a couple of custom slides:

from rich.align import Align
from rich.console import RenderableType
from rich.style import Style
from rich.text import Text
from spiel import Deck, Slide, present


def make_slide(
    title_prefix: str,
    text: Text,
) -> Slide:
    def content() -> RenderableType:
        return Align(text, align="center", vertical="middle")
    return Slide(title=f"{title_prefix} Slide", content=content)

deck = Deck("Test Deck")

title_slide = make_slide(title_prefix="First", text=Text("Python 101 - All About Lists",
                                             style=Style(color="blue")))

intro_slide = make_slide(title_prefix="Second",
                    text=Text("A Python list is",
                              style=Style(color="red"))
                    )

deck.add_slides(title_slide, intro_slide)

if __name__ == "__main__":
    present(__file__)

When you run this code in your terminal, you will see something like this:

Spiel slide example

You can move to the next or previous slide using the arrow keys on your keyboard. If you want to exit, press CTRL+C.

Wrapping Up

Spiel seems like a neat package. It’s a shame that it is currently archived. Hopefully, the author will reopen it at some point, or someone else will pick up the torch. In the meantime, you can easily use it in a Python virtual environment and give it a try.

 

 

The post An Intro to Spiel – Creating Presentations in Your Terminal with Python appeared first on Mouse Vs Python.

July 10, 2026 03:13 PM UTC


Talk Python to Me

#554: Trustworthy AI in Healthcare and Longevity

You ask an AI a question and it answers with total confidence. Most of the time, a confidently wrong answer is just an annoyance. But what if the question is medical, and there's a real patient on the other end? In that world, a hallucination isn't a bug, it's a patient-safety event. Sumit Gundawar is a London-based software engineer who builds the clinical platform for a UK longevity and aesthetic-medicine clinic, and his whole argument is that in high-stakes AI, the model is the easy part. Earning trust is the real engineering. We dig into grounding, refusal logic, human-in-the-loop design, and the messy frontier of longevity and biohacking, plus a live demo of an assistant that refuses to answer when it can't back up the claim. Let's get into it.<br/> <br/> <strong>Episode sponsors</strong><br/> <br/> <a href='https://talkpython.fm/sixfeetup'>Six Feet Up</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>Sumit Gundawar</strong>: <a href="https://www.linkedin.com/in/sumit-gundawar-759470129/?featured_on=talkpython" target="_blank" >linkedin.com</a><br/> <br/> <strong>Course transcripts announcement</strong>: <a href="https://talkpython.fm/blog/posts/announcing-german-subtitles-on-courses/" target="_blank" >talkpython.fm/blog</a><br/> <br/> <strong>Sumit Gundawar - JAX London Speaker</strong>: <a href="https://jaxlondon.com/speaker/sumit-gundawar/?featured_on=talkpython" target="_blank" >jaxlondon.com</a><br/> <strong>Anthropic</strong>: <a href="https://anthropic.com/?featured_on=talkpython" target="_blank" >anthropic.com</a><br/> <strong>OpenAI Platform</strong>: <a href="https://platform.openai.com/?featured_on=talkpython" target="_blank" >platform.openai.com</a><br/> <strong>Anthropic</strong>: <a href="https://anthropic.com/?featured_on=talkpython" target="_blank" >anthropic.com</a><br/> <strong>LangChain</strong>: <a href="https://langchain.com/?featured_on=talkpython" target="_blank" >langchain.com</a><br/> <strong>OWASP</strong>: <a href="https://owasp.org/?featured_on=talkpython" target="_blank" >owasp.org</a><br/> <strong>Pydantic</strong>: <a href="https://pydantic.dev/?featured_on=talkpython" target="_blank" >pydantic.dev</a><br/> <strong>EU AI Act - Regulatory Framework</strong>: <a href="https://digital-strategy.ec.europa.eu/en/policies/regulatory-framework-ai?featured_on=talkpython" target="_blank" >digital-strategy.ec.europa.eu</a><br/> <strong>HIPAA - HHS</strong>: <a href="https://www.hhs.gov/hipaa?featured_on=talkpython" target="_blank" >www.hhs.gov</a><br/> <strong>NHS</strong>: <a href="https://www.nhs.uk/?featured_on=talkpython" target="_blank" >www.nhs.uk</a><br/> <strong>Llama</strong>: <a href="https://llama.com/?featured_on=talkpython" target="_blank" >llama.com</a><br/> <strong>Qwen - QwenLM on GitHub</strong>: <a href="https://github.com/QwenLM?featured_on=talkpython" target="_blank" >github.com</a><br/> <strong>OpenAI Platform</strong>: <a href="https://platform.openai.com/?featured_on=talkpython" target="_blank" >platform.openai.com</a><br/> <strong>Hugging Face</strong>: <a href="https://huggingface.co/?featured_on=talkpython" target="_blank" >huggingface.co</a><br/> <strong>Llama</strong>: <a href="https://llama.com/?featured_on=talkpython" target="_blank" >llama.com</a><br/> <strong>Granola</strong>: <a href="https://www.granola.ai/?featured_on=talkpython" target="_blank" >www.granola.ai</a><br/> <strong>HIPAA - HHS</strong>: <a href="https://www.hhs.gov/hipaa?featured_on=talkpython" target="_blank" >www.hhs.gov</a><br/> <strong>CodeRabbit</strong>: <a href="https://www.coderabbit.ai/?featured_on=talkpython" target="_blank" >www.coderabbit.ai</a><br/> <strong>Cursor Origin</strong>: <a href="https://cursor.com/origin?featured_on=talkpython" target="_blank" >cursor.com</a><br/> <strong>GitHub Status</strong>: <a href="https://www.githubstatus.com/?featured_on=talkpython" target="_blank" >www.githubstatus.com</a><br/> <strong>Midjourney Medical</strong>: <a href="https://www.midjourney.com/medical?featured_on=talkpython" target="_blank" >www.midjourney.com</a><br/> <strong>Neko Health</strong>: <a href="https://www.nekohealth.com/?featured_on=talkpython" target="_blank" >www.nekohealth.com</a><br/> <strong>CERN</strong>: <a href="https://home.cern/?featured_on=talkpython" target="_blank" >home.cern</a><br/> <strong>ATLAS Experiment</strong>: <a href="https://atlas.cern/?featured_on=talkpython" target="_blank" >atlas.cern</a><br/> <br/> <strong>Watch this episode on YouTube</strong>: <a href="https://www.youtube.com/watch?v=pp2v9paEoq4" target="_blank" >youtube.com</a><br/> <strong>Episode #554 deep-dive</strong>: <a href="https://talkpython.fm/episodes/show/554/trustworthy-ai-in-healthcare-and-longevity#takeaways-anchor" target="_blank" >talkpython.fm/554</a><br/> <strong>Episode transcripts</strong>: <a href="https://talkpython.fm/episodes/transcript/554/trustworthy-ai-in-healthcare-and-longevity" 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>

July 10, 2026 05:10 AM UTC

July 09, 2026


EuroPython

Humans of EuroPython: Daria Linhart Grudzień

EuroPython wouldn&apost exist without the wonderful volunteers who pour countless hours into organising it. From contracting the venue to selecting and confirming talks and workshops, hundreds of hours of loving work go into making each edition the best one yet.

Join us in celebrating one of the humans behind the keyboard. Today, we&aposre delighted to share an interview with Daria Linhart Grudzień, our Communications Lead.

Thank you for being the voice of the EuroPython community, Daria!

alt

EP: What first inspired you to volunteer for EuroPython? And which edition of the conference was it?

I got pulled into the team in 2025, tempted with a chance to work with a friend on organising an event for juniors in tech in Czechia, which became the Beginners Day Unconference. I appreciated that a major European conference offered a program for the local community.

EP: Did you make any lasting friendships or professional connections through volunteering?

Lots! The EuroPython team is full of kind and fun people who like to do interesting things in their free time. Being a member of the core organising team gave me a chance to get to know a lot of folks. For the first time I feel like I’m going to the conference to meet up with friends.

EP: What was your primary role as a volunteer, and what did a typical day of contributing look like for you?

After doing the Humans of EuroPython interviews during the winter, I got invited to lead the Communications Team for the 2026 edition. My days include a variety of tasks,which I love. From building a productive team, working on finding media partners, occasional web development, co-ordinating with other teams, building documentation for the next edition, to making sure folks in the team enjoy contributing - I do what’s needed to make sure EuroPython speaks to our community with a friendly, slightly quirky, but always inclusive voice.

EP: Was there a moment when you felt your contribution really made a difference? 

There were a few. Some of the core Python developers reached out to me personally saying that the Communications Team is doing a great job. Seeing our social media posts engage and resonate with the community is another reminder that our work is making an impact.

EP: Would you volunteer again, and why?

Absolutely. Contributing to EuroPython, I feel empowered to bring ideas, experiment, and work on impactful initiatives which benefit the community. I’ve been able to take on roles and projects which allowed me to learn, get out of my comfort zone, and grow. I hope to do more of that in the future, and this is a fantastic group of people to do this with.

EP: If you could describe the volunteer experience in three words, what would they be?

Ownership. Impact. Collaboration.

EP: Did you have any unexpected or funny experiences at EuroPython?

I got invited to talk about the conference on the Real Python Podcast. This wasn’t on my bingo card for this year 🙂

July 09, 2026 05:05 PM UTC


Python Software Foundation

The PSF D&I Workgroup Are Starting Office Hours in July!




Starting Tuesday 28 July, 2026, the PSF Diversity & Inclusion (D&I) Workgroup is opening its virtual doors once a month on Discord. Come chat with workgroup members from all over the world!

Doing diversity and inclusion work in tech can feel isolating sometimes. You might be organizing a meetup, writing a code of conduct, trying to get funding for your community, or helping people feel welcome, often in your spare time, and wondering if anyone else is wrestling with the same things.

They are. We are! And we would love to get all of us in the same room.

This July, the PSF D&I Workgroup will be hosting monthly office hours within Discord. These will be open, text-based conversations where we encourage you to ask questions, sha
re what you are working on, and connect with other people who care about making the Python community more welcoming.

The details

The PSF D&I Office Hours will be on the last Tuesday of every month. Because our community is spread across the globe, we will alternate between two times so we can cover as many time zones as possible:

  • 1 PM UTC / 9 AM US Eastern

  • 9 PM UTC / 5 PM US Eastern

Our first session will be on Tuesday, 28 July 2026 at 1 PM UTC. Here is roughly what that looks like around the world:

Region

Local time on 28 July

US Pacific, Los Angeles – (UTC-7h)

6:00 AM

US Eastern, New York – (UTC-4h)

9:00 AM

Brazil, São Paulo – (UTC-3h)

10:00 AM

UTC

1:00 PM

West Africa, Lagos – (UTC+1h)

2:00 PM

Central Europe, Amsterdam / Berlin / Madrid – (UTC+2h)

3:00 PM

East Africa, Nairobi – (UTC+3h)

4:00 PM

Iran, Tehran – (UTC+3:30h)

4:30 PM

India, New Delhi – (UTC+5:30h)

6:30 PM

China, Beijing – (UTC+8h)

9:00 PM

Japan, Tokyo – (UTC+9h)

10:00 PM

Australia, Sydney – (UTC+10h)

11:00 PM

If 6 AM in Los Angeles or 11 PM in Sydney made you wince, do not worry. The August session will be at 9 PM UTC, and we will keep alternating from there.

You will find us in the #psf-diversity channel on the PSF Discord. If you’re new to Discord, check out some Discord Basics to help you get started. 

What will we talk about

Honestly? Whatever is on your mind related to Python, your communities, and D&I.

Since our workgroup exists to advise the PSF on diversity and inclusion, some conversations we are especially hoping to have include:

  • Ideas for policies, initiatives, and grant proposals to diversify the PSF missions. Feedback from the community about these topics will help the PSF D&I Workgroup provide recommendations to the PSF Board of Directors.

  • Your feedback, plain and simple. We want to understand how the PSF can better serve and grow a diverse membership, and we cannot do that without hearing from the community itself.

  • How things are actually going. Part of our job is measuring and sharing the PSF’s progress on its diversity initiatives, and we would rather do that in conversation with you than in a report nobody reads. We also want to understand and learn about the current state of Python communities around the world.

No camera, no mic, no pressure

Office hours are text chat only.

Show up in your pajamas, join from the bus, lurk quietly for the first twenty minutes. It is all fine.

And if you cannot make it at all, the conversation stays in the channel, so you can catch up later when it suits you. If something in the chat sparks a thought you would like to share with us directly, you are always welcome to email the workgroup at diversity-inclusion-wg@python.org.

Bring your own language

Because we are the D&I Workgroup, our members come from around the world! Alongside the main conversation, we will open threads in other languages where possible. Depending on the presence of our members, we would be happy to chat in Spanish, Portuguese, Chinese, Hindi, French or even Persian! Let us know during the office hour if you have a specific language you hope to converse in, or jump in with whichever language thread feels like home.

See you on the 28th!

The first office hour session is on Tuesday, 28 July 2026 at 1 PM UTC, in #psf-diversity on Discord.

Come say hi, even if it is just to tell us what you are working on with Python. We are really looking forward to meeting you!





July 09, 2026 02:11 PM UTC

July 08, 2026


Django Weblog

Last Call 2026 Django Developer Survey

Time is running out. This is the last call for the 2026 Django Developers Survey, which the Django Software Foundation is running in partnership with JetBrains.

The survey closes on July 13, 2026. It is one of the best measures we have of how Django is used, and it helps guide future technical and community decisions.

So far, over 3,100 people have responded, and we would love to push that number past 4,000. Every response helps us better understand the Django community.

This year's survey was shaped by the Django Steering Council, the Django Fellows, the Django Software Foundation Board of Directors, and several community members. Your feedback helps us understand your needs, see how you use Django, and plan for future development and community needs.

How you can help

Once you’ve done the survey, take a moment to re-share on socials and with your communities. The more diverse the answers, the better the results for all of us. We appreciate everybody helping to get the word out.

Please use the following links:

For more details, read the original announcement on the Django blog.

July 08, 2026 07:31 PM UTC


Mike Driscoll

New Book Release: Python Typing

I am happy to announce that my latest book, Python Typing, is now available on all platforms. You can get your copy on Gumroad or Leanpub or Amazon

Python has had type hinting support since Python 3.5, over TEN years ago! However, Python’s type annotations have changed repeatedly over the years. In Python Typing: Type Checking for Python Programmers, you will learn all you need to know to add type hints to your Python applications effectively.

You will also learn how to use Python type checkers, configure them, and set them up in pre-commit or GitHub Actions. This knowledge will give you the power to check your code and your team’s code automatically before merging, hopefully catching defects before they make it into your products.

Python Typing Book Cover

What You’ll Learn

You will learn all about Python’s support for type hinting (annotations). Specifically, you will learn about the following topics:

Where to Purchase

The post New Book Release: Python Typing appeared first on Mouse Vs Python.

July 08, 2026 06:46 PM UTC


Hugo van Kemenade

Fixing the dictionary with Python 3.14

Yes, but not the dict kind of dictionary.

When working on CPython, we often find obscure bugs elsewhere, in compilers, operating systems and elsewhere:

Since Python 3.8, the release notes have a section called “And now for something completely different”. These have included Monty Python sketches, astrophysics facts and poetry.

For Python 3.14, I’m doing all things π, pie and [mag]pie (more here). As part of the research for this important task, I looked up pi in the Oxford English Dictionary.

As we all recall from the Python 3.14.0b1 release notes, William Jones was the first person to use the π symbol to denote the circle’s circumference to its diameter in his Synopsis Palmariorum Matheseos (1706):

In the Circle, the Diameter is to Circumference as 1 to 16/5 - 4/239 - 1/3 15/(53) - 4/(2393) + 1/5 16/(55) - 4/(2395) -, &c. = 3.1459, &c. = π. This Series..I receiv’d from the Excellent Analyst..Mr. John Machin; and by means thereof, Van Ceulen’s Number..may be Examin’d.

However, the OED’s first citation had a markup bug:

As previous image, but: 1 to Misplaced &, where Misplaced and the ampersand are red on yellow background

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

As previous image, but the sequence is now fixed

Hooray!


Header photo: Part of the definition for “get” in the OED’s 1901 forerunner, A New English Dictionary on Historical Principles (CC BY-NC-SA 2.0 Hugo van Kemenade).

July 08, 2026 04:40 PM UTC


Marc-André Lemburg

My 25th EuroPython - in a row😊

My 25th EuroPython - in a row😊

Next weekend, I&aposll be heading to Kraków, Poland, for my 25th EuroPython conference.

It&aposs been a long ride since the first EuroPython conference in Charleroi, Belgium, but one I wouldn&apost have wanted to miss.

This year, I&aposll be giving a talk about DuckLake, an extension to DuckDB, one of the most exciting new database systems in the last few years.

Come join in.

Cheers,
Marc-André

July 08, 2026 03:48 PM UTC


Python GUIs

Why Widgets Appear as Separate Windows — Understanding widget parenting in Qt and how to fix widgets that float outside your main window

Sometimes when I dynamically add widgets to tabs in my PyQt6 application, they pop out as windows instead. What's going on?

If you're dynamically adding widgets to your PyQt6 application and finding that they pop out as separate floating windows instead of appearing neatly inside your application, you're running into one of Qt's gotchas: widget parenting.

This problem usually shows up when widgets are added from a callback, event listener or signal handler. But there are a million different ways to screw this up. Let's look at why this happens and how to fix it.

How Qt decides what's a window

In Qt, every widget can optionally have a parent widget. The parent determines where a widget lives visually — a widget with a parent is drawn inside that parent. A widget without a parent becomes a top-level window, floating independently on your desktop.

This is the root cause of widgets appearing outside your main window. When you create a widget and it doesn't have a parent — either because you didn't set one, or because the parent was lost somehow — Qt treats it as a standalone window.

Three ways to Get a Parent-less Widget

Here are the most common reasons widgets end up floating:

Creating widgets without a parent

python
# This widget has no parent — it will be a floating window
tabs = QTabWidget()

# This widget has a parent — it will appear inside parent_widget
tabs = QTabWidget(parent_widget)

When you add a widget to a layout, the layout assigns the parent automatically. But if something goes wrong between creation and layout insertion (like an exception, or the widget being shown prematurely), the widget stays parentless.

The safest approach is to pass a parent when creating widgets:

python
def create_new_tab(self):
    wdg = QWidget()
    layout = QGridLayout(wdg)

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

    return wdg

...although, honestly, I don't usually bother. If I know I'll be adding a widget to a layout immediately, I'll omit the parent assignment.

In an window __init__ the safety question is less relevant because, if there is an unhandled exception that blocks the adding your sub-widget to a layout, it will also block the creation of the parent window.

Accidentally recreating a widget

If you have a tab widget stored as self.w and somewhere in your code you do:

python
self.w = QTabWidget()

...the original tab widget is replaced. If the old widget gets garbage collected, all the tabs that had it as their parent suddenly become orphans — parentless widgets that float as independent windows.

Be careful not to reassign widget attributes unintentionally, especially in callbacks that might run multiple times.

Losing the parent reference

If you explicitly set a widget's parent to None, it becomes a top-level window:

python
widget.setParent(None)  # This widget is now a floating window

This sometimes happens indirectly. For example, removing a widget from a layout in certain ways can clear its parent.

A clean approach to dynamic tabs

Here's a complete, working example that dynamically adds tabs without any floating-window issues. It demonstrates the correct way to set up a QTabWidget with a "+" button that adds new tabs:

python
import sys
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QTabWidget,
    QWidget, QVBoxLayout, QLabel
)


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

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

        # Add an initial tab
        self.add_content_tab("Tab 1")

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

        self.setCentralWidget(self.tabs)

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

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

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

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

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

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

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


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

A few things to notice in this example:

Summary

Widget parenting is one of those things in Qt that works invisibly when everything is correct — and causes confusing visual glitches the moment something is slightly off. The good news is that once you understand the pattern, the fix is almost always the same: make sure every widget has a parent.

If you're new to PyQt6, our guide to creating your first window covers the basics of setting up a QMainWindow, while the widgets tutorial walks through the most common widgets and how to use them correctly.

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

July 08, 2026 06:00 AM UTC