-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Add notes on runtime version access #1607
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
Closed
Closed
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
7728f87
Add notes on runtime version access
ncoghlan c51c08d
Fix typo
ncoghlan be8b0d8
Attempt to fix relative link
ncoghlan ca1a74c
Reference glossary terms
ncoghlan 17c0ac0
Handle missing stdlib function ref
ncoghlan 0d76de3
Add co-author attribution
ncoghlan 306a92a
Fix typos
ncoghlan f8aa29b
Use more descriptive link text
ncoghlan 0400251
Misc edits
ncoghlan 1517309
Date bump
ncoghlan d8410cb
Additional cleanups
ncoghlan 2f66077
Delay turning on semantic cross-references
ncoghlan 0e71a8f
Redo co-author commit ready for squash merge
ncoghlan 47bb0e3
Defer another semantic cross-reference
ncoghlan bbf6d66
Restore semantic links now the targets exist
ncoghlan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,173 +1,8 @@ | ||
.. _`Single sourcing the version`: | ||
:orphan: | ||
|
||
=================================== | ||
Single-sourcing the package version | ||
=================================== | ||
.. meta:: | ||
:http-equiv=refresh: 0; url=../../discussions/single-source-version/ | ||
|
||
.. todo:: Update this page for build backends other than setuptools. | ||
Redirecting stale single-source package version link... | ||
|
||
There are many techniques to maintain a single source of truth for the version | ||
number of your project: | ||
|
||
#. Read the file in :file:`setup.py` and get the version. Example (from `pip setup.py | ||
<https://github.com/pypa/pip/blob/003c7ac/setup.py>`_):: | ||
|
||
import codecs | ||
import os.path | ||
|
||
def read(rel_path): | ||
here = os.path.abspath(os.path.dirname(__file__)) | ||
with codecs.open(os.path.join(here, rel_path), 'r') as fp: | ||
return fp.read() | ||
|
||
def get_version(rel_path): | ||
for line in read(rel_path).splitlines(): | ||
if line.startswith('__version__'): | ||
delim = '"' if '"' in line else "'" | ||
return line.split(delim)[1] | ||
else: | ||
raise RuntimeError("Unable to find version string.") | ||
|
||
setup( | ||
... | ||
version=get_version("package/__init__.py") | ||
... | ||
) | ||
|
||
.. note:: | ||
|
||
As of the release of setuptools 46.4.0, one can accomplish the same | ||
thing by instead placing the following in the project's | ||
:file:`setup.cfg` file (replacing "package" with the import name of the | ||
package): | ||
|
||
.. code-block:: ini | ||
|
||
[metadata] | ||
version = attr: package.__version__ | ||
|
||
As of the release of setuptools 61.0.0, one can specify the | ||
version dynamically in the project's :file:`pyproject.toml` file. | ||
|
||
.. code-block:: toml | ||
|
||
[project] | ||
name = "package" | ||
dynamic = ["version"] | ||
|
||
[tool.setuptools.dynamic] | ||
version = {attr = "package.__version__"} | ||
|
||
Please be aware that declarative config indicators, including the | ||
``attr:`` directive, are not supported in parameters to | ||
:file:`setup.py`. | ||
|
||
#. Use an external build tool that either manages updating both locations, or | ||
offers an API that both locations can use. | ||
|
||
Few tools you could use, in no particular order, and not necessarily complete: | ||
`bump2version <https://pypi.org/project/bump2version>`_, | ||
`changes <https://pypi.org/project/changes>`_, | ||
`commitizen <https://pypi.org/project/commitizen>`_, | ||
`zest.releaser <https://pypi.org/project/zest.releaser>`_. | ||
|
||
|
||
#. Set the value to a ``__version__`` global variable in a dedicated module in | ||
your project (e.g. :file:`version.py`), then have :file:`setup.py` read and | ||
``exec`` the value into a variable. | ||
|
||
:: | ||
|
||
version = {} | ||
with open("...sample/version.py") as fp: | ||
exec(fp.read(), version) | ||
# later on we use: version['__version__'] | ||
|
||
Example using this technique: `warehouse <https://github.com/pypa/warehouse/blob/64ca42e42d5613c8339b3ec5e1cb7765c6b23083/warehouse/__about__.py>`_. | ||
|
||
#. Place the value in a simple ``VERSION`` text file and have both | ||
:file:`setup.py` and the project code read it. | ||
|
||
:: | ||
|
||
with open(os.path.join(mypackage_root_dir, 'VERSION')) as version_file: | ||
version = version_file.read().strip() | ||
|
||
An advantage with this technique is that it's not specific to Python. Any | ||
tool can read the version. | ||
|
||
.. warning:: | ||
|
||
With this approach you must make sure that the ``VERSION`` file is included in | ||
all your source and binary distributions (e.g. add ``include VERSION`` to your | ||
:file:`MANIFEST.in`). | ||
|
||
#. Set the value in :file:`setup.py`, and have the project code use the | ||
``importlib.metadata`` API to fetch the value at runtime. | ||
(``importlib.metadata`` was introduced in Python 3.8 and is available to | ||
older versions as the ``importlib-metadata`` project.) An installed | ||
project's version can be fetched with the API as follows:: | ||
|
||
import sys | ||
|
||
if sys.version_info >= (3, 8): | ||
from importlib import metadata | ||
else: | ||
import importlib_metadata as metadata | ||
|
||
assert metadata.version('pip') == '1.2.0' | ||
|
||
Be aware that the ``importlib.metadata`` API only knows about what's in the | ||
installation metadata, which is not necessarily the code that's currently | ||
imported. | ||
|
||
If a project uses this method to fetch its version at runtime, then its | ||
``install_requires`` value needs to be edited to install | ||
``importlib-metadata`` on pre-3.8 versions of Python like so:: | ||
|
||
setup( | ||
... | ||
install_requires=[ | ||
... | ||
'importlib-metadata >= 1.0 ; python_version < "3.8"', | ||
... | ||
], | ||
... | ||
) | ||
|
||
An older (and less efficient) alternative to ``importlib.metadata`` is the | ||
``pkg_resources`` API provided by ``setuptools``:: | ||
|
||
import pkg_resources | ||
assert pkg_resources.get_distribution('pip').version == '1.2.0' | ||
|
||
If a project uses ``pkg_resources`` to fetch its own version at runtime, | ||
then ``setuptools`` must be added to the project's ``install_requires`` | ||
list. | ||
|
||
Example using this technique: `setuptools <https://github.com/pypa/setuptools/blob/main/setuptools/version.py>`_. | ||
|
||
|
||
#. Set the value to ``__version__`` in ``sample/__init__.py`` and import | ||
``sample`` in :file:`setup.py`. | ||
|
||
:: | ||
|
||
import sample | ||
setup( | ||
... | ||
version=sample.__version__ | ||
... | ||
) | ||
|
||
.. warning:: | ||
|
||
Although this technique is common, beware that it will fail if | ||
``sample/__init__.py`` imports packages from ``install_requires`` | ||
dependencies, which will very likely not be installed yet when | ||
:file:`setup.py` is run. | ||
|
||
|
||
#. Keep the version number in the tags of a version control system (Git, Mercurial, etc) | ||
instead of in the code, and automatically extract it from there using | ||
`setuptools_scm <https://pypi.org/project/setuptools-scm/>`_. | ||
If the page doesn't automatically refresh, see :ref:`single-source-version`. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.