Safeguard
Supply Chain•

What dependency version bounds should a Python library declare?

A library should declare minimum versions it has tested and add an upper bound only for a known break. Never pin exact versions or list sub dependencies.

Safeguard Security Team
4 min read

A library should declare loose, tested bounds: a lower bound for the oldest version you support, and an upper bound only when you know a later release breaks you. Do not pin exact versions or list your dependencies' own dependencies. Pins belong in an application's lock file.

Two different jobs

The Python Packaging User Guide separates two things that people mix up. The dependencies in your pyproject.toml, historically install_requires, describe what a project minimally needs to run correctly. Requirements files, on the other hand, usually define a whole environment with exhaustive pinned versions for repeatable installs.

Library dependenciesRequirements or lock file
PurposeWhat the project needs to workRepeatable install of a full environment
StyleAbstract names and rangesExact pins, often with hashes
Read by pip automaticallyYes, on installOnly when you ask for it
Who owns itLibrary authorApplication owner

What the guide recommends

For a library, it is best practice to indicate any known lower or upper bounds. If you know your project needs at least version 1 of one dependency, say so. If a dependency introduced a change in its version 2 that breaks you, it makes sense to exclude it.

[project]
dependencies = [
  "package-a >= 1, < 2",
  "package-b >= 2",
]

The guide also says it is not best practice to pin dependencies to specific versions or to list sub dependencies. That is overly restrictive and prevents users from gaining the benefit of dependency upgrades.

Why exact pins hurt your users

Imagine your library pins one package to a single version. An application that also uses a second library needing a newer version of that package cannot be installed. The resolver reports a conflict that neither you nor the application owner caused, only you can fix it. The more popular your library, the more projects get stuck.

Pins also delay security fixes. If a dependency ships a patch and you pin the vulnerable version, every user must wait for your next release. A range lets them upgrade the dependency themselves.

A practical method

  1. List direct dependencies only. Do not list what they depend on.
  2. Set each lower bound to the oldest version in your CI matrix that passes.
  3. Test against the newest release of each dependency too, in a scheduled job.
  4. Add an upper bound only when a specific release breaks you, and remove it once you fix the incompatibility.
  5. Keep a separate lock file for your own development and CI, outside the published metadata.

For compatible release syntax, see our post on version specifiers.

Optional features

Put dependencies needed only for a feature in optional extras so most users do not install them. The guide shows how these extras are declared and how an all extra can point back at your own project, which avoids maintaining the same list twice. See also extras versus dependency groups.

Why this is a supply chain topic

Everything you declare is a trust decision for your users. Each added dependency is another maintainer and another release stream. Fewer, well chosen, loosely bounded dependencies mean a smaller attack surface and faster fixes.

Try it yourself

Click through it at your own pace, or open it full screen.

Click through the inventory and notice how many packages arrive indirectly. That is the part your bounds influence the most.

Safeguard's SBOM and dependency scanning list direct and indirect components. You can run a free scan.

Quick check

Should my library pin exact versions?

No. The packaging guide says pinning exact versions or listing sub dependencies is overly restrictive. Use ranges, and keep pins in an application lock file.

When is an upper bound justified?

When you know a later major release breaks your code. Remove it as soon as you have fixed the incompatibility.

Where do exact pins belong?

In a requirements or lock file for a full environment, owned by the application that deploys it.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.