How do you find python package metadata information given a module

Viewed 7831

I'm trying to retrieve metadata information for a python package given the name of the module.

I can use importlib-metadata to retrieve the information, but in some cases the top-level module name is not the same as the package name.

example:

>>> importlib_metadata.metadata('zmq')['License']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "c:\Users\xxxxx\AppData\Local\Programs\Python\Python37\Lib\site-packages\importlib_metadata\__init__.py", line 499, in metadata
    return Distribution.from_name(distribution_name).metadata
  File "c:\Users\xxxxx\AppData\Local\Programs\Python\Python37\Lib\site-packages\importlib_metadata\__init__.py", line 187, in from_name
    raise PackageNotFoundError(name)
importlib_metadata.PackageNotFoundError: zmq


>>> importlib_metadata.metadata('pyzmq')['License']
'LGPL+BSD'
2 Answers

I believe something like the following should work:

#!/usr/bin/env python3

import importlib.util
import pathlib

import importlib_metadata

def get_distribution(file_name):
    result = None
    for distribution in importlib_metadata.distributions():
        try:
            relative = (
                pathlib.Path(file_name)
                .relative_to(distribution.locate_file(''))
            )
        except ValueError:
            pass
        else:
            if relative in distribution.files:
                result = distribution
    return result

def alpha():
    file_name = importlib.util.find_spec('easy_install').origin
    distribution = get_distribution(file_name)
    print("alpha", distribution.metadata['Name'])

def bravo():
    file_name = importlib_metadata.__file__
    distribution = get_distribution(file_name)
    print("bravo", distribution.metadata['Name'])

if __name__ == '__main__':
    alpha()
    bravo()

Update (February 2021):

Looks like this could become easier thanks to the newly added packages_distributions() function in importlib_metadata:

Here is a function that does what you want, I believe. It's not terribly efficient, in that it has to enumerate all the installed package distributions and read the list of top-level modules for each — however, I believe it's the best one can do. (Of course, you can also cache a dict mapping from top-level module names to package names.)

from importlib.metadata import Distribution, distributions
from pathlib import Path
from typing import *

def get_pkg_distribution(top_level_module: str) -> Optional[Distribution]:
    pkg_path = Path(__file__).parent
    for dist in distributions():
        package_namespaces = (dist.read_text("top_level.txt") or "").splitlines()
        if top_level_module in package_namespaces:
            return dist
    return None

# Get the package metadata for the current package. Note, `__package__` is actually the name of the top-level module!
pkg_metadata = dict(get_pkg_distribution(__package__).metadata.items())
__version__ = pkg_metadata["Version"]
Related