Versioning#

This discussion covers all aspects of versioning Python packages.

Valid version numbers#

Different Python projects may use different versioning schemes based on the needs of that particular project, but in order to be compatible with tools like pip, all of them are required to comply with a flexible format for version identifiers, for which the authoritative reference is the specification of version specifiers. Here are some examples of version numbers [1]:

  • A simple version (final release): 1.2.0

  • A development release: 1.2.0.dev1

  • An alpha release: 1.2.0a1

  • A beta release: 1.2.0b1

  • A release candidate: 1.2.0rc1

  • A post-release: 1.2.0.post1

  • A post-release of an alpha release (possible, but discouraged): 1.2.0a1.post1

  • A simple version with only two components: 23.12

  • A simple version with just one component: 42

  • A version with an epoch: 1!1.0

Projects can use a cycle of pre-releases to support testing by their users before a final release. In order, the steps are: alpha releases, beta releases, release candidates, final release. Pip and other modern Python package installers ignore pre-releases by default when deciding which versions of dependencies to install, unless explicitly requested (e.g., with pip install pkg==1.1a3 or pip install --pre pkg).

The purpose of development releases is to support releases made early during a development cycle, for example, a nightly build, or a build from the latest source in a Linux distribution.

Post-releases are used to address minor errors in a final release that do not affect the distributed software, such as correcting an error in the release notes. They should not be used for bug fixes; these should be done with a new final release (e.g., incrementing the third component when using semantic versioning).

Finally, epochs, a rarely used feature, serve to fix the sorting order when changing the versioning scheme. For example, if a project is using calendar versioning, with versions like 23.12, and switches to semantic versioning, with versions like 1.0, the comparison between 1.0 and 23.12 will go the wrong way. To correct this, the new version numbers should have an explicit epoch, as in «1!1.0», in order to be treated as more recent than the old version numbers.

Semantic versioning vs. calendar versioning#

A versioning scheme is a formalized way to interpret the segments of a version number, and to decide which should be the next version number for a new release of a package. Two versioning schemes are commonly used for Python packages, semantic versioning and calendar versioning.

Застереження

The decision which version number to choose is up to a project’s maintainer. This effectively means that version bumps reflect the maintainer’s view. That view may differ from the end-users“ perception of what said formalized versioning scheme promises them.

There are known exceptions for selecting the next version number. The maintainers may consciously choose to break the assumption that the last version segment only contains backwards-compatible changes. One such case is when security vulnerability needs to be addressed. Security releases often come in patch versions but contain breaking changes inevitably.

Semantic versioning#

The idea of semantic versioning (or SemVer) is to use 3-part version numbers, major.minor.patch, where the project author increments:

  • major when they make incompatible API changes,

  • minor when they add functionality in a backwards-compatible manner, and

  • patch, when they make backwards-compatible bug fixes.

A majority of Python projects use a scheme that resembles semantic versioning. However, most projects, especially larger ones, do not strictly adhere to semantic versioning, since many changes are technically breaking changes but affect only a small fraction of users. Such projects tend to increment the major number when the incompatibility is high, or to signal a shift in the project, rather than for any tiny incompatibility [2]. Conversely, a bump of the major version number is sometimes used to signal significant but backwards-compatible new features.

For those projects that do use strict semantic versioning, this approach allows users to make use of compatible release version specifiers, with the ~= operator. For example, name ~= X.Y is roughly equivalent to name >= X.Y, == X.*, i.e., it requires at least release X.Y, and allows any later release with greater Y as long as X is the same. Likewise, name ~= X.Y.Z is roughly equivalent to name >= X.Y.Z, == X.Y.*, i.e., it requires at least X.Y.Z and allows a later release with same X and Y but higher Z.

Python projects adopting semantic versioning should abide by clauses 1-8 of the Semantic Versioning 2.0.0 specification.

The popular Sphinx documentation generator is an example project that uses strict semantic versioning (Sphinx versioning policy). The famous NumPy scientific computing package explicitly uses «loose» semantic versioning, where releases incrementing the minor version can contain backwards-incompatible API changes (NumPy versioning policy).

Calendar versioning#

Semantic versioning is not a suitable choice for all projects, such as those with a regular time based release cadence and a deprecation process that provides warnings for a number of releases prior to removal of a feature.

A key advantage of date-based versioning, or calendar versioning (CalVer), is that it is straightforward to tell how old the base feature set of a particular release is given just the version number.

Calendar version numbers typically take the form year.month (for example, 23.12 for December 2023).

Pip, the standard Python package installer, uses calendar versioning.

Other schemes#

Serial versioning refers to the simplest possible versioning scheme, which consists of a single number incremented every release. While serial versioning is very easy to manage as a developer, it is the hardest to track as an end user, as serial version numbers convey little or no information regarding API backwards compatibility.

Combinations of the above schemes are possible. For example, a project may combine date based versioning with serial versioning to create a year.serial numbering scheme that readily conveys the approximate age of a release, but doesn’t otherwise commit to a particular release cadence within the year.

Local version identifiers#

Public version identifiers are designed to support distribution via PyPI. Python packaging tools also support the notion of a local version identifier, which can be used to identify local development builds not intended for publication, or modified variants of a release maintained by a redistributor.

A local version identifier takes the form of a public version identifier, followed by «+» and a local version label. For example, a package with Fedora-specific patches applied could have the version «1.2.1+fedora.4». Another example is versions computed by setuptools-scm, a setuptools plugin that reads the version from Git data. In a Git repository with some commits since the latest release, setuptools-scm generates a version like «0.5.dev1+gd00980f», or if the repository has untracked changes, like «0.5.dev1+gd00980f.d20231217».