skip to navigation
skip to content

Planet Python

Last update: April 21, 2026 09:44 PM UTC

April 21, 2026


PyCoder’s Weekly

Issue #731: Visualize ML, Vector DBs, Type Checker Comparison, and More (April 21, 2026)

#731 – APRIL 21, 2026
View in Browser »

The PyCoder’s Weekly Logo


Machine Learning Visualized

This is a series of Jupyter notebooks that help visualize the algorithms that are used in machine learning. Learn more about neural networks, regression, k-means clustering, and more.
GAVING HUNG

Vector Databases and Embeddings With ChromaDB

Learn how to use ChromaDB, an open-source vector database, to store embeddings and give context to large language models in Python.
REAL PYTHON course

Wallaby for Python runs Tests as you Type and Streams Results Next to Code, Plus AI Context

alt

Wallaby brings pytest / unittest results, runtime values, coverage, errors, and time-travel debugging into VS Code, so you can fix Python faster and give Copilot, Cursor, or Claude the execution context they need to stop guessing. Try it free, now in beta →
WALLABY TEAM sponsor

Python Type Checker Comparison: Speed and Memory Usage

A benchmark comparison of speed and memory usage across Python type checkers including Pyrefly, Ty, Pyright, and Mypy.
AARON POLLACK

PEP 831: Frame Pointers Everywhere: Enabling System-Level Observability for Python (Draft)

This PEP proposes two things:
PYTHON.ORG

PEP 800: Solid Bases in the Type System (Accepted)

PYTHON.ORG

PEP 772: Packaging Council Governance Process (Accepted)

PYTHON.ORG

PEP 832: Virtual Environment Discovery (Draft)

PYTHON.ORG

PEP 830: Add Timestamps to Exceptions and Tracebacks (Draft)

PYTHON.ORG

Discussions

Reverting the Incremental GC in Python 3.14 and 3.15

PYTHON.ORG

Articles & Tutorials

Reassessing the LLM Landscape & Summoning Ghosts

What are the current techniques being employed to improve the performance of LLM-based systems? How is the industry shifting from post-training towards context engineering and multi-agent orchestration? This week on the show, Jodie Burchell, data scientist and Python Advocacy Team Lead at JetBrains, returns to discuss the current AI coding landscape.
REAL PYTHON podcast

Security Best Practices Featuring uv and pip

This collection of security practices explains how to best use your package management tools to help avoid malicious packages. Example: implement a cool-down period; most malicious packages are found quickly, by not installing on the day of a release your chances of getting something bad go down.
GITHUB.COM/LIRANTAL

Beyond Basic RAG: Build Persistent AI Agents

Master next-gen AI with Python notebooks for agentic reasoning, memory engineering, and multi-agent orchestration. Scale apps using production-ready patterns for LangChain, LlamaIndex, and high-performance vector search. Explore & Star on GitHub →
ORACLE sponsor

The Economics of Software Teams

Subtitled “Why Most Engineering Organizations Are Flying Blind”, this article is a breakdown of what software development teams actually cost, what they need to generate to be financially viable, and why most organizations have no visibility into either number.
VIKTOR CESSAN

OWASP Top 10 (2025 List) for Python Devs

The OWASP Top 10 is a list of common security vulnerabilities in code, like SQL injection. The list has recently been updated and Talk Python interviews Tanya Janca to discuss all the big changes this time around.
TALK PYTHON podcast

Textual: An Intro to DOM Queries

The Textual TUI framework uses a tree structure to store all of the widgets on the page. This DOM is query-able, giving you the ability to find widgets on the fly in your code.
MIKE DRISCOLL

Reflecting on 5 Years as the Developer in Residence

Łukasz Langa is stepping down as the Python Software Foundation’s first CPython Developer in residence. This post talks about his experience there and everything accomplished.
PYTHON SOFTWARE FOUNDATION

Decoupling Your Business Logic From the Django ORM

Where should I keep my business logic? This is a perennial topic in Django. This article proposes a continuum of cases, each with increasing complexity.
CARLTON GIBSON

How to Add Features to a Python Project With Codex CLI

Learn how to use Codex CLI to add features to Python projects via the terminal. Master AI-powered coding without needing a browser or IDE plugins.
REAL PYTHON

PyPI Has Completed Its Second Audit

PyPI has completed its second external security audit. This post shows all the things found and what they’re doing about each of them.
MIKE FIEDLER

New Technical Governance: Request for Community Feedback

The Django Steering Council has proposed new governance mechanism and is looking for feedback from the community.
DJANGO SOFTWARE FOUNDATION

Projects & Code

django-modern-rest: REST With Types and Async Support

GITHUB.COM/WEMAKE-SERVICES

django-freeze: Convert Django Sites to Static Ones

GITHUB.COM/FABIOCACCAMO

spy: SPy the Compilable Python-Like Language

GITHUB.COM/SPYLANG

prettytable: Tabular Data in a Visually Appealing ASCII

GITHUB.COM/PRETTYTABLE

endcord: Feature Rich Discord TUI Client

GITHUB.COM/SPARKLOST

Events

Weekly Real Python Office Hours Q&A (Virtual)

April 22, 2026
REALPYTHON.COM

The Carpentries

April 22 to April 24, 2026
INSTATS.ORG

AgentCamp Amsterdam 2026

April 23, 2026
MEETUP.COM

North Bay Python 2026

April 25 to April 27, 2026
NORTHBAYPYTHON.ORG

Python Sheffield

April 28, 2026
GOOGLE.COM


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

April 21, 2026 07:30 PM UTC


Tryton News

Tryton Release 8.0

We are proud to announce the 8.0 LTS release of Tryton.
This release provides many bug fixes, performance improvements and some fine tuning.
You can give it a try on the demo server, use the docker image or download it here.
As usual upgrading from previous series is fully supported.

Here is a list of the most noticeable changes:

Changes for the User

Client

There is now a visual hint on the widgets of modified fields. This way the user can see what was modified before saving.

Web

The tabs can now be reordered.

The logout action has been moved in the same menu as the notification. And an entry for the help was also added. This simplifies visually the content of the header.

Accounting

The reconciliation of accounting lines can now be automated by a scheduled task.

The general ledger display only flat balance which is more comprehensive for a flat list of accounts.

We added the option to calculate rounding for cash using the opposite rounding method. This existed already for standard currency rounding but was missing for cash rounding.

It is now possible to define some payment means on the invoice.
They can be set manually or using a rule engine.
The supported payment means for now are by bank transfer and direct debit.

When invoices are created from an external source (like PEPPOL), we check the amounts against the source when the invoice is validated or posted.
This allows to create invoices for which the calculation is different in Tryton than in the source and let the user manually correct them.

Tryton warns now when the user tries to create an over-payment.

Europe

We added all the VAT exception code on the taxes which can be used in electronic invoices.

Belgium

The taxes are now setup with UNECE and VATEX codes which is useful to generate and parse UBL invoice like on PEPPOL.

Document Incoming

The OCR module supports now the payment reference of the supplier. It is stored on the supplier invoice and used when making a payment.

E-document

We added a button on the PEPPOL document to trigger an update of the status. The users do not always want to wait for the scheduled task to update the status.

We enforce that the unit price of any invoice lines sent to PEPPOL is not negative. This is a rule from the PEPPOL network that is better to enforce before posting the invoice.

The UBL invoice template has been extended to render the buyer’s item identification, the allowance and charges, the billing reference, the payment means, VATEX codes and prepaid amounts.
The UBL invoice parser supports now the payment means.

The UN/CEFACT invoice template renders the payment means and the VATEX codes.

The UNECE module stores now the allowance, charge and special service code on the product. And it stores on the payment means the UNCL4461 code.

Incoterm

The incoterm defines now who is responsible for the export and import duties between the buyer and the seller.

Party

These identifiers have been added: Slovenian Corporate Registration Number, Belgian Social Security Number, Spanish Activity Establishment Code, Russian Primary State Registration Number, Mozambique Tax Number, French Trade Registration Number, Azerbaijan Tax Number and Senegal Tax Number.

The identifiers are now formatted to ease the reading.

We added a new menu entry that lists all the party identifiers.

A “Attn” field has been added to the address. This is useful to manage delivery addresses for web shop when the customer is shipping to another party.

Production

The “Cancellation Stock” group can now also cancel running and done productions.

It is not possible to define if the cost from the time sheets must be included in the production cost calculation. This is defined per work center.

Project

The work efforts are now numbered to ease the communication between employees.

An origin field has been added to the work efforts.

Purchasing

The invoice method “on shipment” has been renamed into “on fulfillment” to be more generic.

The quantities to invoice are now calculated for each purchase line. And purchases with at least one line to invoice is marked as “To invoice”.

Quality

We added a reference field to the inspections. This allows to store an external number if the inspection was performed by an external service.

Sales

The invoice method “on shipment” has been renamed into “on fulfillment” to be more generic.

The quantities to ship and to invoice are now calculated for each sale line. And sales with at least one line to ship or to invoice is marked as “To ship” or “To invoice”.

Stock

We added a special group which is allowed to cancel done shipments and moves. This is useful to correct mistakes.

The shipments have now a wizard to ease the creation of package. It simplifies the operation like putting a package inside another package, putting only a quantity of a move into a package etc.

For UPS carrier, the module charges the duties and taxes to the shipper accordingly to the incoterm.

We store now the original planned date of the requested internal shipments and the requested production. This is useful to find late requests.

Shop

The sales stores now the URL of the corresponding order to the web shop.

Shopify

We support now the payment terms from Shopify. When a sale has a payment term, it is always confirmed in Tryton.

The gift cards are now supported with Shopify. So when a gift card is sold on Shopify, no gift card is created on Tryton. And when the gift card is used on Shopify, it appears as a payment from the gift_card gateway.

The actions from Shopify are now logged on the sale order.

The pick-up delivery method is now supported for Shopify order. When the shipment is packed on Tryton, it is marked as prepared for pickup on Shopify.

New Modules

Account Payment Check

The Account Payment Check Module allows managing and printing checks as payments.

Account Stock EU Excise

The Account Stock EU Excise Module is used to generate the excise duties declaration for European countries.

Production Ethanol

The Production Ethanol Module calculates the gain or loss of alcohol volumes in production.

Sale Project Task Module

The Sale Project Task Module adds the option to create tasks when selling services. The fulfillment of the sales is linked to the progression of these tasks.

Stock Ethanol Module

The Stock Ethanol Module helps to track alcohol in warehouses.

Removed Modules

Those modules have been removed:

You may find alternatives published by the community.

Changes for the System Administrator

Client

Web

The build of the web client does not require bower anymore.

The session is now stored as cookie. This prevents the session to be leaked in case of security issue with our Javascript code.

The web client uses now relative path to perform the server requests. This allows to serve the web client from a sub-directory.

Server

Basic authentication is now supported also for user application. This is useful when the consumer of the user application can not support bearer authentication.

Document Incoming

The Typless modules requires now to define all the fields set on the service in order to generate a complete feedback even for fields that Typless did not recognized.

E-document

It is now possible to setup a webhook for Peppyrus. This allows to receive the PEPPOL invoices as soon as they landed on the inbox.

Inbound Email

The inbound email gains an action to handle replies to chat channels. The text content above a specific line is added as message to the corresponding channel.

Changes for the Developer

This release removes the support for Python 3.9 and adds Python 3.14.

Server

It is now possible to filter the user being notified by a scheduled task using a domain. This is useful for example when the notification applied only to users having access to a specific company.

The report engine can now use MJML as base format and convert it to HTML. This simplifies the creation of email template with compatibility against most common email clients.

The Field.sql_column receives now a tables dictionary and Model as argument.
The Field.sql_column can now be override by a method on the Model named column_<field name>(tables).
This extends the possibilities for more complex type of fields.
With those improvements, we can not support Function fields without getter but only SQL expression via the column_<field name>. Those fields are automatically searchable and sortable without the need to define domain_<field name> nor order_<field name> methods.

A last_modified field has been added to ModelSQL to avoid to duplicate the code write_date or create_date.

The fmany2one field can now be based on Function field.

We have upgraded the PostgreSQL backend to use Psycopg 3. By default Tryton is using server-side binding which allows to remove the limitation on the size of the list of IDs that can be passed by using array for the in operators.
Thus the reduce_ids and grouped_slice (without size) tools has been deprecated and the Database.IN_MAX replaced by backend.MAX_QUERY_PARAMS.

The delete and delete_many methods have been added to the FileStore API which allows to remove the files of Binary fields when they are deleted or updated.

The button states are now checked when executed with check for access. This ensure that a client can not execute a button that should be disable.

A notify_user method has been added to ModelStorage to ease the notification
of a user.

A contextual _log key can be used to force the logging of events even if they do not originate from a user.

New routes have been added to manage the login/logout with cookie.

It is now possible to include sub-directories in the tryton.cfg. This is useful when developing large module to split it into sub-directories.

A new attribute in the XML data allows to define a value as a path relative to the place of the XML file. This feature works in combination with the sub-directories to avoid to repeat the directory name.

The chat channel now send new messages by email new messages to followers who subscribed with emails.

It is now possible to mount the WSGI application under a prefix.

The RPC calls are not prefixed by /rpc/.

A generic REST API has been added as a user application.
It allows to search, retrieve, update and delete any record of a ModelStorage with the access right enforced for the user and to launch any RPC action and report.
The ModelStorage.__json__ method defines the default fields to include in the response based on usage but the client can also explicitly request the fields (with dotted notation).
The context is passed as value of the X-Tryton-Context header encoded in JSON. The language is selected from the Accept-Language header of the client. And the search can be paginated using the Range header.

Naiad

Naiad is a new Python library to access Tryton’s REST API.

Accounting

We removed the default value for the invoice’s type. It must be set explicitly.

An origin invoices field has been added to the invoice.

The Stripe payment module uses now the version 2025-09-30.clover of the API.

E-document

The UBL template filters now the additional documents per MIME type.

Web Shop

Shopify

We replaced the unmaintained ShopifyAPI library by the new shopifyapp.

1 post - 1 participant

Read full topic

April 21, 2026 04:00 PM UTC


Real Python

Leverage OpenAI's API in Your Python Projects

Python’s openai library provides the tools you need to integrate the ChatGPT API into your Python applications. With it, you can send text prompts to the API and receive AI-generated responses. You can also guide the AI’s behavior with developer role messages and handle both simple text generation and more complex code creation tasks.

After watching this video course, you’ll understand how examples like this work under the hood. You’ll learn the fundamentals of using the ChatGPT API from Python and have code examples you can adapt for your own projects.


[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]

April 21, 2026 02:00 PM UTC

Quiz: Leverage OpenAI's API in Your Python Projects

In this quiz, you’ll test your understanding of Leverage OpenAI’s API in Your Python Projects.

By working through this quiz, you’ll revisit key concepts like setting up authentication, sending prompts with the openai library, controlling AI behavior with role-based messages, and structuring outputs with Pydantic models.


[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]

April 21, 2026 12:00 PM UTC

Quiz: uv vs pip: Python Packaging and Dependency Management

In this quiz, you’ll test your understanding of uv vs pip: Python Packaging and Dependency Management.

By working through this quiz, you’ll revisit key differences between uv and pip, including package installation speed, dependency management, reproducible environments, and governance.


[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]

April 21, 2026 12:00 PM UTC

April 20, 2026


Real Python

Gemini CLI vs Claude Code: Which to Choose for Python Tasks

When comparing Gemini CLI vs Claude Code, the answer to “which one is better?” is usually it depends. Both tools boost productivity for Python developers, but they have different strengths. Choosing the right one depends on your budget, workflow, and what you value most in generated code.

Gemini CLI, for instance, is known for its generous free tier, while Claude Code is a paid tool known for its production-ready output.

In this tutorial, you’ll explore features such as user experience, performance, code quality, and usage cost to help make that decision easier. The AI coding assistance these tools provide right in your terminal generally makes writing Python code much more seamless, helping you save time and be more productive.

This table highlights the key differences at a glance:

Use Case Gemini CLI Claude Code
You need free generous usage limits
You need Google Cloud integration
You need faster task completion
You need code close to production quality

You can see that Gemini CLI is a promising choice if you’re looking for free usage limits and prefer Google Cloud integration. However, if you want to complete tasks faster, Claude Code has an edge. Both tools produce code of good quality, but Claude Code generates code that is closer to production quality. If you’d like a more thorough comparison, then read on.

Get Your Code: Click here to download the free sample code for the to-do app projects built with Gemini CLI and Claude Code in this tutorial.

Take the Quiz: Test your knowledge with our interactive “Gemini CLI vs Claude Code: Which to Choose for Python Tasks” quiz. You’ll receive a score upon completion to help you track your learning progress:


Interactive Quiz

Gemini CLI vs Claude Code: Which to Choose for Python Tasks

Compare Gemini CLI and Claude Code across user experience, performance, code quality, and cost to find the right AI coding tool for you.

Metrics Comparison: Gemini CLI vs Claude Code

To ground the comparisons in hands-on data, both tools are tested using the same prompt throughout this tutorial:

Prompt

Build a CLI-based mini to-do application in Python. It should allow users to create tasks, mark tasks as completed, list tasks with filtering for completed and pending tasks, delete tasks, include error handling, persist tasks to a local JSON file, and include basic unit tests.

For a fair comparison, Gemini CLI is tested on its free tier using Gemini 3 Flash Preview, which is the default model the free tier provides access to. Claude Code is tested on the Pro plan using Claude Sonnet 4.6, which is the model Claude Code primarily uses for everyday interactions on that plan.

Each tool will run this prompt three times. Completion time, token usage, and the quality of the generated code are recorded from the runs and are referenced in the Performance, Code Quality, and Usage Cost sections of this tutorial.

Note: If you want to learn more about these tools so you can compare them yourself, Real Python has you covered. The How to Use Google’s Gemini CLI for AI Code Assistance tutorial covers installation, authentication, and hands-on usage, while the Getting Started With Claude Code video course walks you through setup and core features.

You should also be comfortable using your terminal, since both Gemini CLI and Claude Code are command-line tools.

The table below provides more detailed metrics to help with each comparison:

Metric Gemini CLI Claude Code
User Experience Intuitive, browser-based auth, terminal-native Minimal setup, terminal-native, strong project awareness
Performance Good performance, however slower generation speed Good performance, code is generated generally faster
Code Quality Solid, better for exploratory tasks Strong, better for production-grade work
Usage Cost Free tier available; paid plans for heavier use Requires a paid subscription to get started

The following sections explore each metric in detail, so you can decide which tool fits your workflow best.

User Experience

When writing Python programs, it helps to be able to comfortably use your tools without dealing with unintuitive interfaces. Both Gemini CLI and Claude Code prioritize a smooth terminal experience, but user experience goes beyond the interface itself—installation, setup, available models, and features offered are also part of it.

Installation and Setup

A few differences exist between Gemini CLI and Claude Code during installation. Gemini CLI requires a Google account for authentication. Claude Code doesn’t need a Google account. Instead, it requires an Anthropic subscription or API key.

Gemini CLI is first installed using npm:

Shell
$ npm install -g @google/gemini-cli

You can also install Gemini CLI with Anaconda, MacPorts, or Homebrew, which you can find in the Gemini CLI documentation.

When installing Claude Code, you run the following commands:

Read the full article at https://realpython.com/gemini-cli-vs-claude-code/ »


[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]

April 20, 2026 02:00 PM UTC


Mike Driscoll

Textual – Logging to File and to Textual Console

When you are developing a user interface, it can be valuable to have a log of what’s going on. Creating a log in Textual, a text-based user interface, is even easier than creating one for wxPython or Tkinter. Why? Well, because Textual includes a logger that is compatible with Python’s own logging module, so it’s almost plug-and-play to hook it all up!

You’ll learn how to do this in this short tutorial!

Logging to File and the Console

Textual includes a built-in logging-type handler that you can use with Python’s own logging module called TextualHandler. Python has many built-in logging handler objects that you can use to write to stdout, a file, or even to an email address!

You can hook up multiple handlers to a logger object and write to all of them at once, which gives you a lot of flexibility.

To see how this works in Textual, you will create a very simple application that contains only two buttons. Go ahead and open your favorite Python IDE or text editor and create a new file called log_to_file.py. Then enter the following code into it:

# log_to_file.py

import logging

from textual.app import App, ComposeResult
from textual.logging import TextualHandler
from textual.widgets import Button


class LogExample(App):

    def __init__(self) -> None:
        super().__init__()
        self.logger = logging.getLogger(name="log_example")
        self.logger.setLevel(logging.INFO)
        file_handler = logging.FileHandler("tui.log")
        self.logger.addHandler(file_handler)
        formatter = logging.Formatter(("%(asctime)s - %(name)s - %(levelname)s - %(message)s"))
        file_handler.setFormatter(formatter)

        textual_handler = TextualHandler()
        self.logger.addHandler(textual_handler)

    def compose(self) -> ComposeResult:
        yield Button("Toggle Dark Mode", classes="dark mode")
        yield Button("Exit", id="exit")

    def on_button_pressed(self, event: Button.Pressed) -> None:
        if event.button.id == "exit":
            self.logger.info("User exited")
            self.exit()
        elif event.button.has_class("dark", "mode"):
            self.theme = (
                "textual-dark" if self.theme == "textual-light" else "textual-light"
            )

            self.logger.info(f"User toggled app theme to {self.theme}")



if __name__ == "__main__":
    app = LogExample()
    app.run()

As you can see, you have just two buttons for the user to interact with:

No matter which button the user presses, the application will log out something. By default, Textual logs to stdout, but you cannot see it because your application will be on screen. If you want to see the logs, you will need to use one of the Textual Console applications, which is part of Textual’s devtools. If you do not have the dev tools installed, you can do so by running this command:

pip install textual-dev

Now that you have the dev tools handy, open up a new terminal window or tab and run this command:

textual console

To get Textual to send the log messages to console, you need to run your Textual application in developer mode. You will run it in a different terminal than Textual Console!

Here’s the special command:

textual run --dev log_to_file.py

You will see various events and other logged metadata appear in the Textual Console regardless of whether you specifically log to it. However, now if you do call self.log or you use Python’s print() function, you will see those appear in your log.

You will also see your log messages in your log file (tui.log), though it won’t include all the extra stuff that Textual Console displays. You only get what you log explicitly written into your log file.

Wrapping Up

And there you have it. You now know how to use Textual’s own built-in logging handler in conjunction with Python’s logging module. Remember, you can use Textual’s logging handler in addition to one or more of Python’s logging modules. You can format the output any way you want too!

Learn More About Logging

If you want to learn more about logging in Python, you might find my book, Python Logging, helpful.

Python Logging book cover

Purchase the book today on GumroadLeanpub or Amazon!

The post Textual – Logging to File and to Textual Console appeared first on Mouse Vs Python.

April 20, 2026 12:40 PM UTC


Real Python

Quiz: How to Conceptualize Python Fundamentals for Greater Mastery

In this quiz, you’ll test your understanding of How to Conceptualize Python Fundamentals for Greater Mastery.

By working through this quiz, you’ll revisit a framework for forming a clear mental picture of Python concepts, including defining ideas in your own words, finding real-world and software analogies, comparing similar concepts, and learning by teaching.

With this framework in hand, you’ll be better equipped to approach new Python topics with confidence.


[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]

April 20, 2026 12:00 PM UTC


Python Bytes

#477 Lazy, Frozen, and 31% Lighter

<strong>Topics covered in this episode:</strong><br> <ul> <li><strong><a href="https://django-modern-rest.readthedocs.io/en/latest/?featured_on=pythonbytes">Django Modern Rest</a></strong></li> <li><strong>Already playing with Python 3.15</strong></li> <li><strong><a href="https://mkennedy.codes/posts/cutting-python-web-app-memory-over-31-percent/?featured_on=pythonbytes">Cutting Python Web App Memory Over 31%</a></strong></li> <li><strong><a href="https://tryke.dev?featured_on=pythonbytes">tryke - A Rust-based Ptyhon test runner with a Jest-style API</a></strong></li> <li><strong>Extras</strong></li> <li><strong>Joke</strong></li> </ul><a href='https://www.youtube.com/watch?v=WmJtmS5Fn7U' style='font-weight: bold;'data-umami-event="Livestream-Past" data-umami-event-episode="477">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 Training</strong></a></li> <li><a href="https://courses.pythontest.com/p/the-complete-pytest-course?featured_on=pythonbytes"><strong>The Complete pytest Course</strong></a></li> <li><a href="https://www.patreon.com/pythonbytes"><strong>Patreon Supporters</strong></a> <strong>Connect with the hosts</strong></li> <li>Michael: <a href="https://fosstodon.org/@mkennedy">@mkennedy@fosstodon.org</a> / <a href="https://bsky.app/profile/mkennedy.codes?featured_on=pythonbytes">@mkennedy.codes</a> (bsky)</li> <li>Brian: <a href="https://fosstodon.org/@brianokken">@brianokken@fosstodon.org</a> / <a href="https://bsky.app/profile/brianokken.bsky.social?featured_on=pythonbytes">@brianokken.bsky.social</a></li> <li>Show: <a href="https://fosstodon.org/@pythonbytes">@pythonbytes@fosstodon.org</a> / <a href="https://bsky.app/profile/pythonbytes.fm">@pythonbytes.fm</a> (bsky) 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>Monday</strong> at 11am PT. Older video versions available there too. Finally, if you want an artisanal, hand-crafted digest of every week of the show notes in email form? Add your name and email to <a href="https://pythonbytes.fm/friends-of-the-show">our friends of the show list</a>, we'll never share it.</li> </ul> <p><strong>Michael #1: <a href="https://django-modern-rest.readthedocs.io/en/latest/?featured_on=pythonbytes">Django Modern Rest</a></strong></p> <ul> <li>Modern REST framework for Django with types and async support</li> <li>Supports Pydantic, Attrs, and msgspec</li> <li>Has ai coding support with llms.txt</li> <li>See an example at the <a href="https://django-modern-rest.readthedocs.io/en/latest/pages/getting-started.html#showcase">“showcase” section</a></li> </ul> <p><strong>Brian #2: Already playing with Python 3.15</strong></p> <ul> <li><a href="https://blog.python.org/2026/04/python-3150a8-3144-31313/?featured_on=pythonbytes">3.15.0a8, 2.14.4 and 3.13.13 are out</a> <ul> <li>Hugo von Kemenade</li> </ul></li> <li>beta comes in May, CRs in Sept, and Final planned for October</li> <li>But still, there’s awesome stuff here already, here’s what I’m looking forward to: <ul> <li><a href="https://docs.python.org/3.15/whatsnew/3.15.html#whatsnew315-lazy-imports"><strong>PEP 810</strong></a>: Explicit lazy imports</li> <li><a href="https://docs.python.org/3.15/whatsnew/3.15.html#whatsnew315-frozendict"><strong>PEP 814</strong></a>: <code>frozendict</code> built-in type</li> <li><a href="https://docs.python.org/3.15/whatsnew/3.15.html#whatsnew315-unpacking-in-comprehensions"><strong>PEP 798</strong></a>: Unpacking in comprehensions with <code>*</code> and <code>**</code></li> <li><a href="https://docs.python.org/3.15/whatsnew/3.15.html#whatsnew315-utf8-default"><strong>PEP 686</strong></a>: Python now uses UTF-8 as the default encoding</li> </ul></li> </ul> <p><strong>Michael #3: <a href="https://mkennedy.codes/posts/cutting-python-web-app-memory-over-31-percent/?featured_on=pythonbytes">Cutting Python Web App Memory Over 31%</a></strong></p> <ul> <li>I cut 3.2 GB of memory usage from our Python web apps using five techniques: <ul> <li>async workers</li> <li>import isolation</li> <li>the Raw+DC database pattern</li> <li>local imports for heavy libraries</li> <li>disk-based caching</li> </ul></li> <li><a href="https://mkennedy.codes/posts/cutting-python-web-app-memory-over-31-percent/?featured_on=pythonbytes">See the full article</a> for details.</li> </ul> <p><strong>Brian #4: <a href="https://tryke.dev?featured_on=pythonbytes">tryke - A Rust-based Ptyhon test runner with a Jest-style API</a></strong></p> <ul> <li>Justin Chapman</li> <li>Watch mode, Native async support, Fast test discovery, In-source testing, Support for doctests, Client/server mode for fast editor integrations, Pretty, per-assertion diagnostics, Filtering and marks, Changed mode (like pytest-picked), Concurrent tests, Soft assertions,</li> <li>JSON, JUnit, Dot, and LLM reporters</li> <li>Honestly haven’t tried it yet, but you know, I’m kinda a fan of thinking outside the box with testing strategies so I welcome new ideas.</li> </ul> <p><strong>Extras</strong></p> <p>Brian:</p> <ul> <li><a href="https://aleyan.com/blog/2026-why-arent-we-uv-yet/?featured_on=pythonbytes">Why are’t we uv yet?</a> <ul> <li>Interesting take on the “agents prefer pip”</li> <li>Problem with analysis. <ul> <li>Many projects are libraries and don’t publish uv.lock file</li> <li>Even with uv, it still often seen as a developer preference for non-libarries. You can sitll use uv with requirements.txt</li> </ul></li> </ul></li> <li><a href="https://us.pycon.org/2026/schedule/talks/?featured_on=pythonbytes">PyCon US 2026 talks schedule is up</a> <ul> <li>Interesting that there’s an AI track now. I won’t be attending, but I might have a bot watch the videos and summarize for me. :)</li> </ul></li> <li><a href="https://justinjackson.ca/tech-done-to-us?featured_on=pythonbytes">What has technology done to us?</a> <ul> <li>Justin Jackson</li> </ul></li> <li><a href="https://courses.pythontest.com/lean-tdd/?featured_on=pythonbytes">Lean TDD new cover</a> <ul> <li>Also, 0.6.1 is so ready for me to start f-ing reading the audio book and get on with this shipping the actual f-ing book and yes I realize I seem like I’m old because I use “f-ing” while typing. Michael:</li> </ul></li> <li><a href="https://docs.python.org/release/3.14.4/whatsnew/changelog.html?featured_on=pythonbytes">Python 3.14.4 is out</a></li> <li><a href="https://github.com/BeanieODM/beanie/releases/tag/2.1.0?featured_on=pythonbytes">Beanie 2.1 release</a></li> </ul> <p><strong>Joke: <a href="https://motherduck.com/humandb/?featured_on=pythonbytes">HumanDB</a> - Blazingly slow. Emotionally consistent.</strong></p>

April 20, 2026 08:00 AM UTC

April 19, 2026


Django Weblog

DSF member of the month - Rob Hudson

For April 2026, we welcome Rob Hudson as our DSF member of the month! ⭐

Rob Hudson's profile, with bamboos behind him. He wear glasses and slightly smiling in front the camera.

Rob is the creator of django-debug-toolbar (DDT), tool used by more than 100 000 folks in the world. He introduces Content-Security-Policy (CSP) support in Django and contribute to many open source packages. He has been a DSF member since February 2024.

You can learn more about Rob by visiting Rob's website and his GitHub Profile.

Let’s spend some time getting to know Rob better!

Can you tell us a little about yourself

I'm a backend Python engineer based in Oregon, USA. I studied biochemistry in college, where software was just a curiosity and hobby on the side, but I'm grateful that my curiosity turned into a career in tech. My earliest memory of that curiosity was taking apart my Speak & Spell as a kid to see how it worked and never quite getting it back together again.

How did you start using Django?

I followed the path of the "P"s: Perl, then PHP, then Python. When Ruby on Rails arrived it was getting a lot of attention, but I was already enjoying Python, so when Django was announced I was immediately drawn to it. I started building small apps on my own, then eventually led a broader tech stack modernization at work, a health education company where we were building database-driven learning experiences with quizzes and a choose-your-own-adventure flow through health content. Django, Git, and GitHub all came together around that same time as part of that transition. Fun fact: my GitHub user ID is 1106.

What other framework do you know and if there is anything you would like to have in Django if you had magical powers?

I've been building a few projects with FastAPI lately and have really come to appreciate the type-based approach to validation via Pydantic. The way typing syntax influences the validation logic is something I'd love to see influence Django more over time.

Erlang has a feature called the crash dump: when something goes wrong, the runtime writes out the full state of every process to a file you can open and inspect after the fact. As someone who built a debug toolbar because I wanted to see what was going on under the hood. Being provided a freeze frame of the exact moment things went wrong, full state intact, ready to inspect sounds like magic.

The Rust-based tooling emerging in the Python ecosystem is fascinating to watch. Tools like uv, ruff, and efforts around template engines, JSON encoders, ASGI servers, etc. The potential for significant speed improvements without losing what makes Django Django is an interesting space.

What projects are you working on now?

I have a couple of personal fintech projects I'm playing with, one using FastAPI and one using Django. I've been enjoying exploring and wiring up django-bolt for the Django project. I'm impressed with the speed and developer friendliness.

On the django-debug-toolbar front, I recently contributed a cache storage backend and have a longer term idea to add an API layer and a TUI interface that I'd love to get back to working on someday.

Which Django libraries are your favorite (core or 3rd party)?

Django Debug Toolbar (I may be slightly biased). Beyond that: whitenoise and dj-database-url are great examples of libraries that do one thing well and get out of your way. I'd also add granian, a Rust-based ASGI server. And django-allauth, which I'm somehow only just trying for the first time. For settings management I've cycled through a few libraries over the years and am currently eyeing pydantic-settings for a 12-factor approach to configuration.

What are the top three things in Django that you like?

The community. I've been part of it for a long time and it has a quality that's hard to put into words. It feels close knit, genuinely welcoming to newcomers, and there's a rising tide lifts all boats mentality that I don't think you find everywhere. People care about helping each other succeed. The sprints and hallway track at DjangoCon have been a wonderful extension of that.

The ORM. Coming from writing a lot of raw SQL, I appreciate the syntax of Django's ORM which hits a sweet spot of simplicity and power for most use cases.

Stability, documentation, and the batteries included philosophy. I appreciate a framework that at its core doesn't chase trends, has a predictable release cycle, amazingly well written docs (which makes sense coming from its journalism background), and there's enough built in to get surprisingly far without reaching for third party packages.

The inspiration came from Symfony, a PHP framework that had a debug toolbar built in. At the time, I was evaluating frameworks for a tech stack transition at work and thought, why doesn't Django have one of these? So I started hacking on a simple middleware that collected basic stats and SQL queries and injected the toolbar HTML into the rendered page. The first commit was August 2008.

The SQL piece was personally important. Coming from PHP where I wrote a lot of raw SQL by hand, I wanted to see what the ORM was actually generating.

The nudge to actually release it came at the first DjangoCon in 2008 at Google's headquarters. Cal Henderson gave a keynote called "Why I Hate Django" and showed a screenshot of Pownce's debug toolbar in the page header, then talked about internal tooling at Flickr similar to what the Django debug toolbar has currently. Seeing those motivated me to tweet out what I was working on that same day. Apparently I wasn't the only one who wanted to see what the ORM was doing.

It has been created in 2008, what are your reflections on it after so many years?

Mostly gratitude. I had a young family at the time and life got busy, so I stepped back from active maintenance earlier than I would have liked. Watching it flourish under the maintainers who stepped up has been really wonderful to see. They've improved it, kept up with releases, supported the community, and have done a better job of it than I was in a position to do at the time, so I'm grateful to all who carried the torch.

At this point I contribute to it like any other project, which might sound strange for something I created, but it's grown bigger than my early involvement and that feels right. I still follow along and it makes me happy to see it continuing to grow and evolve.

What I didn't anticipate was what it gave back. It helped launch my career as a Django backend developer and I'm fairly certain it played a role in landing me a job at Mozilla. All from of a middleware I hacked together just to see what the ORM was doing.

Being a maintainer is not always easy despite the fact it can be really amazing. Do you have any advice for current and future maintainers in open source?

For what it's worth, what worked for me was building things for fun and to learn rather than setting out to build something popular. I also didn't worry too much about perfection or polish early on.

If life gets busy or your interests move on, I'd say trust the community. Have fun, and if it stops being fun, find some enthusiastic people who still think it's fun and hand it to them gracefully. That worked out better than I could have hoped in my case.

I'm genuinely curious about how AI changes open source. If simple utilities can be generated on the fly rather than installed as packages, what does that mean for small focused libraries? My hope is that the value of open source was never just the code anyway. The collaboration, the issue discussions, the relationships. AI can generate code but it can't replicate those things.

One thing I've noticed is newer developers using AI to generate patches they don't fully understand and submitting them as contributions. I get the impulse, but I'd encourage using AI as a tool for curiosity rather than a shortcut. Let it suggest a fix, then dig into why it works, ask it questions, iterate, which is something I often do myself.

You have introduced CSP support in Django core, congratulations and thank you for this addition! How did the process of creating this contribution go for you?

I picked up django-csp at Mozilla because it had become unmaintained and was a blocker from upgrading to newer Python and Django versions. What started as a simple maintenance task turned into a bit of a yak shave, but a good one. Getting up to speed on CSP led to ticket triage, which led to a refactor, which eventually led me to a 14 year old Django issue requesting CSP in core. Once the refactor was done I made the mistake of actually reading that 14 year old ticket and then felt personally responsible for it.

The more I worked in the space the clearer the ecosystem problem became. As a third party package, django-csp couldn't provide a standardized API that other packages could reliably depend on. If a third party library needed to add nonces to their own templates, they couldn't assume django-csp was installed. Seeing that friction play out in projects like debug toolbar and Wagtail convinced me that CSP support made sense in core.

Working with the Django fellows through the process was a genuine pleasure and I have enormous respect for what they do. They are patient, kind, and shaped what landed in core immensely. What surprised me most was how much they handle behind the scenes and how gracefully they manage the constant demands on their attention. Huge props to Natalia in particular for guiding a large and complex feature through to completion.

Do you remember your first contribution to open source?

Before Django I'd been tinkering on the web for years. I built tastybrew, an online homebrew recipe calculator and sharing site, partly to scratch my own itch and partly to get deeper with PHP and hosting my own projects. Back then open source collaboration wasn't what it is today. Before GitHub there was Freshmeat, SourceForge, emailed patches, maybe your own server with a tarball to download.

My first Django contribution was a small fix to the password reset view in 2006. Over the next several years there were around 40 or so contributions like docs corrections, admin improvements, email handling, security fixes. Contributing felt natural because the code was open and the community was welcoming.

I joined Mozilla in 2011 and shifted focus for a while. Mozilla was quietly contributing quite a bit back to the Django ecosystem during those years, with many 3rd party Django libraries, like django-csp. One of my favorite open source contributions was when I collaborated with a colleague on a Python DSL for Elasticsearch that eventually became the basis for Elastic's official Python client.

What are your hobbies or what do you do when you’re not working?

Reading, cooking, and getting outside when I can. I try to eat a whole food plant based diet and enjoy cooking in that style. Not sure it counts as a hobby but I enjoy wandering grocery stores, browsing what's new, reading ingredients, curious about flavors, thinking about what I could recreate at home.

Getting away from screens is important to me. Gardening, hiking, camping, long walks, travel when possible. Petrichor after rain. Puzzles while listening to audiobooks or podcasts. I brew oolong tea every day, a quiet ritual where the only notification is my tea timer.

Code has always felt more like curiosity than work to me, so I'm not sure where hobby ends and the rest begins.

Anything else you'd like to share?

If you have a Django codebase that needs some love, I'm available for contract work. I genuinely enjoy the unglamorous stuff: upgrading legacy codebases, adding CSP support, and refactoring for simplicity and long term maintainability. There's something satisfying about stepping back, seeing the bigger picture, and leaving things cleaner than you found them. You can find me on GitHub at robhudson.

Doing this interview was a nice way to reflect on my career. I can see that curiosity and adaptation have been pretty good companions. I'm grateful Django and its community have been a big part of that journey.


Thank you for doing the interview, Rob !

April 19, 2026 04:28 PM UTC

April 18, 2026


EuroPython

Humans of EuroPython: Nikoś (nikoshell)

EuroPython wouldn&apost exist without our dedicated volunteers who work tirelessly behind the scenes. They design our website, set up the call for proposals system, review hundreds of submissions, carefully select talks, coordinate speakers, and handle countless logistical details. Every aspect of the conference reflects their passion and expertise. Thank you for making EuroPython possible! 🎉

Below is our conversation with Nikoshell, who worked on the EuroPython 2025 website as well as a part of Communications & Design and Sponsorship teams.

We&aposre grateful for your work on the conference, Nikoshell!

altNikoś aka Nikoshell, contributor and website developer at EuroPython 2025

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

I quickly found a rhythm. Using a streamlined Linux setup with terminal-first tools, I focused on solving problems instead of fighting my tools. I’d catch the European team early, fix blockers like design assets or sponsor content, ship changes, and get feedback within hours. Morning performance fixes allowed richer assets by afternoon, and sponsor updates became social content automatically.

EP: Had you attended EuroPython before, or was volunteering your first experience with it?

First time organizing. Writing Python for 15+ years is one thing; seeing how a conference this size works is different. One code change could impact thousands. It was the most rewarding Python work I’ve done in years.

EP: What&aposs one task you handled that attendees might not realize happens behind the scenes at EuroPython?

I automated sponsor data into social media graphics, saving hours of repetitive work.

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

Whenever a fix cleared busywork and let the team focus on creative work.

EP: Is there anything you took away from the experience that you still use today?

Collaboration patterns. Fast, trusting, distributed teams set a new bar. I still use those workflows and stay connected with the team.

EP: What would you say to someone considering volunteering at EuroPython but feeling hesitant?

Time matters less than impact. You gain skills, cleaner workflows, and strong connections. Just be willing to learn and ship.

EP: What connects Capture The Flag competitions (CTFs), AI automated solutions, and volunteering for EuroPython in your opinion?

Same mental model: find bottlenecks, remove friction, ship. I’ve competed in CTFs—Capture The Flag cybersecurity challenges—with my team justCatTheFish (ranked #1 in Poland, top 10 worldwide), contributed to pwndbg, and built security infrastructure. EuroPython felt like a CTF challenge solved with a high-speed, aligned team.

EP: Thank you for your contributions, Nikoshell!

April 18, 2026 05:00 PM UTC


Seth Michael Larson

More thoughts on Nintendo Switch 2 storage prices

Since my last post about Nintendo Switch 2 storage and prices three major things have happened affecting Switch 2 game prices:

I created a small Python script which produces tables of data comparing physical and digital prices comparing different microSD Express cards and their price-per-GB ratios across different Nintendo Switch 2 games.

Mario Kart World

This is the game people think of for the Switch 2, and the $80 USD price tag across both digital and physical provided some sticker shock for many. I did not understand how the $60 USD standard across all games hung on for as long as it did.

The table below which includes both the price of the game and incremental price of storage (depending on which storage device you purchase) to compare the price between physical and digital.

Edition Storage Total Price Game Price Storage Price Game Size
Physical Cartridge $80.00 $80.00 --- ---
Digital Lexar 1TB (Costco) $83.87 $80.00 $3.87 ($0.18/GB) 22 GB
Digital Lexar 512GB $86.45 $80.00 $6.45 ($0.29/GB) 22 GB
Digital Lexar 1TB $87.20 $80.00 $7.20 ($0.33/GB) 22 GB
Digital Lexar 256GB $87.73 $80.00 $7.73 ($0.35/GB) 22 GB
Digital SanDisk 512GB $87.73 $80.00 $7.73 ($0.35/GB) 22 GB
Digital SanDisk 256GB $88.59 $80.00 $8.59 ($0.39/GB) 22 GB
Digital SanDisk 128GB $92.03 $80.00 $12.03 ($0.55/GB) 22 GB

Yoshi and the Mysterious Book

Now we look at the first game with the new pricing structure in the USA: “Yoshi and the Mysterious Book”. The game is priced at $70 USD physically and $60 USD digitally. Compared to Mario Kart World where all digital editions were more expensive than physical when storage costs are factored in: almost all digital editions are cheaper for Yoshi!

Edition Storage Total Price Game Price Storage Price Game Size
Physical Cartridge $70.00 $70.00 --- ---
Digital Lexar 1TB (Costco) $63.62 $60.00 $3.62 ($0.18/GB) 20.6 GB
Digital Lexar 512GB $66.04 $60.00 $6.04 ($0.29/GB) 20.6 GB
Digital Lexar 1TB $66.74 $60.00 $6.74 ($0.33/GB) 20.6 GB
Digital Lexar 256GB $67.24 $60.00 $7.24 ($0.35/GB) 20.6 GB
Digital SanDisk 512GB $67.24 $60.00 $7.24 ($0.35/GB) 20.6 GB
Digital SanDisk 256GB $68.05 $60.00 $8.05 ($0.39/GB) 20.6 GB
Digital SanDisk 128GB $71.27 $60.00 $11.27 ($0.55/GB) 20.6 GB

MIO: Memories in Orbit

MIO is the cheapest game to date that is published on a non-“Game Key card” cartridge for the Switch 2 at $30 USD physically and $20 USD digitally. The game being only 4GB means the digital edition is much cheaper than the physical edition.

Edition Storage Total Price Game Price Storage Price Game Size
Physical Cartridge $30.00 $30.00 --- ---
Digital Lexar 1TB (Costco) $20.77 $20.00 $0.77 ($0.18/GB) 4.4 GB
Digital Lexar 512GB $21.29 $20.00 $1.29 ($0.29/GB) 4.4 GB
Digital Lexar 1TB $21.44 $20.00 $1.44 ($0.33/GB) 4.4 GB
Digital Lexar 256GB $21.55 $20.00 $1.55 ($0.35/GB) 4.4 GB
Digital SanDisk 512GB $21.55 $20.00 $1.55 ($0.35/GB) 4.4 GB
Digital SanDisk 256GB $21.72 $20.00 $1.72 ($0.39/GB) 4.4 GB
Digital SanDisk 128GB $22.41 $20.00 $2.41 ($0.55/GB) 4.4 GB

Final Fantasy VII Remake Intergrade

And finally, we look at FF7 Remake Intergrade, which according to its Nintendo page is planned to be over 90GB total. This massive game size makes the price to store the game a significant percentage the total price of the game.

Edition Storage Total Price Game Price Storage Price Game Size
Digital Lexar 1TB (Costco) $55.89 $40.00 $15.89 ($0.18/GB) 90.4 GB
Digital Lexar 512GB $66.48 $40.00 $26.48 ($0.29/GB) 90.4 GB
Digital Lexar 1TB $69.57 $40.00 $29.57 ($0.33/GB) 90.4 GB
Digital Lexar 256GB $71.78 $40.00 $31.78 ($0.35/GB) 90.4 GB
Digital SanDisk 512GB $71.78 $40.00 $31.78 ($0.35/GB) 90.4 GB
Digital SanDisk 256GB $75.31 $40.00 $35.31 ($0.39/GB) 90.4 GB
Digital SanDisk 128GB $89.44 $40.00 $49.44 ($0.55/GB) 90.4 GB

It will be interesting seeing how specifically the availability of new cartridge types will change whether companies use Game Key cards for their games. I suspect the pressure to use Game Key cards will still be high as the cost of storage continues to increase for companies and those costs cuts into margins.

None of these tables include the benefits and down-sides of each medium. Many digital game buyers like not having to worry about lost or stolen games while in transit or not having to physically store the boxes and cartridges. Many players may not need to increase their Switch 2 storage if they only play a handful of games. And who knows, maybe the price of storage will decrease in the future?

I hope this information helps you make an informed choice when selecting digital or physical Nintendo Switch 2 games in the future. Happy gaming!



Thanks for keeping RSS alive! ♥

April 18, 2026 12:00 AM UTC

April 17, 2026


Mike Driscoll

Textual – An Intro to DOM Queries (Part I)

In this article, you will learn how to query the DOM in Textual. You will discover that the DOM keeps track of all the widgets in your application. By running queries against the DOM, you can find widgets quickly and update them, too.

You will be learning the following topics related to the DOM:

You will learn more in the second part of this series next week!

You will soon see the value of working with DOM queries and the power that these queries give you. Let’s get started!

The Query One Method

You will find the query_one() method throughout the Textual documentation and many Textual applications on GitHub. You may use query_one() to retrieve a single widget that matches a CSS selector or a widget type.

You can pass in up to two parameters to query_one():

If you pass both, pass the CSS selector first, with the widget type as the second parameter.

Try some of this out. Open up your Python editor and create a file named query_input.py. Then enter this code in it:

# query_input.py

from textual.app import App, ComposeResult
from textual.widgets import Button, Input


class QueryInput(App):

    def compose(self) -> ComposeResult:
        yield Input()
        yield Button("Update Input")

    def on_button_pressed(self) -> None:
        input_widget = self.query_one(Input)
        new_string = f"You entered: {input_widget.value}"
        input_widget.value = new_string


if __name__ == "__main__":
    app = QueryInput()
    app.run()

Your code creates an Input and a Button widget. Enter some text in the Input widget and press the button. Your on_button_pressed() method will get called. You call query_one() and pass it an Input widget. Then, you update the returned Input widget’s value with a new string.

Here is what the application might look like:

Now, you will try writing a new piece of code where you use query_one() with a CSS selector. Create a new file called query_one_same_ids.py and use this code:

# query_one_same_ids.py

from textual.app import App, ComposeResult
from textual.widgets import Button, Label


class QueryApp(App):

    def compose(self) -> ComposeResult:
        yield Label("Press a button", id="label")
        yield Button("Test", id="button")

    def on_button_pressed(self) -> None:
        widget = self.query_one("#label")
        widget.update("You pressed the button!")


if __name__ == "__main__":
    app = QueryApp()
    app.run()

In this example, you create two widgets with different IDs. Then you use query_one() to select the Label widget and update its text.

If you call query_one() and there are no matches, you will get a NoMatches exception. On the other hand, if there is more than one match, the method will return the first item that does match.

What will the following code do if you put it in your example above?

self.query_one(”#label”, Button)

If you guessed that Textual will raise an exception, you should congratulate yourself. You have good intuition! If the widget matches the CSS selector but not the widget type, then you will get a WrongType exception raised.

Textual Queries

Textual has more than one way to query the DOM. You may also use the query() method, which you can use to query or find multiple widgets. When you call query(), it will return a DOMQuery object, which behaves as a list-like container of widgets.

You can see how this works by writing some code. Create a new Python file named query_all.py and add this code to it:

# query_all.py

from textual.app import App, ComposeResult
from textual.widgets import Button, Label


class QueryApp(App):

    def compose(self) -> ComposeResult:
        yield Label("Press a button", id="label")
        yield Button("Test", id="button")

    def on_button_pressed(self) -> None:
        widgets = self.query()
        s = ""
        for widget in widgets:
            s += f"{widget}\n"
        label = self.query_one("#label")
        label.update(s)


if __name__ == "__main__":
    app = QueryApp()
    app.run()

The idea is to get all the widgets in your application and print them out. Of course, you can’t print out anything when your terminal application is blocking stdout, so instead, you create a string of widgets separated by new lines and update the Label widget.

Here is an example of what you might get if you run the code and press the button on your machine:

You might be surprised by that output. Perhaps you thought you would only see a Label and a Button widget in that list? If so, you forgot that a Screen widget is always lurking in the background. But there are also two more: a ToastRack and a Tooltip widget. These come with all your applications. The ToastRack positions Toast widgets, which you use to display a notification message. A Tooltip is a message that appears when you hover your mouse over a widget.

You do not need to know more about those extra widgets now.

Also note that all query methods can be used on both the App and Widget subclasses, which is very handy.

Using Selectors

You can use CSS selectors with query() in much the same way as you can with query_one(). The difference, of course, is that query() always returns an iterable DOMObject.

Let’s pretend you want to get all the Button widgets in your application and iterate over them. Create a new Python script called query_button.py with this code:

# query_buttons.py

from textual.app import App, ComposeResult
from textual.widgets import Button, Label


class QueryApp(App):

    def compose(self) -> ComposeResult:
        yield Label("Press a button", id="label")
        yield Button("One", id="one")
        yield Button("Two", id="two")
        yield Button("Three")

    def on_button_pressed(self) -> None:
        s = ""
        for widget in self.query("Button"):
            s += f"{widget}\n"
        label = self.query_one("#label")
        label.update(s)


if __name__ == "__main__":
    app = QueryApp()
    app.run()

Here you are passing in a string, “Button”, to query(). If using query_one, you would use the Button type directly. Regardless, when you run this code and press the button, you will see the following:

That worked great! This time, you queried the DOM and returned all the Button widgets in your application.

What if you wanted to find all the disabled buttons in your code? You can disable widgets using the disabled style flag or the CSS attribute. To find those widgets, you would update the query like this: widgets = self.query("Button.disabled").

Results

The query objects in Textual also provide a results() method that you can use as an alternative way of iterating over the widgets. For example, you can use results() to rewrite the query above that would retrieve all the disabled buttons to be something like this:

widgets = self.query(".disabled").results(Button)
s = ""
for widget in widgets:
    s += f"{widget}\n"

This code combines the last example query with the last full code example. Although this latter version is more verbose, you might find it easier to read than the original query for disabled widgets.

Another benefit of using results() is that Python type checkers, such as Mypy, can use it to determine the widget type in the loop. When you do not use results(), then Mypy will only know that you are looping over a Widget object, rather than a Button object.

Wrapping Up

You learned the basics of using Textual’s DOM query methods in this article. You can use these query methods to access one or more widgets in your user interface.

Specifically, you learned about the following:

Textual is a great way to create a user interface with Python. You should check it out today!

Learn More

Want to learn more about Textual? Check out my book, Creating TUI Applications with Textual and Python:

The post Textual – An Intro to DOM Queries (Part I) appeared first on Mouse Vs Python.

April 17, 2026 12:57 PM UTC


Real Python

The Real Python Podcast – Episode #291: Reassessing the LLM Landscape & Summoning Ghosts

What are the current techniques being employed to improve the performance of LLM-based systems? How is the industry shifting from post-training towards context engineering and multi-agent orchestration? This week on the show, Jodie Burchell, data scientist and Python Advocacy Team Lead at JetBrains, returns to discuss the current AI coding landscape.


[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]

April 17, 2026 12:00 PM UTC

Quiz: Working With Python Virtual Environments

Test your understanding of the Working With Python Virtual Environments video course.

You’ll revisit why virtual environments matter, how to create and activate them, and how to install and manage packages inside an isolated Python environment.


[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]

April 17, 2026 12:00 PM UTC

April 16, 2026


Talk Python to Me

#545: OWASP Top 10 (2025 List) for Python Devs

The OWASP Top 10 just got a fresh update, and there are some big changes: supply chain attacks, exceptional condition handling, and more. Tanya Janca is back on Talk Python to walk us through every single one of them. And we're not just talking theory, we're going to turn Claude Code loose on a real open source project and see what it finds. Let's do it.<br/> <br/> <strong>Episode sponsors</strong><br/> <br/> <a href='https://talkpython.fm/temporal-replay'>Temporal</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>DevSec Station Podcast</strong>: <a href="https://www.devsecstation.com/?featured_on=talkpython" target="_blank" >www.devsecstation.com</a><br/> <strong>SheHacksPurple Newsletter</strong>: <a href="https://newsletter.shehackspurple.ca/?featured_on=talkpython" target="_blank" >newsletter.shehackspurple.ca</a><br/> <strong>owasp.org</strong>: <a href="https://owasp.org?featured_on=talkpython" target="_blank" >owasp.org</a><br/> <strong>owasp.org/Top10/2025</strong>: <a href="https://owasp.org/Top10/2025/?featured_on=talkpython" target="_blank" >owasp.org</a><br/> <strong>from here</strong>: <a href="https://github.com/awesome-selfhosted/awesome-selfhosted?featured_on=talkpython" target="_blank" >github.com</a><br/> <strong>Kinto</strong>: <a href="https://github.com/Kinto/kinto?featured_on=talkpython" target="_blank" >github.com</a><br/> <strong>A01:2025 - Broken Access Control</strong>: <a href="https://owasp.org/Top10/2025/A01_2025-Broken_Access_Control/?featured_on=talkpython" target="_blank" >owasp.org</a><br/> <strong>A02:2025 - SecuA02 Security Misconfiguration</strong>: <a href="https://owasp.org/Top10/2025/A02_2025-Security_Misconfiguration/?featured_on=talkpython" target="_blank" >owasp.org</a><br/> <strong>ASP.NET</strong>: <a href="https://ASP.NET?featured_on=talkpython" target="_blank" >ASP.NET</a><br/> <strong>A03:2025 - Software Supply Chain Failures</strong>: <a href="https://owasp.org/Top10/2025/A03_2025-Software_Supply_Chain_Failures/?featured_on=talkpython" target="_blank" >owasp.org</a><br/> <strong>A04:2025 - Cryptographic Failures</strong>: <a href="https://owasp.org/Top10/2025/A04_2025-Cryptographic_Failures/?featured_on=talkpython" target="_blank" >owasp.org</a><br/> <strong>A05:2025 - Injection</strong>: <a href="https://owasp.org/Top10/2025/A05_2025-Injection/?featured_on=talkpython" target="_blank" >owasp.org</a><br/> <strong>A06:2025 - Insecure Design</strong>: <a href="https://owasp.org/Top10/2025/A06_2025-Insecure_Design/?featured_on=talkpython" target="_blank" >owasp.org</a><br/> <strong>A07:2025 - Authentication Failures</strong>: <a href="https://owasp.org/Top10/2025/A07_2025-Authentication_Failures/?featured_on=talkpython" target="_blank" >owasp.org</a><br/> <strong>A08:2025 - Software or Data Integrity Failures</strong>: <a href="https://owasp.org/Top10/2025/A08_2025-Software_or_Data_Integrity_Failures/?featured_on=talkpython" target="_blank" >owasp.org</a><br/> <strong>A09:2025 - Security Logging and Alerting Failures</strong>: <a href="https://owasp.org/Top10/2025/A09_2025-Security_Logging_and_Alerting_Failures/?featured_on=talkpython" target="_blank" >owasp.org</a><br/> <strong>A10 Mishandling of Exceptional Conditions</strong>: <a href="https://owasp.org/Top10/2025/A10_2025-Mishandling_of_Exceptional_Conditions/?featured_on=talkpython" target="_blank" >owasp.org</a><br/> <strong>https://github.com/KeygraphHQ/shannon</strong>: <a href="https://github.com/KeygraphHQ/shannon?featured_on=talkpython" target="_blank" >github.com</a><br/> <strong>anthropic.com/news/mozilla-firefox-security</strong>: <a href="https://www.anthropic.com/news/mozilla-firefox-security?featured_on=talkpython" target="_blank" >www.anthropic.com</a><br/> <strong>generalpurpose.com/the-distillation/claude-mythos-what-it-means-for-your-business</strong>: <a href="https://www.generalpurpose.com/the-distillation/claude-mythos-what-it-means-for-your-business?featured_on=talkpython" target="_blank" >www.generalpurpose.com</a><br/> <strong>Python Example Concepts</strong>: <a href="https://blobs.talkpython.fm/owasp-top-10-2025-python-example-concepts.html" target="_blank" >blobs.talkpython.fm</a><br/> <br/> <strong>Watch this episode on YouTube</strong>: <a href="https://www.youtube.com/watch?v=ffid3jWA0JE" target="_blank" >youtube.com</a><br/> <strong>Episode #545 deep-dive</strong>: <a href="https://talkpython.fm/episodes/show/545/owasp-top-10-2025-list-for-python-devs#takeaways-anchor" target="_blank" >talkpython.fm/545</a><br/> <strong>Episode transcripts</strong>: <a href="https://talkpython.fm/episodes/transcript/545/owasp-top-10-2025-list-for-python-devs" 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>

April 16, 2026 08:24 PM UTC


Django Weblog

New Technical Governance - request for community feedback

Hello Django community,

The Steering Council is excited to share our proposed new technical governance and ask for your feedback. Last year we suspended the formal voting process of the Steering Council. The updates we’re proposing would bring how we’ve been operating into alignment with the written governance.

From the motivation section:

This is a revisitation of Django's technical governance in which a simplification and reduction was made to make it more approachable to more people. The goals of these changes are the following:

  • Make it easier to enact our governance.
  • Make it easier for others to understand our governance.
  • Make the governance more flexible, allowing more action with less procedure.

You can read DEP 0019 here.

Adoption plan

The goal is to have this governance accepted and in place by 2026-07-01. Our timeline is as follows, but may change depending on feedback.

What we need from you

We would like to know if we are achieving our goals with this document. For example, do you feel that this makes our governance easier to understand, do you feel like you have a better understanding of who is eligible to run for the Steering Council, is it clear how Django operates from a process perspective?

Beyond that, if you have other feedback around the changes, please share it. This has gone through a high degree of review from the Steering Council and Board over the past 5 months, but that doesn’t mean there aren't areas where it can be improved.

Anyone can participate in this process on the Forum thread here.

April 16, 2026 07:59 PM UTC

PyCharm &amp; Django annual fundraiser

For another year, we are thrilled to partner with our friends at JetBrains on the annual "Buy PyCharm, Support Django" campaign. This is the first of two fundraisers we're running with JetBrains this year, and it's one of the most impactful ways the community can support the Django Software Foundation.

"JetBrains is a cornerstone in the Django community, consistently helping us understand our evolving landscape. Their annual survey provides invaluable insights into the community's needs, trends, and tools, ensuring we stay on the pulse of what matters most."

Jeff Triplett, President, Django Software Foundation

Your support of this campaign helps fund key initiatives such as:

How the campaign works

From today to May 1, when you purchase PyCharm at a 30% discount through our special campaign link, JetBrains will donate an equal amount to the Django Software Foundation. You get a professional IDE that's trusted by Django developers worldwide, and the DSF receives a matched contribution.

Get 30% off PyCharm, Support Django

Thank you, JetBrains

Beyond this campaign, JetBrains contributes to the Django ecosystem in ways that are easy to overlook but hard to overstate. Their Django Developers Survey, State of Django report, and broader Python Developers Survey give the entire community a clearer picture of where Django and Python are heading each year.

"JetBrains is one of our most generous fundraising partners year after year, helping us sustain and grow the Django ecosystem. We deeply appreciate their commitment, leadership, and collaboration."

Thank you to JetBrains for another year of partnership, and thank you to everyone who participates in this campaign. Together, we can ensure the continued success and growth of the framework we all rely on.

Other ways to donate

If you would like to donate in another way, especially if you are already a PyCharm customer, here are other ways to donate to the DSF:

April 16, 2026 05:45 PM UTC


Python Software Foundation

Announcing Python Software Foundation Fellow Members for Q4 2025! 🎉

The PSF is pleased to announce its fourth batch of PSF Fellows for 2025! Let us welcome the new PSF Fellows for Q4! The following people continue to do amazing things for the Python community:

Chris Brousseau

Website, LinkedIn, GitHub, Mastodon, X, PyBay, PyBay GitHub

Dave Forgac

Website, Mastodon, GitHub, LinkedIn

Inessa Pawson

GitHub, LinkedIn

James Abel

Website, LinkedIn, GitHub, Bluesky

Karen Dalton

LinkedIn

Mia Bajić

Tatiana Andrea Delgadillo Garzofino

Website, GitHub, LinkedIn, Instagram

Thank you for your continued contributions. We have added you to our Fellows Roster.

The above members help support the Python ecosystem by being phenomenal leaders, sustaining the growth of the Python scientific community, maintaining virtual Python communities, maintaining Python libraries, creating educational material, organizing Python events and conferences, starting Python communities in local regions, and overall being great mentors in our community. Each of them continues to help make Python more accessible around the world. To learn more about the new Fellow members, check out their links above.

Let's continue recognizing Pythonistas all over the world for their impact on our community. The criteria for Fellow members is available on our PSF Fellow Membership page. If you would like to nominate someone to be a PSF Fellow, please send a description of their Python accomplishments and their email address to psf-fellow at python.org. We are accepting nominations for Quarter 1 of 2026 through February 20th, 2026.

Are you a PSF Fellow and want to help the Work Group review nominations? Contact us at psf-fellow at python.org.

April 16, 2026 02:45 PM UTC


Real Python

Quiz: Welcome to Real Python!

In this quiz, you’ll test your understanding of Welcome to Real Python!

By working through this quiz, you’ll revisit key platform features like video courses, written tutorials, interactive quizzes, Learning Paths, and the Slack community.

You’ll also review strategies for learning effectively, including immersion, daily progress, and building a habit.


[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]

April 16, 2026 12:00 PM UTC

Learning Path: Python Game Development

Build Python games from command-line projects to 2D graphical games with turtle, Tkinter, Pygame, and Arcade.


[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]

April 16, 2026 12:00 PM UTC


death and gravity

Learn Python object-oriented programming with Raymond Hettinger

💢 There must be a better way.

Raymond Hettinger is a Python core developer. Even if you haven't heard of him, you've definitely used his work, bangers such as sorted(), enumerate(), collections, itertools, @lru_cache, and many others. Over the years, he's held lots of great talks, some of them on effective object-oriented programming in Python.

The talks in this article had a huge impact on my development as a software engineer, are some of the best I've heard, and are the single most important reason you should not be afraid of inheritance anymore; don't trust me, look at the YouTube comments!

Note

This list is only to whet your appetite – to get the most out of it, watch the talks in full; besides being a great teacher, Raymond is quite the entertainer, too.

Contents

The Art of Subclassing #

Subclassing can just be viewed as a technique for code reuse.

The Art of Subclassing (2012) is about use cases, principles, and design patterns for inheritance, with examples from the standard library.

The point is to unlearn the animal examples – instead of the classic hierachical view where subclasses are specializations of the parent, there's an operational view where:

This view brings clarity to other related topics:

Finally, a quote about the standard library:

The best way to become a better Python programmer is to spend some time reading the source code written by great Python programmers.

Sounds familiar?

Learn by reading code: Python standard library design decisions explained

Python's Class Development Toolkit #

Each user will stretch your code in different ways.

Python's Class Development Toolkit (2013) is a hands-on exercise: build a single class, encounter common problems users have with it, come up with solutions, repeat.

Use the lean startup methodology to build an advanced circle analytic toolkit with:

Super considered super! #

Super considered super! (2015) goes deep into cooperative multiple inheritance, problems you might encounter, and how to fix them.

The main point is that just like how self refers not to you, but to you or your children, super() does not call your ancestors, but your children's ancestors – it may even call a class that isn't defined yet.

This allows you to change the inheritance chain after the fact; examples include a form of dependency injection, overriding parent behavior without changing its code, and an OrderedCounter class based on Counter and OrderedDict.

The article by the same name is worth a read too, and has different examples.

Object Oriented Programming from scratch (four times) #

Object Oriented Programming from scratch (four times) (2020) does exactly that, each time giving a new insight into what, how, and why we use objects in Python.

The first part shows OOP emerge naturally from the need for more namespaces, by iteratively improving a script that emulates dictionaries.

The second one covers the history of moving from a huge pile of data and functions to:

Sounds familiar?

When to use classes in Python? When your functions take the same arguments
When to use classes in Python? When you repeat similar sets of functions

The third part explains the mechanics of objects via ChainMap, tl;dr:

ChainMap(instance_dict, class_dict, parent_class_dict, ...)

The fourth part highlights how OOP naturally expresses entities and relationships by looking the data model of a Twitter clone and the syntax tree of a compiler.

Beyond PEP 8 – Best practices for beautiful intelligible code #

Well factored code looks like business logic.

Beyond PEP 8 – Best practices for beautiful intelligible code (2015) (code) is about how excessive focus on following PEP 8 can lead to code that is beautiful, but bad, since it distracts from the beauty that really matters:

Pythonic
coding beautifully in harmony with the language to get the maximum benefits from Python

Transform a bad API into a good one using an adapter class and stuff like:

And remember:

If you don't transform bad APIs into Pythonic APIs, you're a fool!

Bonus: The Mental Game of Python #

The computer gives us words that do things; what daddy does is make new words to make computers easier to use.3

The Mental Game of Python (2019) is not just about programming, but about problem solving strategies. The most relevant one for OOP is: build classes independently and let inheritance discover itself; this is because:

A lot of real world problems aren't tic-tac-toe problems, where you can see to the end; they are chess problems, where you can't.

For a more meta discussion of this idea, check out Repeat yourself, do more than one thing, and rewrite everything by tef; it should be considered a classic at this point.

Parting Raymond Hettinger quote:

I came to here you to show you how to chunk, and how to stack one chunk on top of the other, and this is a way to reduce your cognitive load and manage complexity; it is the core of our craft; it's what we're here to do.


Anyway, that's it for now. :)

Who learned something new today? Share it with others!

Want to know when new articles come out? Subscribe here to get new stuff straight to your inbox!

  1. But remember it's a principle, not a law, violations are fine – you may only want substitutability in some places (e.g. contructors are often not substitutable). [return]

  2. ...unless you use double underscores. [return]

  3. In my opinion, this quote alone is reason enough to watch the talk. [return]

April 16, 2026 09:00 AM UTC


Bruno Ponne / Coding The Past

Exploring the MET API with Python - Francisco Goya's Artworks

The act of painting is about one heart telling another heart where he found salvation.

— Francisco Goya


Francisco Goya is one of my favorite artists. His work has a beautiful darkness that tells a lot about his experience in his time. In this post, we’ll dive into his world using the Metropolitan Museum of Art (MET) application programming interface (API), which gives developers access to data on hundreds of thousands of artworks.


You will learn how to interact with the MET API using Python. We will journey through the process of making HTTP requests, parsing the returned JSON data into a structured pandas DataFrame, and exploring the collection to extract meaningful insights about Goya’s work.


1. Requesting data from the API


We begin by importing the requests library, which allows us to send HTTP requests to the MET REST API in Python. We’ll query the search endpoint to find Goya’s paintings. In API terms, an endpoint is a specific URL used to access a particular resource.


The MET API has four endpoints starting with “https://collectionapi.metmuseum.org/”:


You can find more details about each endpoint and its functionality in the official MET API documentation.

tips_and_updates  
A REST (Representational State Transfer) API is a set of rules used to communicate between your computer and the MET server using HTTP methods and endpoints. Note that many APIs require authentication; however, the MET API is public and does not require an API key.


content_copy Copy

import requests
import pandas as pd

search_query = "https://collectionapi.metmuseum.org/public/collection/v1/search?hasImages=true&q=Francisco Goya"

response = requests.get(search_query)
search_data = response.json()

print(f"Found {search_data['total']} artworks for Francisco Goya.")


API endpoints can be followed by query parameters that refine our search. In the example above, hasImages=true filters for objects with images, and q specifies our search term—in this case, the artist’s name.


The requests library contains a method called get(), which we use to send our request to the API, passing our endpoint saved in the string search_query.


The resulting response object can then be parsed into a JSON structure using the .json() method.


2. Converting JSON to a list of painting ids


While JSON is the standard for data exchange, working with raw JSON can be cumbersome for direct data analysis. In Python, you can think of JSON as a dictionary of keys and values. These values can themselves be other dictionaries, lists, numbers, strings, or booleans. By printing the search_data object, we can see that it’s a dictionary containing two main keys:


To retrieve the list of IDs associated with the key “objectIDs” we use the standard dictionary notation search_data["objectIDs"] and save it to the variable goya_ids.


content_copy Copy

print(search_data)
goya_ids = search_data["objectIDs"]


3. Getting the details of each of Goya’s works


To retrieve details for each artwork — such as its title, date, and thematic tags — we need to iterate through the list of IDs and send a request to the /objects/{objectID} endpoint for each item. We implement this using a for loop that repeats the request for each artwork.


(Note: Depending on the number of results, fetching these details can take a few minutes. We use time.sleep(1) to respect the API’s rate limits and avoid being blocked.)


content_copy Copy

import time

all_objects_data = []


for object_id in goya_ids:
    try:
        obj_response = requests.get(f"https://collectionapi.metmuseum.org/public/collection/v1/objects/{object_id}")
        obj_response.raise_for_status() 
        all_objects_data.append(obj_response.json())
    except requests.exceptions.RequestException as e:
        print(f"Error for object ID {object_id}: {e}")
    
    time.sleep(1) # Respect the API, one request per second to be safe

# Convert the gathered data to a DataFrame
goya_df = pd.json_normalize(all_objects_data)

# Filter only Goya works
goya_df = goya_df[goya_df['artistDisplayName'].str.contains('Goya', na=False)]


We use a try-except block to ensure the loop continues even if a specific object ID fails to load. We also log any errors to help with debugging.


Finally, we convert the collected data into a Pandas DataFrame using pd.json_normalize. Since a broad search might return works about Goya or mentioning him in metadata, we filter the DataFrame to ensure the artistDisplayName actually contains “Goya.”


The resulting DataFrame contains intriguing data about each of his works, including name, year when the painting or drawing was started and finished, descriptive tags and dimensions, among other information. Feel free to explore it. We will continue working with the descriptive tags in the next steps.


4. Flattening nested JSON data


For keys whose values are lists or other dictionaries, the resulting columns will contain those respective objects. This happens, for example, with the tags column. When you have nested elements like this, you can “flatten” them into a tabular format.


JSON structure

JSON data structure


Flattening an element changes the granularity of the data. Whereas before each row represented a single artwork, in the flattened table each row represents an individual tag belonging to one artwork.


To flatten these nested tags, we can use json_normalize by specifying the element to unnest in the record_path. We also include the objectID in the meta parameter so we don’t lose the relationship between a tag and its original artwork. Later on, we can join this tags table back to our main DataFrame if we want.


content_copy Copy

tags_df = pd.json_normalize(
    all_objects_data,
    record_path='tags',
    meta=['objectID']
)


5. Visualizing the most frequent themes


The MET API provides a tags field containing descriptive terms associated with each artwork. To understand the prevailing themes in Goya’s works — famous for documenting the social upheaval and dark realities of his era — we can extract these terms and calculate their frequency.


Once we isolate the individual tags into a new column, we can use matplotlib to create a horizontal bar plot of the top 10 terms and check if indeed his artwork contained themes related to death and misery.


content_copy Copy

import matplotlib.pyplot as plt

# Calculate the frequency of each term for the filtered Goya artworks
# We filter tags_df to only include IDs present in our filtered goya_df
term_frequency = tags_df[tags_df['objectID'].isin(goya_df['objectID'])]['term'].value_counts().reset_index()
term_frequency.columns = ['term', 'count']

# Select the top N terms for better readability if there are many unique terms
# For this example, let's take the top 10 terms
top_terms = term_frequency.head(10).sort_values(by='count', ascending=True)

plt.figure(figsize=(12, 8))
plt.barh(top_terms['term'], top_terms['count'], color='#FF6885')

plt.title('Top 10 Most Frequent Terms in Goya Dataset', fontsize=20)
plt.xlabel('Frequency', fontsize=16)
plt.ylabel('Term', fontsize=16)
plt.xticks(fontsize=14)
plt.yticks(fontsize=14)
plt.tight_layout()
plt.show()


Top 10 Most Frequent Terms in Goya Dataset

Top 10 Most Frequent Terms chart


The resulting visualization provides a fascinating window into Goya’s thematic world. Beyond common subjects like “Men,” “Women,” and “Portraits,” we see a strong representation of “Bulls” (reflecting his famous Tauromaquia series) and “Self-portraits.”


Most strikingly, terms like “Death” and “Suffering” appear prominently in the top 10. This data-driven insight confirms Goya’s historical reputation as an artist who didn’t shy away from the darker aspects of the human experience. By quantifying these themes through the MET API, we move from subjective observation to empirical evidence of his artistic focus.


The sleep of reason produces monsters

Plate 43 from "Los Caprichos": The sleep of reason produces monsters (El sueño de la razon produce monstruos)


You could also use the main dataset we created to collect a series of images of Goya works. I am thinking of using AI to help me download all images of Goya in the public domain and try to build a model to describe or classify them in Python. Feel free to use the data and let me know about your analysis. Leave your comments or any questions below and happy coding!


Conclusions




April 16, 2026 12:00 AM UTC

April 15, 2026


Django Weblog

Django Has Adopted Contributor Covenant 3

We’re excited to announce that Django has officially adopted Contributor Covenant 3 as our new Code of Conduct! This milestone represents the completion of a careful, community-driven process that began earlier this year.

What We’ve Accomplished

Back in February, we announced our plan to adopt Contributor Covenant 3 through a transparent, multi-step process. Today, we’re proud to share that we’ve completed all three steps:

Step 1 (Completed February 2026): Established a community-driven process for proposing and reviewing changes to our Code of Conduct.

Step 2 (Completed March 2026): Updated our Enforcement Manual, Reporting Guidelines, and FAQs to align with Contributor Covenant 3 and incorporate lessons learned from our working group’s experience.

Step 3 (Completed April 2026): Adopted the Contributor Covenant 3 with Django-specific enhancements.

Why Contributor Covenant 3?

Contributor Covenant 3 represents a significant evolution in community standards, incorporating years of experience from communities around the world. The new version:

By adopting this widely-used standard, Django joins a global community of projects committed to fostering welcoming, inclusive spaces for everyone.

What’s New in Django’s Code of Conduct

While we’ve adopted Contributor Covenant 3 as our foundation, we’ve also made Django-specific enhancements:

You can view the complete changelog of changes at our Code of Conduct repository.

Community-Driven Process

This adoption represents months of collaborative work. The Code of Conduct Working Group reviewed community feedback, consulted with the DSF Board, and incorporated insights from our enforcement experience. Each step was completed through pull requests that were open for community review and discussion.

We’re grateful to everyone who participated in this process—whether by opening issues, commenting on pull requests, joining forum discussions, or simply taking the time to review and understand the changes.

Where to Find Everything

All of our Code of Conduct documentation is available on both djangoproject.com and our GitHub repository:

How You Can Continue to Help

The Code of Conduct is a living document that will continue to evolve with our community’s needs:

Thank You

Creating a truly welcoming and inclusive community is ongoing work that requires participation from all of us. Thank you for being part of Django’s community and for your commitment to making it a safe, respectful space where everyone can contribute and thrive.

If you have questions about the new Code of Conduct or our processes, please don’t hesitate to reach out to the Code of Conduct Working Group at conduct@djangoproject.com.


Posted by Dan Ryan on behalf of the Django Code of Conduct Working Group

April 15, 2026 08:49 PM UTC


Real Python

Variables in Python: Usage and Best Practices

In Python, variables are symbolic names that refer to objects or values stored in your computer’s memory. They allow you to assign descriptive names to data, making it easier to manipulate and reuse values throughout your code. You create a Python variable by assigning a value using the syntax variable_name = value.

By the end of this tutorial, you’ll understand that:

  • Variables in Python are symbolic names pointing to objects or values in memory.
  • You define variables by assigning them a value using the assignment operator.
  • Python variables are dynamically typed, allowing type changes through reassignment.
  • Python variable names can include letters, digits, and underscores but can’t start with a digit. You should use snake case for multi-word names to improve readability.
  • Variables exist in different scopes (global, local, non-local, or built-in), which affects how you can access them.
  • You can have an unlimited number of variables in Python, limited only by computer memory.

To get the most out of this tutorial, you should be familiar with Python’s basic data types and have a general understanding of programming concepts like loops and functions.

Don’t worry if you don’t have all this knowledge yet and you’re just getting started. You won’t need this knowledge to benefit from working through the early sections of this tutorial.

Get Your Code: Click here to download the free sample code that shows you how to use variables in Python.

Take the Quiz: Test your knowledge with our interactive “Variables in Python: Usage and Best Practices” quiz. You’ll receive a score upon completion to help you track your learning progress:


Interactive Quiz

Variables in Python: Usage and Best Practices

Test your understanding of Python variables, from creation and naming conventions to dynamic typing, scopes, and type hints.

Getting to Know Variables in Python

In Python, variables are names associated with concrete objects or values stored in your computer’s memory. By associating a variable with a value, you can refer to the value using a descriptive name and reuse it as many times as needed in your code.

Variables behave as if they were the value they refer to. To use variables in your code, you first need to learn how to create them, which is pretty straightforward in Python.

Creating Variables With Assignments

The primary way to create a variable in Python is to assign it a value using the assignment operator and the following syntax:

Python Syntax
variable_name = value

In this syntax, you have the variable’s name on the left, then the assignment (=) operator, followed by the value you want to assign to the variable at hand. The value in this construct can be any Python object, including strings, numbers, lists, dictionaries, or even custom objects.

Note: To learn more about assignments, check out Python’s Assignment Operator: Write Robust Assignments.

Here are a few examples of variables:

Python
>>> word = "Python"

>>> number = 42

>>> coefficient = 2.87

>>> fruits = ["apple", "mango", "grape"]

>>> ordinals = {1: "first", 2: "second", 3: "third"}

>>> class SomeCustomClass: pass
>>> instance = SomeCustomClass()

In this code, you’ve defined several variables by assigning values to names. The first five examples include variables that refer to different built-in types. The last example shows that variables can also refer to custom objects like an instance of your SomeCustomClass class.

Setting and Changing a Variable’s Data Type

Apart from a variable’s value, it’s also important to consider the data type of the value. When you think about a variable’s type, you’re considering whether the variable refers to a string, integer, floating-point number, list, tuple, dictionary, custom object, or another data type.

Python is a dynamically typed language, which means that variable types are determined and checked at runtime rather than during compilation. Because of this, you don’t need to specify a variable’s type when you’re creating the variable. Python will infer a variable’s type from the assigned object.

Note: In Python, variables themselves don’t have data types. Instead, the objects that variables reference have types.

For example, consider the following variables:

Python
>>> name = "Jane Doe"
>>> age = 19
>>> subjects = ["Math", "English", "Physics", "Chemistry"]

>>> type(name)
<class 'str'>
>>> type(age)
<class 'int'>
>>> type(subjects)
<class 'list'>

In this example, name refers to the "Jane Doe" value, so the type of name is str. Similarly, age refers to the integer number 19, so its type is int. Finally, subjects refers to a list, so its type is list. Note that you don’t have to explicitly tell Python which type each variable is. Python determines and sets the type by checking the type of the assigned value.

Read the full article at https://realpython.com/python-variables/ »


[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]

April 15, 2026 02:00 PM UTC