I agree with @stefano-m 's philosophy about:
Having version = "x.y.z" in the source and parsing it within
setup.py is definitely the correct solution, IMHO. Much better than
(the other way around) relying on run time magic.
And this answer is derived from @zero-piraeus 's answer. The whole point is "don't use imports in setup.py, instead, read the version from a file".
I use regex to parse the __version__
so that it does not need to be the last line of a dedicated file at all. In fact, I still put the single-source-of-truth __version__
inside my project's __init__.py
.
Folder heirarchy (relevant files only):
package_root/
|- main_package/
| `- __init__.py
`- setup.py
main_package/__init__.py
:
# You can have other dependency if you really need to
from main_package.some_module import some_function_or_class
# Define your version number in the way you mother told you,
# which is so straightforward that even your grandma will understand.
__version__ = "1.2.3"
__all__ = (
some_function_or_class,
# ... etc.
)
setup.py
:
from setuptools import setup
import re, io
__version__ = re.search(
r'__version__\s*=\s*[\'"]([^\'"]*)[\'"]', # It excludes inline comment too
io.open('main_package/__init__.py', encoding='utf_8_sig').read()
).group(1)
# The beautiful part is, I don't even need to check exceptions here.
# If something messes up, let the build process fail noisy, BEFORE my release!
setup(
version=__version__,
# ... etc.
)
... which is still not ideal ... but it works.
And by the way, at this point you can test your new toy in this way:
python setup.py --version
1.2.3
PS: This official Python packaging document (and its mirror) describes more options. Its first option is also using regex. (Depends on the exact regex you use, it may or may not handle quotation marks inside version string. Generally not a big issue though.)
PPS: The fix in ADAL Python is now backported into this answer.