pyproject.tomlsetup.py based project?This discussion covers all aspects of versioning Python packages.
Different Python projects may use different versioning schemes based on theneeds of that particular project, but in order to be compatible with tools likepip, all of them are required to comply with a flexible format forversion identifiers, for which the authoritative reference is thespecification of version specifiers. Here are someexamples 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 usersbefore a final release. In order, the steps are: alpha releases, beta releases,release candidates, final release. Pip and other modern Python packageinstallers ignore pre-releases by default when deciding which versions ofdependencies to install, unless explicitly requested (e.g., withpipinstallpkg==1.1a3 orpipinstall--prepkg).
The purpose of development releases is to support releases made early during adevelopment cycle, for example, a nightly build, or a build from the latestsource in a Linux distribution.
Post-releases are used to address minor errors in a final release that do notaffect the distributed software, such as correcting an error in the releasenotes. They should not be used for bug fixes; these should be done with a newfinal release (e.g., incrementing the third component when using semanticversioning).
Finally, epochs, a rarely used feature, serve to fix the sorting order whenchanging the versioning scheme. For example, if a project is using calendarversioning, with versions like 23.12, and switches to semantic versioning, withversions 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.
A versioning scheme is a formalized way to interpret the segments of a versionnumber, and to decide which should be the next version number for a new releaseof a package. Two versioning schemes are commonly used for Python packages,semantic versioning and calendar versioning.
Caution
The decision which version number to choose is up to aproject’s maintainer. This effectively means that versionbumps reflect the maintainer’s view. That view may differfrom the end-users’ perception of what said formalizedversioning scheme promises them.
There are known exceptions for selecting the next versionnumber. The maintainers may consciously choose to break theassumption that the last version segment only containsbackwards-compatible changes.One such case is when a security vulnerability needs to beaddressed. Security releases often come in patch versionsbut contain breaking changes inevitably.
The idea ofsemantic 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 semanticversioning. However, most projects, especially larger ones, do not strictlyadhere to semantic versioning, since many changes are technically breakingchanges but affect only a small fraction of users. Such projects tend toincrement the major number when the incompatibility is high, or to signal ashift in the project, rather than for any tiny incompatibility[2]. Conversely, a bump of the major version numberis sometimes used to signal significant but backwards-compatible newfeatures.
For those projects that do use strict semantic versioning, this approach allowsusers to make use ofcompatible release version specifiers, with the~= operator. Forexample,name~=X.Y is roughly equivalent toname>=X.Y,==X.*, i.e.,it requires at least release X.Y, and allows any later release with greater Y aslong as X is the same. Likewise,name~=X.Y.Z is roughly equivalent toname>=X.Y.Z,==X.Y.*, i.e., it requires at least X.Y.Z and allows a laterrelease with same X and Y but higher Z.
Python projects adopting semantic versioning should abide by clauses 1-8 of theSemantic Versioning 2.0.0 specification.
The popularSphinx documentation generator is an exampleproject that uses strict semantic versioning (Sphinx versioning policy). The famousNumPyscientific computing package explicitly uses “loose” semantic versioning, wherereleases incrementing the minor version can contain backwards-incompatible APIchanges (NumPy versioning policy).
Semantic versioning is not a suitable choice for all projects, such as thosewith a regular time-based release cadence and a deprecation process thatprovides warnings for a number of releases prior to removal of a feature.
A key advantage of date-based versioning, orcalendar versioning(CalVer), is that it is straightforward to tell how old the base feature set ofa particular release is given just the version number.
Calendar version numbers typically take the formyear.month (for example,23.12 for December 2023).
Pip, the standard Python package installer, uses calendarversioning.
Serial versioning refers to the simplest possible versioning scheme, whichconsists of a single number incremented every release. While serial versioningis very easy to manage as a developer, it is the hardest to track as an enduser, as serial version numbers convey little or no information regarding APIbackwards compatibility.
Combinations of the above schemes are possible. For example, a project maycombine date-based versioning with serial versioning to create ayear.serialnumbering scheme that readily conveys the approximate age of a release, butdoesn’t otherwise commit to a particular release cadence within the year.
Public version identifiers are designed to support distribution viaPyPI. Python packaging tools also support the notionof alocal version identifier, which can beused to identify local development builds not intended for publication, ormodified 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 withFedora-specific patches applied could have the version “1.2.1+fedora.4”.Another example is versions computed bysetuptools-scm, a setuptools pluginthat reads the version from Git data. In a Git repository with some commitssince 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”.
Version information for alldistribution packagesthat are locally available in the current environment can be obtained at runtimeusing the standard library’simportlib.metadata.version() function:
>>>importlib.metadata.version("cryptography")'41.0.7'
Many projects also choose to version their top levelimport packages by providing a package level__version__ attribute:
>>>importcryptography>>>cryptography.__version__'41.0.7'
This technique can be particularly valuable for CLI applications which wantto ensure that version query invocations (such aspip-V) run as quicklyas possible.
Package publishers wishing to ensure their reported distribution package andimport package versions are consistent with each other can review theSingle-sourcing the Project Version discussion for potential approaches to doing so.
As import packages and modules are notrequired to publish runtimeversion information in this way (see the withdrawn proposal inPEP 396), the__version__ attribute should either only bequeried with interfaces that are known to provide it (such as a projectquerying its own version or the version of one of its direct dependencies),or else the querying code should be designed to handle the case where theattribute is missing[3].
Some projects may need to publish version information for external APIsthat aren’t the version of the module itself. Such projects shoulddefine their own project-specific ways of obtaining the relevant informationat runtime. For example, the standard library’sssl module offersmultiple ways to access the underlying OpenSSL library version:
>>>ssl.OPENSSL_VERSION'OpenSSL 3.2.2 4 Jun 2024'>>>ssl.OPENSSL_VERSION_INFO(3, 2, 0, 2, 0)>>>hex(ssl.OPENSSL_VERSION_NUMBER)'0x30200020'
Some more examples of unusual version numbers aregiven in ablog post by Seth Larson.
[2]For some personal viewpoints on this issue, see theseblog posts:by Hynek Schlawak,by Donald Stufft,by Bernát Gábor,byBrett Cannon. For a humoristic take, read aboutZeroVer.
[3]A full list mapping the top level names availablefor import to the distribution packages that provide those import packages andmodules may be obtained through the standard library’simportlib.metadata.packages_distributions() function. This means thateven code that is attempting to infer a version to report for all importabletop-level names has a means to fall back to reporting the distributionversion information if no__version__ attribute is defined. Only standardlibrary modules, and modules added via means other than Python packageinstallation would fail to have version information reported in that case.