Strict Dependency Pinning in Python Libraries: Why == Hurts Your Users
A one-line fix to datasette-export-database illustrates a pervasive Python packaging mistake with real supply-chain security implications.
Key Takeaways
- datasette-export-database 0.3a2 fixed a pyproject.toml that hard-pinned to datasette==1.0a27, making the plugin incompatible with every other Datasette version.
- Library packages should use flexible lower-bound version constraints (>=), not exact pins (==); exact pinning belongs in application lock files, not reusable packages.
- Hard-pinning a library to a specific alpha release can prevent downstream users from receiving security patches — a subtle but real supply-chain risk.
- Automated dependency scanning tools such as pip-audit and OSV-Scanner can detect packages whose constraints silently block patched versions from reaching your environment.
Simon Willison's datasette-export-database plugin version 0.3a2, released 25 June 2026, carried what its author called an "embarrassingly tiny" fix: a single character change in pyproject.toml, replacing datasette==1.0a27 with datasette>=1.0a27. That one character made the plugin installable alongside every post-1.0a27 Datasette release instead of only one specific alpha build. It is a small fix, but the mistake it corrects is endemic across the Python ecosystem — and it carries a supply-chain dimension worth spelling out.
Application Pinning vs. Library Pinning
The distinction between an *application* and a *library* governs how dependencies should be specified. Applications — a deployed service, a CI pipeline script, a containerised workload — should pin exactly, typically via a lock file (pip freeze, poetry.lock, uv.lock). Exact pins reproduce the environment reliably and catch supply-chain substitution attacks at the boundary where code actually runs.
Libraries and plugins are different. When a package published to PyPI hard-pins a dependency with ==, every project that installs it inherits that constraint. If two installed packages demand different exact versions of the same dependency, pip raises a conflict and the install fails. The result is silent incompatibility that surfaces only when a user attempts pip install — often during a production deployment or a CI run.
The Supply-Chain Risk in Strict Alpha Pins
The datasette case involved an alpha release (1.0a27). Alpha versions are, by definition, pre-stability: they may contain unfixed security defects, and they typically do not receive backported patches after a newer alpha ships. A library that hard-pins to an alpha locks every downstream user to that snapshot. If a vulnerability is later found in datasette==1.0a27 and fixed in 1.0a28, the plugin's constraint prevents the fix from propagating — a classic supply-chain pinning trap.
The same pattern is well-documented in the npm ecosystem: a transitive dependency pinned to a version with a known CVE causes automated scanners to flag the issue, but the fix cannot propagate because an upstream library refuses to widen its range. Security teams then face the choice of patching the transitive package out-of-band, maintaining a private fork, or waiting indefinitely for the upstream author to act.
Best Practices for Library Maintainers
- Use a lower-bound pin (
>=1.0a27) to declare the minimum tested API, not an exact lock. - Add an upper-bound exclusion (
<2.0) only when you have evidence of a confirmed breaking change — not as a precaution. - Run your test suite against the latest released version of every dependency in CI to detect breakage before it reaches users.
- Use
pip-auditorosv-scannerin CI to surface known vulnerabilities within your declared dependency range. - Document every dependency-range change in your changelog so downstream consumers understand the impact.
What This Means for Security Teams
If your organisation consumes open-source Python packages, the dependency range policies of your third-party libraries are part of your software supply-chain attack surface. Tools such as pip-audit, Dependabot, and OSV-Scanner should be configured to flag libraries that hard-pin transitive dependencies to outdated or pre-release versions. A library maintainer's one-character oversight — == instead of >= — can silently prevent a security patch from reaching your production environment without any error or warning.
Frequently Asked Questions
Why is exact pinning (==) in a published Python library dangerous?
When a library on PyPI uses an exact version pin (==) for a dependency, it can conflict with other packages requiring different versions and — critically — prevents downstream users from upgrading to a patched release if the pinned version later receives a CVE fix. Lock files are the correct place for exact pins; library pyproject.toml files should use flexible lower-bound constraints.
How do I detect over-pinned dependencies in packages I consume?
Run `pip-audit` against your virtual environment to surface known vulnerabilities in installed packages and their transitive dependencies. Dependabot and OSV-Scanner can also detect packages pinned to versions with known CVEs and open automated pull requests to widen the constraint or trigger an upgrade.
Is the datasette-export-database issue itself a security vulnerability?
No. The 0.3a2 fix addresses a compatibility and correctness issue, not an exploitable vulnerability. The security lesson is structural: the identical pattern — hard-pinning to a specific pre-release — can block security patches in higher-risk libraries, and the mechanism is invisible until a scanner or failed install exposes it.
Sources
- 1datasette-export-database 0.3a2 release notes — Simon Willison
- 2Version specifiers — Python Packaging User Guide — Python Packaging Authority (PyPA)
- 3Dependency resolution — pip documentation — pip project