skip to navigation
skip to content

Planet Python

Last update: October 23, 2026 07:49 PM UTC

October 22, 2026


Daniel Roy Greenfeld

TIL: How to extract subtitles from video

Recently I've been making more and more videos for my youtube channel. I like videos with subtitles, and decided to add them to my own efforts. This is what I do:

This command assumes you have uv and ffmpeg installed.

uvx --python 3.14 --from openai-whisper whisper "name-of-video.mp4" \
  --model small \
  --language English \
  --task transcribe \
  --output_format srt \
  --output_dir captions \
  --fp16 False

The output is put in a captions directory, named after the .mp4 file that is its source.

As a zsh function:

subtitle() {
  uvx --python 3.14 --from openai-whisper whisper "$1" \
    --model small \
    --language English \
    --task transcribe \
    --output_format srt \
    --output_dir captions \
    --fp16 False
}

October 22, 2026 03:40 PM UTC


Mariatta

Python Core Sprint 2026: Day 3

Day 3 of the Python Core Sprint, yesterday, was my last day in the room. Today I’m traveling to Seattle for SeaGL, where I’m one of the keynote speakers. With the bots settled on Cabotage and Heroku scaled to zero, I had the day for the smaller things that had been waiting on the follow-up list.

A prompt generator for NEWS entries, and why I closed it

One of the open pull requests in blurb_it was #456, from An Long (aisk), opened back in July. It added a button to the “add blurb” page that generates a prompt. The contributor copies the prompt into ChatGPT, Claude, Gemini, or DeepSeek, and pastes the resulting NEWS entry back into blurb_it. The prompt includes links to the issue, the PR, the diff, and the changed files, plus the devguide’s guidance on how to write a NEWS entry.

October 22, 2026 02:00 PM UTC


Rodrigo Girão Serrão

Module profiling overview

Learn about the new module profiling that was introduced in Python 3.15 to organise the built-in profiling tools.

The standard library module profiling organises the built-in profiling tools that Python has into a single namespace. Introduced in Python 3.15, it gives access to two different profilers: a sampling profiler called Tachyon, and a tracing profiler.

This article will present an overview of those two profilers.

15 days of Python 3.15

To celebrate the release of Python 3.15, over the next 15 business days I'll be writing about a new 3.15 feature every day. Explained clearly and with examples so you don't have to sift through the changelog.

Subscribe to receive this free email series:

If you want, you can also check the schedule of the upcoming emails.

Historical note

Before Python 3.15, Python had a module in the standard library called profile. The module profile was a pure-Python profiler that was of little practical use. This module was deprecated in Python 3.15 and is scheduled for removal in Python 3.17.

In addition to the pure-Python module profile, Python 3.14 and earlier included a tracing profiler called cProfile. This profiler now lives under profiling.tracing and it's one of the tools you'll learn about in this article.

Understanding the two Python profilers

The Python standard library provides two profilers under the module profiling:

  1. profiling.sampling: a statistical profiler that uses high-frequency sampling to profile your code with virtually zero overhead. This profiler is commonly known as Tachyon.
  2. profiling.tracing: a tracing profiler that instruments your code to profile your code deterministically but with some overhead. This profiler used to be called cProfile.

The key difference between...

October 22, 2026 01:23 PM UTC


Tryton News

Information regarding eMudhra / emSign CA certificate security incident

Following Information for Python users regarding eMudhra / emSign CA certificate security incident - Announcements - Discussions on Python.org, we have updated the Docker images of Tryton because certifi is an indirect dependency of Tryton.
It is recommended to update your deployment to use the updated images.
If you deploy Tryton using a virtual environment, it must be updated following the instructions.

1 post - 1 participant

Read full topic

October 22, 2026 10:00 AM UTC


Python Software Foundation

Mark Your Calendars! PSF Board Office Hours for the Year Ahead

Greetings, Pythonistas- thank you so much for supporting the work of the PSF and the Python community! The newest PSF Board has decided to continue investing in connecting with the global Python community by running the PSF Board Office Hour sessions on the PSF Discord into 2027. 

We began running these sessions in September 2024, and they have proven to be a great method for the community to connect with the board and vice versa. This venue has been invaluable to the PSF as we work on critical areas over the last year such as the PSF Strategic Plan, elections, and the recent PSF Grants Program Funding Round. The sessions have also provided the PSF Board and Staff the opportunity to hear what our community is up to and what challenges they are facing.

What are the PSF Board Office Hours?

During the PSF Board Office Hours, you can participate in a text-based live chat with PSF Board Directors. This is a chance to connect, share, and collaborate with the PSF Board and Staff to improve our community together. Occasionally, we will have dedicated topics such as PyCon US and the PSF Board Elections for the office hour sessions. 

Here is some of the work that we collaborate with staff and volunteers on:

Unless we have a dedicated topic for a session, you are not limited to talking with us about the above topics, although the discussions should be focused on Python, the PSF, and our community. If you think there’s something we can help with or we should know, we welcome you to come and talk to us!

Joining the office hours

The office hour sessions will take place on the PSF Discord server in the #psf-board channel. If you are new to Discord, make sure to check out a tutorial on how you can download the app and sign up for free– then join us on the PSF Discord! To make the office hours more accessible, the office hours will be scheduled at alternating times, so no matter where you are based, you can find a time that is most convenient for you! Here is a list of the dates and times:

Each session lasts for an hour. Sessions after October 2027 will be announced in the future.

Who will be at the office hours?

Some board members of the PSF will be attending each office hour, as well as members of the PSF Staff. The list of the PSF Board Directors can be found on our website. We are passionate Python community members who are excited to listen, help, and provide support to you. We are happy to follow up with you if there are any issues we cannot address immediately during the office hour sessions. As always, you can email us at psf-board@python.org with inquiries, feedback, or comments any time.

October 22, 2026 08:02 AM UTC


Peter Hoffmann

Loading 33,000 Peaks on Demand with a z10 Tile Grid

biwak.info is my mobile-first map of Alpine huts and bivouacs. Recently I wanted to add peaks: tap anywhere near a summit and get its name, elevation, prominence, a photo and the Wikipedia link. The data for that comes from OpenStreetMap and Wikidata: 33,437 named peaks between Nice and Vienna.

As a single peaks.json file this is 15.4 MB (1.8 MB gzipped). That's fine for a desktop, but on a phone with one bar of signal in a valley it is not, and most of it is never used: you look at one valley, not the whole Alps.

The site is hosted as plain static files, without a database or an application server, and I wanted to keep it that way. So the question was: how do you load only the peaks the user is looking at, using nothing but files?

The 356 z10 tiles that contain peaks, darker means more peaks. The Matterhorn tile 533/364 is outlined in red.

Use the map's own tile grid

Leaflet already splits the world into a grid: the slippy map tiles z/x/y that every OpenStreetMap-style basemap uses. At zoom level z there are 2^z × 2^z tiles, and any coordinate maps to exactly one of them with two lines of math:

def lon2x(lon, z=10):
    return math.floor((lon + 180) / 360 * 2**z)


def lat2y(lat, z=10):
    lat_rad = math.radians(lat)
    return math.floor((1 - math.log(math.tan(lat_rad) + 1 / math.cos(lat_rad)) / math.pi) / 2 * 2**z)

The idea: cut the peaks into files along that same grid at one fixed zoom level, one JSON file per tile. The browser computes which tiles cover the current view and fetches only those. Because the build (Python) and the client (JavaScript) use the same formula, they always agree on which file a peak is in. The floor also means every peak belongs to exactly one tile, so nothing is duplicated.

The Matterhorn (45.97639, 7.65833) for example lands in tile 10/533/364. I keep a handful of these as unit test fixtures for the build and the client.

Why zoom level 10

The zoom level of the data grid is independent of the zoom level the map is showing. It is a trade-off between the number of requests and the size of each file:

At z9 the tiles would hold four times as many peaks, so most of each download would be off-screen. At z11 a view would need four times as many requests. z10 is the sweet spot for this density of data.

The build: one small file per tile

A Python script (tools/build_peaks.py) reads the big peaks.json and writes the grid:

data/peaks/manifest.json      non-empty tiles with peak counts
data/peaks/10/{x}/{y}.json    one file per tile

Each tile is a compact table: a fields header and one array per peak, instead of repeating the key names thousands of times. Coordinates are stored as integers in 1e-5 degrees, which is about one meter and saves the long float strings:

{"cat": 1,
 "fields": ["id", "lat_e5", "lon_e5", "ele", "rank", "name", "prominence", "wikipedia", "wikidata", "image", ...],
 "rows": [
   ["n26863664", 4597643, 765860, 4478, 3, "Matterhorn", 1038, "it:Cervino", "Q1374", "https://...", ...],
   ["n1436639948", 4603418, 761193, 4358, 3, "Dent Blanche", 915, null, "Q15125", "https://...", ...]
 ],
 "x": 533, "y": 364, "z": 10}

The manifest lists only the tiles that exist (7 KB). The client checks it first and never requests a tile over the Po valley or the Mediterranean that would only return a 404.

The build is deterministic: the same input gives byte-identical files. That keeps the git diffs readable when the OSM data changes, and rsync only uploads the tiles that really changed.

Not every peak at every zoom

At zoom 12 the Alps are full of small bumps that are technically named peaks. So every peak gets a rank from 0 to 3 at build time. The rank comes from the prominence (how far you have to descend before you can climb anything higher) or, if that is unknown, from the isolation (the distance to the nearest higher peak):

def peak_rank(ele, prominence, isolation_km):
    if ele is None:
        return 0
    if prominence is not None:
        return 3 if prominence >= 500 else 2 if prominence >= 150 else 1 if prominence >= 50 else 0
    if isolation_km is None:
        return 0
    return 3 if isolation_km >= 5 else 2 if isolation_km >= 2 else 1 if isolation_km >= 0.8 else 0

That gives 1,558 peaks with rank 3, 7,385 with rank 2, 18,015 with rank 1 and 6,479 with rank 0. The rows in each tile are sorted by rank, then by elevation. The map shows rank ≥ 2 at zoom 12, rank ≥ 1 at zoom 13 and everything from zoom 14 on.

The client: fetch what the view needs

In the browser (peaks.js) it all hangs off Leaflet's moveend event. From zoom 11 on, one zoom level before anything is drawn, the visible bounds plus a 25% margin are turned into tile keys. Missing tiles are queued:

function* tileKeys(bounds) {
  const x0 = lon2x(bounds.getWest()), x1 = lon2x(bounds.getEast());
  const y0 = lat2y(bounds.getNorth()), y1 = lat2y(bounds.getSouth());
  for (let x = x0; x <= x1; x++) for (let y = y0; y <= y1; y++) yield `${x}/${y}`;
}

function loadVisiblePeakTiles() {
  if (!peakManifest || map.getZoom() < LOAD_FROM_ZOOM) return;
  for (const key of tileKeys(map.getBounds().pad(VIEW_PAD))) {
    if (!(key in peakManifest.tiles) || peakTiles.has(key) || peakTileState.has(key)) continue;
    peakTileState.set(key, 'loading');
    fetchQueue.push(key);
  }
  pumpFetchQueue();
}

map.on('moveend', loadVisiblePeakTiles);

A few details made it work well on a phone:

Drawing: one canvas, no markers

33,000 Leaflet markers would be far too heavy, and the basemap (OpenTopoMap) already prints peak names and elevations. So the peaks layer is a single <canvas> on its own map pane that draws small, slightly transparent grey triangles after each move. During a zoom animation the canvas is hidden, because it isn't transformed with the map. It is redrawn on zoomend.

Tapping is a nearest-neighbour search over the loaded tiles: anything within 30 px of the tap counts, from zoom 12 on. The search box can span a tile border, so it looks at all tiles it touches. A peak right at the edge of its tile is still found when you tap just across the line.

Deep links come for free

Since then every hut and peak also has a stable URL like biwak.info/p/matterhorn. For a peak, the slug lookup only needs to store its coordinates: the client computes the tile from them with the same lon2x/lat2y, fetches that one file and opens the peak. The grid works as a spatial index that needs no extra code.

What I'd do next

For data of this size, tens of thousands of points over a mountain range, the plain file grid works very well: no server, no database, a few kilobytes per view, and the whole thing is about 430 lines of Python and JavaScript.

October 22, 2026 12:00 AM UTC


Zero-with-Dot (Oleg Żero)

Text-to-Diagram for AI Systems: Designing a DSL an LLM Gets Right

Introduction

I have worked on sensor design, e-commerce, airlines, medicine and CAD software, in several countries. The domains could not be more different, yet one problem kept coming back: people do not understand each other. Not because they are careless. Everybody simply looks at the same system from a different side. Legal sees data, the CEO sees cost, DevOps sees uptime.

Take one sentence from a kick-off meeting:

“We are building an agent to fix invoices.”

What does fix mean? Who uses it? Does it see customer data? Where does it run, and who owns it? Nobody asks, because everybody thinks they know. A year later the project has five different answers to each of those questions.

Large language models make this more urgent, not less. Anybody can now describe a system in a paragraph, and a model will happily draw it. The open question is whether that drawing is the architecture, or just an architecture.

Gnomon is my attempt at an answer. It is a small language, gnomocode, and a compiler that checks every statement against an ontology. A language model translates what people say into gnomocode; the compiler decides what is allowed; and the result can be drawn, queried and read back as prose. The compiler is written in Python, on top of Lark, rdflib and pySHACL.

I presented it as a keynote at DevAI by Data Science Summit in Warsaw on 20 October 2026, under the title of this article. This is the written version of that talk: why I built Gnomon, how it works, and what the measurements say about it.

Status

Work in progress, October 2026. A public repository is being prepared. Until it exists, this article is the best description of what is built and why it looks the way it does.

Motivation

Draw the invoice agent and you are done in five seconds: invoices go in, an agent does something, fixed invoices come out. The box is easy. The questions around it are the architecture: who owns the data, where the model runs, whether personal data is involved, who reviews the edge cases. That gap between words and a diagram somebody can be held to is what I wanted to close.

It has to work in both directions. Forward, from a conversation to a model that a developer, or an agent, can build from. Backward, from existing code and diagrams to text that people can read. The forward direction is the harder one, and it is what this article is mostly about.

Before writing any code, I wrote down what a language sitting in the middle has to offer:

The last one turned out to matter more than I expected.

Approach

I did not start with a language. I started with the cheapest option and let it fail.

Just ask the model. “Draw an architecture for an agent that fixes invoices.” The answer is fluent, plausible and different every time. It invents words, gives no errors and no line numbers, and there is nothing to diff. When it is wrong, nothing tells you where.

Objects. Pydantic [12] classes, filled in by the model through structured output. Validation is real and every provider supports it. But an architecture is a graph, not a tree. A missing fact is either an error or a null, and a conversation with a client is mostly missing facts. Two statements cannot be merged, nor can they contradict each other, and every new term means a new class and a new release. The vocabulary ends up cemented in code.

Existing notations. UML, BPMN, C4 [10], ArchiMate and SysML v2 [11] are standards, and people understand them. They come with fixed symbols and a fixed depth, none of them has a “maybe”, and only some of them check whether a diagram makes sense, as opposed to whether it is drawn correctly.

A language of my own, with a compiler. This is the one that stayed. The idea is old and boring, which is how I like my ideas: let the model talk, let the compiler decide.

Pipeline: natural language goes to text2gnc (a language model), then to gnomocode, then to a compiler with rules only, then to a graph, then to views, diagrams and recommendations. Compiler errors loop back to text2gnc for up to three rounds; gnc2text turns the graph back into prose.
Fig. 1 — The language model translates; it never gets the last word. Compiler errors go back to it for up to three rounds of repair.

The translator, text2gnc, turns natural language into gnomocode. The compiler, which is plain rules with no model inside, checks each statement and builds a graph. When it finds errors, it sends them back to the translator with line numbers and hints. Everything else, diagrams for different audiences, recommendations, even prose, is drawn from the graph.

Two consequences matter to me. The script is the source of truth, not a single call to a model: the same script always compiles to the same graph. And errors are located and readable, so a person and a model can both act on them.

The ontology is the key

Before designing a single keyword, I went looking for who had already described this world. Plenty of people had. A taxonomy says what is a kind of what: a language model is a machine learning model. An ontology goes further and adds relations and rules: an agent uses a model, a model runs on an environment, and a dataset cannot run on anything at all. Such vocabularies have been written in RDF [1] for years, and some are widely used: SKOS [3] for taxonomies, PROV-O [2] for provenance, DCAT [4] for data catalogues, DPV [7] for privacy, QUDT [8] for units and schema.org [9] for the web. OWL [6] defines the classes and SHACL [5] checks the graphs.

So the rule became: take terms, relations, types and hierarchy from what exists, and add the minimum. The result is a kernel of 57 terms and twelve modules (systems, deployment, integration, data, ML, agents, processes, operations, security, legal, cost and service levels) that a script imports only when it needs them. From day one there was also a licence rule: nothing copyleft goes in, so that the whole thing can later be distributed freely and commercially.

Names of existing vocabularies (PROV-O, DCAT, DPV, SKOS, schema.org, QUDT, OWL, SHACL, ODRL, Dublin Core, SPDX, ML Schema, AIRO, Croissant, TOSCA) funnel into the Gnomon tree: a kernel of 57 terms and modules for systems, deployment, integration, data, ML, agents, processes, operations, security, legal, cost and service levels.
Fig. 2 — Many sources in, one small vocabulary out.

Why call the ontology the key? Because every other part of Gnomon is generated from it or checked against it. Take a single word that a script writes, runs_on. In the ontology it has three layers: a lexicon entry that gives the word and its inverse reading, a property with a definition, and a shape that says what may stand on either side of it.

ontology (abridged)

gnlex:runs-on a skos:Concept ; skos:prefLabel "runs on"@en ;
    gn:inverseReading "hosts"@en ;
    gn:denotes gn:runsOn .

gn:runsOn a owl:ObjectProperty ;
    rdfs:label "runs on"@en ;
    skos:definition "The subject executes in or on the object."@en ;
    gn:acyclic true .

gnsh:runsOn-subject a sh:NodeShape ;
    sh:targetSubjectsOf gn:runsOn ;
    sh:or ( [ sh:class gn:System ]
            [ sh:class gn:Model ]
            [ sh:class gn:Activity ] ) ;
    sh:message "only a system, a model or an activity can run on something" ;
    gn:diagnosticCode "type/signature" .

That one definition feeds the whole tool chain. The lexicon decides which words a script may use, and therefore which words a model may generate. The shape becomes the compiler’s error message, word for word. The labels become the English sentences in which a script is read back. The concerns attached to each term decide which audience sees it on a diagram. And because Gnomon’s classes are declared as subclasses of the borrowed ones (a gn:Dataset is also a dcat:Dataset and a prov:Entity), the compiled graph is readable by tools that have never heard of Gnomon.

Info

The grammar of gnomocode is generated from the kernel alone. Words come from the lexicon, so adding a module adds vocabulary without changing the grammar. Extending the language is a matter of editing data, not releasing code.

The language

Why not let the model write RDF directly and validate it with SHACL? RDF is precise, but it is not something I want to read in a review. Here is the invoice agent as gnomocode, 23 lines:

fix-invoices.gnc

---
gnomon: 0.2.2
as_of: 2026-10
state: TARGET
import: systems, deployment, ml, agent, process
---

finance_team: Team
erp: Application
    owned_by = finance_team
    runs_on = CloudEnvironment() ~0.5        # "in the cloud, I think"
invoices: Dataset
    stored_in = erp
    contains_personal_data = true

invoice_agent: AIAgent
    uses = LanguageModel()
    classifies(invoices, frequency = 200/DAY)

fixing: Process = check >> ("amount > 10k" => review | post)
review.performer = finance_team
invoices -> check -> checked_invoices
checked_invoices: Dataset

It compiles to 76 lines of RDF. Three forms carry almost everything: name: Type declares an element, property = value states a fact about it, and action(object) says what it does. Flows connect steps (>>) and data (->). The notation borrows from places people already know: front matter from Markdown, indented blocks and assignments from Python, arrows from graph query languages. Even the case of a word tells you its kind: Type, property, CONSTANT.

The front matter pins the release of the ontology the script was written against, says whether it describes the current state or a target, and imports only the modules the text needs. An old script therefore compiles against the same release, or gets migrated to a new one on purpose.

Look at the ~0.5 on the runs_on line. It means “I think so, but I am only half sure”, and it is part of the language. Confidence has a few reference points: 1 is confirmed in a system or a document, 0.8 is stated by someone who does the work, 0.5 is second-hand, 0.2 is a guess. Below 0.7 a fact is not an error, but it is reported as a finding. In the graph it becomes an annotated statement, so you can query for everything that still needs confirming:

fix-invoices.trig (excerpt)

    rdf:subject :erp ;
    rdf:predicate gn:runsOn ;
    rdf:object minted:cloud-environment-0 ;
    gn:confidence 0.5 .

The syntax at a glance

The invoice script uses only part of the language. Here is most of the rest in one script that compiles, with a comment on what each line does:

support.gnc

---
gnomon: 0.2.2
as_of: 2026-10
state: CURRENT
import: systems, deployment, ml, agent, process
---

sales_team: Team                             # declares an element
support_team: Team
cloud_account: CloudEnvironment
tickets: Dataset
product_catalogue: Dataset

crm: Application
    runs_on = cloud_account ~0.8             # a fact and its confidence
    owned_by = sales_team                    # inverse reading of owns
    search_tool: Tool                        # a part of crm

support_agent: AIAgent
    uses = search_tool, LanguageModel()      # two values, one unnamed
    reads(product_catalogue)                 # an action: a new activity
    summarises(tickets, frequency = 50/DAY)  # with its own properties

LanguageModel[0] is gpt_4o                   # two names, one element
gpt_4o: LanguageModel
    name = "GPT-4o"

triage: Process = sort >> ("urgent" => call_back | answer)
sort: Classification
    performer = support_agent
    duration = 3 MIN                         # a quantity with a unit
call_back.performer = support_team
tickets -> sort -> sorted_tickets            # data flows through a step
sorted_tickets: Dataset

A few things are worth noticing. A declaration inside a block makes the element a part of its parent, so nesting is how you say “consists of”. An action such as reads(product_catalogue) does not need a name: the compiler creates an unnamed activity of the right kind, with the agent as its performer and the catalogue as its input, and numbers it so that later lines can refer to it. And | in a flow means exactly one branch is taken, while & means all of them are.

A compiler in Python, built on Lark

The compiler is plain Python 3.14, managed with uv. Parsing is done by Lark [20], a parsing toolkit for Python under the MIT licence. The grammar is not written by hand: gnomon grammar generates it from the kernel of the ontology, with one slot per class of words. A new module adds words, never grammar rules. An excerpt:

gnomocode.lark (excerpt)

declaration: NAME ":" type_ref confidence? assigned_flow? _NEWLINE block?
block: _INDENT member+ _DEDENT
own_property: NAME "=" values _NEWLINE
own_action: NAME "(" [arguments] ")" confidence? _NEWLINE
step_flow: step_term (">>" step_term)+
data_flow: reference ("->" reference)+
group: "(" branch ("|" branch)* ")" -> one_of
     | "(" branch ("&" branch)+ ")" -> all_of
confidence: "~" NUMBER

TYPE: /[A-Z][A-Z0-9]*[a-z][A-Za-z0-9]*/
CONST: /[A-Z][A-Z0-9_]*(?![A-Za-z0-9_])/
NAME: /[^\W\dA-Z]\w*/

Indentation works exactly as in Python, because Lark ships a post-lexer for it. Subclass Indenter, name the tokens, and the LALR parser receives _INDENT and _DEDENT like any other token:

syntax.py (abridged)

from lark import Lark
from lark.indenter import Indenter


class GnomocodeIndenter(Indenter):
    NL_type = "_NEWLINE"
    OPEN_PAREN_types = ["LPAR", "LSQB"]
    CLOSE_PAREN_types = ["RPAR", "RSQB"]
    INDENT_type = "_INDENT"
    DEDENT_type = "_DEDENT"
    tab_len = 4


parser = Lark(
    grammar,
    parser="lalr",
    postlex=GnomocodeIndenter(),
    propagate_positions=True,
)

LALR keeps parsing fast and deterministic, which matters when the studio rechecks the script after every pause in typing. propagate_positions keeps the line and column of every node, so each diagnostic can point at the exact span of the source. A Lark Transformer turns the parse tree into statements; rdflib [21] builds the graph from them; the rules of the release derive further facts; and pySHACL [22] validates the result against the shapes of the ontology. Every violation is mapped back to the line it came from.

What the compiler says

Mistype a word and the compiler answers like a linter, with the line, a caret and a suggestion taken from the lexicon:

typos.gnc

---
gnomon: 0.2.2
as_of: 2026-10
state: TARGET
import: systems, ml, agent
---

finance_team: Team
erp: Application
invoices: Dataset
    stored_at = erp
reviewer: HumanRol

gnomon check typos.gnc

typos.gnc:11:5: error type/vocabulary: unknown property `stored_at`
   |
11 |     stored_at = erp
   |     ^^^^^^^^^
   = hint: did you mean `stored_in` or `stores`?
typos.gnc:12:11: error type/vocabulary: unknown type `HumanRol`
   |
12 | reviewer: HumanRol
   |           ^^^^^^^^
   = hint: did you mean `HumanRole` or `HumanReview`?
typos.gnc: blocked (2 errors, 0 warnings, 0 infos)

Syntax errors look the same. Indent a block by three spaces instead of four and you get:

gnomon check

syntax.gnc:9:1: error syntax/indentation: an indentation of 3 spaces
  |
9 |    owned_by = finance_team
  | ^^^
  = hint: indent with four spaces per level

A script that compiles still gets its findings. For support.gnc above the verdict is compiles (0 errors, 12 warnings, 18 infos), starting with lines such as:

gnomon check support.gnc

support.gnc:11:1: warning finding/missing-owner: no owner: `tickets`
support.gnc:11:1: warning finding/missing-personal-data-marker: personal data not marked: `tickets`
support.gnc:19:1: warning finding/missing-owner: no owner: `support_agent`
support.gnc:28:52: warning finding/missing-performer: no performer: `answer`
support.gnc:19:1: info finding/missing-runs-on: where it runs is not stated: `support_agent`
support.gnc: compiles (0 errors, 12 warnings, 18 infos)

With --format json the same diagnostics come as records. This is what the translator receives in the repair loop:

gnomon check --format json typos.gnc (first record)

{
  "file": "typos.gnc",
  "line": 11,
  "column": 5,
  "end_line": 11,
  "end_column": 14,
  "severity": "error",
  "code": "type/vocabulary",
  "message": "unknown property `stored_at`",
  "hint": "did you mean `stored_in` or `stores`?",
  "rules": []
}

From a sentence to a line to a picture

Each line of the script answers something somebody said. Suppose the kick-off meeting went on a few minutes longer. This is how its sentences map onto the script, and how the compiler reads each line back, in its own controlled English (gnomon explain, no model involved):

What was saidGnomocodeWhat the compiler reads back
”We are building…”state: TARGETThis description is of the target state, as of 2026-10.
”…an agent…”invoice_agent: AIAgentInvoice agent is an AI agent.
”…that uses an LLM…”uses = LanguageModel()Invoice agent uses the unnamed language model 0.
”About 200 invoices a day.”classifies(invoices, frequency = 200/DAY)Invoice agent classifies invoices (classification 0). The frequency of the unnamed classification 0 is 200 per day.
”Invoices hold customer data.”contains_personal_data = trueInvoices contains personal data.
”The ERP is in the cloud, I think.”runs_on = CloudEnvironment() ~0.5Possibly (confidence 0.5): erp runs on the unnamed cloud environment 0.
”Anything over 10k goes to review.”check >> ("amount > 10k" => review | post)After check comes exactly one of these: review when “amount > 10k”; post.
”Finance does the review.”review.performer = finance_teamThe performer of review is finance team.

The read-back matters more than it looks. A person who will never learn gnomocode can still check, sentence by sentence, whether the script says what they meant. And a language model that turns the script into prose works from these sentences, so it has nothing to guess.

The same lines are also a picture. The process line becomes the process view, with the branch, its condition and the performer of the review:

Isometric process view: the activity check, marked as an exclusive branch, followed by review and post, all three on a plate labelled fixing; review carries a small cube marking its performer, the finance team.
Fig. 3 — One line of gnomocode, check >> ("amount > 10k" => review | post), drawn as the process view in Gnomon Studio.

Vague is valid, contradictions are not

This is the principle I am most attached to. A real conversation is full of gaps and guesses. A language that rejects every incomplete statement will never be used; a language that accepts everything is useless. So the compiler draws a different line: it accepts vagueness and rejects contradictions.

invoice_agent: AIAgent is valid. It is vague, but it is true. The statements below are not, and the compiler says why, with the line and the reason taken straight from the ontology:

invalid.gnc

---
gnomon: 0.2.2
as_of: 2026-10
state: CURRENT
import: systems, deployment, process
---

finance_team: Team
erp: Application
invoices: Dataset
    runs_on = erp
check: Activity
    automation_level = MANUAL
    automation_level = AUTOMATED
erp: Dataset
module_a: System
    part_of = module_b
module_b: System
    part_of = module_a

gnomon check invalid.gnc

invalid.gnc:9:1: error inconsistency/disjoint-kinds: disjoint kinds: `erp`
invalid.gnc:11:15: error type/signature: only a system, a model or an activity can run on something: `invoices`
invalid.gnc:14:24: error inconsistency/single-value: two values of a single-valued term: `check`
invalid.gnc:17:15: error inconsistency/cycle: partOf cycle: `module_a`
invalid.gnc:19:15: error inconsistency/cycle: partOf cycle: `module_b`
invalid.gnc: blocked (5 errors, 8 warnings, 5 infos)

The compiler talks back

Diagnostics have three levels. Errors block; warnings and infos pass. The useful side effect is that the missing information turns into questions. The invoice script compiles, with 11 warnings and 15 infos, and those read like the agenda for the next meeting:

The compiler saysThe question it is asking
no owner: invoicesWho owns the invoices?
no tool: invoice_agentWhat can the agent actually do?
where it runs is not stated: invoice_agentWhere is it hosted?
personal data not marked: checked_invoicesDoes the output fall under GDPR too?
a fact below the confidence threshold: erp runs onIs the ERP really in the cloud?

These are the questions nobody asked at the kick-off. The same messages, with their line numbers and hints, are what the translator receives in the repair loop. In practice that is what makes a language model usable here: it gets an error it can act on, not a shrug.

One model, many pictures

A diagram is a projection of the graph, not the model itself. Gnomon draws three views of a script (architecture, process and data lineage), filters them for an audience (executives, architects, legal and compliance, security, operations) and folds them to a level of detail. A lawyer sees the personal data and where it is kept; an architect sees every part.

The drawing itself is replaceable too. The same compiled graph goes to Gnomon’s own isometric canvas, to Mermaid, or to LikeC4, and nothing in the script changes:

The architecture view of the invoice script drawn three ways: on the left an isometric canvas with coloured blocks for the ERP on a cloud environment slab, the invoices, the invoice agent and the language model; top right a Mermaid flowchart; bottom right a LikeC4 diagram with coloured cards.
Fig. 4 — The architecture view of fix-invoices.gnc, drawn by Gnomon's canvas (left), Mermaid (top right) and LikeC4 (bottom right). One model, three renderers.

Each renderer makes its own choices. The canvas sets the ERP on the slab of the cloud it runs on and marks personal data with a red lock. Mermaid labels every edge. LikeC4 stacks the same edges top-down. The facts underneath are identical, which is the point: you can argue about the picture without arguing about the system.

From the same graph, the rules of the ontology also recommend where an agent could take over work, ranked by effort, with the facts still missing to decide.

Evaluation

I did not want to show only a demo, so I built a way to measure the translation.

Evaluation scheme: the original text goes through text2gnc to a script, then gnc2text to prose (the round trip); a hand-written golden script goes through gnc2text to prose (the ceiling); an LLM judge compares both proses with the original for coverage and faithfulness; code compares the generated graph with the golden graph for graph F1.
Fig. 5 — The round trip: text to script to prose, judged against the original. A second path through a hand-written golden script shows what the language itself can carry.

There are three measures. Coverage says how much of the original the prose still says. Faithfulness says how much of the prose the original supports. Graph F1 counts the facts two graphs share, and it is computed by code, with no model involved. The first two use a language model as a judge [13]: it extracts claims from a text, each with a quote of the original that code then verifies, and gives a verdict per claim.

The test set is 54 texts about architectures, agent systems, data pipelines, processes and audits, all from public sources that allow reuse: 40 from the Azure Architecture Center [18], 5 from the GDS Way [17] and 9 algorithmic transparency records of the UK government [19]. Each has a golden script written for it.

Does it compile?

With Claude Sonnet 5.5 as the translator, all 54 texts ended up as scripts that compile. 29 compiled at once, 23 needed one repair round, and 2 needed two or three.

Stacked bar of 54 texts: 29 compiled at once, 23 after one repair round, 2 after two or three rounds; 25 texts were saved by the repair loop.
Fig. 6 — The repair loop earns its keep: without it, nearly half of the texts would not compile.

Where do the facts go?

Of 100 claims in an original text, 58 survive the round trip. The interesting part is where the other 42 go. Splitting the loss into layers shows that about 40 are lost because the language, and my golden scripts, cannot say them yet. The translator loses only 7.

Waterfall chart per 100 claims: 100 claims of the original, minus 39.6 lost by the language and the golden scripts, minus 7.0 lost by the translator, plus 4.5 gained when telling the script as prose, leaving 58.0 kept; whiskers show 95% bootstrap intervals.
Fig. 7 — Where 100 claims of an original go. The language and the golden scripts lose about 40, the translator 7. The prose restores 4.5 that its controlled sentences do not state.

I find this the most useful result of the whole project. The bottleneck is the language, not the translator. That is a very different to-do list from “use a bigger model”, and it tells me which words to add next.

How far to trust the judge, and the graph?

Not blindly. The judge finds every claim of a text in the text itself, and 99.7 of 100 in a text with a fifth of its sentences removed. But when asked to notice the removed claims, it caught only 57 of 100. Two different judges (gpt-oss-120b and Claude Haiku 4.5) gave the same verdict on 73% of 6,054 claims, a Cohen’s kappa of 0.55, which counts as moderate [14].

The graph measure needs no judge. Against its own golden graph, a generated graph scores an F1 of 0.56 on average; against the golden graph of a different text, 0.29.

Dot plot of 54 texts sorted by graph F1: each text has a gold dot for F1 against its own golden graph (mean 0.56) and a grey dot for F1 against the golden graph of another text (mean 0.29), joined by a thin line.
Fig. 8 — Each text twice: against its own golden graph (gold) and against another text's (grey, the chance level). The graph is about the right text, but only about half of its facts match.

So the graph is clearly about the right text, and only about half of its facts match. Part of that is the translator choosing different relations and levels of detail for the same fact: two translations of one text agree with each other at an F1 of 0.74. This is enough for a demo, not yet proof, and I would rather say so here than have you find out.

What it looks like

All of this sits behind a local web page, Gnomon Studio: the chat on the left, the script in the middle with live diagnostics and its description in plain English, and the diagram on the right. You describe a system, a script appears, compiles or repairs itself, and the picture updates. Every later message extends the script instead of starting again.

Gnomon Studio: on the left a chat where the user describes how a support team answers customer emails and then asks for an AI agent to draft the answers; in the middle 40 lines of gnomocode that compile with 5 warnings and 4 infos, with the description in plain English below; on the right an isometric diagram of the CRM on a cloud environment, the shared mailbox and the emails dataset.
Fig. 9 — Gnomon Studio after two messages: the chat, the script with its diagnostics and description, and the architecture view.

Conclusions

  1. Everyone sees a different system. This is a people problem before it is a technology problem.
  2. A shared language beats a better drawing. A drawing can be nicer; only a language can be checked, diffed and versioned.
  3. The ontology carries the weight. Vocabulary, rules, error messages, read-back and views all come from one place. Borrowing it from existing standards made it smaller and more trustworthy than anything I would have invented.
  4. Let the model talk, let the compiler decide. Models are excellent at turning messy words into structure and poor at being consistent. Put the determinism somewhere else.
  5. Measure the layers separately. “58%” alone would have told me to improve the model. Splitting the loss showed me where the loss really was.
  6. Allow “I don’t know” in the language itself. It changes what people are willing to write down.

What is next: separating the public repository (the ontology, the compiler and the studio); conventions that narrow the ways of writing one fact, since the same text still gives different scripts; words for the 40% the language cannot say yet; and the backward direction at scale, from existing code and diagrams to text.

If you work on architecture descriptions, agent systems, or on making language models and rules cooperate, I would be glad to hear from you. Gnomon is also meant as a working tool for audits: mapping the processes and data of a company before anyone builds an agent for them. The project page collects the scenarios I have translated so far.

References

  1. W3C, RDF 1.1 Concepts and Abstract Syntax, Recommendation, 25 February 2014.
  2. W3C, PROV-O: The PROV Ontology, Recommendation, 30 April 2013.
  3. W3C, SKOS Simple Knowledge Organization System Reference, Recommendation, 18 August 2009.
  4. W3C, Data Catalog Vocabulary (DCAT), Version 3, Recommendation, 22 August 2024.
  5. W3C, Shapes Constraint Language (SHACL), Recommendation, 20 July 2017.
  6. W3C, OWL 2 Web Ontology Language Document Overview (Second Edition), Recommendation, 11 December 2012.
  7. Data Privacy Vocabularies and Controls Community Group, Data Privacy Vocabulary (DPV), W3C Community Group report.
  8. QUDT, Quantities, Units, Dimensions and Types.
  9. Schema.org.
  10. S. Brown, The C4 model.
  11. Object Management Group, Systems Modeling Language (SysML) 2.0, 2025.
  12. Pydantic documentation.
  13. L. Zheng et al., Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena, NeurIPS 2023 Datasets and Benchmarks Track.
  14. J. R. Landis, G. G. Koch, The Measurement of Observer Agreement for Categorical Data, Biometrics 33(1), 1977, pp. 159–174.
  15. Model Context Protocol.
  16. Agent2Agent (A2A) Protocol.
  17. Government Digital Service, How GDS provides user support, The GDS Way, Open Government Licence v3.0.
  18. Microsoft, Azure Architecture Center, CC BY 4.0.
  19. GOV.UK, Algorithmic transparency records, Open Government Licence v3.0.
  20. Lark, a parsing toolkit for Python, MIT licence; documentation.
  21. rdflib, a Python library for working with RDF.
  22. pySHACL, a SHACL validator in Python.

October 22, 2026 12:00 AM UTC

October 21, 2026


Jaime Buelta

Jev webinar video

I hosted a webinar on the 20th talking about the Jev model, which is a new AI decision model that works well in responding quickly to simple questions to a particular piece of information. This model has been talked about quite a lot recently when it was introduced, as it helps in a lot of workflows where it can be an alternative that’s faster and cheaper than LLMs. It has also spurred a lot of competing decision models, including of course Open Weights alternatives. You can see the recording of the webinar... Read More

October 21, 2026 06:41 PM UTC


Python Engineering at Microsoft

Pylance + Pyrefly: bring your preferred type checker to Visual Studio Code

At Microsoft, our focus is on providing the best possible experience for Python developers – both for everyday coding and for the agentic workflows that are increasingly central to how developers build software. A great type checking experience is a core part of that, not only for surfacing accurate and actionable diagnostics and providing a strong foundation for Intellisense, but as the bedrock of static semantic intelligence for software agents. Today we’re announcing the Type Server Protocol (TSP) and our partnership with Meta to bring Pyrefly support to Pylance.

Introducing the Type Server Protocol

TSP is an open standard that extends LSP – the Language Server Protocol that underpins editor integrations across the industry – designed to allow any editor or tool to integrate type checkers as first-class backends.

Until now, Pylance and Pyright were tightly coupled. TSP creates a clean separation between the two: Pylance becomes the VS Code IDE intelligence layer for Python, and the type checker becomes an interchangeable static analysis component underneath it. Pyright remains the default and continues to power the experience for the vast majority of users. But for developers who have a strong preference – or whose codebase is better served by a different checker – switching is now a straightforward operation, right from the VS Code status bar.

What stays constant regardless of which checker you use is Pylance’s completions, Quick Info, environment management, and critically its deep integration with GitHub Copilot. Those experiences are built on Pylance, not on any individual type checker. TSP means you can bring the checker you want without giving up any of those features that you know and love.

Partnering with Meta to bring Pyrefly to Pylance

We are excited to announce that we have partnered with Meta to design and implement the TSP protocol and make sure that Pyrefly fully supports it. This collaboration is built on a shared belief that the Python ecosystem is better served by interoperability than by fragmentation. Meta brings deep expertise in fast static type analysis and a particular strength in AI/ML library support. Microsoft brings the VS Code IDE integration layer and the GitHub Copilot experience. TSP is the protocol that makes them work together.

Meta has been at the forefront of Python typing longer than almost anyone. Since 2017, they have been solving type checking at scale on some of the world’s largest Python codebases, including Instagram. That experience shaped every design decision in Pyrefly, which checks code at 1.8 million lines per second and was built from the ground up to deliver a consistent experience in both CI and the IDE. Meta has also invested heavily in the broader ecosystem, bringing NumPy to nearly 90% type completeness, completing pandas‘ public API type coverage, and publishing the most comprehensive public performance benchmarks for Python type checkers.

We are proud to partner with Meta on bringing Pyrefly to Pylance and we look forward to the ecosystem growing around this open standard, and to bringing even more choice to Python developers in the future.

Why Pylance + TSP + Pyrefly instead of Pyrefly standalone?

This is the right question to ask. Pyrefly already has a highly performant VS Code extension and is an excellent choice for type checking in continuous integration (CI). Why use it through Pylance instead of directly?

Pylance provides tight integrations with the GitHub Copilot experience that a standalone type checker extension can’t replicate – context provision for AI completions, intelligent code understanding that feeds into GitHub Copilot Chat, MCP tools for Python codebases, and the full suite of Python developer tooling that Pylance has built over years in production. In decoupling Pyright from Pylance we now have a clear separation of concerns where pylance can provide an intelligence layer for Python developers and delegate the heavy lifting of type checking. By using Pyrefly with Pylance via TSP, you get Pyrefly’s type analysis powering the engine, with Pylance’s AI-aware intelligence layer on top. You don’t have to choose.

How to try it

TSP support with Pyrefly requires a Pylance version of 2026.4.1 or newer. Here’s how the experience works.

Automatic detection

When you open a workspace, Pylance checks your Python environments for a Pyrefly installation. If it finds one – whether installed in your virtual environment or signaled by a pyrefly.toml or tool.pyrefly config in your pyproject.toml – you’ll see a notification:

Pyrefly was found in your Python environment. Would you like to use it as your source of diagnostics in VS Code?

Toast asking

Click Switch to Pyrefly and Pylance will activate Pyrefly as your diagnostics source immediately, registering the path to the Pyrefly executable automatically. No manual configuration needed. and the choice to “Switch to Pyrefly” can always be reverted or reenabled via Settings.

Manual setup via settings

You can also switch at any time through the VS Code Settings UI. Under Python › Analysis: Diagnostics Source, you’ll find a dropdown with three options:

Screenshot of the **Python › Analysis: Diagnostics Source** setting expanded

When Pylance + Pyrefly is selected, Pylance will search for a Pyrefly installation in your virtual environments or download it automatically.

A companion Python › Analysis: Pyrefly Version setting lets you pin a specific version, point to a local executable, or leave it empty to always use the latest. The minimum supported version is 0.60.0.

Status bar

Once active, the status bar shows Diagnostics Source: Pylance + Pyrefly alongside your interpreter and environment.

Screenshot of the language status bar

Clicking it opens the Python › Analysis: Diagnostics Source setting.

What do you think?

Try the new Pylance + Pyrefly experience today in VS Code and let us know your thoughts. Share any feedback by opening an issue or voting on an existing one. You can also leave a comment below or reach us on X.

The Pyrefly team have their own blog post on TSP and the work we have done together.

The post Pylance + Pyrefly: bring your preferred type checker to Visual Studio Code appeared first on Microsoft for Python Developers Blog.

October 21, 2026 04:42 PM UTC


Rodrigo Girão Serrão

Tachyon: sampling profiling in Python

Learn how to use Tachyon, Python's sampling profiler.

“Tachyon” is a new sampling profiler that was introduced in Python 3.15. It provides a CLI that allows running and profiling scripts or attaching to running processes.

The Tachyon logo, featuring a blue snake with wings coming out its head and the word Tachyon written in the middle.Tachyon logo

In this article you'll learn what statistical profiling is and how to use the Tachyon CLI to identify performance bottlenecks in your code.

15 days of Python 3.15

To celebrate the release of Python 3.15, over the next 15 business days I'll be writing about a new 3.15 feature every day. Explained clearly and with examples so you don't have to sift through the changelog.

Subscribe to receive this free email series:

If you want, you can also check the schedule of the upcoming emails.

What is statistical profiling

Statistical profiling is the practice of profiling your code by periodically taking snapshots of the state of your Python program. Since you're periodically snapshotting the call stack of your program, you call Tachyon a sampling profiler.

Tachyon collects all of its samples over a profiling run and uses it to estimate how much time is being spent in each part of your code. Suppose Tachyon collects 1,000,000 samples over the course of 10 seconds. Out of those 1,000,000 samples, assume 25,000 samples (2.5%) show the function foobar. Tachyon estimates that you spent 2.5% of the 10 seconds, or 250 miliseconds, inside the function foobar.

The longer Tachyon runs, and the higher its frequency (the shorter the interval between snapshots), the more accurate the results that Tachyon produces.

Using the Tachyon CLI

Tachyon is the “cool” name for the module profiling.sampling from the standard library. Since it's a module...

October 21, 2026 03:50 PM UTC


Mariatta

Python Core Sprint 2026: Day 2

Day 2 of the Python Core Sprint was all about one thing for me: moving our GitHub bots, bedevere, blurb_it, and miss-islington, off Heroku and onto the PSF’s Cabotage. Monday was prep. Yesterday was the switch.

The facts first. The downtime was announced for 11 AM to 12 noon Pacific. Here is how it went, in Pacific time:

Zachary Ware commented that it sounded like it went very smoothly.

October 21, 2026 02:00 PM UTC


Python Insider

Python 3.15.1, 3.14.9 and 3.13.17 are out

Three certified security releases for 3.15, 3.14 and 3.13.

October 21, 2026 12:00 AM UTC

October 20, 2026


PyCoder’s Weekly

Issue #757: Django Migrations, Functional Programming, MVC, and More (2026-10-20)

#757 – OCTOBER 20, 2026
View in Browser »

The PyCoder’s Weekly Logo


Django ORM, Migrations and makemigrations

This tutorial builds a small coffee and tea store one migration at a time, then breaks it on purpose. You’ll backfill a required field, roll a migration back, untangle two developers’ migrations and fix a slow page with Django 6.1’s new fetch modes.
MY-SPACE.IO • Shared by Mirza Ishraq Yeahia

Functional Programming in Python: When and How to Use It

See how Python supports functional programming, what the paradigm actually is, and where it fits in the code you write day to day.
REAL PYTHON

MCP Auth That Passes the Security Review

alt

When your customers connect AI agents to your product, their security team will have questions. PropelAuth has the answers built in: role-gated scopes, enterprise SSO through Okta and Entra ID, phishing-resistant consent screens, and an audit log of every agent connection. Learn More →
PROPELAUTH sponsor

Exploring the MVC Pattern in Python

Learn how the Model-View-Controller pattern organizes Python web apps, then build a small Flask app using models, views, and controllers.
REAL PYTHON course

Quiz: Exploring the MVC Pattern in Python

REAL PYTHON

Python 3.16.0 Alpha 1 Is Now Available

PYTHON.ORG

Python Bytes Podcast Hits Episode 500!

PYTHON BYTES

PyPy v8.0.1 Release

PYPY.ORG

Claude Code Course: Stop Copy-Pasting From ChatGPT

Build a complete Python CLI app from an empty directory with Claude Code, in two live 4-hour sessions on October 24–25. You’ll plan features, run tests, manage git, and debug AI-generated code.
REAL PYTHON

Articles & Tutorials

PyTorch Year in Review: OpenTeams’ Contributions in 2026

OpenTeams’ PyTorch team reviews their 2026 contributions, in a year when PyTorch received 58% more pull requests than the same period of 2025, a growing share of them written with AI coding agents. Highlights include a redesign of the symbolic interpreter in torch.compile, day 1 compiler support for Python 3.15, a new operator, and a build backend re-write!
ANDREW JAMES • Shared by Andrew James

Bayesian Optimization for Efficient Hyperparameter Search

Instead of testing every hyperparameter combination, Bayesian optimization uses results from earlier trials to decide what to try next. This hands-on tutorial builds it step by step with scikit-learn’s GaussianProcessRegressor and a custom Expected Improvement function, plotting the model’s predictions and uncertainty along with each point the search picks.
KHUYEN TRAN • Shared by Khuyen Tran

Pendulum and the Most Cursed + Operator in All of Python

Pendulum, a popular Python datetime library, has a + operator that inspects the call stack to decide which answer to give you. The post explains how it ended up there, and why the design behind it, a “drop-in” subclass of datetime, still matters for your code now that the hack itself has been patched.
ARIE BOVENBERG • Shared by Arie Bovenberg

Checking Email Reputation, and Allowing Outlook Again

Two years ago, PyPI prohibited Outlook email domains in the registration of new accounts due to a large malware campaign. With an update to how email reputation is calculated, addresses from outlook.com and hotmail.com can once more be used.
MIKE FIEDLER

Why Parquet Dominates Modern Data Engineering

How Parquet’s columnar layout, encodings, zstd compression, and row group statistics make it smaller and faster to read than CSV, measured on NYC taxi trips data with Polars, DuckDB, and PyArrow.
ORAL ERSOY DOKUMACI • Shared by Oral Ersoy Dokumaci

Gemini 4 Argon Draws a Python Reading a Book

Explore Gemini 4 Argon’s Python code, turtle drawing, and responses to small tasks that check modern syntax, release knowledge, focused edits, and made-up functions.
REAL PYTHON

Python Project Structure: How to Choose the Right Layout

Learn how to structure Python projects: pick between flat and src/ layouts, organize your code, and apply the patterns to CLI, web, and GUI apps.
REAL PYTHON

Docling vs LlamaParse: How to Extract PDF Tables in Python

Learn how to extract tables from a PDF in Python with Docling and LlamaParse, and compare local parsing against a cloud API on speed and cost.
REAL PYTHON

Quiz: Docling vs LlamaParse

REAL PYTHON

Strings and Character Data in Python

Work with Python strings using a rich set of operators and functions: string literals, the str() function, and the methods you’ll use most.
REAL PYTHON

How to Use Python: Taking Your First Steps

Take your first steps with Python. Install it, run your first program, and work with variables, data types, loops, functions, and errors.
REAL PYTHON course

11 Beginner Tips for Learning Python Programming

Python tips for beginners: the learning strategies and habits that take you from your first script to code you’re proud of.
REAL PYTHON

Projects & Code

Full-Text Search for Django Models Using RediSearch

GITHUB.COM/SAADMK11 • Shared by Maksudul Haque

PyGithub: Typed Interactions With the GitHub API v3

GITHUB.COM/PYGITHUB

actually_interactive_rebase: TUI for Git Rebase

GITHUB.COM/TOLOMEA

hefty: Forecasting Tools for Solar, Wind, and Load

GITHUB.COM/WILLIAMHOBBS

django-benchmark: Django Performance Benchmark Profiler

GITHUB.COM/JUSTINMAYER

Events

Weekly Real Python Office Hours Q&A (Virtual)

October 21, 2026
REALPYTHON.COM

Swiss Python Summit 2026

October 22 to October 24, 2026
PYTHON-SUMMIT.CH

PyCon Panamá 2026

October 22 to October 24, 2026
PYCON.PA

Django Girls Ho 2026

October 22 to October 24, 2026
DJANGOGIRLS.ORG

PyHo 2026

October 23 to October 25, 2026
PYTHONHO.COM

PyBeach 2026

October 24 to October 25, 2026
PYBEACH.ORG

FastAPI Conf 2026

October 28 to October 29, 2026
FASTAPICONF.COM

PyConFR 2026

October 29 to November 2, 2026
PYCON.FR


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

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 ]

October 20, 2026 07:30 PM UTC


Python Bytes

#501 A Nod to Sir-Mix-Alot

<strong>Topics covered in this episode:</strong><br> <ul> <li><strong><a href="https://blog.jupyter.org/posts/2026/notebooks-on-demand-nod-a-jupyterlab-extension-for-a/?featured_on=pythonbytes">Nod: pause your Python program and open a notebook at any line</a></strong></li> <li><strong><a href="https://pola.rs/posts/release-polars-2/?featured_on=pythonbytes">Polars 2.0 ships: streaming engine by default, spill-to-disk, and a row-order gotcha</a></strong></li> <li><strong><a href="https://github.com/noripyt/django-cachalot?featured_on=pythonbytes">django-cachalot caches your ORM queries and invalidates them for you</a></strong></li> <li><strong><a href="https://noumenal.es/stack-report/towards-async-transactions-in-django/?featured_on=pythonbytes">Towards async transactions in Django</a></strong></li> <li><strong>Extras</strong></li> <li><strong>Joke</strong></li> </ul><a href='https://www.youtube.com/watch?v=TGDze5W1EZ8' style='font-weight: bold;' data-umami-event="Livestream-Past" data-umami-event-episode="501">Watch on YouTube</a><br> <p><strong>About the show</strong></p> <p>Sponsored by us! Support our work through:</p> <ul> <li>Our <a href="https://training.talkpython.fm/?featured_on=pythonbytes"><strong>courses at Talk Python</strong></a></li> <li>Consulting from <a href="https://sixfeetup.com/?featured_on=pythonbytes"><strong>Six Feet Up</strong></a> <strong>Connect with the hosts</strong></li> <li>Michael: <a href="https://fosstodon.org/@mkennedy">Mastodon</a> / <a href="https://bsky.app/profile/mkennedy.codes?featured_on=pythonbytes">BlueSky</a> / <a href="https://x.com/mkennedy?featured_on=pythonbytes">X</a> / <a href="https://www.linkedin.com/in/mkennedy/?featured_on=pythonbytes">LinkedIn</a></li> <li>Calvin: <a href="https://sixfeetup.social/@calvin?featured_on=pythonbytes">Mastodon</a> / <a href="https://bsky.app/profile/calvinhp.com?featured_on=pythonbytes">BlueSky</a> / <a href="https://x.com/calvinhp?featured_on=pythonbytes">X</a> / <a href="https://www.linkedin.com/in/calvinhp/?featured_on=pythonbytes">LinkedIn</a></li> <li>Show: <a href="https://fosstodon.org/@pythonbytes">Mastodon</a> / <a href="https://bsky.app/profile/pythonbytes.fm">BlueSky</a> / <a href="https://x.com/PythonBytes?featured_on=pythonbytes">X</a></li> <li>Join us on YouTube at <a href="https://pythonbytes.fm/stream/live"><strong>pythonbytes.fm/live</strong></a> to be part of the audience. Usually <strong>Tuesday at 7am PT</strong>. Older video versions available there too.</li> <li>Finally, if you want an artisanal, hand-crafted digest of every week of the show notes in email form? Add your name and email to <a href="https://pythonbytes.fm/friends-of-the-show">our friends of the show list</a>, we'll never share it.</li> </ul> <p><strong>Michael #1:</strong> <a href="https://blog.jupyter.org/posts/2026/notebooks-on-demand-nod-a-jupyterlab-extension-for-a/?featured_on=pythonbytes">Nod: pause your Python program and open a notebook at any line</a></p> <p>Nod (Notebooks-on-Demand) is a JupyterLab extension by Eric Rawn that drops a notebook into a running Python program. Add a <code>notebook()</code> call anywhere, run the program with <code>nod</code>, and execution pauses there with a notebook containing all variables at that point in the call stack. Edits go back to the source file with a button, with cells converted to code and markdown to comments via Jupytext.</p> <ul> <li>It answers the classic 'this would be easier in a notebook' debugging moment without copying snippets out to files.</li> <li>Like a breakpoint with a notebook inside: the left panel walks the call stack and notebook state switches with it</li> <li>Install is pip install nodpy plus nod --install-kernel, then run with nod python -m myfile</li> <li>The round trip back to source is the differentiator; markdown cells become comments, cells become newlines, and notebooks are disposable since the source file recreates them</li> <li>nodLog() stashes variable values across calls to explore later; the article also pitches notebook() for stepping through AI-generated code line by line</li> <li>Early stage: repo from December 2025 with 16 stars and essentially one contributor, so expect rough edges</li> </ul> <p><strong>Calvin #2: <a href="https://pola.rs/posts/release-polars-2/?featured_on=pythonbytes">Polars 2.0 ships: streaming engine by default, spill-to-disk, and a row-order gotcha</a></strong></p> <ul> <li>Polars 2.0 released Oct 6, 2026 (Ritchie Vink). The post says it's "not intended to be a big feature release", but it bundles a few big changes.</li> <li>LazyFrame.collect now defaults to the streaming engine, with large memory and speed gains on most queries.</li> <li>Breaking: streaming doesn't guarantee row order for join, group_by and unpivot. Use maintain_order=True if order matters. This is why it's a major version.</li> <li>Out-of-core (spill-to-disk) is on by default. It spills at about 80% of RAM ("may need tuning") with a 64 GB default disk budget. It covers sort, window functions and many expressions. Joins and group-bys are planned, not shipped.</li> <li>The new native Map dtype (Arrow MapType) replaces the old List(Struct(key, value)) reading. It comes with map.get, map.contains_key, map.len, map.keys and map.values.</li> <li>Stricter by design: errors should surface up front and implicit handling of data mismatches is opt-in. collect_schema() catches schema mismatches without reading data.</li> <li>SQL is now a first-class interface, with join reordering, better common-subplan elimination, and dynamic predicates/bloom filters.</li> <li>Benchmarks are self-run TPC-H/TPC-DS derived numbers against DuckDB 1.5.6 and 2.0 alpha, and DataFusion 54. They are not official TPC results.</li> <li>Polars was fastest on all but one benchmark. At 192 threads its constant overhead hurts small data (SF10 TPC-DS is 1.8x slower), and a fix is hoped for in the next release.</li> <li>Roadmap, not released: out-of-core joins and group-bys, better scaling at high core counts, Polars Cloud, and early GeoPolars work.</li> <li>Upgrade guide: https://docs.pola.rs/releases/upgrade/2/</li> </ul> <p><strong>Michael #3:</strong> <a href="https://github.com/noripyt/django-cachalot?featured_on=pythonbytes">django-cachalot caches your ORM queries and invalidates them for you</a></p> <p>django-cachalot is a Django package that caches ORM queries and invalidates them automatically when the underlying data changes. Install it, add one line to INSTALLED_APPS, and read queries run through your cache backend with no code changes. The trick is that it caches whole tables, so any write flushes that table's cached queries, making it a strong fit for read-heavy sites and a poor one for frequently updated tables. It supports Django 4.2 through 6.1 and Python 3.8 to 3.14.</p> <ul> <li>ORM caching that normally takes careful engineering drops in with pip install and one settings line, invalidation included.</li> <li><strong>Near-100x on joins:</strong> the README reports nearly 100x speedups for initial deployments heavy on select_related and prefetch_related.</li> <li>It caches the entire table, so a hot table with frequent writes invalidates constantly; docs suggest staying under 50 modifications per minute and mixing in cache-machine for hot paths.</li> <li>Setup is two lines, plus caveats: sync clocks across servers sharing one cache, and run a <a href="http://manage.py/?featured_on=pythonbytes">manage.py</a> command after restoring a database outside Django.</li> </ul> <p><strong>Calvin #4: <a href="https://noumenal.es/stack-report/towards-async-transactions-in-django/?featured_on=pythonbytes">Towards async transactions in Django</a></strong></p> <ul> <li>Carlton Gibson, The Stack Report, Oct 9, 2026. It's about getting async with transaction.atomic() into Django, hoped for 6.2 and not a commitment. Status check: both PRs are still open as of Oct 19-20, with no approvals and no milestone.</li> <li>The problem: asyncio tasks share one thread and so one DB connection. A slow transaction that rolls back also discards a sibling task's quick write. Today transactions go through sync_to_async, so everything runs serially on one thread.</li> <li>Design: async with transaction.atomic() opens a nested ThreadSensitiveContext, which gets its own worker thread and connection. On exit the connection is cleaned up and control returns to the request's connection. Siblings keep using the request's connection in autocommit, so their writes survive a rollback.</li> <li>Only the task that opened the block may nest further atomic() blocks. Any other task raises TransactionManagementError, and this is enforced.</li> <li>Subtasks inside a transaction are discouraged, since the connection is serial and parallel queries gain little. If you need them, use structured concurrency (TaskGroup or an awaited gather()) so they finish before the block exits. That rule is not enforced yet, and Gibson wants Django to catch late queries before 6.2.</li> <li>Caveats: each async transaction uses an extra connection for its duration, so pools may need tuning. The advice to disable CONN_MAX_AGE for async still applies.</li> <li>Origin: the Django on the Med sprint in Pescara (hosted by Paolo Melchiorre). An expert panel on Day 1 included Lily Acorn, Simon Charette, Jacob Walls, and Charlie Denton and Sam Searles-Bryant of Kraken Tech (authors of django-subatomic, which is being integrated into Django). Caleb Hattingh then investigated the design and opened both PRs.</li> <li>PRs: Django #22074 ("Fixed #33882 -- async with transaction.atomic") has review comments from four people, with the latest commit on Oct 19 adding a test for a late child-task query. asgiref #596 (ThreadSensitiveContext changes) had Gibson's review on Oct 19 and follow-up edits on Oct 20.</li> </ul> <p><strong>Extras</strong></p> <p>Calvin:</p> <ul> <li>Jazzband is Shutting Down (announced in March), but there are alternatives like https://django-commons.org/</li> <li>Treemd — nice TUI for viewing Markdown https://github.com/Epistates/treemd Michael:</li> <li>Talk Python <a href="https://talkpython.fm/community?featured_on=pythonbytes">Membership</a></li> <li>Talk Python <a href="https://talkpython.fm/guides?featured_on=pythonbytes">Visual topic guides</a></li> <li>Quick Follow Up to Episode #499 where we shared PEP 824 None coalescing operator, we also have <a href="https://peps.python.org/pep-0823/?featured_on=pythonbytes">PEP 823</a> (None Aware access operators) the successor to PEP 505. - via Nick Muoh and <a href="https://social.tchncs.de/@rafaelwo/117415996506597741?featured_on=pythonbytes">Rafael</a></li> <li><a href="https://github.com/pola-rs/polars/issues/27955?featured_on=pythonbytes">Free Threaded Polars, but staled on PyPI</a>?</li> </ul> <p><strong>Joke:</strong> <a href="https://www.youtube.com/watch?v=3TNpOD6bov8">LGTM (Looks Good to Me) - Claude Opus 5.5 music video</a></p>

October 20, 2026 07:18 PM UTC


Rodrigo Girão Serrão

DX improvements in Python 3.15

Learn about the top 3 developer experience improvements from Python 3.15

This article covers the top 3 improvements made to the developer experience of Python 3.15 users:

  1. improved error messages
  2. more colours
  3. default UTF-8 encoding

15 days of Python 3.15

To celebrate the release of Python 3.15, over the next 15 business days I'll be writing about a new 3.15 feature every day. Explained clearly and with examples so you don't have to sift through the changelog.

Subscribe to receive this free email series:

If you want, you can also check the schedule of the upcoming emails.

Improved error messages

Over the past few years, lots of effort has been poured into improving Python's error messages. Python 3.15 is no exception, as it introduces three improved types of error messages.

Errors in accessing nested attributes

If you get an AttributeError exception when accessing a name of an object, but that object has another member that has the correct name, Python suggests it:

class Inner:
    def __init__(self):
        self.value = 42

class Outer:
    def __init__(self):
        self.inner = Inner()

o = Outer()
o.value
# AttributeError: 'Outer' object has no attribute 'value'.
# Did you mean '.inner.value' instead of '.value'?

Incidentally, while Python 3.15 was in alpha, I found and reported a bug with this feature that was fixed in just a couple of hours!

If you get an AttributeError when trying to access a method from a built-in type, Python starts by checking if that's a typo. For example, list.pp will ask if you meant to type list.pop. Now, if there's no “obvious” typo, Python will also check a static table of common...

October 20, 2026 03:56 PM UTC


Mariatta

Python Core Sprint 2026: Day 1

This week I’m in San Francisco for the annual Python Core Sprint. This year the sprint is hosted by Brandt Bucher from OpenAI, at OpenAI’s office, and runs Monday to Friday, October 19 to 23. I’m only here for the first three days. On Thursday I fly out to Seattle for SeaGL, where I’m one of the keynote speakers :)

I could not make it to last year’s sprint at Arm in Cambridge, UK, and it is unlikely I’ll be able to make it to next year’s European sprint either, so I’m glad this one is on the West Coast and within reach. The sprint is one of the very few times in a year when this many Python core team members are in the same room, focused on nothing but Python. It is still one of my favorite Python events of the year. Thank you to Brandt and OpenAI for hosting us!

October 20, 2026 02:00 PM UTC


Python Software Foundation

Wikis: Relaunched!

In February 2026, the Python Software Foundation (PSF) made the PSF, Python, and Jython wikis read-only due to excessive automated traffic and security concerns on outdated infrastructure. This sudden change was disruptive to the many community members who rely on and contribute to these wikis. We are pleased to share that we have stood up new wikis through GitHub Pages that address the automated traffic and security concerns. The new wikis will have a more modern front-end, as well as a permissions-based content management system (CMS) for community members to make updates.

Find the new wikis here:

More details and context

Excessive automated traffic

With the old wiki structure, we received a large volume of alerts at all hours, each serious enough to page and wake our on-call infrastructure staff, which is very limited (just one person currently, although this will be changing soon!). The quantity of scraping overloaded our servers, causing outages, which happened often enough that these issues required a significant amount of effort for the PSF staff to manage. On review back in February, that traffic turned out to be almost entirely AI bots that were trying to scrape the entire internet.

Security

Additionally, some portions of the wikis were still built with Python 2. Python 2 reached end of life on January 1, 2020, and no longer receives upstream security fixes. Moving to a static site makes it very fast and reduces security risks, while also having the benefit of a more modern user interface.

Impact

The PSF acknowledges that retiring the old wikis without notice was frustrating to the overall community and especially so to those community members who have spent a lot of time contributing to the wikis over the years. The abrupt nature of this change did not reflect the care and respect that our contributors deserve, and we recognize the frustration this caused. We built the new wikis with the guiding principle of how to best serve the community, and we hope they will continue to be a valuable community resource, and hopefully improve on the experience. The new CMS means you can even create pull requests in your code editor if you prefer!

Platform

GitHub was chosen for the home of the new wikis as this is where CPython, python.org, and other properties the PSF hosts currently live. The hope is to continue to streamline the PSF's hosting and stewardship of services to simplify for both PSF staff and the community folks who contribute to them.

How to Contribute

Previously, the Python and Jython wikis were user-editable by invitation of an existing editor and the PSF wiki was permissions based. Moving forward, all three of the wikis will be permissions based via the GitHub repository they are hosted on, and changes can be requested through pull requests by anyone in the community.

We realize that moving to GitHub, with an entirely permissions based process, is a big change from the previous form. As stewards of Python's web presence, we've opted to provide a more modern interface, relying on GitHub, the platform that already powers CPython development. GitHub allows us to distribute granular control, review changes through pull requests, and maintain the uptime this service deserves. Also, since the site is now generated statically, it removes any worry about AI bots overwhelming the hosts that the wikis were previously hosted on.

Please review the repository's README file to learn more about contributing. We welcome the community to come join in on updating, cleaning up, and adding pages to the new wiki! We will happily grant write access to the Python wiki to contributors who are known stewards of the previous wiki, and look forward to bringing on more contributors in the future.

October 20, 2026 12:29 PM UTC


LernerPython blog, from Reuven Lerner

Pandas DatetimeIndex slicing: Fix KeyError with sort_index

Pandas DatetimeIndex slicing: Fix KeyError with sort_index

Slice an unsorted datetime index in Python Pandas, and you’ll likely get:

KeyError: ‘Value based partial slicing on non-monotonic DatetimeIndexes with non-existing keys is not allowed.’

Why? If the dates are unsorted, where should Pandas start/stop?

Solution: sort_index!

The post Pandas DatetimeIndex slicing: Fix KeyError with sort_index appeared first on LernerPython.

October 20, 2026 06:00 AM UTC


Seth Michael Larson

Documenting certificate distribution pain points for the Python ecosystem

I'm writing this blog post on the same day as the eMudhra Intermediate CA compromises news was published so the memory of what was required from me and unfortunately volunteer maintainers today is still fresh and top-of-mind. Fixing the systemic issues that many open source projects faced today takes longer than a day of hurriedly patching everything and publishing advisories, so I'm leaving these as notes for future us.

I'm only writing from the perspective of the Python ecosystem, although I know at least some of these issues exist elsewhere. Here's some context:

Python and pip

pip bundles its dependencies because bootstrapping is a really hard problem, including certifi. Python bundles pip because bootstrapping is a really hard problem. So a critical issue in certifi means potentially updating both pip and Python on short notice.

Python distributes pip through ensurepip, so the Python Security Response Team sometimes needs to respond by upgrading pip in case of a vulnerability that affects whether pip can be securely used to upgrade itself... like in the case of a CA certificate compromise... So if there's an issue in certifi not only do we need to publish a release of pip, we need to head-of-line block on that release landing in Python (across relevant release branches, usually 3-4), and then do releases of those branches.

pip already uses truststore (a library I maintain) by default. For compatibility reasons, pip still adds certifi root certificates to be considered as roots. By removing the certifi default (which appears to be happening) this would slightly improve the situation for Linux-based users, where they could upgrade their system CA certificates and be safe, still no revocation mechanism. The big benefit is that pip maintainers wouldn't need to do anything in the case of a certifi CA store compromise anymore.

Many other packaging tools and infra had to take action on short notice, too, like the manylinux build images, various bots and cron jobs, Python build bots, Sigstore CLI for Python, and likely many more.

Once all the patching is done, the biggest problem then is alerting users as they too have to take action to upgrade dependencies. I published a topic on discuss.python.org, sent an alert to security-announce@python.org, added banners to pypi.org and discuss.python.org, and posted on social media (thanks for boosting).

PyPI as a certificate distribution channel

An over-arching opinion that I have (and I am willing to be wrong) is that PyPI should no longer be a certificate distribution channel. Packages like certifi were necessary in the past to get a decent CA certificates bundle to platforms without one. Remember, Python is about the same age as TLS and much older than the expectation that your system has decent CA certificate bundles. Today I am not aware of a major platform that exists without a decent CA certificate bundle, as Python isn't the only application that needs CA certificates. And I'm not sure how much it is Python or pip's responsibility that your system provides a decent CA certificate bundle.

Maybe it's time to evaluate leaning on system-provided certificate stores more generally in the Python ecosystem. This would mean less work for open source maintainers that provide applications (Python, pip), by relying on maintainers of systems instead (distros, browsers, services like Let's Encrypt). Systems maintainers have to do this work anyway, can we rely more on their work so we don't have to painfully duplicate that same work across an ecosystem?

Revocation for OpenSSL-based platforms

This is still an open question for me, what is the best way forward towards certificate revocation for OpenSSL-based platforms like Linux distributions. I haven't spent a lot of time thinking about this, but if we could have this in a privacy and performance preserving way that would go a long way to reduce the amount of short-notice demands on volunteers or open source projects.

That's all for now, time to log off and rest up.



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



October 20, 2026 12:00 AM UTC


PyCon Ireland

Schedule for PyCon Ireland 2026

We are delighted to announce the schedule for PyCon Ireland 2026, to be held at Chartered Accountants House in Dublin on Saturday 21 November. You can choose from more than thirty talks across three tracks, at least four workshops, and two panels, as well as keynotes and lightning talks, given by dozens of speakers.

We’ll have lots of sessions on AI and Machine Learning, including

But it’s not just AI. There will also be sessions on

We had more than 170 submissions that needed to be evaluated and arranged. We thank everyone who took the time to make a submission. We had difficult choices to make when drawing up the schedule.

Tickets

If you’ve been waiting for the schedule to appear before buying your ticket, wait no more! Tickets are being sold now. The General ticket costs €59, the Student ticket is only €35, and the Corporate ticket is €159. With these all-inclusive tickets, as well as access to all the sessions and networking with the speakers and other attendees, you get a light breakfast, a sandwich lunch, and morning and afternoon refreshment breaks.

We do expect to make some further additions to the schedule, so check regularly for updates.

October 20, 2026 12:00 AM UTC

October 19, 2026


Rodrigo Girão Serrão

Startup configuration files

Learn how to use startup configuration files to run package entry points when the Python interpreter starts up.

A startup configuration file is a file with the extension .start. Starting from Python 3.15, the Python interpreter uses startup configuration files to run package entry points when the interpreter starts up.

Here is an example of a simple startup configuration file that installs error-links when the Python interpreter starts:

error_links:install

The entry point error_links:install from the above points to the function install from the module error_links. In general, an entry point resembles an import:

  1. the import path
  2. followed by a colon
  3. followed by the callable that gets called

For the entry point error_links:install, the equivalent import would be from error_links import install.

The module site and some historical context

The module site, from the standard library, is imported automatically when the Python interpreter starts up. When imported, the module site does a couple of things. Among them, it adds some paths to the module search path and then checks them for configuration paths.

In Python 3.14 or earlier, configuration files were .pth files. .pth files can be used to extend sys.path by listing directories. Alongside that, and for backwards compatibility reasons, .pth files could also include import lines.

import lines in a .pth represent a security risk, so PEP 829 introduced a safer way of running custom hooks during Python interpreter start up. import lines in .pth files are now deprecated and Python 3.15+ also looks for .start files to run the listed entry points.

Python 3.15 brings much more to the table

From the two new built-ins frozendict and sentinel, to lazy imports, package startup configuration files, typing improvements, or more colour everywhere, Python 3.15 has a lot more to offer. If you want to learn more about what's new in Python 3.15, take a look at the “15 days of Python 3.15” series I'm running. For 15 days, I'll explain a new Python 3.15 feature every day.

Get the free 3.15 email series:

October 19, 2026 07:19 PM UTC


Django Weblog

Quarterly contributor outreach

Hi Django community, thank you all for continuing to contribute to Django.

The Steering Council is reaching out to bring people's attention to a few areas. We have major features we'd like to test and review for 6.2, smaller changes that could use attention and then various New Feature ideas we'd like community input on.

Major features

These PRs are things we'd like to refine, test and get merged in. We're looking for people to test the features themselves and review the PRs. Please see our guidelines on reviewing PRs.

  1. CompositeField (multi-column subqueries)
  2. Only needs testing, not review.
  3. DEFAULT_PK_FIELD setting (e.g. UUID primary keys)
  4. Move models between apps

Sage Abdullah has a great Django London talk on how to test your project against Django's main branch. Please note that you would need to change the branch to point to the PR’s specific branch.

Other features

These are other PRs that we thought would be cool to have included in 6.2. They are in various states of refinement. We're looking for people to test and review the PRs. They are smaller in nature, so it may be easier to get involved here.

  1. Adding QuerySet.executed_queries
  2. Partitioned cookies
  3. Support for the HTTP QUERY method
  4. Improve CSRF debug messaging behind proxies

New Features

This section is bringing attention to the New Features queue. Ideas are in various stages and require different types of input. With over 150 ideas so far, we'll try to provide direction for those who have limited time to participate.

Ideas needing technical vetting [medium+ effort]

These are ideas the community would like integrated, but we need contributor opinion on. The links are to proposals that could use vetting.

  1. Ability to customize model Meta and migration generation
  2. Implement modern CSRF protection
  3. Add timeout API for tasks
  4. Subresource integrity (SRI)
  5. Adding import maps to Django

Ideas needing community input [low effort]

We encourage people to review the entire New Feature queue and use the emoji reactions. Please use the 😕 emoji to register a "saw this, but have no opinion". This helps us understand what has been reviewed by the community. We have selected a few from the list that we'd like people to weigh in on:

  1. Better hooks to customize PasswordResetForm email sending
  2. Add PR help in Working with Git GitHub contribution documentation
  3. Service URL parsing in core (e.g. database and cache URLs)
  4. Standardise Task execution order
  5. Update AuthenticationMiddleware to delegate to Authentication Backends
  6. Support sending to Unicode email addresses via SMTP

Thank you

Django is built by the community and every bit helps move Django forward. We encourage everyone to feel empowered to participate. If you aren't sure where to start or have questions, ask on the Django Forum or in the Django Discord. We're happy to help you find your way in.

- The Django Steering Council

October 19, 2026 05:28 PM UTC


Hugo van Kemenade

Releasing Python 3.15.0

Prologue #

Like last year, I livetooted the release of Python 3.15.0. Here it is in blogpost form!


Two weeks #

Two weeks until Python 3.15.0 final!

#Python #Python315
Fri, Sep 18, 2026, 00:30 EEST

One week #

One week until Python 3.15.0 final!!

What are you most looking forward to?

#Python #Python315
Tue, Sep 22, 2026, 21:17 EEST

Python 3.15.0 release day! #

First blocker, we’re getting 503s on docs.python.org and buildbot.python.org, the latter of which is needed to check the tier 1 and 2 buildbots are passing, needed for a release.

It’s a certificate issue from an incompatible pyOpenSSL upgrade. We’re waiting for the US to wake up and poke it with a stick.

buildbot.python.org: 503 Service Unavailable. No server is available to handle this request

docs.python.org: 503 Service Unavailable. No server is available to handle this request

#Python #Python315
Thu, Oct 1, 2026, 14:19 EEST

Red #

The US woke up and they’re both back up!

Now, there’s one tier 1 failure to check.

Python Release Status Dashboard: 3.16 is yellow, 3.14-3.11 are green but 3.15 is red and ‘✘ Unreleasable: Tier-1 build failed’

3.15 Tier-1 build failed, Tier-2 build failed (1): AMD64 Fedora Stable Refleaks 3.15)

#Python #Python315 #release
Thu, Oct 1, 2026, 14:22 EEST

Flaky #

RuntimeError: join took too long: <Process name='Process-2107' pid=2290504 parent=2276730 started daemon>

Looks like a flaky test, and python/cpython#157184 has a fix to increase a timeout. Will try restarting for now.

#Python #Python315 #release
Thu, Oct 1, 2026, 14:25 EEST

Green #

That passed, the buildbots are back to green ✅️

Python Release Status Dashboard: 3.16 is yellow, 3.15-3.11 are green: ✔ Releasable

Next, issues/PRs with release-blocker label.

One is a straightforward docs update, I marked as blocker so I don’t forget, to change future tense to present.

Then three last-minute lazy import blockers that need a closer look.

List of GitHub issues and PRs labelled as release-blocker

#Python #Python315 #release
Thu, Oct 1, 2026, 15:11 EEST

Traditional surprise rc3 #

Following recent tradition, I’m going to add a surprise third release candidate!

It makes sense to include the last-minute lazy-import fixes in 3.15.0 final.

It also makes sense to give time to test them properly, so today’s planned 3.15.0 final is postponed, and I’m going to release a 3.15.0rc3 tomorrow, assuming everything is in place.

And then 3.15.0 final will follow about week later. Exact date to be announced.

We’re nearly there! 🤞

#Python #Python315 #release
Fri, Oct 2, 2026, 00:49 EEST

Missing backport #

The three lazy blockers are finally merged into main, two have been backported to 3.15, but wait, the third failed, because it depends on some tests not yet backported.

Okay, backport created, merge conflict fixed, CI is now spinning, and automerge enabled. That’ll merge in about 15 mins, then we can update it into the last blocker PR, that should merge about 30 mins later, and rc3 can begin!

#Python #Python315 #release
Fri, Oct 2, 2026, 20:58 EEST

rc3 #

Get yer rc3 here!

You know the drill: please test and report bugs!

Library maintainers, join the Cool Testers’ Club 😎 and add 3.15 to your test matrix.

Python 3.15.0 final planned for 2026-10-09.

#Python #Python315 #release
Sat, Oct 3, 2026, 00:14 EEST

Python 3.15.0 release day (second attempt)! #

✅️ Release date updated in What’s New in Python 3.15

✅️ No release blockers

✅️ No deferred blockers

✅️ Buildbots all green

Python Release Status Dashboard: 3.16-3.11 all green: ✔ Releasable

#Python #Python315 #release
Fri, Oct 9, 2026, 13:03 EEST

Cooldowns #

Pydantic finally added support for Python 3.15 yesterday, and aiohttp on Monday, so I could use Python 3.15 rc3 to run the release scripts and release Python 3.15.0 final!

But… I’m going to respect our seven-day cooldown, and stick with 3.14.

You also use cooldowns for your dependencies, right?

#Python #Python315 #release
Fri, Oct 9, 2026, 13:12 EEST

blurb bug #

Pushing the big launch button:

.venv/bin/python run_release.py --repository ../cpython --ssh-user hugovk --release 3.15.0

… and immediately ran into a bug with our changelog tool blurb, because we’ve had no new NEWS fragments since rc3 :)

Start of the release script, showing a number of successful checks, before the start of a traceback from blurb Remainder of blurb traceback, ending with BlurbError: Error in Misc/NEWS.d/3.15.0.rst:8: Invalid gh-issue number: ‘0’ (must be at least 32426)

This needs a fix in blurb, a new PyPI release, and then the docs lockfile regenerated and backported to 3.15.

Fix PR: python/blurb#92

#Python #Python315 #release
Fri, Oct 9, 2026, 13:43 EEST

blurb fix #

OK, merged and released (really easy using Trusted Publishing), lockfile updated and backported.

Restarted the release and that’s better now.

A temporary branch and tag has been pushed to my fork, and I need to verify the release commit does not contain any unexpected changes.

All looks good. Next to start the installer and artifact builds.

#Python #Python315 #release
Fri, Oct 9, 2026, 14:21 EEST

Installers #

The release artifact build failed, need to bump blurb in that lockfile too.

Like this: python/release-tools#451

Now restarted that workflow with the existing CPython tag, no need for retag.

This builds source+docs tarballs and Android+iOS artifacts. Takes ~30 mins.

GitHub Actions building docs, source, iOS and Android artifacts

Ned Deily has started the macOS installer builds. And I’ve started the Windows installer builds, takes ~2 hours.

Azure Pipelines building Windows installers

#Python #Python315 #release
Fri, Oct 9, 2026, 14:56 EEST

SBOM #

In the meantime, the GitHub Actions build has finished, SBOMs created and some things uploaded.

Release script showing download of artifact files, SBOM creation, upload to PSF servers, and waiting for Windows and Mac files

The Windows process has paused a couple of times, awaiting button clicks to approve signing of binaries. The whole release team gets emails about this, so no sneaking out signed releases. 🥷

Azure Pipelines: 1 approval needs your review before this run can continue to Sign binaries

Azure Pipelines: 1 approval needs your review before this run can continue to Pack

#Python #Python315 #release
Fri, Oct 9, 2026, 16:04 EEST

Nearly there #

Windows complete, tags pushed upstream, Sigstore signing done, adding files to python.org… nearly there…

#Python #Python315 #release
Fri, Oct 9, 2026, 17:11 EEST

Out now #

🐍🚀 Out now: Python 3.15.0 final!

💤 Lazy imports!
🧊 frozendict builtin!
💂 sentinel builtin!
📉 Tachyon profiler!
🖼️ Frame pointers everywhere!
🛅 Unpacking in comprehensions!
🗣️ UTF-8 as the default encoding!
🛞 Stable ABI for free-threading!
🆕 Package startup config files!
⌨️ TypedDict & TypeForm!
🐎 Faster JIT!
🎨 More colour!
🚌 & more!

Huge thanks to the Sovereign Tech Agency supporting my work as release manager and enabling another smooth release!

Python 3.15 logo: a purple heart with “3.15” in blue and yellow, flanked by a blue and a yellow snake, with lightning bolts, pink clouds and laurel branches, framed by the release’s headline features

#Python #Python315 #CPython #release
Fri, Oct 9, 2026, 17:23 EEST

It’s not over yet! #

I updated PEP 790, which also updates the PEPs API, which is used to point the docs at /3/ to 3.15, kicked off a docs build: docs.python.org/3/

Unpin one toot and pin another to my profile.

(3.14 might not be the newest, but we can still celebrate with pie!)

And then unlock the 3.15 branch so the core team can start merging 185 PRs that didn’t get in during RC.

#Python #Python315 #release
Fri, Oct 9, 2026, 18:27 EEST

Also #

✅️ update 3.15 label
✅️ rebuild devguide to update: devguide.python.org/versions/
✅️ unlock 3.15 branch

And I think that’s it!

Hey, @savannah, you’re up next with the first 3.16 alpha on Tuesday! 🚀

#Python #Python315 #Python316 #release
Fri, Oct 9, 2026, 18:58 EEST

October 19, 2026 10:21 AM UTC


Python Software Foundation

Announcing Python Software Foundation Fellow Members for Q3 2026! 🎉

The PSF is pleased to announce its third batch of PSF Fellows for 2026. Let us welcome the new PSF Fellows for Q3! The following people continue to do amazing things for the Python community:

Aldo Caneo

LinkedIn, GitHub

Donghee Na

LinkedIn, GitHub

Paul Moore

GitHub, Mastodon

Takayuki Shimizukawa

Website

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

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

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

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

October 19, 2026 09:14 AM UTC


LernerPython blog, from Reuven Lerner

Pandas date range selection: Slicing a datetime index with .loc

Pandas date range selection: Slicing a datetime index with .loc

Want to select a chunk of time from a Python Pandas data frame?

Make the datetime column the index, then use .loc and a slice to choose dates:

(
  df
  .set_index('tpep_pickup_datetime')
  .sort_index()
  .loc['2025-11-10':'2025-11-20']
  ['trip_distance']
  .mean()
)

The post Pandas date range selection: Slicing a datetime index with .loc appeared first on LernerPython.

October 19, 2026 06:00 AM UTC