Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create a discussion on versioning #1455

Merged
merged 9 commits into from
Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@
"flexx": ("https://flexx.readthedocs.io/en/latest/", None),
"flit": ("https://flit.pypa.io/en/stable/", None),
"nox": ("https://nox.thea.codes/en/latest/", None),
"numpy": ("https://numpy.org/doc/stable/", None),
"openstack": ("https://docs.openstack.org/glance/latest/", None),
"packaging": ("https://packaging.pypa.io/en/latest/", None),
"pip": ("https://pip.pypa.io/en/latest/", None),
Expand Down
1 change: 1 addition & 0 deletions source/discussions/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ specific topic. If you're just trying to get stuff done, see
.. toctree::
:maxdepth: 1

versioning
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
deploying-python-applications
pip-vs-easy-install
install-requires-vs-requirements
Expand Down
176 changes: 176 additions & 0 deletions source/discussions/versioning.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
.. _versioning:
.. _`Choosing a versioning scheme`:

==========
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
:ref:`pip`, all of them are required to comply with a flexible format for
version identifiers, for which the authoritative reference is the
:ref:`specification of version specifiers <version-specifiers>`. Here are some
examples of version numbers:

- 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
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
- A simple version with only two components: 23.12
- A simple version with just one component (possible, but discouraged): 42
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
- A version with an epoch: 1!1.0
webknjaz marked this conversation as resolved.
Show resolved Hide resolved

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``).
jeanas marked this conversation as resolved.
Show resolved Hide resolved

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
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
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.

jeanas marked this conversation as resolved.
Show resolved Hide resolved

Semantic versioning
-------------------

The idea of *semantic versioning* (or SemVer) is to use 3-part version numbers,
*major.minor.maintenance*, where the project author increments:
webknjaz marked this conversation as resolved.
Show resolved Hide resolved

- *major* when they make incompatible API changes,
- *minor* when they add functionality in a backwards-compatible manner, and
- *maintenance*, 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
[#semver-strictness]_.

jeanas marked this conversation as resolved.
Show resolved Hide resolved
For those projects that do use strict semantic versioning, this approach allows
users to make use of :ref:`compatible release version specifiers
<version-specifiers-compatible-release>`, 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 <semver_>`_.

The popular :doc:`Sphinx <sphinx:index>` documentation generator is an example
project that uses strict semantic versioning (:doc:`Sphinx versioning policy
<sphinx:internals/release-process>`). The famous :doc:`NumPy <numpy:index>`
scientific computing package explicitly uses "loose" semantic versioning, where
releases incrementing the minor version can contain backwards-incompatible API
changes (:doc:`NumPy versioning policy <numpy:dev/depending_on_numpy>`).


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_>`_
(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).

:doc:`Pip <pip:index>`, the standard Python package installer, uses calendar
versioning.


Other schemes
-------------

Serial versioning refers to the simplest possible versioning scheme, which
jeanas marked this conversation as resolved.
Show resolved Hide resolved
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 :term:`PyPI
<Python Package Index (PyPI)>`. Python packaging tools also support the notion
of a :ref:`local version identifier <local-version-identifiers>`, 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".


--------------------------------------------------------------------------------


.. [#semver-strictness] For some personal viewpoints on this issue, see these
blog posts: `by Hynek Schlawak <semver-hynek-schlawack_>`_, `by Donald Stufft
<semver-donald-stufft_>`_, `by Bernát Gábor <semver-bernat-gabor_>`_, `by
Brett Cannon <semver-brett-cannon_>`_. For a humoristic take, read about
ZeroVer_.



.. _zerover: https://0ver.org
.. _calver: https://calver.org
.. _semver: https://semver.org
.. _semver-bernat-gabor: https://bernat.tech/posts/version-numbers/
.. _semver-brett-cannon: https://snarky.ca/why-i-dont-like-semver/
.. _semver-donald-stufft: https://caremad.io/posts/2016/02/versioning-software/
.. _semver-hynek-schlawack: https://hynek.me/articles/semver-will-not-save-you/
.. _setuptools-scm: https://setuptools-scm.readthedocs.io
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
117 changes: 2 additions & 115 deletions source/guides/distributing-packages-using-setuptools.rst
Original file line number Diff line number Diff line change
Expand Up @@ -287,124 +287,11 @@ achieve cross-platform compatibility is to use :ref:`console_scripts` entry
points (see below).




webknjaz marked this conversation as resolved.
Show resolved Hide resolved
.. _`Choosing a versioning scheme`:

Choosing a versioning scheme
----------------------------

Standards compliance for interoperability
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Different Python projects may use different versioning schemes based on the needs of that
particular project, but all of them are required to comply with the flexible :pep:`public version
scheme <440#public-version-identifiers>` specified
in :pep:`440` in order to be supported in tools and libraries like ``pip``
and ``setuptools``.

Here are some examples of compliant version numbers::

1.2.0.dev1 # Development release
1.2.0a1 # Alpha Release
1.2.0b1 # Beta Release
1.2.0rc1 # Release Candidate
1.2.0 # Final Release
1.2.0.post1 # Post Release
15.10 # Date based release
23 # Serial release

To further accommodate historical variations in approaches to version numbering,
:pep:`440` also defines a comprehensive technique for :pep:`version
normalisation <440#normalization>` that maps
variant spellings of different version numbers to a standardised canonical form.

Scheme choices
~~~~~~~~~~~~~~

Semantic versioning (preferred)
*******************************

For new projects, the recommended versioning scheme is based on `Semantic Versioning
<https://semver.org/>`_, but adopts a different approach to handling pre-releases and
build metadata.

The essence of semantic versioning is a 3-part MAJOR.MINOR.MAINTENANCE numbering scheme,
where the project author increments:

1. MAJOR version when they make incompatible API changes,
2. MINOR version when they add functionality in a backwards-compatible manner, and
3. MAINTENANCE version when they make backwards-compatible bug fixes.

Adopting this approach as a project author allows users to make use of :pep:`"compatible release"
<440#compatible-release>` specifiers, where
``name ~= X.Y`` requires at least release X.Y, but also allows any later release with
a matching MAJOR version.

Python projects adopting semantic versioning should abide by clauses 1-8 of the
`Semantic Versioning 2.0.0 specification <https://semver.org/>`_.

Date based 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 is that it is straightforward to tell how old the
base feature set of a particular release is given just the version number.

Version numbers for date based projects typically take the form of YEAR.MONTH (for example,
``12.04``, ``15.10``).

Serial versioning
*****************

This is the simplest possible versioning scheme, and consists of a single number which is
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.

Hybrid schemes
**************

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.

Pre-release versioning
~~~~~~~~~~~~~~~~~~~~~~

Regardless of the base versioning scheme, pre-releases for a given final release may be
published as:

* zero or more dev releases (denoted with a ".devN" suffix)
* zero or more alpha releases (denoted with a ".aN" suffix)
* zero or more beta releases (denoted with a ".bN" suffix)
* zero or more release candidates (denoted with a ".rcN" suffix)

``pip`` and other modern Python package installers ignore pre-releases by default when
deciding which versions of dependencies to install.


Local version identifiers
~~~~~~~~~~~~~~~~~~~~~~~~~

Public version identifiers are designed to support distribution via
:term:`PyPI <Python Package Index (PyPI)>`. Python's software distribution tools also support
the notion of a :pep:`local version identifier
<440#local-version-identifiers>`, 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 ``<public version identifier>+<local version label>``.
For example::

1.2.0.dev1+hg.5.b11e5e6f0b0b # 5th VCS commit since 1.2.0.dev1 release
1.2.1+fedora.4 # Package with downstream Fedora patches applied
See :ref:`versioning` for information on common version schemes and how to
choose between them.


Working in "development mode"
Expand Down
2 changes: 2 additions & 0 deletions source/specifications/version-specifiers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ aside from always being the lowest possible value in the version ordering.
sections.


.. _local-version-identifiers:

Local version identifiers
-------------------------

Expand Down