How can I get the version defined in setup.py (setuptools) in my package?
Interrogate version string of already-installed distribution
To retrieve the version from inside your package at runtime (what your question appears to actually be asking), you can use:
import pkg_resources # part of setuptools
version = pkg_resources.require("MyProject")[0].version
Store version string for use during install
If you want to go the other way 'round (which appears to be what other answer authors here appear to have thought you were asking), put the version string in a separate file and read that file's contents in setup.py
.
You could make a version.py in your package with a __version__
line, then read it from setup.py using execfile('mypackage/version.py')
, so that it sets __version__
in the setup.py namespace.
Warning about race condition during install
By the way, DO NOT import your package from your setup.py as suggested in another answer here: it will seem to work for you (because you already have your package's dependencies installed), but it will wreak havoc upon new users of your package, as they will not be able to install your package without manually installing the dependencies first.
What is the correct way to share package version with setup.py and the package?
Set the version in setup.py
only, and read your own version with pkg_resources
, effectively querying the setuptools
metadata:
file: setup.py
setup(
name='foobar',
version='1.0.0',
# other attributes
)
file: __init__.py
from pkg_resources import get_distribution
__version__ = get_distribution('foobar').version
To make this work in all cases, where you could end up running this without having installed it, test for DistributionNotFound
and the distribution location:from pkg_resources import get_distribution, DistributionNotFound
import os.path
try:
_dist = get_distribution('foobar')
# Normalize case for Windows systems
dist_loc = os.path.normcase(_dist.location)
here = os.path.normcase(__file__)
if not here.startswith(os.path.join(dist_loc, 'foobar')):
# not installed, but there is another version that *is*
raise DistributionNotFound
except DistributionNotFound:
__version__ = 'Please install this project with setup.py'
else:
__version__ = _dist.version
display __version__ using setuptools.setup values in setup.py
Adding __version__
to all top-level modules and packages is a recommendation from PEP 396.
Lately I have seen growing concerns raised about this recommendation and its actual usefulness, for example here:
- https://gitlab.com/python-devs/importlib_resources/-/issues/100
- https://gitlab.com/python-devs/importlib_metadata/-/merge_requests/125
- some more that I can't find right now...
Such a thing is often solved like the following:
# my_top_level_module/__init__.py
import importlib.metadata
__version__ = importlib.metadata.version('MyProject')
References:- https://docs.python.org/3/library/importlib.metadata.html
- https://importlib-metadata.readthedocs.io/en/latest/using.html#distribution-versions
Automatic version number both in setup.py (setuptools) AND source code?
You could also reverse the dependency: put the version in mylib/__init__.py
, parse that file in setup.py to get the version parameter, and use git tag $(setup.py --version) on the command line to create your tag.
git tag -a v$(python setup.py --version) -m 'description of version'
Is there anything more complicated you want to do that I haven’t understood? Set `__version__` of module from a file when configuring setuptools using `setup.cfg` without `setup.py`
In the course of formulating the above question, I incrementally solved it to find three methods that work:
- Two methods use the
attr:
special directive insetup.cfg
. Of these:- One puts the version number directly in the package’s
__init__.py
file. - The other puts the version number in a separate file (
__version__.py
) and then__init__.py
imports the version string from that separate file.
- One puts the version number directly in the package’s
- The third method uses instead the
file:
special directive insetup.cfg
.- This reads the separate version-specifying file (
VERSION
) directly and doesn’t involve the package’s__init__.py
file at all.
- This reads the separate version-specifying file (
__version__.py
and VERSION
:my-project/
├── LICENSE
├── pyproject.toml
├── README.md
├── setup.cfg
├── src/
│ └── my_package/
│ ├── __init__.py
│ ├── __version__.py
│ ├── VERSION
│ └── my_module.py
└── tests/
Of course, you’d have at most one of those two files, depending on which of the three solutions you chose to implement.The two attr:
special-directive solutions
In both of the solutions using the setup.cfg
’s attr:
special-directive, setup.cfg
obtains the import package’s version from the import package’s __version__
attribute. (When you import some_package
that has a version, use dir(some_package)
and you’ll see that it has a __version__
attribute.) Now you see the connection here between the attr:
name of the special directive and our goal.The key task: how to assign the __version__
attribute to my_package
?
We assign the __version__
attribute, either directly or indirectly, using the package’s __init__.py
file, which already exists (assuming you have a traditional package rather than a namespace package, which is outside the scope of this answer).
The snippet in setup.cfg
that is common in both Method A and Method B
In both of these attr:
special-directive solutions, the configuration of the setup.cfg
file is the same, with the following snippet:
[metadata]
name = my-project
version = attr: my_package.__version__
To be clear, here the .__version__
references an attribute, not a file, subpackage, or anything else.Now we branch depending on whether the version information goes directly into __init__.py
or instead into its own file.
Method A: Put the assignment into the package’s __init__.py
file
This method doesn’t use a separate file for specifying the version number, but rather inserts it into the package’s __init__.py
file:
# path-to/my-project/src/my_package/__init__.py
__version__ = '0.0.2'
Note two elements of the assignment:- the left-hand side (
__version__
) corresponds to theattr:
line insetup.cfg
(version = attr: my_package.__version__
) - the legitimate version string on the right-hand side is a string enclosed by quotes.
Method B: Put the assignment into a __version__.py
file and import it in __init__.py
Create __version__.py
and put the version string in it
We construct a new Python file and locate it at the same level as the import package’s __init__.py
file.We insert the exact same __version__
directive that we inserted in __init__.py
in Method A:
# my-project/src/my_package/__version__.py
__version__ = '0.0.2'
From within __init__.py
, import
__version__
from __version__.py
In __init__.py
, we do a relative import to access the __version__
that was assigned in the separate file:# path-to/my-project/src/my_package/__init__.py
from . __version__ import __version__
To unpack this a little…- We’re doing a relative import, so we have to use the
from … import …
syntax. (Absolute imports may use either theimport <>
orfrom <> import <>
syntax, but relative imports may only use the second form.) - The
.
indicates a relative import, starting with the current package. - The first occurrence of
__version__
refers to the “module”__version__.py
.- This file name doesn’t have to be
__version__.py
. That’s just conventional. Whatever the filename is, however, it must match the name afterfrom .
(except that the.py
is stripped off in thefrom . import
statement).
- This file name doesn’t have to be
- The second occurrence of
__version__
refers to the assignment statement inside of__version__.py
.- I’m not sure whether this string needs to be
__version__
, but it certainly at a minimum needs to match the assignment statement.
- I’m not sure whether this string needs to be
Method C: Using the file:
special directive
The separate file is populated differently
In this method, we use a separate file for the version number, as in Method B. Unlike Method B, we read the contents of this file directly, rather than importing it.
To prevent confusion, I’ll call this file simply VERSION
. Like __init__.py
and Method B’s __version__.py
, VERSION
is at the root level of import package. (See the directory/file diagram.) (Of course, in this method, you won’t have __version__.py
.)
However, the contents of this VERSION
file are much different than the contents of Method B’s __version__.py
.
Here’s the contents of my-project/src/my_package/VERSION
:
0.0.2
Note that:- This file contains nothing but the contents of the version string itself. In particular, do not enclose this string in quotation marks!
- There’s also no assignment syntax going on; there’s no “
__version__ =
” preamble to the assignment string. - This isn’t even a Python file, so I didn’t include a comment string with the path to the file, because that would be enough to give the error
VERSION does not comply with PEP 440: # comment line
.
setup.cfg
is different than before
There are two points of note that distinguish setup.cfg
in Method C from the setup.cfg
that was common to both Methods A and B.
setup.cfg
uses file:
rather than attr:
In Method C, we use a different formulation in setup.cfg
, swapping out the attr:
special directive and replacing it with the file:
special directive. The new snippet is:[metadata]
name = my-project
version = file: src/my_package/VERSION
The file path to VERSION
is relative to the project directory
Note the path to VERSION
in the assignment statement: src/my_package/VERSION
.The relative file path to the VERSIONS
file is relative to the root of the project directory my-project
. This differs from Method B, where the relative import was relative to the import-package root, i.e., my_package
.
We’re done with Method C.
Pros and cons
Method A might be seen to have a virtue of needing no additional file to set the version (because, in addition tosetup.cfg
, which is needed in any case, Method A uses only __init__.py
, which likewise already exists). However, having a separate file for the version number has its own virtue of being obvious where the version number is set. In Method A, sending someone to change the version number who didn’t already know where it was stored might take a while; it wouldn’t be obvious to look in __init__.py
.Method C might seem to have the advantage over Method B, because Method B requires modification to two files (__init__.py
and __version__.py
) rather than only one for Method C (VERSION
). The only perhaps countervailing advantage of Method B is that its __version__.py
is a Python file that allows embedded comments, which Method C’s VERSION
does not.
Standard way to embed version into Python package?
Not directly an answer to your question, but you should consider naming it __version__
, not version
.
This is almost a quasi-standard. Many modules in the standard library use __version__
, and this is also used in lots of 3rd-party modules, so it's the quasi-standard.
Usually, __version__
is a string, but sometimes it's also a float or tuple.
As mentioned by S.Lott (Thank you!), PEP 8 says it explicitly:
You should also make sure that the version number conforms to the format described in PEP 440 (PEP 386 a previous version of this standard).Module Level Dunder Names
Module level "dunders" (i.e. names with two leading and two trailing
underscores) such as__all__
,__author__
,__version__
, etc.
should be placed after the module docstring but before any import
statements except from__future__
imports.
Install specific version of setuptools as a dependency of package
Thanks to the answers and comments I can make a conclusion.
To use a specific version of setuptools it is necessary to have it in both locations - in pyproject.toml and at the beginning of install_requires of setup.py.
The tool like pip will use the version from pyproject.toml to build the project. However, if there is any dependency that has the latest version of setuptools in its requirements, then the latest version will be used to install the dependency. Also, the environment will keep the version that was last installed.
How can I specify library versions in setup.py?
I'm not sure about buildout, however, for setuptools/distribute, you specify version info with the comparison operators (like ==
, >=
, or <=
).
For example:
install_requires = ['django-pipeline==1.1.22', 'south>=0.7']
Bash script get version from setup.py or from PKG-INFO file and export as environment variable
It was easier than I though:
VERSION=$(python setup.py --version)
echo $VERSION
In the same manner you can also get the module name:MODULE_NAME=$(python setup.py --name)
echo $MODULE_NAME
Related Topics
Pandas - Add New Column to Dataframe from Dictionary
How to Get All the Request Headers in Django
Can't Get Python to Import from a Different Folder
How to Flatten a Pandas Dataframe with Some Columns as JSON
Simple Way to Query Connected Usb Devices Info in Python
Parallelize Apply After Pandas Groupby
Pandas - Filter Dataframe by Another Dataframe by Row Elements
Scale Matplotlib.Pyplot.Axes.Scatter Markersize by X-Scale
Increment Numpy Array with Repeated Indices
Append Dataframe to Excel with Pandas
How to Set Opacity of Background Colour of Graph with Matplotlib
Opencv Python: Draw Minarearect ( Rotatedrect Not Implemented)
How to Crop the Internal Area of a Contour
Using Configparser to Read a File Without Section Name
Create Spark Dataframe. Can Not Infer Schema for Type