Static linter that checks a requirements file is fully version-pinned and hash-pinned, for CI and pre-commit.
Project description
pinlint
A static linter that checks a requirements file is fully version-pinned and hash-pinned. Built for CI and pre-commit, so unpinned or unhashed dependencies fail review before anything is installed.
Install
pip install pinlint
30-second example
pinlint requirements.txt
requirements.txt:3: unpinned: requests is not pinned to an exact version (use ==)
requirements.txt:7: missing-hash: flask has no --hash
2 issue(s) found
Exit code is 1 when there are findings, 0 when the file is clean, so it drops straight into CI or a pre-commit hook.
As a library:
from pinlint import lint_file
findings = lint_file(
"requirements.txt", require_hashes=True, allow_unpinned=False, follow_includes=True
)
for f in findings:
print(f.file, f.line, f.code, f.message)
Why this exists
Reproducible, tamper-evident installs need every requirement pinned to an exact version
and carrying a hash. The existing tools each do something adjacent: pip-compile --generate-hashes generates such a file, pip install --require-hashes enforces hashes
at install time, and requirements-txt-fixer tidies formatting. None of them is a fast,
static check you can run in review to assert that an arbitrary requirements file is fully
pinned and hashed. pinlint is that check.
Comparison
| pinlint | pip-compile | pip --require-hashes | requirements-txt-fixer | |
|---|---|---|---|---|
| Static check, no install | yes | n/a | no (install time) | yes |
| Flags unpinned versions | yes | generates | at install | no |
| Flags missing hashes | yes | generates | at install | no |
| CI / pre-commit gate | yes | partial | no | yes (formatting only) |
Checks
unpinned: the requirement is not pinned with==or===to an exact version.missing-hash: the requirement has no--hash(unless--no-hashes).unpinnable: an editable, URL, or VCS install that cannot be version-pinned.parse-error: the line could not be parsed as a requirement.
It understands comments, blank lines, backslash line continuations, --hash options,
environment markers and extras, and -r and -c includes (followed with cycle
protection). The only dependency is packaging, the canonical PEP 508 parser.
Options
--allow-unpinneddo not require exact version pins.--no-hashesdo not require--hashentries.--no-followdo not follow-rand-cincludes.--allow PACKAGEignore findings for a package name (repeatable).--format text|json|sarifchoose the output format.jsonsuits CI and editors;sarifemits SARIF 2.1.0 for GitHub code scanning and other analysis tools.--write-baseline PATHwrite all current findings to a baseline JSON file, then exit 0.--baseline PATHsuppress findings present in the baseline; exit nonzero only when new findings remain.
Baseline: adopt pinlint incrementally
If an existing project has many unpinned requirements you cannot fix all at once, use a baseline to suppress the known findings and fail only on new ones.
# Record the current state.
pinlint requirements.txt --write-baseline .pinlint-baseline.json
# In CI, suppress known findings and fail only on new ones.
pinlint requirements.txt --baseline .pinlint-baseline.json
The baseline file is deterministic and human-readable, so it diffs cleanly in code review. Findings are fingerprinted by rule code, file path, requirement text, and package name -- not by line number -- so adding or removing unrelated lines above a requirement does not invalidate its suppression.
Commit .pinlint-baseline.json to version control. When you fix a requirement, re-run
--write-baseline and commit the smaller file; the diff shows the fix.
Pre-commit
pinlint ships a hook, so you can add it to .pre-commit-config.yaml:
repos:
- repo: https://github.com/amaar-mc/pinlint
rev: v0.2.0
hooks:
- id: pinlint
The hook runs on files matching requirements.*\.txt.
Testing
pip install -e ".[dev]"
pytest
Tests use golden requirements files for each rule, including includes, cycles, line continuations, and the CLI exit codes.
Contributing
Issues and pull requests are welcome. See CONTRIBUTING.md.
License
MIT. See LICENSE.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pinlint-0.4.0.tar.gz.
File metadata
- Download URL: pinlint-0.4.0.tar.gz
- Upload date:
- Size: 967.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
93a8004adc988252e0ad630805fe5829014e42c5dfc501dffddb0902a0ecc847
|
|
| MD5 |
05f52863a20cbfa4285f1a7098e86317
|
|
| BLAKE2b-256 |
873b0befdfabafbf07e766fecaaf8665ed3980e4a36b3678a962e6bceb57fa48
|
File details
Details for the file pinlint-0.4.0-py3-none-any.whl.
File metadata
- Download URL: pinlint-0.4.0-py3-none-any.whl
- Upload date:
- Size: 12.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dd65bbbba4c1a65d08734249146f98530f246904a7be74b312f11610c08a8e3d
|
|
| MD5 |
4aa6a2733c57b4b6ae7e827f36438cb0
|
|
| BLAKE2b-256 |
5443266838488035be79654dde9916089e5058c680b89b028382aa4b02d53f4d
|