# What is requires-python?


`requires-python` is a field in {{< term "pyproject-toml" "pyproject.toml" >}} that declares which Python versions your project supports. Package managers read it before installing anything, and {{< term "resolver" "resolvers" >}} use it to filter out incompatible dependency versions. It lives in the `[project]` table, standardized by [PEP 621](https://pydevtools.com/handbook/explanation/what-is-pep-621-compatibility.md):

```toml
[project]
name = "my-project"
requires-python = ">=3.10"
```

That single line tells every tool in the ecosystem: this project runs on Python 3.10 and above.

## How the syntax works

The value is a [version specifier](https://pydevtools.com/handbook/explanation/what-is-a-version-specifier.md) string, the same syntax used for package version constraints. The most common pattern is a lower bound:

| Specifier | Meaning |
|---|---|
| `>=3.10` | Python 3.10 or newer (the default recommendation) |
| `>=3.10,<4` | Python 3.10 through 3.x (equivalent in practice, since Python 4 does not exist) |
| `>=3.10,<3.14` | Python 3.10 through 3.13 only |
| `==3.12.*` | Only Python 3.12.x (any patch) |

A bare lower bound like `>=3.10` is the right choice for most projects. It supports every future Python release without requiring a metadata update each October when a new [CPython](https://pydevtools.com/handbook/reference/cpython.md) version ships.

## How uv uses requires-python

[uv](https://pydevtools.com/handbook/reference/uv.md) treats `requires-python` as a hard constraint at two points:

**Interpreter selection.** When `uv run` or `uv sync` picks a Python {{< term "interpreter" >}}, the interpreter's version must satisfy `requires-python`. If the pinned version in [`.python-version`](https://pydevtools.com/handbook/explanation/what-is-a-python-version-file.md) falls outside the range, uv stops with an [incompatibility error](https://pydevtools.com/handbook/how-to/how-to-fix-python-version-incompatibility-errors-in-uv.md).

**Dependency resolution.** `uv lock` resolves dependencies across the full range that `requires-python` allows. A project declaring `>=3.9` forces the resolver to find versions compatible with 3.9 through the latest release. If a {{< term "dependency" >}} dropped 3.9 support, the resolver fails even on a machine running 3.14. Raising the `requires-python` floor is often the fix. See [how to debug uv dependency resolution failures](https://pydevtools.com/handbook/how-to/how-to-debug-uv-dependency-resolution-failures.md).

## How pip uses requires-python

[pip](https://pydevtools.com/handbook/reference/pip.md) checks `requires-python` during installation. If the running interpreter falls outside the declared range, pip skips that release and tries an older one whose `requires-python` matches. If no compatible release exists, the install fails.

Build tools like [setuptools](https://pydevtools.com/handbook/reference/setuptools.md) and the [uv build backend](https://pydevtools.com/handbook/explanation/what-is-the-uv-build-backend.md) embed `requires-python` into the package's [core metadata](https://pydevtools.com/handbook/explanation/what-is-core-metadata.md) (`Requires-Python` header) when building a {{< term "wheel" >}} or sdist. That metadata travels with the package to {{< term "pypi" "PyPI" >}}, where the index uses it to filter downloads by interpreter version.

## How requires-python differs from .python-version

These two files both name Python versions but answer different questions:

| | `.python-version` | `requires-python` |
|---|---|---|
| Purpose | Pins the development interpreter | Declares supported versions |
| Typical value | `3.13` (one version) | `>=3.10` (a range) |
| Who reads it | Version managers (uv, pyenv) | Package managers, resolvers, PyPI |
| Shipped with the package | No | Yes (embedded in metadata) |
| Affects end users | No | Yes |

Your `.python-version` might say `3.13` while `requires-python` says `>=3.10`. That means you develop on 3.13 but your package works for anyone on 3.10 or newer. The pinned version must fall within the `requires-python` range, or uv reports an incompatibility error.

For the full story on `.python-version`, see [What is a .python-version file?](https://pydevtools.com/handbook/explanation/what-is-a-python-version-file.md)

## Common mistakes

**Too-narrow bounds.** Setting `==3.12.*` locks the project to one minor version. Every user on 3.13 or 3.14 is excluded, and the constraint must be manually widened each year. Use `>=3.12` unless the project genuinely cannot run on newer interpreters.

**Too-wide bounds.** Claiming `>=3.8` while only testing on 3.12 and 3.13 promises compatibility that does not exist. The resolver believes the claim, and users on 3.8 discover the lie at runtime. Set the floor to the oldest version your CI matrix covers.

**Forgetting to update after dropping old versions.** Removing Python 3.9 compatibility from code (dropping `from __future__ import annotations`, using `match` statements, removing 3.9-era workarounds) without raising the `requires-python` floor leaves the metadata out of sync. pip and uv still offer the package to 3.9 users, who then hit runtime errors.

**Confusing it with .python-version.** Editing `requires-python` does not change which interpreter `uv run` uses; that is controlled by `.python-version`. Editing `.python-version` does not change which versions the package supports; that is `requires-python`. Changing supported versions requires editing both: lower the `requires-python` floor, then `uv python pin` the new development version. See [how to change the Python version of a uv project](https://pydevtools.com/handbook/how-to/how-to-change-the-python-version-of-a-uv-project.md).

## When to raise the floor

The CPython [release cycle](https://pydevtools.com/handbook/reference/cpython.md) gives each version five years of support. When a version reaches end-of-life, dropping it from `requires-python` is safe for most projects. Raising the floor unlocks newer syntax, drops compatibility workarounds, and (in uv projects) can resolve dependency conflicts caused by old versions dragging in outdated transitive pins.

A practical rule: set the floor to the oldest version your CI tests against, and raise it when you stop testing against an older version.
