Skip to content Skip to sidebar Skip to footer

Getting Python Package Distribution Version From Within A Package

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, howev

Solution 1:

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_dircur_dir= path.realpath(path.dirname(cur_dir))
    ifprev_dir== cur_dir:
        raise StopIteration()return get_first_setup_py(cur_dir)

Now using Python's ast library:

defparse_package_name_from_setup_py(setup_py_file_name):
    withopen(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]
                     ifisinstance(sym, ast.If)).body

    setup_call = next(sym.value
                      for sym in main_body[::-1]
                      ifisinstance(sym, ast.Expr) andisinstance(sym.value, ast.Call) and
                      sym.value.func.idinfrozenset(('setup',
                                                      'distutils.core.setup',
                                                      'setuptools.setup')))

    package_version = next(keyword
                           for keyword in setup_call.keywords
                           if keyword.arg == 'version'andisinstance(keyword.value, ast.Name))

    # Return the raw string if it is oneifisinstance(package_version.value, ast.Str):
        return package_version.s

    # Otherwise it's a variable at the top of the `if __name__ == '__main__'` blockelifisinstance(package_version.value, ast.Name):
        returnnext(sym.value.s
                    for sym in main_body
                    ifisinstance(sym, ast.Assign)
                    andisinstance(sym.value, ast.Str)
                    andany(target.id == package_version.value.idfor target in sym.targets)
                    )

    else:
        raiseNotImplemented('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

Solution 2:

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

defget_project_distribution():
    for dist in importlib_metadata.distributions():
        try:
            relative = pathlib.Path(__file__).relative_to(dist.locate_file(''))
        except ValueError:
            passelse:
            if relative in dist.files:
                return dist
    returnNone

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:

Solution 3:

After a couple of hours of exploring pkg_resources and reading the source for pip's uninstall I've got the following working:

import inspect
import pkg_resources
import csv

classApp(object):defget_app_version(self) -> str:# Iterate through all installed packages and try to find one that has the app's file in it
        app_def_path = inspect.getfile(self.__class__)
        for dist in pkg_resources.working_set:try:
                filenames = [
                    os.path.normpath(os.path.join(dist.location, r[0]))
                    for r in csv.reader(dist.get_metadata_lines("RECORD"))
                ]
                if app_def_path infilenames:return dist.version
            except FileNotFoundError:# Not pip installed or something
                pass
        return"development"

This iterates through all installed packages and for each of those iterates through its list of files and tries to match that to the current file, this matches the package to the distribution. It's not really ideal, and I'm still open to better answers.

Post a Comment for "Getting Python Package Distribution Version From Within A Package"