Method for informing pip, etc. that another package satisfies requirements

In the case of a library that has a dependency that can be met by more than one package, it would be incredibly useful for the library to tell the packaging tools that any of them, if present, is sufficient to meet the install requirements - and if none are present, install a default.

My first idea about this is to extend metadata to allow install_requires to list every package that meets the actual runtime dependencies. While I prefer this option, I can see how this would be a burden on both the PyPA and library maintainers.

My second idea is for pip install and friends to grow an argument to allow the user to declare that another package+version will substitute in for the one listed in requirements (with the obligatory warning that the software being installed may not be tested in that configuration, if it breaks you get to keep the pieces, etc.)

The real world use case that I have run into is a library that depends on Qt. There are many bindings to Qt with nearly identical APIs, and social or legal reasons to choose one over the other. There are also abstraction layer libraries that pave over the few differences between versions of Qt bindings (and in some cases major versions of Qt itself). The problem lies in the case where the library needs to choose a binding to Qt for install_requires so their library will work, but cannot tell pip that any bindings to Qt will work just fine.

Provides-Dist (documented here) is the mechanism for this, in theory. In practice, I don’t know how well the tools support it yet (in particular, I’ve a feeling pip doesn’t handle it). Contributions to support it properly would obviously be welcome :slightly_smiling_face:

1 Like

This is great if (e.g.) Riverbank and the Qt company can get on board for it, but I’m not going to hold my breath. :slight_smile: It also solves a subtly different problem. In the example of Qt binding implementations, that would be helpful if i wanted to declare that my software is in fact equivalent - I as the package maintainer make the assertion.

What I would like is the ability for me as the maintainer of a secondary package (I depend on Qt) to assert that PySide2 and PyQt5 are to be treated as the same for the purposes of dependency resolution.

What I would happily settle for is the ability as a final user of a package (I’m running pip install on the secondary package) to tell pip at runtime that PySide2 and PyQt5 are equivalent, even if it means weird syntax in my requirements file.

Would it be feasible for pip and the like for the end user, the person running pip install, to fake that metadata at install time? Is that something PyPA/pip would even be interested in doing?

Debian’s syntax for this is |, i.e. they can write dependencies like:

Depends: foo (>= 2.1) | bar | baz

I guess there isn’t any deep reason why this would be hard for a resolver to support, because conceptually our requirements are already alternatives: a requirement for foo >= 2.1 is equivalent to foo == 2.1 | foo == 2.2 | foo == 2.3 | .... So any resolver algorithm already needs to have some strategy for handling the case where there are multiple packages that can satisfy a single requirement.

That said, pip’s current resolver is very limited and greedy, so it might not be able to do anything smart with this right now.

So my guess is this is one of those thing where there isn’t any real reason not to do it, but I don’t think it’s at the top of any of the current maintainers’ priority list, so someone would need to do the work to make it happen. That would include at least writing a PEP to specify the detailed syntax and semantics of the new feature (you’ll want to start with PEP 508), making sure that the pip/pipenv/poetry maintainers are on board, and likely implementing the feature in at least pip.

1 Like

I concur with all that @njs said above.

I do think this should be fairly straightforward to implement with a “proper” resolver – we’d need to care about more “candidates” like foo == 2.2 and bar == 1.0, for a given “requirement” like foo | bar, which is fine. I’m on board for this, once a better resolution algorithm is rolled out.

After the or, you’ll run into needing (pkgA | (pkgB & pkgC)). There’s also the issue of selecting which of the alternatives to install if none is satisfied – you’ll want to do that consistently, and perhaps enable the packager to have some influence.
An initial implementation doesn’t need to have such complications, but it should leave room to add them :‍)

FWIW, here’s how rpm does it:

1 Like

FWIW, I think Debian’s approach is:

  • They don’t have any and operator. I guess in the cases where they need it they make a virtual package that depends on all of the individual packages, and then depend on that? I have trouble thinking of any cases where this would be important though. Do you have any examples of places where RPM distros actually use and? I’m curious what the use cases are.

  • For priority, the default is whichever alternative appears first. So for example a common pattern is to depend on something like exim | mail-transfer-agent, which means: “this package needs some kind of mail-transfer-agent, so any package that Provides: mail-transfer-agent will do. But if we don’t have one installed already and the user didn’t specify, then use exim.”

1 Like

It’s not very widely used, but for example snapd has Requires: ((squashfuse and fuse) or kmod(squashfs.ko))
Of course, this is not strictly necessary, since there’s an implicit and between all the individual requirements. But in some cases the conjunctive normal form isn’t the most readable.