What this PEP doesn’t seem to address (please correct me if I’m wrong) is: what happens if we want to switch away from CalVer ? Python software generally expects version numbers to be monotonically increasing, so that testing for a given level of feature support can be spelled out as e.g. sys.version_info >= (3, 10) (yes, sometimes you might prefer EAFP style; but EAFP is not always desirable or even reasonable).
If the choice is to use 3.YY.Z CalVer numbers, then switching away from CalVer still means you get “reasonable” version numbers. But that’s precisely because 3.YY.Z CalVer numbers are not obviously CalVer in the first place (see the multiple people above who didn’t notice that pip used CalVer!).
If, however, the choice is to use unambiguously CalVer numbers, such as 3.YYYY.Z or YYYY.Z, then switching back to a non-CalVer scheme leads you either to very CalVer-looking non-CalVer numbers (3.YYYY+1), or to ditch the 3 major number which has implications for software compatibility (4.0, etc.). Good luck trying to get the message that 3.YYYY+1 is actually a SemVer-like version number instead of being determined by the release date.
However, if we kept an annual cadence, what might be the motivation for switching away and desiring to make them look non-CalVer looking? You mention SemVer: if the reason was to adopt SemVer, that would likely warrant a 4.0 (or 5.0 if we want to leapfrog 4.0) anyway. I would probably suggest something like that if desiring a clean break.
Do you mean “re-adopt”? As @steve.dower more eloquently explained above, Python very much uses some kind of SemVer currently, and behaves accordingly most of the time.
A related question would be: should the chosen CalVer scheme be non-SemVer? Or would it still have some element of SemVer? That is, it seems, the crux of the dilemma between YYYY.N (“pure” CalVer) and 3.YYYY.N (Cal+SemVer).
Coming in a little late here… I can say that I am not a fan of switching to CalVer. I don’t expect anything to follow semver.org to the letter but encoding a semantic in the version number is a very useful construct. Most projects follow the theory more than the letter of the law. That is, the significance of the change is clear from which digit is bumped. Personally, I don’t mind the 3.x.y where x is breaking change and y is incremental. It is easy to explain as others have said. I also have no fear about 4.x.y if we want to adopt purer semantic versioning. I feel that as a community we are too fearful of breaking changes in many cases.
The largest problem that I have with CalVer is that we lose the semantic value that we have in faux-semver. Will the differences between Python 2025.10 and Python 2026.03 require me to rewrite large portions of my application because we decided to make implicit async a feature? There is no real way to tell how large the change is unless you imply it from the range of dates.
That is my issue with the PEP as it is written – there is nothing in our version number that indicates the amount of change between one release and the next. Let’s face it, one of the main purposes for version numbers other than continuously incrementing integers is to provide additional detail. CalVer places the release date as the most important detail which is good for operating systems and desktop software. Semver encodes change semantics into the dotted version which is good when you are trying to figure out the risk of breaking your software by a large or small amount of change. Personally, I would rather know that switching my application from X.Y.Z to (X+1).*.* is risker than the change to X.(Y+1).* than having to look carefully for breaking changes in release notes of every release.
I maintain quite a bit of python software in “the place that pays the bills”. We adopted a upgrade cadence of updating to the active version every two years (most recently 3.12) with “dot releases” being applied as they come out. This works very very well in practice since it let’s the engineering team concentrate on delivering value with a wide upgrade window happening every other year. It also makes the process of managing the change very simple. We haven’t had any breaking issues with the “dot releases” and we can dedicate planned time to update when we adopt a new major version.
If we are going to adopt CalVer as a community, then:
use a four digit year please
drop the 3. prefix altogether. This is going to be a breaking change so let’s do it cleanly and decisively.
consider using the EOL year instead of the year of release
figure out how to make it clear how much expected (breaking) change is expected between releases – can we use PEP-440 epochs?
don’t assume that the release schedule will not change… we haven’t been on a regular cadence for too many years in the scheme of things
#1 & #2 were both discussed but I do feel strongly enough about them to repeat the refrain. This is going to break things so let’s adopt it without reservations and move on.
#3 seems pretty strange at first but think about it for a few minutes. The year of release and the year which it is EOL are both simply numbers. If we switch to less granular releases (eg, more than a year between releases), then two adjacent releases will not be consecutive numbers. This also makes the EOL date absolutely clear regardless of changes in release cycle or EOL window changes. I will admit that it seems a little strange but a number really is simply a number.
#4 is a requirement for the people that maintain software based on the Python stack. PEP-440 was great progress since I can at least parse a version number reliably now. I would hate to completely lose visibility into how much change occurred between releases.
While that DOES imply that there’d be some actual semantic value, it comes with a much worse problem: if the support lifetime ever changes, EOL years will be non-monotonic. “A number is simply a number”, but there is a general expectation that newer versions have higher numbers than older versions.
Not to mention that Python 2.7 is still being supported, just not by us. So “EOL” is a fairly nebulous concept to begin with when we release sources and encourage others to take responsibility for it.
More precisely I think, it’s good for things that are bundles of semantically versioned components (such as operating systems), or those that guarantee “document” compatibility incredibly strongly (such as practically all the apps I can think of that put the year in the name). Python does neither of these things.
For me, a big selling point for the PEP is that it is not a big change – it only skips 11 version numbers.
It doesn’t solve all issues that a completely new versioning scheme could theoretically address, but that’s OK.
(FWIW, if we did adopt four-digit year version component, I’d be very tempted to abbreviate it anyway.)
What would skipping 11 versions break? All I can think of is planned changes (like deprecations) that would need to be renumbered, across the ecosystem. Thankfully, unless a change is planned a decade or more in advance, it’ll be clear if the number is old or new.
If CalVer is adopted, and later we need to switch away from it (e.g. we want to do more feature releases per year), we can skip enough releases so the that number doesn’t look like a year any more, and treat the number as a counter again.
Thanks for the PEP, @hugovk, but I’m not convinced
There’s a lot of value in monotonicity and not breaking sequences, since code tends to rely on such features.
It is also not set in stone that we’ll have yearly releases going forward. This may change again as needs change. Similarly, releases may get pushed across a year boundary, or there may be two releases in a single year for whatever reason.
Or we may decide to switch to major version 4 in say 2033. We’d certainly not want to start that major release with version 4.33, since people are used to expecting initial hickups with .0 releases, but not with .33 ones. Think of the early 3.x versions.
The current scheme gives us enough flexibility to handle all these cases without breaking monotonicity and allows us to manage expectations, as well as not restricting us in any major way.
So -1 on a change to enforce calendar versioning, even if it looks like just skipping 11 version numbers at this point.
For me that’s the big problem with the PEP It’s not going all-in on calver, it’s making a gesture in that direction, but without doing so much that it will upset anyone. And in doing so, it’s not offering enough benefits to be convincing. If we want to adopt calver, we should be able and willing to make a much bigger deal of it.
I’m personally not in favour of the change - I like our existing “semantic but not semvar” versioning. This PEP doesn’t feel like it’s trying to persuade me that the change is a good thing, it feels like it’s trying to reassure me that it’s not disruptive enough that I need to be upset by it…
This is an important point. In my experience, calver is successful when it’s tied to a strict time-based release policy. So the “hidden catch” in this PEP is that it’s committing us to (strict) time-based releases unless we’re willing to change our versioning policy again in the future. And a change in versioning is much more publicly visible than a change in release schedules.
It would not necessarily be a final decision, frozen forever. Should the prevailing mood change, a new PEP can be written to replace this. We’ve had more than 30 such PEPs replacing others.
I don’t see 3.35 → 4.36 (or 3.2035 → 4.2036) as beyond awkward. It communicates two things. The major bump is whatever the replacement PEP defines it as. And minor is still the year, communicating when this change happened (plus all the other date-based reasoning benefits, re: PEP 2026: Calendar versioning for Python - #25 by ncoghlan).
The thing is, I don’t think we want a “earth-shattering” 3.x → 4.x change, and you should already be carefully reading the release notes for 3.12 → 3.13 in the same way you should for 3.26 → 3.27 (or 3.2026 → 3.2027), they all have breaking changes.
Hmm, this would lock us in to the 5-year support window, making it difficult to change both its length (releases either side of such a change might end up EOL in the same year) and adopt variable/LTS support (compare Ubuntu 18.04 and 22.10, both EOL in 2023).
Out of interest, has the idea ever been previously discussed of switching to actual semver? I.e. going to Python 4 and then assessing whether each annual release is “breaking” or not and then being less scared of bumping the major version going forward?
I do get the feeling that some annual releases are more important/breaking than others and it would be cool to know that everything for Python 8 will run with any version of Python 8 or whatever.
Or is it pretty much the case that there’s at least some breaking stuff in every annual release?
To my knowledge this has never been considered. There would be an endless debate on what “breaking” means. Were you relying on dataclasses not having a module-level name KW_ONLY? Sorry, I broke that code in 3.10. That’s clearly a silly example, but every change would need to be evaluated.
Put another way, can Python not be considered to already be doing semantic versioning in a way? Since there’s deprecations with every annual release, and none in between, is the Python versioning not just semver but with the “major” release being represented by the middle number?
The same question can still be asked. What counts as breaking? For the vast majority of Python scripts, something written on 3.X will run on 3.X+1 without any changes. When I’m specifically targeting a range of versions (say, 3.8-3.13), I almost never have to think about the upper end, only the lower. The last time that I can recall having to fix a script due to a 3.X release was when I had a function called “await”, which I had to rename to “wait”. But that is, in most people’s eyes, NOT something that would constitute a 4.0 release. It’s a new feature. Meanwhile, the deprecations in PEP 594 have impacted… exactly zero of my scripts. Go ahead, find someone whose code was broken by the removal of the sunau module. I’ll wait. (I’m aware that there have been a few issues as a result of the cgi module, but most of those dead batteries truly are junk by now.)
Major version X (X.y.z | X > 0) MUST be incremented if any backward incompatible changes are introduced to the public API. It MAY also include minor and patch level changes. Patch and minor versions MUST be reset to 0 when major version is incremented.
Sort of, the current scheme is documented at General Python FAQ — Python 3.12.4 documentation and referenced in the PEP: the minor version “is incremented for less earth-shattering changes”, which is a kind of semantic versioning (lowercase), but there’s more going on with Semantic Versioning aka SemVer.
I’d prefer this, since it clearly communicates that we’re using calendar versioning, but I’m fine with the two-digit variant also. I really like the PEP and hope it goes on to be accepted.
Whatif’s about possible future versioning and release schemes is IMO a pointless exercise; I really don’t see how they are relevant to this PEP.