Idea: collect package from different source if there are no matching versions

Currently, a package (pylint) contains a new feature on the main branch. Version 2.16.0.dev0 is released without it. If the next version is released (2.16.0.dev1), it will contain that feature.
It would be great if I can specify to collect the package from elsewhere (GitHub in this case) if that version is unavailable.

It would be great if I can somehow specify a “fallback” source to pip. An idea for the syntax (there may be much better ways to do this):
pylint>=2.16.0.dev1,unavailable=git+https://github.com/PyCQA/pylint

Currently, I need to use the GitHub link, then manually change it when there’s a release. This is suboptimal, as collecting from GitHub and building a wheel is a lot slower than using a cached wheel like it can with a version from PyPI.

Are you specifying a development version of pylint as a dependency of some application or library? If so, I would recommend finding an alternative, as I’m sure the devs of pylint put no guarantee on the stability or security of these versions (and your downstream users will by default pick up these versions).

For example, you could vendor pylint into your application or library, or you could incorporate parts of pylint that you use, provided you follow the GPLv2 license (you can’t do this if you don’t use the GPLv2 license, eg for internal software see below).

Next, regardless when using a Git URL I suggest specifying a Git ref (tag or commit), eg bb5781cd94eace021edf82954d151810770d241b. As a bonus, if you specify the requirement in the following manner, pip will cache and reuse the build wheel:

pylint @ git+https://github.com/PyCQA/pylint@bb5781cd94eace021edf82954d151810770d241b

Are you specifying a development version of pylint as a dependency of some application or library?

No, I’m specifying it as requirements for linting an application, and I want to use some new checkers that they implemented in 2.16.

Next, regardless when using a Git URL I suggest specifying a Git ref

The downside of this is that if there’s a new update on the branch (for example a bugfix) it won’t be downloaded.

As a bonus, if you specify the requirement in the following manner, pip will cache and reuse the build wheel:

Ah, I didn’t know that, that’s pretty useful.

This might not be the most useful addition, but it can have a use.
For example, say I’m developing a package A, and I’m working on a new release that depends on that dev version or B, I can then release A as dev version, so users that want the new feature and use the dev version of A don’t unnecessarily download B from GitHub if it’s already available on PyPI.
While I’m writing this, I’m already questioning how useful this would be…

A more general problem to this is that there’s currently no way to specify OR dependencies, i.e. please install one of the followings. In addition to multiple sources, a project may experience renames or forks (e.g. PIL vs Pillow), and it’s likely beneficial to implement for this sort of needs.

Another similar use case is for a package to support multiple implementations of a same idea, such as a webserver can let the user choose between different ASGI implementations, or how chardet and charset_normalizer are used by different projects for the same purpose even if the user can allow GPL.

Yup; another very common case of this is with GUI backends; e.g. with Spyder we (and a lot of other people) use our QtPy as an abstraction layer to allow use with both Qt5 and Qt6, and with the PyQt and PySide Python bindings. However, we have to arbitrarily pick one to require with Spyder, and cannot constrain the compatible versions of the others. Default extras would solve part of it for the cases you mentioned, and pip has a --constraints option that solves another part of it, but neither are nearly complete solutions.

A possible solution is to build a package index with the Git URLs. For example, an HTML page with the following content:

<html>
<body>
    <a href="git+https://github.com/PyCQA/pylint#egg=pylint-2.16.0.dev1">pylint</a>
</body>
</html>

And install it with pip install pylint>=2.16.0.dev1 --find-links <url_or_path>, where <url_or_path> can be either the path to the local HTML file or the URL of the statically hosted version. When the new version is available on PyPI, it will be preferred. There is only a minor caveat for this approach: the version in the #egg= part must match what is actually built into the metadata.

This does not work, it still clones and builds it if I run it twice. Am I doing something wrong?
pip install pylint@git+https://github.com/PyCQA/pylint@a6a544613fd6cd28c3213751173f1b0e4205d1c0

Not sure what you mean by “internal software”, but my understanding of the GPL was that you have to make the source code available to anyone who has the binary, which means that internal software can have internally-shared source code. I’m not a lawyer though.

Yup, see this GPL FAQ entry. By the same token, if I were to download some GPL software and modify it, I wouldn’t immediately be obligated to post the change on GItHub, only if I shared the modified version.

In a broader sense, copyright cannot generally compel anyone who has lawfully received a copy of a work that they must do something with it (and if unlawfully, only compel them to destroy the copy and pay damages); it can generally only constrain what they can do with it. For example, if I rented a camera memory card, took some photos on it and then returned it without copying them for myself, the company owning it would not have any explicit right to view, copy, share or use the photos (except incidentally, via fair use or if specified in the rental agreement), but as they lawfully own the memory card and it was I that placed the photos on it and then gave it back to them, I could not legally compel them to share the photos with me. But I digress…

Works for me, on Ubuntu 22.04 with Python 3.10

python3 -m venv venv
. venv/bin/activate
pip install -U pip
pip install pylint@git+https://github.com/PyCQA/pylint@a6a544613fd6cd28c3213751173f1b0e4205d1c0
deactivate
python3 -m venv venv2
. venv2/bin/activate
pip install -U pip
pip install pylint@git+https://github.com/PyCQA/pylint@a6a544613fd6cd28c3213751173f1b0e4205d1c0

Works for me now too, I don’t know why it didn’t work before, maybe I did it wrong.