Skip to main content

Static linter that checks a requirements file is fully version-pinned and hash-pinned, for CI and pre-commit.

Project description

pinlint

pinlint logo

PyPI CI License: MIT

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-unpinned do not require exact version pins.
  • --no-hashes do not require --hash entries.
  • --no-follow do not follow -r and -c includes.
  • --allow PACKAGE ignore findings for a package name (repeatable).
  • --format text|json|sarif choose the output format. json suits CI and editors; sarif emits SARIF 2.1.0 for GitHub code scanning and other analysis tools.
  • --write-baseline PATH write all current findings to a baseline JSON file, then exit 0.
  • --baseline PATH suppress 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

pinlint-0.4.0.tar.gz (967.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pinlint-0.4.0-py3-none-any.whl (12.6 kB view details)

Uploaded Python 3

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

Hashes for pinlint-0.4.0.tar.gz
Algorithm Hash digest
SHA256 93a8004adc988252e0ad630805fe5829014e42c5dfc501dffddb0902a0ecc847
MD5 05f52863a20cbfa4285f1a7098e86317
BLAKE2b-256 873b0befdfabafbf07e766fecaaf8665ed3980e4a36b3678a962e6bceb57fa48

See more details on using hashes here.

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

Hashes for pinlint-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 dd65bbbba4c1a65d08734249146f98530f246904a7be74b312f11610c08a8e3d
MD5 4aa6a2733c57b4b6ae7e827f36438cb0
BLAKE2b-256 5443266838488035be79654dde9916089e5058c680b89b028382aa4b02d53f4d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page