Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions .github/workflows/test_action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,14 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
excluded_packages: ["", "numpy,\nscikit-learn"]
name: "Run action with exclusions: ${{ matrix.excluded_packages }}"
include:
- excluded_packages: ""
spec0_support_years: ""
- excluded_packages: "numpy,\nscikit-learn"
spec0_support_years: ""
- excluded_packages: "ipython matplotlib networkx numpy pandas scikit-image scikit-learn scipy xarray zarr python"
spec0_support_years: "3"
name: "Run action with support years: '${{ matrix.spec0_support_years }}', exclusions: '${{ matrix.excluded_packages }}'"
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand All @@ -31,10 +37,16 @@ jobs:
create_pr: false
schedule_path: tests/test_data/test_schedule.json
excluded_packages: ${{ matrix.excluded_packages }}
spec0_support_years: ${{ matrix.spec0_support_years }}
- name: Check dependency updates
env:
EXCLUDED_PACKAGES: ${{ matrix.excluded_packages }}
SPEC0_SUPPORT_YEARS: ${{ matrix.spec0_support_years }}
run: |
if [ -n "$SPEC0_SUPPORT_YEARS" ]; then
git diff --exit-code -- tests/test_data/pyproject.toml
exit 0
fi
git diff -U0 -- tests/test_data/pyproject.toml > changes.diff
grep -q '^+.*pandas' changes.diff
if [ -n "$EXCLUDED_PACKAGES" ]; then
Expand Down
16 changes: 12 additions & 4 deletions action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,17 @@ inputs:
description: "GitHub token with pull-requests write permission to create pull requests. Defaults to the built-in GITHUB_TOKEN."
required: false
update_all:
description: "If set, also update all non-SPEC0 dependencies to versions released within the last N years (e.g., 2)."
description: "If set, update PEP dependencies without an applicable schedule floor or explicit core-package support period to versions first released within the last N years (e.g., 2)."
required: false
default: ""
excluded_packages:
description: "Comma- or whitespace-separated package names to leave unchanged, including with update_all. Use python to exclude Python requirements."
required: false
default: ""
spec0_support_years:
description: "If set, compute SPEC 0 core-package floors from PyPI using this many years of support (e.g., 3). Python still follows the supplied schedule; exclusions take precedence."
required: false
default: ""
runs:
using: "composite"
steps:
Expand Down Expand Up @@ -70,6 +74,7 @@ runs:
PROJECT_FILE_NAME: ${{ inputs.project_file_name }}
SCHEDULE_INPUT: ${{ inputs.schedule_path }}
UPDATE_ALL: ${{ inputs.update_all }}
SPEC0_SUPPORT_YEARS: ${{ inputs.spec0_support_years }}
EXCLUDED_PACKAGES: ${{ inputs.excluded_packages }}
run: |
set -e
Expand All @@ -79,11 +84,14 @@ runs:
SCHEDULE_PATH="${GITHUB_WORKSPACE}/schedule.json"
fi
echo "Updating ${PROJECT_FILE_NAME} using schedule ${SCHEDULE_PATH}"
UPDATE_ALL_ARGS=()
UPDATE_ARGS=()
if [ -n "$UPDATE_ALL" ]; then
UPDATE_ALL_ARGS=(--update-all "$UPDATE_ALL")
UPDATE_ARGS+=(--update-all "$UPDATE_ALL")
fi
if [ -n "$SPEC0_SUPPORT_YEARS" ]; then
UPDATE_ARGS+=(--spec0-support-years "$SPEC0_SUPPORT_YEARS")
fi
pixi run --manifest-path "${GITHUB_ACTION_PATH}/pyproject.toml" update-dependencies "${GITHUB_WORKSPACE}/${PROJECT_FILE_NAME}" "$SCHEDULE_PATH" --excluded-packages "$EXCLUDED_PACKAGES" "${UPDATE_ALL_ARGS[@]}"
pixi run --manifest-path "${GITHUB_ACTION_PATH}/pyproject.toml" update-dependencies "${GITHUB_WORKSPACE}/${PROJECT_FILE_NAME}" "$SCHEDULE_PATH" --excluded-packages "$EXCLUDED_PACKAGES" "${UPDATE_ARGS[@]}"
- name: Changes
id: changes
shell: bash
Expand Down
69 changes: 51 additions & 18 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ name: Update SPEC 0 dependencies

on:
schedule:
# Day 3 of each quarter. Allows one day buffer after the quarterly schedule release on day 1
# Day 3 of each quarter.
Allows one day buffer after the quarterly schedule release on day 1
- cron: "0 0 3 1,4,7,10 *"
workflow_dispatch:

Expand All @@ -40,25 +41,27 @@ The built-in `GITHUB_TOKEN` is used by default as long as the workflow has `pull

### Parameters

| Input | Required | Default | Description |
| ------------------- | -------- | ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `token` | no | `GITHUB_TOKEN` | Token with `pull-requests: write` permission to open PRs |
| `project_file_name` | no | `pyproject.toml` | Path to the file to update, relative to repository root |
| `schedule_path` | no | — | Path to a custom `schedule.json`, relative to repository root. Uses the latest release if unset |
| `target_branch` | no | `main` | Branch to open the PR against |
| `create_pr` | no | `true` | Set to `false` for a dry run |
| `pr_title` | no | `chore: Drop support for unsupported packages conform SPEC 0` | Title of the opened PR |
| `commit_msg` | no | `chore: Drop support for unsupported packages conform SPEC 0` | Commit message for the version update commit |
| `update_all` | no | — | If set to a number N, also update non-SPEC0 dependencies to versions released within the last N years (e.g. `2`) |
| `excluded_packages` | no | — | Comma- or whitespace-separated package names to leave unchanged, including with `update_all` |
| Input | Required | Default | Description |
| --------------------- | -------- | ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `token` | no | `GITHUB_TOKEN` | Token with `pull-requests: write` permission to open PRs |
| `project_file_name` | no | `pyproject.toml` | Path to the file to update, relative to repository root |
| `schedule_path` | no | — | Path to a custom `schedule.json`, relative to repository root. Uses the latest release if unset |
| `target_branch` | no | `main` | Branch to open the PR against |
| `create_pr` | no | `true` | Set to `false` for a dry run |
| `pr_title` | no | `chore: Drop support for unsupported packages conform SPEC 0` | Title of the opened PR |
| `commit_msg` | no | `chore: Drop support for unsupported packages conform SPEC 0` | Commit message for the version update commit |
| `update_all` | no | — | If set to N years, update PEP dependencies without a schedule floor or custom core-package policy to the oldest stable version first released within that window |
| `spec0_support_years` | no | — | Override the support period for SPEC 0 core packages using current PyPI release history (e.g. `3`); Python still follows the supplied schedule |
| `excluded_packages` | no | — | Comma- or whitespace-separated package names to leave unchanged, including with `update_all` |

For examples of before/after see [tests/test_data/pyproject.toml](./tests/test_data/pyproject.toml) and [tests/test_data/pyproject_updated.toml](./tests/test_data/pyproject_updated.toml).

SPEC 0 packages include `ipython`, `matplotlib`, `networkx`, `numpy`, `pandas`, `scikit-image`, `scikit-learn`, `scipy`, `xarray`, and `zarr`.

### Excluding packages

To keep a package's lower bound as-is, for example to stay compatible with NumPy 1.26 while everything else updates, list it in `excluded_packages` before the action raises its bound (bounds are never lowered). Separate names with commas or whitespace; `python` excludes the Python requirement:
To keep a package's lower bound as-is, for example to stay compatible with NumPy 1.26 while everything else updates, list it in `excluded_packages` before the action raises its bound (bounds are never lowered).
Separate names with commas or whitespace; `python` excludes the Python requirement:

```yaml
with:
Expand All @@ -68,18 +71,46 @@ with:
python
```

Exclusions win over the schedule and `update_all`. The CLI takes the same value via `--excluded-packages`.
Exclusions win over the schedule, `spec0_support_years`, and `update_all`, and excluded packages cause no PyPI lookup.
The CLI takes the same value via `--excluded-packages`.

### Changing the core-package support period

The supplied schedule gives SPEC 0 packages two years of support.
To use three years for those packages while updating other dependencies independently:

```yaml
with:
spec0_support_years: 3
update_all: 2
```

For each release (`X.Y.0`; pre-, post-, and patch releases are ignored), support ends at the start of the quarter containing its release date plus the period, and the floor moves to the next release.
The schedule generator and custom support periods use the earliest PyPI upload of a release, whether a source distribution or wheel; later uploads do not reset its age.
Floors come from the PyPI release history, so any explicit value, can differ from a published schedule snapshot.
Python always follows the schedule.

Precedence is `excluded_packages`, then `spec0_support_years` for SPEC 0 packages, then the schedule, then `update_all` for the remaining PEP dependencies.
If PyPI cannot be reached, the affected dependency stays unchanged with a warning rather than falling back to the schedule or `update_all`.

The CLI takes `--spec0-support-years 3` and the Python API `spec0_support_years=3`.
Extras such as `xarray[io]>=2026.5.1` and environment markers are preserved when bounds change.

## Limitations

1. The action only tightens lower bounds and leaves upper bounds untouched. An update can produce an unsolvable environment — for example `numpy = ">=1.25.0,<2"` becomes `numpy = ">=2.0.0,<2"`. Keeping the environment solvable is out of scope; adjust upper bounds manually if needed.
1. The action only tightens lower bounds and leaves upper bounds untouched.
A proposed floor that conflicts with an existing constraint is skipped; for example, `numpy = ">=1.25.0,<2"` stays unchanged when the proposed floor is `2.0.0`.
It does not solve the full dependency graph or guarantee a compatible environment.
2. Only `pyproject.toml` is currently supported.
This includes Pixi tables within the file; standalone Conda environment files are unsupported.
Conda-only version expressions such as `>=1.26|>=2.0` are left unchanged.

## Maintainer notes

### Releasing a new action version

Action versions are **git tags only**, do not create a GitHub Release for them. GitHub Releases in this repository are reserved for the quarterly schedule data.
Action versions are **git tags only**, do not create a GitHub Release for them.
GitHub Releases in this repository are reserved for the quarterly schedule data.

```bash
git tag v1.x
Expand All @@ -88,13 +119,15 @@ git push origin v1.x

### Schedule releases

The SPEC 0 schedule (`schedule.json` and `schedule.md`) is published as a GitHub Release quarterly by the [Update SPEC 0 schedule](./.github/workflows/update_schedule.yml) workflow. Releases are tagged `schedule-YYYY-QN` (e.g. `schedule-2026-Q2`).
The SPEC 0 schedule (`schedule.json` and `schedule.md`) is published as a GitHub Release quarterly by the [Update SPEC 0 schedule](./.github/workflows/update_schedule.yml) workflow.
Releases are tagged `schedule-YYYY-QN` (e.g. `schedule-2026-Q2`).

The action always fetches `schedule.json` from the **latest** GitHub Release in this repository, which will always be a schedule release as long as action versions are never published as releases.

#### Bootstrap

Before the first quarterly schedule release exists, the action will fail. To create the initial release, trigger the workflow manually:
Before the first quarterly schedule release exists, the action will fail.
To create the initial release, trigger the workflow manually:

1. Go to **Actions → Update SPEC 0 schedule**
2. Click **Run workflow**
Expand Down
10 changes: 9 additions & 1 deletion run_spec0_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,14 @@
type=float,
default=None,
metavar="YEARS",
help="Also update all non-SPEC0 dependencies to versions released within the last YEARS years (e.g., 2).",
help="Update PEP dependencies without an applicable schedule floor or explicit core-package support period to versions first released within the last YEARS years (e.g., 2).",
)
parser.add_argument(
"--spec0-support-years",
type=float,
default=None,
metavar="YEARS",
help="Compute SPEC 0 core-package floors from PyPI using YEARS of support; Python still follows the supplied schedule.",
)
parser.add_argument(
"--excluded-packages",
Expand All @@ -47,6 +54,7 @@
project_data,
schedule_data,
update_all=args.update_all,
spec0_support_years=args.spec0_support_years,
excluded_packages=args.excluded_packages.replace(",", " ").split(),
)
write_toml(toml_path, project_data)
Loading