Skip to content
← Back to explainers

Python explainer

Pick the Oldest Python Version You Support

About 4 min to read. Allow extra time to work through examples.

Your oldest supported Python decides which syntax and libraries you can use. Make requires-python, mypy, Ruff and CI agree on it, and raise it on purpose.

This article uses Python. You can make it your practice mode to browse related content across the site.

Your package declares requires-python = ">=3.13". A contributor who works on Python 3.14 adds a template string, and guards it with a version check:

import sys

name = "Ada"
print("starting")
if sys.version_info >= (3, 14):
    message = t"Hello {name}"
else:
    message = f"Hello {name}"

On Python 3.13, this file fails with SyntaxError, and starting never prints. Python parses the whole file before it runs any of it, so the if never runs. If you support a Python version, every line of your package has to run on it. This article shows how to check that.

Know when each feature arrived

Feature First Python
except* 3.11
The type statement, and def first[T](...) generics 3.12
copy.replace, typing.TypeIs, typing.ReadOnly 3.13
Type parameter defaults, as in class Box[T = int] 3.13
Template strings, as in t"Hello {name}" 3.14

Each kind of feature needs a different approach on an older Python.

  • Syntax must parse on the oldest version. To use newer syntax, put it in a separate module and import that module only on newer Pythons. Each branch is then one more path to test.
  • Library functions can sometimes come from a backport or a small fallback you write. Check that the replacement behaves the same, including its exceptions.
  • Typing features often have backports in the typing_extensions package, which gives older Pythons objects such as TypeIs and ReadOnly. It cannot add syntax. typing_extensions.TypeVar("T", default=int) works on 3.12, but class Box[T = int] still needs 3.13.

See the typing_extensions documentation.

Make the metadata and the tools agree

For a project whose oldest Python is 3.14, put these settings in its pyproject.toml:

[project]
requires-python = ">=3.14"

[tool.mypy]
python_version = "3.14"

[tool.ruff]
target-version = "py314"

requires-python tells installers which Pythons you support. python_version makes mypy report syntax and standard library names that the oldest version lacks. target-version stops Ruff from suggesting rewrites that need a newer Python. Neither tool runs your code on that version, so CI still has to.

mypy checks only the branch of an if sys.version_info >= ... test that matches its target, and it skips the other branch. If your code has such branches, run mypy once for each target. See mypy's python_version and Ruff's target-version.

Check your understanding A library supports Python 3.14 and newer. Can it use the type statement and except*? Show answer

Yes. The type statement arrived in Python 3.12 and except* in 3.11, so both work on every version the library supports. Tests on the oldest version still have to show that the code itself works.

Test the oldest and the newest

Run your tests on the oldest Python you support and on the newest. Add the versions in between, and other operating systems, where your users need them. Build the wheel, install it in a fresh environment and check that the installed package works, as the CI article showed. Also check that the installed package reports the Requires-Python you meant, with importlib.metadata.metadata("mypkg")["Requires-Python"].

Raise the minimum on purpose

Installers use Requires-Python to skip releases that do not support the running Python. Say version 2 of your package requires Python 3.15, and version 1 supports 3.14. pip on Python 3.14 then installs version 1. If the user asked for version 2 exactly, or another package needs version 2, the install fails instead. The field does nothing for someone who copies your source files.

Check your understanding What should go with a release that raises the minimum Python? Show answer

Update requires-python, the mypy and Ruff targets, the CI versions and the documentation in the same release. Say so in the release notes, and name the last release that still supports the old minimum. Raise the minimum before you ship code that needs it. You can also raise it only to stop supporting an old Python, as long as you tell your users.

See the Requires-Python specification.

step 41 of 41

Modules, Packaging and Tooling

You've reached the last step

Revisit the track to review its lessons and exercises, or explore another topic.

Explore more tracks