Skip to content

Declare minimum Python version in metadata#2726

Merged
mhammond merged 2 commits intomhammond:mainfrom
konstin:konsti/python-requires
Mar 29, 2026
Merged

Declare minimum Python version in metadata#2726
mhammond merged 2 commits intomhammond:mainfrom
konstin:konsti/python-requires

Conversation

@konstin
Copy link
Copy Markdown
Contributor

@konstin konstin commented Mar 25, 2026

We got a bug report in uv because pywin32 only has wheels for Python 3.8+ and no source distribution, but doesn't declare its minimum requires Python version (astral-sh/uv#15069). This prevents package managers from correctly rejecting it as an unsupported version when resolving for a range of Python versions. This PR adds this bound as Requires-Python in the wheel metadata.


Closes #2028
Closes #1448

We got a bug report in uv because pywin32 only has wheels for Python 3.8+ and no source distribution, but doesn't declare its minimum requires Python version (astral-sh/uv#15069). This prevents package managers from correctly rejecting it as an unsupported version when resolving for a range of Python versions. This PR adds this bound as `Requires-Python` in the wheel metadata.
Comment thread setup.py Outdated
Co-authored-by: Avasam <samuel.06@hotmail.com>
@mhammond
Copy link
Copy Markdown
Owner

I've a similar question to mine in #2028, which is probably my misunderstanding - but what's the actual use-case here - why can't package managers running Python 3.7 already reject this because such a wheel doesn't exist? How do we ensure this doesn't go stale over time?

@konstin
Copy link
Copy Markdown
Contributor Author

konstin commented Mar 26, 2026

Usually, packages provide both a source distribution and a Requires-Python bound as part of the core metadata (that tells tools not to try building that source distribution), which also gets used when working on the package itself. I'd argue there's no reason to not provide it given that it's part of the core metadata.

Packages that do not provide a source distribution are rare and usually packages that have very custom platform requirements (torch, tensorflow). While it's technically possible to infer the bound from wheel tags, it's both a lot of complexity for a case that rarely exists (pywin32 is currently the only case I'm aware of). It also means that we can't resolve from core metadata alone anymore, but always need to process the list of files (the list of files is currently only required for a universal resolution when a specific platform is required, it's otherwise possible from metadata alone).

How do we ensure this doesn't go stale over time?

This bound usually gets bumbed together with the CI and docs changes when dropping support for a Python version, similar to #2413.

@mhammond
Copy link
Copy Markdown
Owner

Usually, packages provide both a source distribution

We do too though?

This bound usually

It's the "usually" that worries me here - the fact no binary exists is more difficult to accidentally forget. If we fail to update this, what's the error scenario?

@mhammond
Copy link
Copy Markdown
Owner

It also means that we can't resolve from core metadata alone anymore, but always need to process the list of files (the list of files is currently only required for a universal resolution when a specific platform is required, it's otherwise possible from metadata alone).

I get that, but I don't see how failing earlier here is better than failing later. Python 3.7 is going to fail in either case.

@konstin
Copy link
Copy Markdown
Contributor Author

konstin commented Mar 26, 2026

We do too though?

There is no source distribution for the latest release on PyPI, if that's what you mean:

image

I get that, but I don't see how failing earlier here is better than failing later. Python 3.7 is going to fail in either case.

Resolvers will backtrack if the Python version bound is too high. For example, if you're on Python 3.12 (for pip or uv pip) or require a minimum of Python 3.12 in your project (for poetry or uv), and you try to add a package that requires python >=3.13 for its latest version, it backtracks to an older release that supports >=3.12. There's some subtlety involved where you can pick different versions for different python ranges, but that too requires backtracking on the python version lower bound.

@Avasam
Copy link
Copy Markdown
Collaborator

Avasam commented Mar 27, 2026

Whilst I personally prefer having the lower bounds (no upper though !), hence approval. I'm not gonna merge until Mark is convinced.

Usually, packages provide both a source distribution

We do too though?

We don't, not on PyPI, as Konstin showed above.

And I don't mind if it stays that way as building pywin32 has a certain set of requirements and I'd rather leave that to advanced users that will know to clone the project or build from source from GitHub. (sdists also have replicability issues). Have inexperienced users be told we don't support their platform/python (missing wheel) rather than getting reports of failed sdist builds from PyPI.

If we fail to update this, what's the error scenario?

For sdists: In the hypothetical we started shipping it, AFAICT, the most recent pywin32 will try to install and build on unsupported Python version, then either fail at build or runtime (depending on where we introduced the breaking change). Which is solved by pinning pywin32

For bdist: I'll let @konstin answer here. But I'd assume a resolver would try the latest pywin32 version, fail to find a binary for the unsupported python version, then fallback to the slow resolution (or fail entirely, but also solved by pinning pywin32)


Also note that if we eventually move static project metadata (title, author, version, links, readme, license, etc.) from setup.py to pyproject.toml, then we could deduplicate/remove target-version = "py39" from ruff.toml as it'll use python-version from pyproject.toml directly, making it even harder to forget.

@mhammond mhammond merged commit db5d4e3 into mhammond:main Mar 29, 2026
53 of 54 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pywin32 version 227 supports Python 3.8 but does not advertise that on Pypi

3 participants