python / importlib_metadata

Library to access metadata for Python packages
https://importlib-metadata.readthedocs.io
Apache License 2.0
122 stars 79 forks source link

How to check if a requirement is met without pkg_resources #450

Closed HansBug closed 1 year ago

HansBug commented 1 year ago

In our work, we need to use the Python source code to check if the dependencies specified in requirements.txt are fully satisfied. Previously, we used pkg_resources.require like this (https://stackoverflow.com/a/16298328/6995899 ), but now that it has been deprecated, how can we achieve the above functionality in new packages?

jaraco commented 1 year ago

importlib metadata doesn't provide this functionality. It seems that packaging does not either. Someone will need to write that functionality, and since it probably will depend on packaging, it probably should be in that package or in something that can depend on it (importlib metadata cannot because it's part of stdlib). This issue has been reported in https://github.com/pypa/packaging-problems/issues/317.

HansBug commented 1 year ago

Hi @jaraco, I found the solution without pkg_resources after submitting this issue, here is the code: https://github.com/HansBug/hbutils/blob/main/hbutils/system/python/package.py#L202 .

This is the core function to iterate all the requirements need to be installed:

def _yield_reqs_to_install(req: Requirement, current_extra: str = ''):
    if req.marker and not req.marker.evaluate({'extra': current_extra}):
        return

    try:
        version = importlib_metadata.distribution(req.name).version
    except importlib_metadata.PackageNotFoundError:  # req not installed
        yield req
    else:
        if req.specifier.contains(version):
            for child_req in (importlib_metadata.metadata(req.name).get_all('Requires-Dist') or []):
                child_req_obj = Requirement(child_req)

                need_check, ext = False, None
                for extra in req.extras:
                    if child_req_obj.marker and child_req_obj.marker.evaluate({'extra': extra}):
                        need_check = True
                        ext = extra
                        break

                if need_check:  # check for extra reqs
                    yield from _yield_reqs_to_install(child_req_obj, ext)

        else:  # main version not match
            yield req
jaraco commented 1 year ago

Glad you found a solution. You may want to post that over in packaging-problems, as that will help a wider audience.

HansBug commented 1 year ago

Glad you found a solution. You may want to post that over in packaging-problems, as that will help a wider audience.

Posted https://github.com/pypa/packaging-problems/issues/664