The PEP 440 Version Identification and Dependency Specification document specifies a standard for version and dependency specification. It is a long document that covers accepted version specification schemes and defines how version matching and comparison in Python packaging tools should work. If you are using or plan to use a complex project version numbering scheme, then you should definitely read this document carefully. If you are using a simple scheme that consists just of one, two, three, or more numbers separated by dots, then you don't have to dig into the details of PEP 440. If you don't know how to choose the proper versioning scheme, I greatly recommend following the semantic versioning scheme that was already briefly mentioned in Chapter 1, Current Status of Python.
The other problem related to code versioning is where to include that version specifier for a package or module. There is PEP 396 (Module Version Numbers) that deals exactly with this problem. PEP 396 is only an informational document and has a deferred status, so it is not a part of the official Python standards track. Anyway, it describes what seems to be a de facto standard now. According to PEP 396, if a package or module has a specific version defined, the version specifier should be included as a __version__ attribute of package root __init__.py INI file or distributed module file. Another de facto standard is to also include the VERSION attribute that contains the tuple of the version specifier parts. This helps users to write compatibility code because such version tuples can be easily compared if the versioning scheme is simple enough.
So many packages available on PyPI follow both conventions. Their __init__.py files contain version attributes that look like the following:
# version as tuple for simple comparisons VERSION = (0, 1, 1) # string created from tuple to avoid inconsistency __version__ = ".".join([str(x) for x in VERSION])
The other suggestion of PEP 396 is that the version argument provided in the setup() function of the setup.py script should be derived from __version__, or the other way around. The Python Packaging User Guide features multiple patterns for single-sourcing project versioning, and each of them has its own advantages and limitations. My personal favorite is rather long and is not included in the PyPA's guide, but has the advantage of limiting the complexity only to the setup.py script. This boilerplate assumes that the version specifier is provided by the VERSION attribute of the package's __init__ module and extracts this data for inclusion in the setup() call. Here is an excerpt from some imaginary package's setup.py script that illustrates this approach:
from setuptools import setup
import os
def get_version(version_tuple):
# additional handling of a,b,rc tags, this can
# be simpler depending on your versioning scheme
if not isinstance(version_tuple[-1], int):
return '.'.join(
map(str, version_tuple[:-1])
) + version_tuple[-1]
return '.'.join(map(str, version_tuple))
# path to the packages __init__ module in project
# source tree
init = os.path.join(
os.path.dirname(__file__), 'src', 'some_package',
'__init__.py'
)
version_line = list(
filter(lambda l: l.startswith('VERSION'), open(init))
)[0]
# VERSION is a tuple so we need to eval 'version_line'.
# We could simply import it from the package but we
# cannot be sure that this package is importable before
# installation is done.
PKG_VERSION = get_version(eval(version_line.split('=')[-1]))
setup(
name='some-package',
version=PKG_VERSION,
# ...
)