Skip to content

Commit fb3ec88

Browse files
updated single_source_version with a much simpler page -- essentially
refering folks to their build system of choice.
1 parent 53f8c14 commit fb3ec88

File tree

1 file changed

+22
-88
lines changed

1 file changed

+22
-88
lines changed

source/single_source_version.rst

Lines changed: 22 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -7,111 +7,45 @@ Single-sourcing the Project Version
77
:Page Status: Complete
88
:Last Reviewed: 2015-09-08
99

10+
One of the challenges in building packages is that the version string can be required in multiple places.
1011

11-
There are a few techniques to store the version in your project code without duplicating the value stored in
12-
``setup.py``:
12+
* It needs to be specified when building the package (e.g. in pyproject.toml)
13+
- That will assure that it is properly assigned in the distribution file name, and in teh installed package.
1314

14-
#. Read the file in ``setup.py`` and parse the version with a regex. Example (
15-
from `pip setup.py <https://github.com/pypa/pip/blob/1.5.6/setup.py#L33>`_)::
15+
* Some projects require that there be a version string available as an attribute in the importable module, e.g::
1616

17-
def read(*names, **kwargs):
18-
with io.open(
19-
os.path.join(os.path.dirname(__file__), *names),
20-
encoding=kwargs.get("encoding", "utf8")
21-
) as fp:
22-
return fp.read()
17+
import a_package
18+
print(a_package.__version__)
2319

24-
def find_version(*file_paths):
25-
version_file = read(*file_paths)
26-
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
27-
version_file, re.M)
28-
if version_match:
29-
return version_match.group(1)
30-
raise RuntimeError("Unable to find version string.")
20+
While different projects have different needs, it's important to make sure that there is a single source of truth for the version number.
3121

32-
setup(
33-
...
34-
version=find_version("package", "__init__.py")
35-
...
36-
)
22+
In general, the options are:
3723

38-
.. note::
24+
1) If the code is in a version control system (VCS), e.g. git, then the version can be extracted from the VCS.
3925

40-
This technique has the disadvantage of having to deal with complexities of regular expressions.
26+
2) The version can be hard-coded into the `pyproject.toml` file -- and the build system can copy it into other locations it may be required.
4127

42-
#. Use an external build tool that either manages updating both locations, or
43-
offers an API that both locations can use.
28+
3) The version string can be hard-coded into the source code -- either in a special purpose file, such as `_version.txt`, or as a attribute in the `__init__.py`, and the build system can extract it at build time.
4429

45-
Few tools you could use, in no particular order, and not necessarily complete:
46-
`bumpversion <https://pypi.python.org/pypi/bumpversion>`_,
47-
`changes <https://pypi.python.org/pypi/changes>`_, `zest.releaser <https://pypi.python.org/pypi/zest.releaser>`_.
30+
If the version string is not in the source, it can be extracted at runtime with code in `__init__.py`, such as::
4831

32+
import importlib.metadata
33+
__version__ = importlib.metadata.version('the_distribution_name')
4934

50-
#. Set the value to a ``__version__`` global variable in a dedicated module in
51-
your project (e.g. ``version.py``), then have ``setup.py`` read and ``exec`` the
52-
value into a variable.
5335

54-
Using ``execfile``:
36+
Consult your build system documentation for how to implement your preferred method.
5537

56-
::
38+
Put links in to build system docs?
39+
-- I have no idea which are currently robust and maintained -- do we want to get into seeming endorsing particular tools in this doc?
5740

58-
execfile('...sample/version.py')
59-
# now we have a `__version__` variable
60-
# later on we use: __version__
6141

62-
Using ``exec``:
42+
* setuptools:
6343

64-
::
44+
* hatch:
6545

66-
version = {}
67-
with open("...sample/version.py") as fp:
68-
exec(fp.read(), version)
69-
# later on we use: version['__version__']
46+
* poetry:
7047

71-
Example using this technique: `warehouse <https://github.com/pypa/warehouse/blob/master/warehouse/__about__.py>`_.
48+
* PyBuilder:
7249

73-
#. Place the value in a simple ``VERSION`` text file and have both ``setup.py``
74-
and the project code read it.
50+
* Others?
7551

76-
::
77-
78-
with open(os.path.join(mypackage_root_dir, 'VERSION')) as version_file:
79-
version = version_file.read().strip()
80-
81-
An advantage with this technique is that it's not specific to Python. Any
82-
tool can read the version.
83-
84-
.. warning::
85-
86-
With this approach you must make sure that the ``VERSION`` file is included in
87-
all your source and binary distributions.
88-
89-
#. Set the value in ``setup.py``, and have the project code use the
90-
``pkg_resources`` API.
91-
92-
::
93-
94-
import pkg_resources
95-
assert pkg_resources.get_distribution('pip').version == '1.2.0'
96-
97-
Be aware that the ``pkg_resources`` API only knows about what's in the
98-
installation metadata, which is not necessarily the code that's currently
99-
imported.
100-
101-
102-
#. Set the value to ``__version__`` in ``sample/__init__.py`` and import
103-
``sample`` in ``setup.py``.
104-
105-
::
106-
107-
import sample
108-
setup(
109-
...
110-
version=sample.__version__
111-
...
112-
)
113-
114-
Although this technique is common, beware that it will fail if
115-
``sample/__init__.py`` imports packages from ``install_requires``
116-
dependencies, which will very likely not be installed yet when ``setup.py``
117-
is run.

0 commit comments

Comments
 (0)