Standard way to embed version into Python package?
Not directly an answer to your question, but you should consider naming it
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.
__version__ is a string, but sometimes it's also a float or tuple.
Edit: as mentioned by S.Lott (Thank you!), PEP 8 says it explicitly:
Module Level Dunder Names
Module level "dunders" (i.e. names with two leading and two trailing underscores) such as
__version__, etc. should be placed after the module docstring but before any import statements except from
I use a single
_version.py file as the "once cannonical place" to store version information:
It provides a
It provides the standard metadata version. Therefore it will be detected by
pkg_resourcesor other tools that parse the package metadata (EGG-INFO and/or PKG-INFO, PEP 0345).
It doesn't import your package (or anything else) when building your package, which can cause problems in some situations. (See the comments below about what problems this can cause.)
There is only one place that the version number is written down, so there is only one place to change it when the version number changes, and there is less chance of inconsistent versions.
Here is how it works: the "one canonical place" to store the version number is a .py file, named "_version.py" which is in your Python package, for example in
myniftyapp/_version.py. This file is a Python module, but your setup.py doesn't import it! (That would defeat feature 3.) Instead your setup.py knows that the contents of this file is very simple, something like:
__version__ = "3.6.5"
And so your setup.py opens the file and parses it, with code like:
import reVERSIONFILE="myniftyapp/_version.py"verstrline = open(VERSIONFILE, "rt").read()VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]"mo = re.search(VSRE, verstrline, re.M)if mo: verstr = mo.group(1)else: raise RuntimeError("Unable to find version string in %s." % (VERSIONFILE,))
Then your setup.py passes that string as the value of the "version" argument to
setup(), thus satisfying feature 2.
To satisfy feature 1, you can have your package (at run-time, not at setup time!) import the _version file from
myniftyapp/__init__.py like this:
from _version import __version__
Here is an example of this technique that I've been using for years.
The code in that example is a bit more complicated, but the simplified example that I wrote into this comment should be a complete implementation.
If you see anything wrong with this approach, please let me know.
After 13+ years of writing Python code and managing various packages, I came to the conclusion that DIY is maybe not the best approach.
I started using the
pbr package for dealing with versioning in my packages. If you are using git as your SCM, this will fit into your workflow like magic, saving your weeks of work (you will be surprised about how complex the issue can be).
As of today, pbr has 12M mongthly downloads, and reaching this level didn't include any dirty tricks. It was only one thing -- fixing a common packaging problem in a very simple way.
pbr can do more of the package maintenance burden, and is not limited to versioning, but it does not force you to adopt all its benefits.
So to give you an idea about how it looks to adopt pbr in one commit have a look switching packaging to pbr
Probably you would observed that the version is not stored at all in the repository. PBR does detect it from Git branches and tags.
No need to worry about what happens when you do not have a git repository because pbr does "compile" and cache the version when you package or install the applications, so there is no runtime dependency on git.
Here is the best solution I've seen so far and it also explains why:
# Store the version here so:# 1) we don't load dependencies by storing it in __init__.py# 2) we can import it in setup.py for the same reason# 3) we can import it into your module module__version__ = '0.12'
from .version import __version__
exec(open('yourpackage/version.py').read())setup( ... version=__version__, ...
If you know another approach that seems to be better let me know.