Skip to content
← Back to explainers

Python explainer

Choose Version Ranges for Your Dependencies

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

Read a version specifier such as >=2.1,<3 or ~=1.4, choose a lower and an upper bound for a reason, and see why a library needs wider ranges than an application.

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

Your package calls a function that example-lib added in version 2.1. So in pyproject.toml you write:

[project]
dependencies = ["example-lib>=2.1"]

>=2.1 is a version specifier, a rule that says which versions of a dependency your package accepts. The installer picks a version that satisfies it. Versions compare as numbers, one segment at a time, not as text:

assert "1.10" < "1.9"      # as text, "1" sorts before "9"
assert (1, 10) > (1, 9)    # as numbers, 10 is greater than 9

Read a specifier

A specifier is one or more clauses joined by commas, and a version must satisfy every clause:

Specifier Allows
>=2.1 2.1 and anything newer
>=2.1,<3 2.1 and newer, up to but not including 3.0
==2.4.7 Only 2.4.7
>=2.1,!=2.3.0 2.1 and newer, except 2.3.0

Missing segments count as zero, so 1.0 and 1.0.0 are the same version.

The compatible-release operator ~= combines a lower bound with a fixed prefix:

Requirement Same as
~=1.4.2 >=1.4.2, ==1.4.*
~=1.4 >=1.4, ==1.*

So 1.5.0 satisfies ~=1.4 but not ~=1.4.2. The prefix is the version you wrote minus its last segment. That last segment, and any after it, can grow.

Check your understanding Which versions satisfy ~=2.1: 2.1.0, 2.4.7, 3.0.0 or 2.0.9? Show answer

2.1.0 and 2.4.7. Both are at least 2.1, and both start with the prefix 2. 3.0.0 has the wrong prefix, and 2.0.9 is below the lower bound.

Choose the bounds

Set the lower bound at the first release that has what you need, such as the function you call or a bug fix you rely on.

Add an upper bound, such as <3, only for a reason you can name. Maybe you know that 3.0 breaks your code, or the project says it breaks its API in every major release. Write the reason in a comment next to the line, and widen the range once you have tested the new release.

Both directions cost something. A range that is too wide lets in a release that breaks your code. A range that is too narrow stops your users from installing your package next to another package that needs a newer version. A version number alone does not tell you that two releases are compatible, so test new releases.

When two packages ask for ranges that do not overlap, such as example-lib>=2,<3 and example-lib>=3, no version satisfies both. The installer's resolver, the part that picks one version of each package, then tries other versions of those two packages. It fails if none of them fit together.

Give a library wider ranges than an application

An application installs exact versions from a lockfile, which a later article covers. It still needs ranges, because they decide which versions an update can move to. A library is different. Its users install it next to their own packages, and their installer reads your ranges. They never see your lockfile. So give a library the widest ranges you can support, and test more than one combination.

Apply the upper-bound rule to requires-python too. If your package needs Python 3.14 or newer, write requires-python = ">=3.14". Don't add a bound such as <3.16 only because you have not tried 3.16 yet. pip would then refuse to install your package on Python 3.16, even if it works there.

Going further

Real version numbers can also carry prereleases such as 2.0rc1, post-releases, epochs and local labels. In production code, compare versions with the packaging library instead of your own parser. The exercise below handles release numbers only, so you can write the comparison yourself.

Some tools let you override a dependency's declared range, such as uv's dependency overrides. An override replaces the requirement but does not make the combination work, so test it and write down why it exists. See the version specifier specification.

step 27 of 41

Modules, Packaging and Tooling

Up next Check a Version Against a Specifier