Skip to main content

Static CI linter for PEP 751 pylock.toml: hash completeness, sdist-only builds, platform portability, and drift vs pyproject.toml.

Project description

pylock-lint

A pylock.toml that installs cleanly on your laptop can still be broken: wheels with no hashes, packages that only ship an sdist (so CI silently builds from source), or a lock generated on Linux that has no wheels for the Windows runner it'll actually run on. Nothing in the standard toolchain flags any of that before it reaches a teammate's machine.

pylock-lint is a static CI check for PEP 751 lockfiles. It reads a committed pylock.toml, reports the reproducibility and portability gaps, and exits non-zero so a bad lock fails the build instead of the deploy.

$ pylock-lint
pylock.toml [numpy]: ERROR PL001 wheel numpy-2.1.0-...-manylinux_2_17_x86_64.whl has no hashes
pylock.toml [somepkg]: WARNING PL002 sdist-only (no wheels): install must build from source...
pylock.toml: WARNING PL020 single-environment lock (no 'environments' declared) but it pins
  platform-specific wheels (numpy); installs on another OS/Python will fail or fall back to source

2 error(s), 1 warning(s) across 1 file(s)
$ echo $?
1

Why this exists

pip 26.1 (April 2026) shipped pip install -r pylock.toml, and pip's own generated locks are single-platform and single-Python — it "avoids some advanced features (like environment markers) for now." uv and PDM export cross-platform locks; Poetry has none. So teams are starting to commit pylock.toml to their repos, and the file's failure modes are exactly the kind that don't show up until someone on a different OS runs pip install. packaging parses the schema and pip-audit scans for CVEs; neither answers "is this lock actually reproducible and portable?"

Install

pip install pylock-lint

Or run it without installing:

pipx run pylock-lint

Usage

pylock-lint                          # lint ./pylock.toml (and pylock.*.toml)
pylock-lint path/to/pylock.toml      # lint a specific file or glob
pylock-lint --pyproject pyproject.toml   # also check the lock is in sync with declared deps
pylock-lint --require-cross-platform     # fail if wheels cover only one platform
pylock-lint --strict                 # treat warnings as failures too
pylock-lint --format json            # machine-readable output
pylock-lint --ignore PL002,PL020     # skip specific rules

Default exit code is 1 when any error-level finding is present, 0 otherwise. --strict also fails on warnings.

Checks

Code Severity What it catches
PL001 error An sdist, wheel, or archive with no hashes — the lock can't verify integrity.
PL002 warning A package with an sdist but no wheels — install builds from source, which isn't byte-reproducible and needs a toolchain.
PL003 error A package with no installable source at all.
PL010 error A package entry missing its required name.
PL011 error Mutually-exclusive source types on one package (PEP 751 allows exactly one).
PL012 error Missing or unsupported lock-version.
PL013 warning Missing created-by (provenance).
PL014 warning Lockfile declares no packages.
PL015 error A package with sdist/wheels missing its required version.
PL020 warning Single-environment lock that pins platform-specific wheels — non-portable.
PL021 error With --require-cross-platform: wheels cover only one platform.
PL030 error With --pyproject: a dependency declared in pyproject.toml is absent from the lock.
PL031 warning With --pyproject: requires-python disagrees between the two files.

GitHub Action

- uses: fernforge/pylock-lint@v1
  with:
    paths: pylock.toml
    pyproject: pyproject.toml
    strict: "false"

The action installs the package and runs it against your committed lock on every push.

pre-commit

Add it to .pre-commit-config.yaml so a bad lock is caught before it's committed:

- repo: https://github.com/fernforge/pylock-lint
  rev: v0.1.1
  hooks:
    - id: pylock-lint

The hook runs only when a pylock.toml (or pylock.*.toml) is staged, and passes the changed files straight to the linter.

What it does not do

No network calls. It won't fetch the referenced wheels or re-resolve your dependency graph — it reads what's committed and checks the file against the spec and your pyproject.toml. That keeps it fast and safe to run on untrusted branches, but it means PL030 checks presence, not that every locked version satisfies every declared constraint.

License

MIT

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

pylock_lint-0.1.1.tar.gz (9.9 kB view details)

Uploaded Source

Built Distribution

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

pylock_lint-0.1.1-py3-none-any.whl (10.9 kB view details)

Uploaded Python 3

File details

Details for the file pylock_lint-0.1.1.tar.gz.

File metadata

  • Download URL: pylock_lint-0.1.1.tar.gz
  • Upload date:
  • Size: 9.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.2

File hashes

Hashes for pylock_lint-0.1.1.tar.gz
Algorithm Hash digest
SHA256 c12790401ad2b81e3c8f15f08c36de5dca709f8b2efaa1164d37dd80817f58f9
MD5 52e04744fd5f697b1c1102a5c36b0b4c
BLAKE2b-256 2e95ac25fd82c156dc07706471d43a75a72a159bd419ea10988d2f8d466f1273

See more details on using hashes here.

File details

Details for the file pylock_lint-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: pylock_lint-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 10.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.2

File hashes

Hashes for pylock_lint-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 929f19c7cc4c2227be8404a3466238f1c539a1d8dfa4f5c8a3fa713f471ed984
MD5 1b4a2b0f7478a38b6ecf629d99f03d92
BLAKE2b-256 8a5cf38a2d4365575fe467f36fe59cf0e006d3071efe10cd69ce32d326ebefaa

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