Considering the idea of YYYY.MM.nn versioning further, some of the specific disruptions can be eliminated by making the stable ABI versioning independent of the reported Python runtime version, and switching some other things to be based on the stable ABI version rather than the Python runtime version. (These ideas would also apply to YYYY.serial_within_year.maintenance, since the version info fields are still just called major, minor, micro rather than being renamed to year, month, micro)
The concerns raised in PEP 2026 – Calendar versioning for Python | peps.python.org and PEP 641 – Using an underscore in the version portion of Python 3.10 compatibility tags | peps.python.org are taken into account below, but some of them would come up again in a few decades when 2100.10.0 was imminent (but adding the required disambiguating underscores could happen anytime before then).
Edits:
- changed the definition of
sys.cpython_abi.max_version to provide a nicer backwards compatibility workaround than str(sys.version_info.major)[-2:] for the fields where switching from 2 to 4 digits would be problematic.
- added details on CPython C API implications (with a further change to
sys.cpython_abi.max_version to bump the values in the major and minor fields down to the minor and micro fields to make room to set max_version.major to the ABI generation)
Hypothetical CPython changes
-
CPython changes sys.version_info to put the release year in the major field and the release month in the minor field. The fields names are left unchanged since they’re common across implementations.
-
CPython continues to make sys.implementation.version an alias for sys.version_info (i.e. the major, minor, micro field names are retained, rather than attempting to rename the first two fields to year and month)
-
CPython adds sys.cpython_abi with the following fields (see below for rationale):
-
generation: CPython stable ABI version number (currently always 3)
-
version: max CPython stable ABI version that is supported by this runtime. Extension modules that use functions from later major versions of even the same CPython ABI generation aren’t expected to work since symbols they need will be missing. Even on CPython, this would NOT be the same as sys.version_info:
major: always the same value as sys.cpython_abi.generation
minor: set to sys.version_info.major - 2000 on CPython (so it won’t go over two digits until 2100)
micro: set to sys.version_info.minor on CPython
releaselevel: updated for YYYY.MM.0 pre-releases, but locked at "final" after that point
serial: updated for YYYY.MM.0 pre-releases, but locked at 0 after that point
-
the idea behind having a cpython_ prefix on the new attribute is to let other implementations explicitly declare their support for the CPython stable ABI (if any) independently of their support for Python runtime APIs. More details on that below.
-
CPython’s C API versioning would diverge from its Python runtime API versioning:
- The existing
Py_*_VERSION macros would map to the corresponding sys.cpython_abi.max_version fields rather than to sys.version_info
Py_RUNTIME_*_VERSION fields would be added that retained the current meaning of the Py_*_VERSION macros (mapping to sys.version_info)
-
the sys.version string would be updated to have a new “CPython ABI” section appended to the end (e.g. (CPython ABI: 3.26.10)) when sys.cpython_abi is not None)
-
the pythonN executable name would be f"python{sys.cpython_abi.generation} rather than the current f"python{sys.version_info.major} (i.e. python3 remains the versioned binary name rather than it becoming python2026)
-
the pythonN.X executable name would be f"python{sys.cpython_abi.max_version.major}.{python{sys.cpython_abi.max_version.minor}" rather than the current f"python{sys.version_info.major}.{sys.version_info.minor} (i.e python3.26 instead of python3.15, python3.2026 or python2026.10)
-
sys.implementation.cache_tag would be f"cpython-{sys.cpython_abi.max_version.major}{sys.cpython_abi.max_version.minor}" rather than the current f"cpython-{sys.version_info.major}.{sys.version_info.minor}" (i.e cpython-326 instead of cpython-315, cpython-32026, cpython-3_2026, cpython-202610 or cpython-2026_10)
-
similarly, the SOABI config var would start with cpython-326 instead of cpython-315 or any of the other potential combinations
-
any other case where {sys.version_info.major} and {sys.version_info.minor} are combined to produce a string would similarly be replaced with the combination of {sys.cpython_abi.max_version.major} and {sys.cpython_abi.max_version.minor}
-
properly dealing with the year 2100 would likely require finally introducing disambiguating underscores sometime in the next few decades, but the above scheme is designed to automatically use 3100 in affected fields if nothing changes (just as it would in 2111 when we reached 3.100 with the current versioning scheme and release cadence).
-
handling all of the above situations while allowing for multiple releases per year would be left as a question for a multi-release-per-year PEP to address (if anyone ever advocates for that), as it would require either adding sys.cpython_abi.max_version.micro info to affected fields in some way, or else continuing to restrict ABI changes to one release per year, even if runtime feature releases became more frequent (with the latter approach, releases without ABI changes would report the ABI version info for the relevant ABI release in sys.cpython_abi.max_version rather than following the scheme described above).
Interpreting sys.cpython_abi
This hypothetical new field is added to allow the following interpretations:
- if
sys.cpython_abi is missing, then checks for CPython ABI support should fall back to the status quo of looking at sys.implementation.name and sys.version_info or inspecting the values of relevant sysconfig vars.
- if
sys.cpython_abi is set to None, the implementation is explicitly stating that CPython extension modules are not supported and won’t work
- if
sys.cpython_abi is set and not None, then extension modules for cp{sys.cpython_abi.generation}-abi{sys.cpython_abi.generation} are expected to work, as are versioned stable modules up to cp{sys.cpython_abi.version.major}{sys.cpython_abi.max_version.minor}-abi{sys.cpython_abi.generation}
Preserving C API compatibility means that some field needs to go away (just as the leading 3 would get dropped from sys.version_info), but the right one to drop for ABI versioning purposes is the always 0 micro version rather than the 3 version prefix. This reflects the fact that CPython micro releases aren’t permitted to break ABI compatibility, and are even discouraged from making additions to the ABI due to the potential for extension module portability problems.
ABI generations
In the above sketch, I suggest adding sys.cpython_abi.generation to describe the current abi3 generation, rather than attempting to reuse the word version. This is mostly a matter of choosing and explicitly documenting an alternative term that is “like a version, but different” so that the distinction between the Python version and the ABI generation is clearer. The word epoch is avoided, since that already means something else in package versioning.
It would also be reasonable to refer to this as a “Python generation”, but that seems more likely to bring back bad Python 2->3 transitions memories than calling it an ABI generation.
The new field is technically redundant (since sys.cpython_abi.generation and sys.cpython_abi.max_version.major are defined as having the same value), but I think the dedicated generation attribute conveys the significance better than merely defining sys.cpython_abi_version with the same structure as sys.version_info would.
Backwards compatibility
C API
Breaking the direct link between the build time CPython API/ABI versioning and the CPython runtime versioning definitely has the potential to cause problems if extension modules that don’t use the stable ABI are expecting the build time values of the Py_*_VERSION macros to always match the runtime contents of sys.version_info.
These can already differ for modules using the stable ABI, so those modules shouldn’t encounter any new problems along those lines, and modules affected by the problem would need changes anyway if they were ever ported to the stable ABI.
The transition could potentially be made smoother by introducing the Py_RUNTIME_*_VERSION macros and sys.cpython_abi interface in CPython 3.14 (while still using the current incremental versioning scheme), with the only semantic change being that the values of Py_MICRO_VERSION, Py_RELEASE_LEVEL, and Py_RELEASE_SERIAL (and their corresponding fields in sys.cpython_abi) wouldn’t change after the 3.14.0 release.
Runtime version checks
The assessment of YY.0 from PEP 2026 still applies for YYYY.MM.0
Checks for major Python versions that are greater than 2, or not exactly 2 will still work as expected when the major version is in the thousands instead of being exactly 3.
Checks specifically for sys.version_info.major == 3 or sys.version.startswith("3.") will break, but have always been semantically dubious and actively discouraged.
String slicing on sys.version would already have run into problems with previous releases, either with the 2.7.10 release back in 2015 (if attempting to retrieve all fields), or else with the 3.9 to 3.10 version change which meant the length of the version as a string could now vary from 5 characters (x.y.z for versions prior to 3.10), up to 7 characters (x.yy.zz for maintenance releases of versions after 3.9 later in their lifecycle, starting with ‘3.10.10’ in Feb 2023). This means that any such code would have already broken, and needed to switch to either accessing sys.version_info or else handling variable length fields in their version string parsing. It isn’t a new problem being introduced by considering a switch to CalVer.
Packaging ecosystem
The hypothetical approach described above is intentionally designed to mimic the way the 3.9 to 3.10 transition was handled via the workaround of defining the CPython ABI’s sys.cpython_abi.max_version.minor as the CPython runtime’s sys.version_info.major - 2000 rather than using the full 4-digit year.
PEP 641 already described the right way to fix the overall ambiguity in version numbers: it’s a matter of including underscores where appropriate.
While the hypothetical approach addresses most potential problems, there are still genuine compatibility issues when it comes to code that attempts to predict how a CPython release will behave without actually running it. Such code will make guesses like cpython-202610 for the SOABI prefix based on the existing CPython logic unless it is updated to take the concept of CPython ABI generations into account.
My own thoughts
I think this still qualifies as “more trouble than it would be worth”, but I do feel it’s important to compare 3.YY.x to the most compatible approach to YYYY.MM.x that we can devise, rather than comparing it to the more simplistic approaches that really would break the world when it came to package installation (I’m less concerned about trying to keep incorrectly structured runtime version checks “working”).
If we went down this path, we would at least get the genuine benefit of decoupling the extension module ABI compatibility versioning from the Python runtime versioning, so implementations other than CPython could accurately report their support for the CPython stable ABI (if they offer it).