Should Python adopt CalVer?

Here's my slides from the Language Summit at PyCon US 2024.

Hello, my name is Hugo van Kemenade, I’ve been a core dev for about two years, and I’m the next release manager.

I work for Dead Set Bit in Helsinki and sometimes cycle on the sea.

Technically, this is the first talk of PyCon, and it’s also my first talk at PyCon!

I’m going to propose we adopt Calendar Versioning.

But first, here’s the current scheme. It’s major dot minor dot micro.

Major is bumped for very big changes, like 2-to-3.

Minor for less big changes, like the 3.13 feature release.

And micro or patch is bumped for bug fixes.

But look at this, it suggests the major version is only incremented for earth-shattering changes! Let’s come back to this.

Semantic Versioning is a popular scheme which aims to communicate the intent of a release:

A major bump means you make breaking API changes.

Minor means you add functionality without intentional breaking changes.

And patch is for bug fixes.

Every Python feature release, like 3.8 or 3.13, can and does contain breaking changes.

People often assume Python follows semantic versioning and complain about breaking changes in feature releases.

But Python’s scheme predates SemVer by at least 15 years:

The SemVer spec was introduced in 2009.

The Python scheme was added to source control in 1994 for the 1.0 release.

It was 30 years old in January!

Here’s someone who thought removing the dead batteries should wait for 4.0.

If Python adopted SemVer, we’d basically get a new major bump every year when we remove deprecations.

Some projects have adopted another versioning scheme based on the calendar.

With Calendar Versioning, you include some element of the date in the version number.

Here’s some examples:

Ubuntu and Black use the year and month.

For example, Ubuntu 24.04 just came out last month, in April 2024.

pip and PyCharm use only the year.

And here are some programming languages, all using some form of the year.

Either two or four digits.

Since 2019, we’ve made a release each year.

This is sort of calendar-based, it’s just that it’s offset by 11 years.

The simplest CalVer option would be to stick with major version 3, and put the year in the minor version.

For example, 3.26 will be released in 2026.

It makes it obvious when a release came out.

Right now, it’s a little tricky to work out when a release is end-of-life.

First you have to look up when it was initially released, then add 5 years.

But if the initial release date is right there in the version, it’s much easier.

I don’t think there’s much appetite for version 4.

It’s become a bit of a taboo.

We don’t want to repeat 2-to-3, and 4 has a lot of expectations by now.

We don’t want “earth-shattering changes”.

Perhaps 4 could be reserved for something big like removing the GIL, but the Steering Council made it clear the rollout must be gradual.

Will we stick on version 3 forever?

Another option is to put the year in the major version.

For example, 26.0 will be released in 2026.

This means we can take a nice big leap over all that 4.0 baggage!

Or we could also include the release month as the minor version, like Ubuntu and Black.

For example, 26.10 will be released in October 2026.

This makes it clear when in the year it was released, and also when in the year it will reach end-of-life.

With all these options, we’d keep compatibility with the familiar three-part version.

Like now, only the micro would be updated for bug and security releases.

There’d be no change to the major and minor.

Many other things would not change, like the annual cadence, the pre-releases, or the support durations.

To get an idea of changes for CPython, I made a couple of quick test builds for 3.24 and 24.0.

3.24 seems pretty straightforward, very similar to a 3.14 bump.

I needed to update some deprecations to point to their new planned removal versions.

And the CI passed.

For 24.0 there are at least four places that assume the major version is 3.

I quickly hacked these out, commented out some related tests, and the rest mostly passed (except one test on Windows).

Well, more work would be needed.

Of course, the big question is packaging.

The major and minor versions are joined together without a dot to use as tags in wheel filenames.

During the 3.10 alpha, there was ambiguity because 3-1-0 can be interpreted as 3.10, 31.0, or 310.

The spec says an underscore can be used if needed and PEP 641 proposed this, as in 3_10, and this approach could be used for options 2 and 3.

At the time, it was rejected because it was unknown what side effects there would be on code we’re not aware of.

And I think it was also rejected because we were half way through the 3.10 alpha, the clock was ticking, and we needed a decision.

So maybe we’d need to redo this PEP.

Will changing the major version to double digits break code?

Yes, changing the version in any new way always does because people make assumptions.

For example, that it’s always made up of single digits, or always begins with 3.

The last one here is most relevant for options 2 and 3.

So the first option is the safest, because the shape of the version doesn’t change.

If we do go for any of these options, these lint rules help identify the problems on the previous slide.

I recommend them even if we maintain the status quo.

We should also give plenty of notice before a change: I suggest not changing the Python version of any branch under development, giving at least one whole version’s notice, if not longer.

We can make preview builds which only change the version number for early testing, for example like deadsnakes did for 3.10, which was the first version with double minor digits.

And as always, continue to encourage early testing of alphas.

Here’s a summary, and now I’d like to hear your thoughts. Thank you!

Made with Keynote Extractor. Calendar icon by Icons8.