skip to navigation
skip to content

Planet Python

Last update: March 17, 2026 10:45 AM UTC

March 16, 2026


Mike Driscoll

Textual – Creating a Custom Checkbox

Textual is a great Python user interface package. Textual lets you create a GUI-like interface in your terminal. You can use many different widgets in Textual. However, the widget you will be focusing on in this tutorial is the humble checkbox. Checkboxes are used for Boolean choices. They return a True if checked and a […]

The post Textual – Creating a Custom Checkbox appeared first on Mouse Vs Python.

March 16, 2026 07:42 PM UTC


Ari Lamstein

acs-nativity: A Python Package for Analyzing Changes in the Foreign-Born Population

President Trump has made reducing illegal immigration and increasing deportations central goals of his second administration (1, 2). This is causing many people to ask: how are these policies changing the country’s population? To help answer that, I built a new open-source Python package called acs-nativity. It provides a simple interface for accessing and visualizing […]

March 16, 2026 04:00 PM UTC


PyCon

Attend PyCon US for a day of Trailblazing Python Security!

March 16, 2026 03:58 PM UTC


Real Python

Spyder: Your IDE for Data Science Development in Python

Learn how to use the Spyder IDE, a Python code editor built for scientists, engineers, and data analysts working with data-heavy workflows.

March 16, 2026 02:00 PM UTC

Quiz: Speed Up Python With Concurrency

Test your Python concurrency knowledge: CPU vs I/O-bound tasks, GIL, asyncio, race conditions, and multiprocessing.

March 16, 2026 12:00 PM UTC


Python Bytes

#473 A clean room rewrite?

Topics include , refined-github, , and Agentic Engineering Patterns.

March 16, 2026 08:00 AM UTC

March 15, 2026


The Python Coding Stack

The Weird and Wonderful World of Descriptors in Python • The Price is Right

The Weird and Wonderful World of Descriptors in Python • Let’s demystify one of the trickiest topics around

March 15, 2026 09:27 PM UTC

March 14, 2026


Real Python

Quiz: Splitting, Concatenating, and Joining Python Strings

Brush up on splitting, concatenating, and joining strings in Python. Test your understanding of methods, immutability, and common pitfalls.

March 14, 2026 12:00 PM UTC


Marcos Dione

Block sizes from OSM data

My city has big blocks. One close to mine is around 6km of circumference. I wanted to make a map that show the sizes of the blocks. What I have is a rendering DB for my area. Let's see what I can do.

An obvious solution would be to use any routing database, which would convert squiggly segments into edges of a graph. I feebly tried that, but no routing system I know is packaged for Debian, and one of them asked me to compile it. I could compile it, but I was lazy that way.

So the plan is this: take the whole street network1 and recursively remove the dead end streets. The recursion part is because there are areas where there are whole branches of dead end streets, so if in one pass a street A might look not dead because it connects to another B, but B is removed, the next pass should remove A too. So, for each street, take each end, and see how many other segments it's connected to; if 0, it's a dead end and we should remove it. Keep going until no new streets were removed.

One problem is how the data is represented. In a rendering database, streets do not exist, just segments with a consistent tagging, so streets can be split if, for instance, max speed changes, or there's a bridge. But one thing they're not split on is where another street joins them (which would be reflected in a routing db, lazy me! :)

This does not matter when searching for connected streets to a point, but a segment might have parts that are a dead end, and parts that are not, because they're connected to other streets(segments) that are connected too. So now, for each end, take the point, and if it's not connected, removed the point and try again. The full algo:

Looks like an infinite problem! The city I'm interested in has 16k segments, and this looks worse than O(N²)! But we can do some shortcuts. One of the expensive operations is "the end is connected" or "find all the other segments that include this point". Luckily, a rendering DB has a geographic index, and we can do trics like looking for segments only around the area of the segment you're looking at, so reducing the amount of comparisons by a lot: less that 10 instead of 16k! And of course, you should not consider segments you already removed.

To be honest, I thought this would take way more effort and code. Yes, I changed the algo thrice, but all in all it took me like 6h to get it as it is. Now, it is not perfect. It does not detect some artifacts (mostly cycles connected to a single segment), but is good enough for me for now. Not bad for <200 lines of Python and SQL :)

#! /usr/bin/env python3

import psycopg2
from shapely import from_wkb, set_srid, LineString, Point


def main():
    print('Fetching all segments, takes a while.')
    conn = psycopg2.connect(dbname='europe')
    cur = conn.cursor()

    cur.execute(f"""
        WITH marseille AS (
            SELECT way
            FROM planet_osm_polygon
            WHERE osm_id = -76469
        )
        SELECT line.osm_id, line.way
        FROM
            planet_osm_line AS line,
            marseille
        WHERE
            line.way && marseille.way AND
            line.highway IN (
                'primary',
                'primary_link',
                'secondary',
                'secondary_link',
                'tertiary',
                'tertiary_link',
                'residential',
                'unclassified',
                'living_street',
                'road'
            ) AND
            (line.access IS NULL OR line.access = 'yes')
            { f" LIMIT {sys.argv[1]}" if len(sys.argv) > 1 else '' };
    """)

    segments = {}
    segments_removed = set([666])  # fake osm_id so line.osm_id NOT IN %s AND works (empty sets are syntax errors)

    # first pass: collect all segments
    for data in cur.fetchall():
        osm_id = data[0]
        segment = from_wkb(data[1])

        segments[osm_id] = segment

    print(f"Found {len(segments)} segments.")

    # second pass: eliminate segments for which we can find an unconnected end
    pass_number = 1

    while len(segments) > 0:
        print(f"PASS {pass_number:2d}")

        total_count = 0
        total_changed = 0
        to_remove = set()

        for osm_id, segment in segments.items():
            changed = False
            removed = False
            connections = 0

            for direction in -1, 0:
                while len(segment.coords) > 0:
                    point = Point(segment.coords[direction])

                    cur.execute("""
                        WITH segment AS (
                            SELECT way
                            FROM planet_osm_line
                            WHERE osm_id = %s
                        )
                        SELECT
                            line.osm_id,
                            line.name
                        FROM planet_osm_line AS line, segment
                        WHERE
                            line.way && ST_Buffer(ST_Envelope(segment.way), 10) AND
                            line.osm_id != %s AND
                            line.osm_id NOT IN %s AND
                            line.highway IN (
                                'trunk',
                                'trunk_link',
                                'primary',
                                'primary_link',
                                'secondary',
                                'secondary_link',
                                'tertiary',
                                'tertiary_link',
                                'residential',
                                'unclassified',
                                'living_street',
                                'road'
                            ) AND
                            (access IS NULL OR access = 'yes') AND
                            ST_Intersects(line.way, ST_GeomFromWKB(%s, 3857));
                    """, (osm_id, osm_id, tuple(segments_removed), point.wkb))

                    data = cur.fetchall()

                    if len(data) == 0:
                        if len(segment.coords) == 2:
                            # removing one point removes the segment
                            removed = True
                            to_remove.add(osm_id)
                            break

                        # remove the point
                        if direction == 0:
                            segment = LineString(segment.coords[1:])
                        else:
                            segment = LineString(segment.coords[:-1])

                        changed = True
                    else:
                        connections += len(data)
                        break

            if changed:
                segments[osm_id] = segment
                total_changed += 1

            total_count += 1
            match total_count % 10, changed, removed:
                case _, True, _:
                    print('*', end='', flush=True)
                case _, False, True:
                    print('_', end='', flush=True)
                case 0, _, _:
                    print('|', end='', flush=True)
                case 5, _, _:
                    print(',', end='', flush=True)
                case _:
                    print('.', end='', flush=True)

            if total_count % 100 == 0:
                print()

        segments_removed.update(to_remove)

        print()
        print(', '.join([ str(id) for id in to_remove ]))

        for osm_id in to_remove:
            del segments[osm_id]

        print(f"{total_changed} changed, {len(to_remove)} removed, {len(segments)} left.")
        print()

        # if only some segments were changed, the topology does not change, so we save one pass
        if len(to_remove) == 0:
            break

        pass_number += 1

    print("Saving segments...")
    for segment in segments.values():
        cur.execute("""
            INSERT INTO streets (way)
            VALUES (ST_GeomFromWKB(%s, 3857))
        """, (segment.wkb, ))

    conn.commit()

    conn.commit()
    print()

    print("Cutting...")
    for index, segment in enumerate(segments.values()):
        buffered = segment.buffer(1)

        marseille = difference(marseille, buffered)

        match (index + 1) % 10:
            case 0:
                print('|', end='', flush=True)
            case 5:
                print(',', end='', flush=True)
            case _:
                print('.', end='', flush=True)

        if (index + 1) % 100 == 0:
            print()

    print()

    out = open('blocks.geojson', 'w+')
    # GeoJSONstream, one GeoJSON object per line
    # GDAL/QGIS won't accept 
    for polygon in marseille.geoms:
        out.write(f"{json.dumps(mapping(polygon))}\n")


main()

Runtime takes a while because someone correctly micromapped a 350m dead end road due to diffrences in road side parking, 14 segments/passes in total; otherwise 8 would have suffised. I could have added a heuristic where, when a pass removed just a few segments, the next pass would only search among the segments close to those. Technically I could do this from the first pass.

One thing that surprised me whas this: QGIS can read GeoJSON files, but if they're going to be a colleciton of things, better be a GeoJSONseq, that is a file with a GeoJSON object per line. But since these files do not include info about the EPSG, you have to set it by hand on QGIS. now, this would be fine if QGIS would ask about it when loading the layer, but instead two things happen: it confuses the EPSG, but would still correctly zoom to the layer assuming that the layer has the same projection as the project. Thanks to uglyhack#qgis@libera.chat.

Several caveats:

I didn't include motorways or tunks in the block computation, because they create a complication in the definition of block, specially with bridges over or tunnels under other roads, of which there are a lot here. I know at least 3 bridges that still break the graph; there are few enough that I can ignore them.

The city has big blocks of industrial areas. Service roads are not included, and in any case most are dead ends.

The biggest areas are actually not urban, including a big chunk of a National Park in the two big red areas to the South2 and the ports and the sea nearby3. Extracting a real urban area is possible, but harder.

On top I also drew three things, roads (grey, white, blue), rivers (thick blue) and train tracks (black), to give a better idea of the complexity of the city. This time it includes motorways, trunks, service and private roads. The latter also give an idea of how much is in private hands (white). The thin blue lines are the streets thad define the blocks, and the grey are public but dead ends.


  1. I'm focused on a traffic issue: the city is not navigable by car, leading to lots of traffic in the few streets available, leading to lots of noise from impatient drivers. 

  2. The map is rotated 108° so I could zoom in as much as possible, so South is rougly to the left. 

  3. See where all the ferry lines go to; that's the Vieux Port (Old Port), and the new, industrial one is in the same area to the North/right. 

March 14, 2026 11:16 AM UTC


Seth Michael Larson

I’ve added human.json to my website

March 14, 2026 12:00 AM UTC

March 13, 2026


EuroPython

Humans of EuroPython: Kshitijaa Jaglan

Discover the motivations behind volunteering, the challenges and rewards of organizing such a significant conference, and the impact it has on both attendees and contributors. Hear personal stories and learn how individuals like our interviewee help shape the future of Python through their commitment and collaboration.

In our latest interview

March 13, 2026 11:24 PM UTC


Talk Python to Me

#540: Modern Python monorepo with uv and prek

Monorepos -- you've heard the talks, you've read the blog posts, maybe you've seen a few tantalizing glimpses into how Google or Meta organize their massive codebases. But it's often in the abstract and behind closed doors. What if you could crack open a real, production monorepo, one with over a million lines of Python and over 100 of sub-packages, and actually see how it's built, step by step, using modern tools and standards? That's exactly what Apache Airflow gives us. On this episode, I sit down with Jarek Potiuk and Amogh Desai, two of Airflow's top contributors, to go inside one of the largest open-source Python monorepos in the world and learn how they manage it with uv, pyproject.toml, and the latest packaging standards, so you can apply those same patterns to your own projects.

March 13, 2026 09:17 PM UTC


PyCon

Launching the PyCon US 2026 Schedule!

March 13, 2026 04:45 PM UTC


PyCharm

Last week marked the fruition of almost a year of hard work by the entire PyCharm team. On March 4th, 2026, we hosted Python Unplugged on PyTV, our first-ever community conference featuring a 90s music-inspired online conference for the Python community. The PyCharm team is a fixture at Python conferences globally, such as PyCon US […]

March 13, 2026 04:41 PM UTC


Rodrigo Girão Serrão

TIL #141 – Inspect a lazy import

Today I learned how to inspect a lazy import object in Python 3.15.

Python 3.15 comes with lazy imports and today I played with them for a minute. I defined the following module mod.py:

print("Hey!")

def f():
    return "Bye!"

Then, in the REPL, I could check that lazy imports indeed work:

>>> # Python 3.15
>>> lazy import mod
>>>

The fact that I didn't see a "Hey!" means that the import is, indeed, lazy. Then, I wanted to take a look at the module so I printed it, but that triggered reification (going from a lazy import to a regular module):

>>> print(mod)
Hey!
<module 'mod' from '/Users/rodrigogs/Documents/tmp/mod.py'>

So, I checked the PEP that introduced explicit lazy modules and turns out as soon as you reference the lazy object directly, it gets reified. But you can work around it by using globals:

>>> # Fresh 3.15 REPL
>>> lazy import mod
>>> globals()["mod"]
<lazy_import 'mod'>

This shows the new class lazy_import that was added to support lazy imports!

Pretty cool, right?

March 13, 2026 01:38 PM UTC


PyCharm

Python Unplugged on PyTV Recap

March 13, 2026 01:05 PM UTC


Real Python

The Real Python Podcast – Episode #287: Crafting and Editing In-Depth Tutorials at Real Python

What goes into creating the tutorials you read at Real Python? What are the steps in the editorial process, and who are the people behind the scenes? This week on the show, Real Python team members Martin Breuss, Brenda Weleschuk, and Philipp Acsany join us to discuss topic curation, review stages, and quality assurance.

March 13, 2026 12:00 PM UTC

Quiz: Your Python Coding Environment on Windows: Setup Guide

Test your knowledge of setting up a Python dev environment on Windows, from updates and terminals to paths, tools, and WSL.

March 13, 2026 12:00 PM UTC


PyPy

PyPy v7.3.21 release

PyPy v7.3.21: release of python 2.7, 3.11

The PyPy team is proud to release version 7.3.21 of PyPy after the previous release on July 4, 2025. This is a bug-fix release that also updates to Python 3.11.15.

The release includes two different interpreters:

The interpreters are based on much the same codebase, thus the double release. This is a micro release, all APIs are compatible with the other 7.3 releases.

We recommend updating. You can find links to download the releases here:

https://pypy.org/download.html

We would like to thank our donors for the continued support of the PyPy project. If PyPy is not quite good enough for your needs, we are available for direct consulting work. If PyPy is helping you out, we would love to hear about it and encourage submissions to our blog via a pull request to https://github.com/pypy/pypy.org

We would also like to thank our contributors and encourage new people to join the project. PyPy has many layers and we need help with all of them: bug fixes, PyPy and RPython documentation improvements, or general help with making RPython's JIT even better.

If you are a python library maintainer and use C-extensions, please consider making a HPy / CFFI / cppyy version of your library that would be performant on PyPy. In any case, cibuildwheel supports building wheels for PyPy.

What is PyPy?

PyPy is a Python interpreter, a drop-in replacement for CPython It's fast (PyPy and CPython performance comparison) due to its integrated tracing JIT compiler.

We also welcome developers of other dynamic languages to see what RPython can do for them.

We provide binary builds for:

PyPy supports Windows 32-bit, Linux PPC64 big- and little-endian, Linux ARM 32 bit, RISC-V RV64IMAFD Linux, and s390x Linux but does not release binaries. Please reach out to us if you wish to sponsor binary releases for those platforms. Downstream packagers provide binary builds for debian, Fedora, conda, OpenBSD, FreeBSD, Gentoo, and more.

What else is new?

For more information about the 7.3.21 release, see the full changelog.

Please update, and continue to help us make pypy better.

Cheers, The PyPy Team

March 13, 2026 10:00 AM UTC


eGenix.com

PyDDF Python Spring Sprint 2026

The following text is in German, since we're announcing a Python sprint in Düsseldorf, Germany.

Ankündigung

Python Meeting Spring Sprint 2026 in
Düsseldorf

Samstag, 21.03.2026, 10:00-18:00 Uhr
Sonntag, 22.03.2026. 10:00-18:00 Uhr

Atos Information Technology GmbH, Am Seestern 1, 40547 Düsseldorf

Informationen

Das Python Meeting Düsseldorf (PyDDF) veranstaltet mit freundlicher Unterstützung von Atos Deutschland ein Python Sprint Wochenende.

Der Sprint findet am Wochenende 21/22.03.2026 in der Atos Niederlassung, Am Seestern 1, in Düsseldorf statt.Folgende Themengebiete sind als Anregung bereits angedacht:
Natürlich können die Teilnehmenden weitere Themen vorschlagen und umsetzen.

Anmeldung, Kosten und weitere Infos

Alles weitere und die Anmeldung findet Ihr auf unserer Blog Seite:

WICHTIG: Ohne Anmeldung können wir den Gebäudezugang nicht vorbereiten. Eine spontane Anmeldung am Sprint Tag wird daher vermutlich nicht funktionieren.

Teilnehmer sollten sich zudem in der PyDDF Telegram Gruppe registrieren, da wir uns dort koordinieren:

Über das Python Meeting Düsseldorf

Das Python Meeting Düsseldorf ist eine regelmäßige Veranstaltung in Düsseldorf, die sich an Python-Begeisterte aus der Region wendet.

Einen guten Überblick über die Vorträge bietet unser PyDDF YouTube-Kanal, auf dem wir Videos der Vorträge nach den Meetings veröffentlichen.

Veranstaltet wird das Meeting von der eGenix.com GmbH, Langenfeld, in Zusammenarbeit mit Clark Consulting & Research, Düsseldorf.

Viel Spaß !

Marc-André Lemburg, eGenix.com

March 13, 2026 09:00 AM UTC


Daniel Roy Greenfeld

To return a value or not return a value

I believe operations that change things should always return values.

March 13, 2026 02:41 AM UTC


The Python Show

56 - Python Illustrated

In this episode, we hear from two sisters who put together a beginner's book about Python.

March 13, 2026 02:13 AM UTC


Audrey M. Roy Greenfeld

Staticware 0.2.0: The first cut

This is an early release. Staticware does something very satisfyingly today: it serves static files with content-hashed URLs for cache busting. That means when you edit your CSS then redeploy and restart your server, visitors get the latest CSS without forcing a refresh. More will come.

March 13, 2026 12:00 AM UTC

March 12, 2026


Python Morsels

Standard error

Standard error is one of the two writable file streams that is used for printing errors, warning messages, or any outputs that shouldn't be mixed with the main program.

Table of contents

  1. Printing writes to "standard output" by default
  2. Python also has a "standard error" stream
  3. Standard output vs. standard error
  4. Standard error isn't just for errors
  5. When is standard error usually used?
  6. Print atypical output to standard error

Printing writes to "standard output" by default

When we call Python's print function, Python will write to standard output:

>>> print("Hi!")
Hi!

Standard output is a file-like object, also known as a file stream. The standard output file-like object is represented by the stdout object in Python's sys module.

If we look at the documentation for Python's print function, we'll see that by default, print writes to sys.stdout:

>>> help(print)

Help on built-in function print in module builtins:

print(*args, sep=' ', end='\n', file=None, flush=False)
    Prints the values to a stream, or to sys.stdout by default.

    sep
      string inserted between values, default a space.
    end
      string appended after the last value, default a newline.
    file
      a file-like object (stream); defaults to the current sys.stdout.
    flush
      whether to forcibly flush the stream.

If we call the write method on sys.stdout, text will write to the terminal screen:

>>> import sys
>>> bytes_written = sys.stdout.write("Hello!\n")
Hello!

Python also has a "standard error" stream

Standard output is actually one …

Read the full article: https://www.pythonmorsels.com/standard-error/

March 12, 2026 02:45 PM UTC


Real Python

Quiz: How to Use Ollama to Run Large Language Models Locally

Test your knowledge of running LLMs locally with Ollama. Install it, pull models, chat, and connect coding tools from your terminal.

March 12, 2026 12:00 PM UTC