On Tue, 27 Apr 2021 at 18:01, Brett Cannon
Unfortunately I thought importlib.metadata would have used the module name instead of the metadata details, but in hindsight am guessing that the .dist-info is what it's using to do the lookup and that's based on the package name instead of the project name.
This is a long-standing issue with projects that use project names which differ from their module name, but there's no good way without checking what files a project installed (which is what you're doing below).
That's correct. There is no link from a given import name back to the PyPI project name. And the metadata for installed packages (defined here: https://packaging.python.org/specifications/recording-installed-packages/) is keyed on the PyPI project name, as the OP noted ("beautifulsoup4" rather than "bs4", "setuptools" rather than "pkg_resources", etc).
This is the best I could come up with from reading the docs:
import bs4 #<- This is the module we want the version of
import importlib import sys from itertools import chain from pathlib import Path
loaders = sys.meta_path
target_path = Path(bs4.__file__)
distros = list(chain(*(finder.find_distributions() for finder in loaders if hasattr(finder, 'find_distributions')))) distros_files = chain(*(f for f in (d.files for d in distros))) distro_files = [(d, d.locate_file(f)) for d in distros if d.files for f in d.files] matching = [d for d, f in distro_files if f == target_path]
for match in matching: print("Found Version:", match.version)
The following is a bit simpler. The file.locate() method of a distribution is undocumented - but I tried to use resolve() on the path object I got from dist.files, and it appears not to be implemented (at least in Python 3.9) - PackagePath objects are a subclass of *Pure* Path objects, not concrete paths :-( TBH, I suspect the fact that it's undocumented is an oversight, it's clearly deliberately added. import importlib.metadata from pathlib import Path import pkg_resources target = Path(pkg_resources.__file__) for dist in importlib.metadata.distributions(): for file in dist.files: path = file.locate() if path == target: print(f"{dist.metadata['name']}: {dist.version}") break To be honest, if anyone were interested in making a PEP from any of this, having Python modules contain a __distribution_name__ attribute that links the module back to the PyPI distribution, would probably be more useful than standardising __version__. But I'm not sufficiently interested to do anything more than mention that as a possibility. Paul