Lightweight alternative to `pip check`?

In Nixpkgs we recently made the migration from using pip for building and installing wheels to using build and installer. It was only after that we realized that installer does not actually check whether runtime dependencies are met.

Is there any lightweight alternative to what pip does for checking runtime dependencies?

1 Like

I had the same question while working on this problem in conda. For me, it would even help if someone could point me where the logic for pip check is hidden in pip’s source code. Code search/navigation didn’t help me much.

1 Like

It’s pretty easy to write your own using importlib.metadata and packaging. Here’s something I wrote a long time ago but probably still works well:

def check_dependency_issues() -> None:
    # This is very similar to `pip check`
    versions = {}
    reqs = {}
    for dist in importlib.metadata.distributions():
        metadata = dist.metadata
        name = canonical_name(metadata["Name"])
        versions[name] = packaging.version.parse(metadata["Version"])
        reqs[name] = [
            req
            for r in (dist.requires or [])
            if (req := packaging.requirements.Requirement(r)) is not None
        ]

    # Like `pip check`, we don't handle extras very well https://github.com/pypa/pip/issues/4086
    # This is because there's no way to tell if an extra was requested. If we wanted, we could
    # do slightly better than pip by finding all requirements that require an extra and using that
    # as a heuristic to tell if an extra was requested.

    def req_is_needed(req: packaging.requirements.Requirement) -> bool:
        if req.marker is None:
            return True
        try:
            return req.marker.evaluate()
        except packaging.markers.UndefinedEnvironmentName:
            # likely because the req has an extra
            return False

    for package, version in versions.items():
        for req in reqs[package]:
            req_name = canonical_name(req.name)

            if not req_is_needed(req):
                continue

            if req_name in versions:
                if not req.specifier.contains(versions[req_name], prereleases=True):
                    print_err(
                        f"{package} {version} requires {req}, "
                        f"but {versions[req_name]} is installed"
                    )
                continue

            print_err(f"{package} {version} is missing requirement {req}")
2 Likes

Looks like the core of the code is https://github.com/pypa/pip/blob/324dd444956283661dce0dc282cbdaad0405d921/src/pip/_internal/operations/check.py#L54 and the entrypoint is https://github.com/pypa/pip/blob/324dd444956283661dce0dc282cbdaad0405d921/src/pip/_internal/commands/check.py#L17

https://pypa-build.readthedocs.io/en/stable/api.html#build.check_dependency

2 Likes

Looking at the code for pip and build, I’m wondering if I am over-complicating things here? https://github.com/pypa/hatch/blob/4ddbf0a9a720caed18d19c083ff88427c9d2a993/backend/src/hatchling/dep/core.py#L43