You can get the version of a python distribution using
import pkg_resources
pkg_resources.get_distribution("distro").version
This is great if you know the distribution name, however I need to dynamically figure out my distribution name at runtime.
# Common framework base app class, extended by each app
class App(object):
def get_app_version(self) -> str:
package_name = self.__class__.__module__.split('.')[0]
try:
return pkg_resources.get_distribution(package_name).version
except Exception:
return "development"
This works for cases where the app's package name is the same as the distribution name (e.g. requests
). However this fails once they don't match (e.g. my-app
containing package my_app
).
So what I need is a mapping between distributions and their packages, which I'm sure must exist somewhere since pip seems to know what to delete when you call uninstall:
$ pip uninstall requests
Uninstalling requests-2.21.0:
Would remove:
/home/user/.virtualenvs/app/lib/python3.6/site-packages/requests-2.21.0.dist-info/*
/home/user/.virtualenvs/app/lib/python3.6/site-packages/requests/*
How do I programatically access this mapping?
It provides a __version__ attribute. It provides the standard metadata version. Therefore it will be detected by pkg_resources or other tools that parse the package metadata (EGG-INFO and/or PKG-INFO, PEP 0345).
Store version string for use during install 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.
setuptools.readthedocs.io , PyPI page. Setuptools is a package development process library designed to facilitate packaging Python projects by enhancing the Python standard library distutils (distribution utilities).
If you're looking for a solution that works both from your development—not installed, or just locally called—version, and an installed version, then try this solution.
Imports:
import ast
import csv
import inspect
from os import listdir, path
import pkg_resources
Utility function:
def get_first_setup_py(cur_dir):
if 'setup.py' in listdir(cur_dir):
return path.join(cur_dir, 'setup.py')
prev_dir = cur_dir
cur_dir = path.realpath(path.dirname(cur_dir))
if prev_dir == cur_dir:
raise StopIteration()
return get_first_setup_py(cur_dir)
Now using Python's ast
library:
def parse_package_name_from_setup_py(setup_py_file_name):
with open(setup_py_file_name, 'rt') as f:
parsed_setup_py = ast.parse(f.read(), 'setup.py')
# Assumes you have an `if __name__ == '__main__':`, and that it's at the end:
main_body = next(sym for sym in parsed_setup_py.body[::-1]
if isinstance(sym, ast.If)).body
setup_call = next(sym.value
for sym in main_body[::-1]
if isinstance(sym, ast.Expr) and
isinstance(sym.value, ast.Call) and
sym.value.func.id in frozenset(('setup',
'distutils.core.setup',
'setuptools.setup')))
package_version = next(keyword
for keyword in setup_call.keywords
if keyword.arg == 'version'
and isinstance(keyword.value, ast.Name))
# Return the raw string if it is one
if isinstance(package_version.value, ast.Str):
return package_version.s
# Otherwise it's a variable at the top of the `if __name__ == '__main__'` block
elif isinstance(package_version.value, ast.Name):
return next(sym.value.s
for sym in main_body
if isinstance(sym, ast.Assign)
and isinstance(sym.value, ast.Str)
and any(target.id == package_version.value.id
for target in sym.targets)
)
else:
raise NotImplemented('Package version extraction only built for raw strings and '
'variables in the same function that setup() is called')
Finally replace the function in @Gricey's answer by changing return "development"
to:
return parse_package_name_from_setup_py(get_first_setup_py(path.dirname(__file__)))
Taken from my answer https://stackoverflow.com/a/60352386
I believe the project's name should be hard-coded if possible. If not then some function like the following could help figuring out the metadata for the installed distribution containing the current file (__file__
):
import pathlib
import importlib_metadata
def get_project_distribution():
for dist in importlib_metadata.distributions():
try:
relative = pathlib.Path(__file__).relative_to(dist.locate_file(''))
except ValueError:
pass
else:
if relative in dist.files:
return dist
return None
project_distribution = get_project_distribution()
if project_distribution:
project_name = project_distribution.metadata['Name']
version = project_distribution.metadata['Version']
Update (February 2021):
Looks like this could become easier thanks to the newly added packages_distributions()
function in importlib_metadata
:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With