329 lines
12 KiB
ReStructuredText
329 lines
12 KiB
ReStructuredText
PEP: 396
|
||
Title: Module Version Numbers
|
||
Author: Barry Warsaw <barry@python.org>
|
||
Status: Withdrawn
|
||
Type: Informational
|
||
Topic: Packaging
|
||
Created: 16-Mar-2011
|
||
Post-History: 05-Apr-2011
|
||
|
||
.. withdrawn::
|
||
|
||
Refer to :ref:`packaging:runtime-version-access` and
|
||
:ref:`packaging:single-source-version` in the Python
|
||
Packaging User Guide for up to date recommendations on
|
||
accessing package version information at runtime,
|
||
and on defining runtime ``__version__`` attributes which are
|
||
automatically kept consistent with package distribution metadata
|
||
|
||
|
||
Abstract
|
||
========
|
||
|
||
Given that it is useful and common to specify version numbers for
|
||
Python modules, and given that different ways of doing this have grown
|
||
organically within the Python community, it is useful to establish
|
||
standard conventions for module authors to adhere to and reference.
|
||
This informational PEP describes best practices for Python module
|
||
authors who want to define the version number of their Python module.
|
||
|
||
Conformance with this PEP is optional, however other Python tools
|
||
(such as ``distutils2`` [1]_) may be adapted to use the conventions
|
||
defined here.
|
||
|
||
|
||
PEP Rejection/Withdrawal
|
||
========================
|
||
|
||
This PEP was formally rejected on 2021-04-14. The packaging ecosystem
|
||
has changed significantly in the intervening years since this PEP was
|
||
first written, and APIs such as ``importlib.metadata.version()`` [11]_
|
||
provide for a much better experience.
|
||
|
||
This rejection was reclassified as a withdrawal on 2024-10-21,
|
||
as the previous state was being misinterpreted [12]_ as suggesting
|
||
that *no* modules should be defining ``__version__`` attributes,
|
||
which definitely isn't the case.
|
||
|
||
Modules are still free to define ``__version__`` if they choose to.
|
||
However, choosing *not* to do so won't interfere with looking up
|
||
the version information for installed distribution packages, so an
|
||
Informational PEP isn't the right tool to document community
|
||
conventions around the use of module ``__version__`` attributes
|
||
(they're better covered as part of the Python Packaging User Guide).
|
||
|
||
|
||
User Stories
|
||
============
|
||
|
||
Alice is writing a new module, called ``alice``, which she wants to
|
||
share with other Python developers. ``alice`` is a simple module and
|
||
lives in one file, ``alice.py``. Alice wants to specify a version
|
||
number so that her users can tell which version they are using.
|
||
Because her module lives entirely in one file, she wants to add the
|
||
version number to that file.
|
||
|
||
Bob has written a module called ``bob`` which he has shared with many
|
||
users. ``bob.py`` contains a version number for the convenience of
|
||
his users. Bob learns about the Cheeseshop [2]_, and adds some simple
|
||
packaging using classic distutils so that he can upload *The Bob
|
||
Bundle* to the Cheeseshop. Because ``bob.py`` already specifies a
|
||
version number which his users can access programmatically, he wants
|
||
the same API to continue to work even though his users now get it from
|
||
the Cheeseshop.
|
||
|
||
Carol maintains several namespace packages, each of which are
|
||
independently developed and distributed. In order for her users to
|
||
properly specify dependencies on the right versions of her packages,
|
||
she specifies the version numbers in the namespace package's
|
||
``setup.py`` file. Because Carol wants to have to update one version
|
||
number per package, she specifies the version number in her module and
|
||
has the ``setup.py`` extract the module version number when she builds
|
||
the *sdist* archive.
|
||
|
||
David maintains a package in the standard library, and also produces
|
||
standalone versions for other versions of Python. The standard
|
||
library copy defines the version number in the module, and this same
|
||
version number is used for the standalone distributions as well.
|
||
|
||
|
||
Rationale
|
||
=========
|
||
|
||
Python modules, both in the standard library and available from third
|
||
parties, have long included version numbers. There are established
|
||
de facto standards for describing version numbers, and many ad-hoc
|
||
ways have grown organically over the years. Often, version numbers
|
||
can be retrieved from a module programmatically, by importing the
|
||
module and inspecting an attribute. Classic Python distutils
|
||
``setup()`` functions [3]_ describe a ``version`` argument where the
|
||
release's version number can be specified. :pep:`8` describes the
|
||
use of a module attribute called ``__version__`` for recording
|
||
"Subversion, CVS, or RCS" version strings using keyword expansion. In
|
||
the PEP author's own email archives, the earliest example of the use
|
||
of an ``__version__`` module attribute by independent module
|
||
developers dates back to 1995.
|
||
|
||
Another example of version information is the sqlite3 [5]_ module
|
||
with its ``sqlite_version_info``, ``version``, and ``version_info``
|
||
attributes. It may not be immediately obvious which attribute
|
||
contains a version number for the module, and which contains a version
|
||
number for the underlying SQLite3 library.
|
||
|
||
This informational PEP codifies established practice, and recommends
|
||
standard ways of describing module version numbers, along with some
|
||
use cases for when -- and when *not* -- to include them. Its adoption
|
||
by module authors is purely voluntary; packaging tools in the standard
|
||
library will provide optional support for the standards defined
|
||
herein, and other tools in the Python universe may comply as well.
|
||
|
||
|
||
Specification
|
||
=============
|
||
|
||
#. In general, modules in the standard library SHOULD NOT have version
|
||
numbers. They implicitly carry the version number of the Python
|
||
release they are included in.
|
||
|
||
#. On a case-by-case basis, standard library modules which are also
|
||
released in standalone form for other Python versions MAY include a
|
||
module version number when included in the standard library, and
|
||
SHOULD include a version number when packaged separately.
|
||
|
||
#. When a module (or package) includes a version number, the version
|
||
SHOULD be available in the ``__version__`` attribute.
|
||
|
||
#. For modules which live inside a namespace package, the module
|
||
SHOULD include the ``__version__`` attribute. The namespace
|
||
package itself SHOULD NOT include its own ``__version__``
|
||
attribute.
|
||
|
||
#. The ``__version__`` attribute's value SHOULD be a string.
|
||
|
||
#. Module version numbers SHOULD conform to the normalized version
|
||
format specified in :pep:`386`.
|
||
|
||
#. Module version numbers SHOULD NOT contain version control system
|
||
supplied revision numbers, or any other semantically different
|
||
version numbers (e.g. underlying library version number).
|
||
|
||
#. The ``version`` attribute in a classic distutils ``setup.py``
|
||
file, or the :pep:`345` ``Version`` metadata field SHOULD be
|
||
derived from the ``__version__`` field, or vice versa.
|
||
|
||
|
||
Examples
|
||
========
|
||
|
||
Retrieving the version number from a third party package::
|
||
|
||
>>> import bzrlib
|
||
>>> bzrlib.__version__
|
||
'2.3.0'
|
||
|
||
Retrieving the version number from a standard library package that is
|
||
also distributed as a standalone module::
|
||
|
||
>>> import email
|
||
>>> email.__version__
|
||
'5.1.0'
|
||
|
||
Version numbers for namespace packages::
|
||
|
||
>>> import flufl.i18n
|
||
>>> import flufl.enum
|
||
>>> import flufl.lock
|
||
|
||
>>> print flufl.i18n.__version__
|
||
1.0.4
|
||
>>> print flufl.enum.__version__
|
||
3.1
|
||
>>> print flufl.lock.__version__
|
||
2.1
|
||
|
||
>>> import flufl
|
||
>>> flufl.__version__
|
||
Traceback (most recent call last):
|
||
File "<stdin>", line 1, in <module>
|
||
AttributeError: 'module' object has no attribute '__version__'
|
||
>>>
|
||
|
||
|
||
Deriving
|
||
========
|
||
|
||
Module version numbers can appear in at least two places, and
|
||
sometimes more. For example, in accordance with this PEP, they are
|
||
available programmatically on the module's ``__version__`` attribute.
|
||
In a classic distutils ``setup.py`` file, the ``setup()`` function
|
||
takes a ``version`` argument, while the distutils2 ``setup.cfg`` file
|
||
has a ``version`` key. The version number must also get into the PEP
|
||
345 metadata, preferably when the *sdist* archive is built. It's
|
||
desirable for module authors to only have to specify the version
|
||
number once, and have all the other uses derive from this single
|
||
definition.
|
||
|
||
This could be done in any number of ways, a few of which are outlined
|
||
below. These are included for illustrative purposes only and are not
|
||
intended to be definitive, complete, or all-encompassing. Other
|
||
approaches are possible, and some included below may have limitations
|
||
that prevent their use in some situations.
|
||
|
||
Let's say Elle adds this attribute to her module file ``elle.py``::
|
||
|
||
__version__ = '3.1.1'
|
||
|
||
|
||
Classic distutils
|
||
-----------------
|
||
|
||
In classic distutils, the simplest way to add the version string to
|
||
the ``setup()`` function in ``setup.py`` is to do something like
|
||
this::
|
||
|
||
from elle import __version__
|
||
setup(name='elle', version=__version__)
|
||
|
||
In the PEP author's experience however, this can fail in some cases,
|
||
such as when the module uses automatic Python 3 conversion via the
|
||
``2to3`` program (because ``setup.py`` is executed by Python 3 before
|
||
the ``elle`` module has been converted).
|
||
|
||
In that case, it's not much more difficult to write a little code to
|
||
parse the ``__version__`` from the file rather than importing it.
|
||
Without providing too much detail, it's likely that modules such as
|
||
``distutils2`` will provide a way to parse version strings from files.
|
||
E.g.::
|
||
|
||
from distutils2 import get_version
|
||
setup(name='elle', version=get_version('elle.py'))
|
||
|
||
|
||
Distutils2
|
||
----------
|
||
|
||
Because the distutils2 style ``setup.cfg`` is declarative, we can't
|
||
run any code to extract the ``__version__`` attribute, either via
|
||
import or via parsing.
|
||
|
||
In consultation with the distutils-sig [9]_, two options are
|
||
proposed. Both entail containing the version number in a file, and
|
||
declaring that file in the ``setup.cfg``. When the entire contents of
|
||
the file contains the version number, the ``version-file`` key will be
|
||
used::
|
||
|
||
[metadata]
|
||
version-file: version.txt
|
||
|
||
When the version number is contained within a larger file, e.g. of
|
||
Python code, such that the file must be parsed to extract the version,
|
||
the key ``version-from-file`` will be used::
|
||
|
||
[metadata]
|
||
version-from-file: elle.py
|
||
|
||
A parsing method similar to that described above will be performed on
|
||
the file named after the colon. The exact recipe for doing this will
|
||
be discussed in the appropriate distutils2 development forum.
|
||
|
||
An alternative is to only define the version number in ``setup.cfg``
|
||
and use the ``pkgutil`` module [8]_ to make it available
|
||
programmatically. E.g. in ``elle.py``::
|
||
|
||
from distutils2._backport import pkgutil
|
||
__version__ = pkgutil.get_distribution('elle').metadata['version']
|
||
|
||
|
||
PEP 376 metadata
|
||
================
|
||
|
||
:pep:`376` defines a standard for static metadata, but doesn't
|
||
describe the process by which this metadata gets created. It is
|
||
highly desirable for the derived version information to be placed into
|
||
the :pep:`376` ``.dist-info`` metadata at build-time rather than
|
||
install-time. This way, the metadata will be available for
|
||
introspection even when the code is not installed.
|
||
|
||
|
||
References
|
||
==========
|
||
|
||
.. [1] Distutils2 documentation
|
||
(http://distutils2.notmyidea.org/)
|
||
|
||
.. [2] The Cheeseshop (Python Package Index)
|
||
(http://pypi.python.org)
|
||
|
||
.. [3] http://docs.python.org/distutils/setupscript.html
|
||
|
||
.. [5] sqlite3 module documentation
|
||
(http://docs.python.org/library/sqlite3.html)
|
||
|
||
.. [8] pkgutil - Package utilities
|
||
(http://distutils2.notmyidea.org/library/pkgutil.html)
|
||
|
||
.. [9] https://mail.python.org/pipermail/distutils-sig/2011-June/017862.html
|
||
|
||
.. [11] importlib.metadata
|
||
(https://docs.python.org/3/library/importlib.metadata.html#distribution-versions)
|
||
|
||
.. [12] Misinterpreting the significance of this PEP's rejection
|
||
(https://discuss.python.org/t/please-make-package-version-go-away/58501)
|
||
|
||
|
||
Copyright
|
||
=========
|
||
|
||
This document has been placed in the public domain.
|
||
|
||
|
||
|
||
..
|
||
Local Variables:
|
||
mode: indented-text
|
||
indent-tabs-mode: nil
|
||
sentence-end-double-space: t
|
||
fill-column: 70
|
||
coding: utf-8
|
||
End:
|