A dependency linter for Python projects using UV and pyproject.toml. Pinnochio
helps prevent accidental dependency upgrades that break APIs by enforcing best
practices for version pinning.
- Upper Bound Enforcement: Ensures all dependencies have upper version
bounds (e.g.,
>=2.25.0,<2.26.0) to prevent breaking changes - Automatic Sorting: Keeps dependency groups alphabetically sorted for consistency
- Version Drift Detection: Identifies when the same package has different version constraints across dependency groups
- Redundancy Checks: Detects dependencies duplicated between core dependencies and optional groups
- Auto-fix Support: Automatically fixes many common issues with the
--fixflag
uv tool install pinnochio
# Or pipx install pinnochioRun in your project directory:
# Check for issues
pinnochio
# Check and automatically fix issues
pinnochio --fix
# Override pinning strategy for this run
pinnochio --fix --pinning-strategy minorWarning! The following dependencies aren't pinned from above
dependencies:
requests>=2.25.0
numpy>=1.20.0
Warning: The following dependency groups aren't sorted:
dev
With --fix, pinnochio will automatically add upper bounds:
Fixing: Adding upper bounds to unpinned dependencies...
Fixed: requests>=2.25.0 -> requests>=2.25.0,<3.0.0
Fixed: numpy>=1.20.0 -> numpy>=1.20.0,<2.0.0
Fixed: Upper bounds have been added where possible.
Fixing: Sorting dependency groups...
Fixed: Dependency groups have been sorted.
Changes have been written to pyproject.toml
Pinnochio can be configured via the [tool.pinnochio] section in your
pyproject.toml:
[tool.pinnochio]
pinning-strategy = "major" # Default: "major"The pinning-strategy option controls how upper bounds are added to
dependencies. You can set it in your config file or override it with the
--pinning-strategy CLI flag:
-
"major"(default): Allows minor and patch updates within the same major version- Example:
>=1.2.3becomes>=1.2.3,<2.0.0 - Follows semantic versioning: breaking changes at major version boundaries
- Recommended for most projects
- Example:
-
"minor": Only allows patch updates within the same minor version- Example:
>=1.2.3becomes>=1.2.3,<1.3.0 - More restrictive, useful for projects requiring high stability
- Prevents feature additions that might affect behavior
- Example:
-
"patch": No automatic updates, only the exact patch version- Example:
>=1.2.3becomes>=1.2.3,<1.2.4 - Most restrictive, useful for critical production systems
- Requires manual version bumps for any updates
- Example:
Note: The --pinning-strategy CLI flag always takes precedence over the
config file setting.
-
Upper Bound Pinning: Dependencies with only lower bounds (e.g.,
>=1.0.0) are flagged and can be auto-fixed to include upper bounds based on your configured pinning strategy -
Dependency Sorting: All dependency groups must be alphabetically sorted
-
Version Consistency: The same package must have identical version constraints across all dependency groups
-
No Redundancy: Dependencies in the core
dependencieslist should not be duplicated inoptional-dependenciesordevgroups
Add pinnochio to your .pre-commit-config.yaml:
repos:
- repo: https://github.com/BlakeJC94/pinnochio
rev: v1.0.0 # Use the latest version
hooks:
- id: pinnochioThis will run pinnochio automatically whenever you commit changes to
pyproject.toml. If issues are found, the commit will be blocked until they're
fixed.
To automatically fix issues during pre-commit, you can pass the --fix flag:
repos:
- repo: https://github.com/anomalyco/pinnochio
rev: v1.0.0
hooks:
- id: pinnochio
args: [--fix]- Python >=3.11
pyproject.tomlwith UV-style dependency groups
MIT