On Tue, 27 Apr 2021 at 18:01, Brett Cannon <br...@python.org> wrote:

> 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
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/python-dev@python.org/message/66G7A5ZZRYT7H6MRJVELFMC23IDP67CL/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to