Clarify recommended use of `requires-python`
I realize this PR was opened just to add new guidance, but we should really update this paragraph to specify that, even with lower bounds, requires-python is not necessary for every package and is only something to be used when the author has confidence there are versions of Python the package versions Must Not be installed for.
Originally posted by @ucodery in https://github.com/pyOpenSci/python-package-guide/pull/501#discussion_r2096358188
@lwasser to address your reply made in the originating PR, I am personally against adding requires-python until the author has some confirmed knowledge that some version of Python is incompatible with the code they are writing. And I would like the upper bound option to either not be mentioned, or explicitly advocated against.
With almost 14 releases of Python3, there is often some lower bound for which some freshly written Python code is incompatible, but figuring out exactly what that version is is not always easy, and possibly unfindable for a more novice Pythonista. I am saying if it is known, go ahead and write it down. But not knowing and guessing, or putting the current development version down as the limit, because it is there, is actively harmful to the packaging ecosystem.
Even when working with highly experienced package authors, I wouldn't expect a >=3, nor would I even expect that every known lower limit be written out. I think I have a personal rule of thumb that if the lower bound is an EOL version of Python, and the package is a new release not expected to serve legacy code, I don't need to see that bound. Even with EOL support libraries, which I have done many, more than 2 versions behind EOL and I don't really need to see it. For example, async code should all have a >=3.5 limit, even if all other syntax is compatible back to 3.0, but I don't think anyone does this.
There is a very good post/discussion by Henry about this problem https://discuss.python.org/t/requires-python-upper-limits
I totally agree on not adding upper bounds, and also agree on the point made in that thread that "guaranteed to be compatible with" is bad wording in the spec since you can't guarantee anything about future compatibility without knowing what it is.
I do think that adding a lower bound is good though, even for newbies, for a few reasons. First is sort of a practical/tooling consideration: ruff and other linting tools use the requires-python field to determine which linting rules should apply, which can cause code to accidentally break older versions that are not yet EOL. For example, if you don't have a lower bound below 3.10 and have the UP rules enabled, it will convert all your Union[int, str] to int | str, and then you will break 3.9 by accident. That can be set in the ruff config separately from the package config, but i think it's generally good to set these things in one place rather than making carveouts (because if you have a ruff target of >=3.10 and it breaks running on 3.9, then you should have requires-python >=3.10 because that's a correct lower bound!). That can become an actual problem e.g. if someone is developing with an older version of python like the one that is distributed by their OS - then their linter will be fighting their dev environment, where each time you lint you put the package in a state where you can't run it. the other end of that if you have nothing specified, then ruff will not apply any of those rules, etc.
Second is that I think that it's much easier to loosen the limits in a later version than it is to tighten them and that is sort of important for newbies. Say you have some package released at version 1.0 that is not compatible e.g. with 3.8 but you don't have requires-python>=3.9. Then say someone raises an issue saying "this isn't compatible with 3.8!" but you don't have the time to fix it, you might then say "sorry we don't support EoL python versions" and release 1.1 that has requires-python>=3.9. Then everyone going to install the package on 3.8 will resolve against the known-broken 1.0 version. If on the other hand you had set a too-tight requires-python>=3.13 in 1.0 and someone raised an issue saying "can u plz lower the bound," then you just release 1.1 that lowers it, then there is no problem.
I also agree that we shouldn't ask every newbie to go and learn all the lore of version compatibility to set a correct lower bound, but maybe a reasonable recommendation is
- total newbie: "set the lower bound at the lowest active python version"
- level above that: "have your CI tests run on all the active python versions. If the lowest bound tests fail, raise the lower bound to the one that passes" (or fix the bugs, but for newbies probably just raise the bound).
but also no strong feelings. definite no on upper bounds, mild yes on lower bounds.
Your point on easier to remove a restriction than to add it later is well taken. With PyPI's write-only scheme it is actually impossible to address in some cases, as you mentioned.
I will still push back a little and say that correcting an overly restrictive version requires that someone notice the failure to install (or maybe just a backtrack!) is a false negative, report that bug, and the author is willing to make the change- for that version which might not be current. This is certainly a plausible scenario and has probably actually happened, but requires a somewhat advanced Python user and author. By contrast, never setting the lower version will never give false negatives, that version will alway work with every compatible Python version. Of course, there will be versions it crashes with, but as the package consumer you can adjust you version range to exclude that combination. Importantly, he user has the power to fix their environment immediately and without involving the package maintainers; overly restrictive ranges are very difficult to impossible to fix on your own as a user.
Also, just my impression here, but I'm not sure asking a total newbie to figure out what is the lowest active python version (on every release because it is a moving target) is that much easier than teaching them the lore of version compatibility. And a CI version matrix is great, but still advanced mojo; still a moving target on every release.