Skip to main content

pl_vendor

pl_vendor copies Git repositories or selected upstream directories into another repository as ordinary files and records their exact commits in a deterministic YAML lockfile. It supports optional downstream patches and can verify that checked-in vendor trees still match their locked upstream commits.

Requirements

  • Python 3.13 or newer
  • Git 2.30 or newer

Rich provides terminal progress and styling, while TOMLKit preserves unrelated content and comments when pl-vendor edits pyproject.toml.

Every command checks the installed Git version before reading or changing vendored trees.

Configuration

Initialize a repository before adding packages:

pl-vendor init

This adds [tool.pl-vendor] to pyproject.toml with a required-version requirement for the running minor version (for example, ^0.4). It preserves unrelated content and comments, creates pyproject.toml when absent, and refuses to replace an existing [tool.pl-vendor] table. This is safe to run immediately after uv add --dev pl_vendor, while pyproject.toml still has uncommitted package changes.

Add a package interactively:

pl-vendor add

Or provide its settings as arguments (the path defaults to ./.plvendor/NAME and an omitted branch tracks the repository's default branch):

pl-vendor add example https://github.com/example/example.git

The default destination is relative to the project root that uv would discover from the current directory. In a workspace, this places it under the workspace root. This discovery reads local pyproject.toml files and does not require the uv executable.

Packages can instead be pinned to an exact commit. Use --subdirectory to export only one directory from an upstream repository:

pl-vendor add prairielearn-schemas \
  https://github.com/PrairieLearn/PrairieLearn.git \
  .prairielearn/schemas \
  --commit 2c60f70a0ef817a2dc33fe15cb8e56de5035d6ed \
  --subdirectory apps/prairielearn/src/schemas/schemas

This creates or updates pyproject.toml at the root of the consuming Git repository, fetches the package, and records its revision. The resulting manifest looks like:

[tool.pl-vendor]
required-version = "^0.4"

[tool.pl-vendor.packages.example]
path = "./.plvendor/example"
url = "https://github.com/example/example.git"

The required-version requirement uses caret semantics. ^0.4 accepts compatible 0.4.x versions of pl-vendor and rejects versions from another minor release. Version 0.4 reads vendoring configuration only from this pyproject.toml namespace; the former standalone plvendor.toml format is not loaded.

A commit-pinned subdirectory package looks like this:

[tool.pl-vendor.packages.prairielearn-schemas]
path = ".prairielearn/schemas"
url = "https://github.com/PrairieLearn/PrairieLearn.git"
commit = "2c60f70a0ef817a2dc33fe15cb8e56de5035d6ed"
subdirectory = "apps/prairielearn/src/schemas/schemas"

Packages track the repository's default branch when both branch and commit are omitted. Set branch to track a specific branch, or commit to pin a complete lowercase 40-character Git SHA; branch and commit are mutually exclusive. subdirectory is relative to the upstream repository and defaults to its root.

The generated plvendor-lock.yaml records the resolved commit:

lockfileVersion: 2

packages:
  example: '0123456789abcdef0123456789abcdef01234567'

Optional patches live at .vendor-patches/<package-name>.patch and are applied after selecting the upstream subdirectory, so patch paths are relative to the installed vendored tree.

Commands

pl-vendor help
pl-vendor version
pl-vendor version example
pl-vendor version first second
pl-vendor init
pl-vendor self-update
pl-vendor update
pl-vendor update example
pl-vendor update example --force
pl-vendor add example https://github.com/example/example.git --branch release
pl-vendor remove example
pl-vendor remove first second
pl-vendor lock
pl-vendor lock --check
pl-vendor lock --upgrade
pl-vendor lock --upgrade-package example
pl-vendor sync
pl-vendor sync example
pl-vendor sync example --force
pl-vendor check
pl-vendor check example second
pl-vendor require-latest
pl-vendor require-latest example second
pl-vendor require-latest --exact-revision
pl-vendor verify

pl-vendor version prints the installed tool version. When package names are provided, it instead prints their eight-character locked revisions, one per line in the requested order.

pl-vendor self-update updates the dependency-groups.dev entry to require at least the running version and updates [tool.pl-vendor].required-version to that minor release. When uv is available on PATH, it asks whether to run uv sync. Answer never to add run-uv-sync = false to the base [tool.pl-vendor] table and skip this prompt on future self-updates.

Like uv add and uv remove, pl-vendor add and pl-vendor remove update the manifest, lockfile, and installed files together. Remove accepts one or more package names and deletes their complete managed trees, including local or generated content. Removing the final package removes the [tool.pl-vendor] configuration and lockfile while preserving the rest of pyproject.toml; downstream patches are retained.

pl-vendor lock fills missing lock entries while preferring existing revisions. Pass --upgrade or --upgrade-package NAME to explicitly advance branch packages; commit-pinned packages never advance. A commit pin is authoritative, so lock reconciles a stale lock entry to the manifest without contacting the remote. This does not modify vendored files.

pl-vendor update remains a convenience command that advances branch packages and syncs one or all packages in one step. For a pinned package, it installs the manifest commit and updates the lockfile to that exact value without rewriting the manifest. If the manifest exists but the lockfile does not, update bootstraps a complete lockfile from the manifest instead of requiring a separate lock step. Because the lockfile is generated state, update also replaces it when it has uncommitted changes; uncommitted pyproject.toml changes remain protected.

When a lockfile exists, update and sync compare each selected vendored tree with its locked, patched upstream contents and refuse to overwrite any differences. This protects edits whether or not they are committed to the consuming repository. Pass --force to replace those edits explicitly. Destinations that already exactly match the requested upstream trees are left untouched; when neither the selected trees nor the lockfile changes, the command reports No changes were made.

pl-vendor sync does not update the lockfile: it replaces the selected vendored trees with their exact locked revisions and reapplies downstream patches. As with uv's default exact sync, generated files inside those managed trees are removed. Replacing other local differences requires --force.

During update, sync, and verify, entries from the same source share a temporary blobless Git object store. Each revision is checked out once, using a sparse worktree containing only the configured upstream subdirectories unless an entry requests the repository root. This avoids fetching unrelated file contents and reuses unchanged objects when entries select different commits from the same source. Branch lookups are also batched per source URL.

Fetches from different sources run concurrently, with a progress bar for each source revision in an interactive terminal. Revisions from one source run in sequence so they can safely share their Git object store. The concurrency limit defaults to 50, matching uv's download limit. Set concurrent-downloads under [tool.pl-vendor] in the consuming project's pyproject.toml to change it.

Like uv, pl-vendor writes human-readable operation status to standard error and keeps standard output empty. In an interactive terminal, add, update, sync, and verify show transient fetch and install progress. Redirected output retains only deterministic phase summaries and installed package records:

Fetched 2 source revisions in 1.24s
Installed 3 packages in 84ms
 + first abcdef12 -> vendor/first
 ~ second 12345678 -> vendor/second (patched)
 ~ third 87654321 -> vendor/third

Fetch counts represent unique source URL and revision pairs, while install counts represent destinations that were created or replaced. Unchanged destinations are not included in the install summary or package records. + marks a newly created destination and ~ marks an existing destination that was replaced. Revisions are shortened to eight characters for display; the lockfile remains authoritative for complete revisions.

pl-vendor check [PACKAGE ...] reports selected commit-pinned packages as pinned and checks selected branch packages for newer revisions. Use pl-vendor require-latest [PACKAGE ...] in CI when pins should be forbidden: it fails for every selected commit-pinned package and for every selected branch package whose locked, patched vendored contents differ from the branch head. An upstream commit that changes only files outside a package's configured subdirectory does not fail this check, and the lockfile remains unchanged. Pass --exact-revision to require the locked commit SHA to match the branch head, even when the vendored contents are unchanged. With no package names, either command checks all packages.

Repository commands discover the consuming repository from the current directory. Pass --root PATH after the command to operate on a different repository. Pass --no-progress to suppress transient animation without hiding the durable summaries. --color auto|always|never controls styling; auto is the default and also honors standard terminal color environment variables such as NO_COLOR.

Development

This repository uses uv 0.9 or newer to manage its development environment:

uv sync --dev
make test
make format
make build

The test suite is kept in the top-level tests directory so it is separate from the installable pl_vendor package.

To run a disposable walkthrough of the main workflow backed by local Git repositories:

./scripts/demo.sh

Pass --keep to retain the generated upstream and consumer repositories for inspection.

To publish a release, start from a clean working tree and run:

make publish-version VERSION=0.2.0

This runs the test suite and static checks, updates pyproject.toml and uv.lock, builds the distributions, creates a release commit and annotated v0.2.0 tag, and atomically pushes both to origin. The tag starts the PyPI publishing workflow. Use REMOTE=name to publish through a different Git remote. The requested version must be semantically newer than the current package version and all local or remote release tags.

If pyproject.toml and uv.lock already contain the target version in an unpushed commit, publish that commit without creating a redundant release commit:

make publish-version VERSION=0.4.0 ALLOW_PREBUMPED_VERSION=1

This bypass requires the requested version to equal the current project version, the release tag to be absent locally and remotely, and the local branch to be ahead of its remote branch.

Release files for pl_vendor 0.4.4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pl_vendor 0.4.4
File Size Uploaded
pl_vendor-0.4.4.tar.gz 51.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pl_vendor 0.4.4
File Interpreter ABI Platform
pl_vendor-0.4.4-py3-none-any.whl Python 3 none any Details

Total release size: 83.1 kB

Release files / pl_vendor-0.4.4.tar.gz

Download URL pl_vendor-0.4.4.tar.gz
Size 51.2 kB
Tags Source
SHA-256 checksum
How to use checksums
c54251a59cbea54562ae40d3b625b1d624f293e68a43ae9a689b03e806ca3fbc
BLAKE2b-256 checksum
How to use checksums
d0e28a7dec94fcc4e9768873f5682cdf62cd5b7d8aa2595bd58962594bbecc63
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.

Transparency log

Release files / pl_vendor-0.4.4-py3-none-any.whl

Download URL pl_vendor-0.4.4-py3-none-any.whl
Size 31.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
cbae89ef709b438305483dfda72a5679deb0cccc3dad34afc9f606ab2fb979ff
BLAKE2b-256 checksum
How to use checksums
ecefd46d5b9cddced05c20c0309e82f2a79a0e09b1920f6781a49659991ee864
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.

Transparency log

Release history Release notifications | RSS feed

0.4.7

2 release files

0.4.6

2 release files

0.4.5

2 release files

This release

0.4.4 This release

2 release files

0.4.3

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.4

2 release files

0.3.3

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page