| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
Raises (or refreshes) an upgrade pull request in one repository that depends on a Python package: it moves the version specifier, relocks, and opens or updates the PR.
This is the second half of a publisher-side dependency fan-out. Pair it with python-discover-dependents, which finds the repositories worth bumping — so a release reaches its consumers without anyone opening five PRs by hand.
It is deliberately mechanical: it moves the version specifier and relocks. A release that changes the API still needs a human to migrate the call sites. The PR is the starting point for that work, not a substitute for it.
If none of that applies to you — a public package on PyPI, no hand-written migration commits on the bump branch — reach for Dependabot first. It is less machinery.
Two steps, because the fan-out is a job matrix and only the caller can declare one.
name: Bump dependents
on:
release:
types: [released]
workflow_dispatch:
permissions:
contents: read
# Never cancel: two runs racing on the same branch would have the second push rejected as
# non-fast-forward, and a run cancelled mid-fan-out leaves the consumers it had not reached on
# the previous version. Queueing means each release appends its commit in order.
concurrency:
group: bump-dependents
cancel-in-progress: false
jobs:
discover:
runs-on: ubuntu-24.04
outputs:
version: ${{ steps.discover.outputs.version }}
repositories: ${{ steps.discover.outputs.repositories }}
has-dependents: ${{ steps.discover.outputs.has-dependents }}
steps:
- uses: CVector-Energy/python-discover-dependents@v1
id: discover
with:
package: my-package
app-id: ${{ vars.APP_ID }}
app-private-key: ${{ secrets.APP_PRIVATE_KEY }}
bump:
needs: discover
if: needs.discover.outputs.has-dependents == 'true'
runs-on: ubuntu-24.04
strategy:
fail-fast: false # one broken consumer must not stop the others getting their PR
max-parallel: 4
matrix:
repository: ${{ fromJSON(needs.discover.outputs.repositories) }}
steps:
- uses: CVector-Energy/python-bump-dependents@v1
with:
package: my-package
repository: ${{ matrix.repository }}
version: ${{ needs.discover.outputs.version }}
app-id: ${{ vars.APP_ID }}
app-private-key: ${{ secrets.APP_PRIVATE_KEY }}
reviewer: ${{ github.event.release.author.login || github.actor }}Publishing usually runs off the same release event, so discovery can start before the wheel is on the index. wait-command is polled until it exits zero; it runs with PACKAGE and VERSION in the environment. pre-lock-command runs just before uv lock and can export credentials to $GITHUB_ENV.
Configure cloud credentials in the caller — a composite action cannot host another action's steps mid-sequence.
discover:
permissions:
contents: read
id-token: write
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::000000000000:role/MyPackage-Bump
aws-region: us-east-1
- uses: CVector-Energy/python-discover-dependents@v1
id: discover
with:
package: my-package
app-id: ${{ vars.APP_ID }}
app-private-key: ${{ secrets.APP_PRIVATE_KEY }}
wait-command: |
aws codeartifact describe-package-version \
--domain my-domain --domain-owner 000000000000 \
--repository internal --format pypi \
--package "$PACKAGE" --package-version "$VERSION" >/dev/null 2>&1 - uses: CVector-Energy/python-bump-dependents@v1
with:
# ...
pre-lock-command: |
TOKEN=$(aws codeartifact get-authorization-token \
--domain my-domain --domain-owner 000000000000 \
--region us-east-1 --query authorizationToken --output text)
echo "::add-mask::${TOKEN}"
echo "UV_INDEX_MY_INDEX_USERNAME=aws" >> "$GITHUB_ENV"
echo "UV_INDEX_MY_INDEX_PASSWORD=${TOKEN}" >> "$GITHUB_ENV"Give the bump role read-only index access, not publish. The relock resolves the dependents' full dependency trees, and uv builds any sdist whose metadata it cannot read statically — which runs third-party code in the same job that holds the registry token.
| Input | Required | Default | Description |
|---|---|---|---|
| package | yes | Package name as it appears in the dependent's pyproject.toml. | |
| repository | yes | Bare name of the dependent to bump. | |
| version | yes | Version to roll out, no leading v. | |
| app-id | yes | Client ID of a GitHub App installed on the dependent. | |
| app-private-key | yes | Private key of that app. | |
| owner | no | calling repo's owner | Org that owns the dependent. |
| branch | no | deps/<package> | Branch to raise the PR from. |
| committer-name | no | github-actions[bot] | Commit author name. |
| committer-email | no | the bot's noreply | Commit author email. |
| reviewer | no | Login to request review from; best-effort. | |
| labels | no | Comma-separated labels for the PR. | |
| pre-lock-command | no | Shell snippet run before uv lock. | |
| setup-uv | no | true | Install uv. |
| Output | Description |
|---|---|
| relevant | "true" if the repository actually resolves the package in a uv.lock. |
| pushed | "true" if a commit was pushed to the bump branch. |
| pull-request-number | Number of the PR opened or updated, empty if none. |
A repository that does not really depend on the package is skipped. Discovery matches text, so a pyproject.toml naming the package in a comment can land in the matrix. This action checks uv.lock for a resolved name = "<package>" entry first and reports relevant: false if it isn't there — the resolver's own answer, rather than the same text the search matched.
Unconstrained dependencies are left alone. A bare "my-package" already resolves to the newest release; pinning it would be a policy change nobody asked for. The relock still runs for those repositories, so their lockfile moves even though the manifest doesn't.
Names are matched per PEP 503. A consumer depending on my_package is bumped by a release of my-package.
The bump branch does not follow its base. A later release appends another commit rather than rebasing, so unmerged work on the PR is kept. Rebase or merge it as you would any other long-lived PR.
Relock runs twice. A monorepo keeps one lock per project, and a lock records the requirements of its path dependencies, so raising a shared library's floor leaves dependent locks stale regardless of the order the first pass ran in. Two passes settle that without modelling the dependency graph.
A partial relock failure still raises the PR, with a warning listing the projects that need relocking by hand. Every project failing stops the run instead.
uv run --group dev pytest
shellcheck scripts/*.shThe logic that would be painful to debug from a failed fan-out lives in scripts/ and is tested directly: rewrite_specifier.py (which specifiers move and which don't) and relock.sh (the failure policy, driven through a stub uv).
The rest of the Python release pipeline these actions are built for:
Built on:
Alternatives worth considering before this one — see Why not Dependabot, Renovate, or create-pull-request? for when each is the better fit:
MIT
| Back | FazBrowse Home | New Git URL |