The documentation for the python_requires argument of setup.py gives some examples of various valid statements/strings/values for this argument. One of these is python_requires='>=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4', while the other relevant example (the >=3 one) does not include the <4. This has led to some doubts for whether to include the <4 when specifying version ranges.
pypa/sampleproject’s sample of python_requires includes <4, but there was no discussion about this (AFAICT) in the PR that added it other than referencing the above documentation.
I asked on Stack Overflow as well, and it seems so far the “consensus” among answerers there is to not use it, even if it’s actually used in official Python documentation. Then I asked in pypa/packaging-problems on GitHub, where the so far only response has been that I should include the <4. This doesn’t inspire much confidence in either, so now I’m trying here hoping to get a more canonical answer.
When and in which case should python_requires include <4 (or less-than-next-major-version in general)?
Given that there’s no indication that Python 4 will be any sort of “breaking” release (there seem to be 2 main schools of thought, one is that we simply continue with 3.X indefinitely, the other that 4.0 will just be the next release after some 3.X, nothing special), there doesn’t seem to be any reasonable justification for using python_requires='<4' at all.
I’m not sure there’s a “right thing to do” here any more than there’s a “right thing to do” when declaring dependencies in install_requires. I would assume that the documentation string python_requires='>=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4' is not intended as the correct string to use, but rather as a plausible example of a version string that makes use of many of the operations combined. Note that it uses >=, < and !=, so with a little extrapolation, you can assume that ==, > and <= are available as well, and you are pretty well equipped to describe the versions your software supports.
I think the reason there’s not a “right” answer is that there are trade-offs to be made, and each project needs to decide what side of the line to fall on:
If you pin an upper limit on your version support, you are describing the current state of the support as of release time. The danger of doing this is that because PyPI metadata is immutable, if there is a release that breaks your software, you cannot simply add the upper limit pin, because pip will fall back to the most recent version that does not have this pin (which is still broken), and erroneously install it. The only thing you can do remedy the situation is cut a new release that supports the new version.
Of course, even if you could retroactively fix the metadata to add the version pin, all that will do is make it so that pip fails to find your package and fails to install it, so it’s not like it’s a huge improvement for the end users.
By pinning to the upper limit, you don’t necessarily have the above problem, but you do have the problem that you are encoding in python_requires a guess about whether a certain version of Python will break your package. As @pf_moore mentioned, there’s good reason to believe that if a Python 4 release occurs, it will not be a breaking change, and it’s worth noting that minor breaking changes occur in most/all point releases as well (per the deprecation policy), so your software may still be subject to the problems you have by not pinning or the problems you have by erroneously putting in a version pin for a version that works just fine for your project.
I tend to come down on the side of “don’t pin”, because it not only inaccurately reflects the state of Python version support as of release time by suggesting that certain versions will not work even though I have no specific information to that effect, and because even in the situation where a new Python version breaks my software, the advantage of having predicted this is minimal.
The one time I think it makes sense to pin your versions is when you know that there’s a breaking change in a given version. Even then it doesn’t provide too much value unless you’ve had that version pinned from the beginning.