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
2 changes: 1 addition & 1 deletion .github/workflows/test_action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
name: Run action on test file in repo
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Generate version data using local action
uses: ./
with:
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/test_bench.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- uses: prefix-dev/setup-pixi@v0.9.5
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: prefix-dev/setup-pixi@5185adfbffb4bd703da3010310260805d89ebb11 # v0.9.6
with:
pixi-version: "v0.49.0"
pixi-version: "v0.69.0"
- run: |
pixi run test
29 changes: 29 additions & 0 deletions .github/workflows/update_schedule.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Update SPEC 0 schedule

on:
schedule:
- cron: "0 0 1 1,4,7,10 *" # Quarterly: 1st Jan, Apr, Jul, Oct
workflow_dispatch:

jobs:
update-schedule:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: prefix-dev/setup-pixi@5185adfbffb4bd703da3010310260805d89ebb11 # v0.9.6
with:
pixi-version: "v0.69.0"
- name: Generate schedule files
run: pixi run generate-schedules
- name: Publish schedule as release asset
env:
GH_TOKEN: ${{ github.token }}
run: |
QUARTER="Q$(( ($(date +%-m) - 1) / 3 + 1 ))"
YEAR=$(date +%Y)
TAG="schedule-${YEAR}-${QUARTER}"
gh release create "$TAG" schedule.json schedule.md \
--title "SPEC 0 Schedule ${YEAR}-${QUARTER}" \
--notes "Quarterly auto-generated SPEC 0 support schedule. Downloaded automatically by spec0-action."
10 changes: 5 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: cef0300fd0fc4d2a87a85fa2093c6b283ea36f4b # frozen: v5.0.0
rev: v6.0.0
hooks:
- id: check-added-large-files
- id: check-ast
Expand All @@ -15,19 +15,19 @@ repos:
- id: mixed-line-ending
- id: trailing-whitespace
- repo: https://github.com/rbubley/mirrors-prettier
rev: 787fb9f542b140ba0b2aced38e6a3e68021647a3 # frozen: v3.5.3
rev: v3.8.3
hooks:
- id: prettier
files: \.(css|html|md|yml|yaml|gql)
args: [--prose-wrap=preserve]
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: 971923581912ef60a6b70dbf0c3e9a39563c9d47 # frozen: v0.11.4
rev: v0.15.14
hooks:
- id: ruff
- id: ruff-check
args: ["--fix", "--show-fixes", "--exit-non-zero-on-fix"]
- id: ruff-format
- repo: https://github.com/codespell-project/codespell
rev: "63c8f8312b7559622c0d82815639671ae42132ac" # frozen: v2.4.1
rev: "v2.4.2"
hooks:
- id: codespell

Expand Down
43 changes: 28 additions & 15 deletions action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
name: "Update SPEC 0 dependencies"
description: "Update the lower bounds of Python dependencies covered by the Scientific Python SPEC 0 support schedule"
author: Scientific Python Developers

inputs:
target_branch:
description: "Target branch for the pull request"
Expand All @@ -15,7 +14,7 @@ inputs:
create_pr:
description: "Whether the action should open a PR or not. Set to false for dry-run/testing."
required: true
default: true
default: "true"
commit_msg:
description: "Commit message for the commit to update the versions. by default 'Drop support for unsupported packages conform SPEC 0'. has no effect if `create_pr` is set to false"
required: false
Expand All @@ -25,39 +24,53 @@ inputs:
required: false
default: "chore: Drop support for unsupported packages conform SPEC 0"
schedule_path:
description: "Path to the schedule.json file relative to the project root. If missing, it will be downloaded from the latest release of savente93/SPEC0-schedule"
default: "schedule.json"
description: "Path to the schedule.json file relative to the project root. If not provided, the schedule bundled with the action is used."
required: false
default: ""
token:
description: "GitHub token with repo permissions to create pull requests"
required: true

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)."
required: false
default: ""
runs:
using: "composite"
steps:
- name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Git
shell: bash
run: |
git config user.name "Scientific Python [bot]"
git config user.email "scientific-python@users.noreply.github.com"
- uses: prefix-dev/setup-pixi@1b2de7f3351f171c8b4dfeb558c639cb58ed4ec0 # v0.9.5
- uses: prefix-dev/setup-pixi@5185adfbffb4bd703da3010310260805d89ebb11 # v0.9.6
name: Setup Pixi
with:
pixi-version: v0.49.0
pixi-version: v0.69.0
manifest-path: ${{ github.action_path }}/pyproject.toml
- name: Fetch Schedule from release
- name: Fetch schedule from release
if: ${{ inputs.schedule_path == '' }}
uses: robinraju/release-downloader@28fc21f50d76778e7023361aa1f863e717d3d56f # v1.13
with:
repository: "savente93/SPEC0-schedule"
repository: "scientific-python/spec0-action"
latest: true
fileName: "schedule.json"
- name: Run update script
shell: bash
run: |
set -e
echo "Updating ${{ inputs.project_file_name }} using schedule ${{ inputs.schedule_path }}"
pixi run --manifest-path ${{ github.action_path }}/pyproject.toml update-dependencies "${{ github.workspace }}/${{ inputs.project_file_name }}" "${{ github.workspace }}/${{ inputs.schedule_path }}"
if [ -n "${{ inputs.schedule_path }}" ]; then
SCHEDULE_PATH="${{ github.workspace }}/${{ inputs.schedule_path }}"
else
SCHEDULE_PATH="${{ github.workspace }}/schedule.json"
fi
echo "Updating ${{ inputs.project_file_name }} using schedule $SCHEDULE_PATH"
UPDATE_ALL_FLAG=""
if [ -n "${{ inputs.update_all }}" ]; then
UPDATE_ALL_FLAG="--update-all ${{ inputs.update_all }}"
fi
pixi run --manifest-path ${{ github.action_path }}/pyproject.toml update-dependencies "${{ github.workspace }}/${{ inputs.project_file_name }}" "$SCHEDULE_PATH" $UPDATE_ALL_FLAG
- name: Changes
id: changes
shell: bash
Expand All @@ -73,7 +86,7 @@ runs:
if: ${{ fromJSON(inputs.create_pr) && fromJSON(steps.changes.outputs.changes_detected) }}
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
token: ${{ inputs.token }}
token: ${{ inputs.token || github.token }}
commit-message: ${{ inputs.commit_msg }}
title: ${{ inputs.pr_title }}
body: "This PR was created automatically"
Expand Down
80 changes: 48 additions & 32 deletions readme.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,21 @@
# SPEC 0 Versions Action

This repository contains a Github Action to update Python dependencies in your `pyproject.toml` such that they conform to the SPEC 0 support schedule.
[You can find this schedule here.](https://scientific-python.org/specs/spec-0000/)
A GitHub Action that updates the lower bounds of Python dependencies in `pyproject.toml` to conform to the [SPEC 0 support schedule](https://scientific-python.org/specs/spec-0000/).

## Using the action

### Example workflow

To use the action you can copy the yaml below, and paste it into `.github/workflows/update-spec0.yaml`.
Whenever the action is triggered it will open a PR in your repository that will update the dependencies of SPEC 0 to the new lower bound.
For this you will have to provide it with a PAT that has write permissions in the `contents` and `pull request` scopes.
[Please refer to the GitHub documentation for instructions on how to do this here.](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens)
Copy the yaml below into `.github/workflows/update-spec0.yaml`.
On each run the action opens a PR updating dependency lower bounds to match the current SPEC 0 schedule.

```yaml
name: Update SPEC 0 dependencies

on:
schedule:
# At 00:00 on day-of-month 3 in every 3rd month. (i.e. every quarter)
# Releases should happen on the second day of the quarter in savente93/SPEC0-schedule to
# avoid fence post errors, so allow one day as a buffer to avoid timing issues here as well.
- cron: "0 0 3 */3 *"
# On demand:
# 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:

permissions:
Expand All @@ -32,32 +26,54 @@ jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: scientific-python/spec0-action@v1.0.0
with:
token: ${{ secrets.GH_PAT }} # <- GH_PAT you will have to configure in the repo as a secret
- uses: scientific-python/spec0-action@v1
```

It should update any of the packages listed in the `dependency`, or `tool.pixi.*` tables.
For examples of before and after you can see [./tests/test_data/pyproject.toml](./tests/test_data/pyproject.toml) and [./tests/test_data/pyproject_updated.toml](./tests/test_data/pyproject_updated.toml) respectively.
Other tools are not yet supported, but we are open to feature requests.

The newest lower bounds will be downloaded from [https://github.com/scientific-python/spec0-action](https://github.com/scientific-python/spec0-action) but you should not have to worry about this.
No PAT required.
The built-in `GITHUB_TOKEN` is used by default as long as the workflow has `pull-requests: write` permission.

### Parameters

| Input | Required | Default | Description |
| ----------------- | -------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| token | yes | — | Personal access token with `contents` & `pull-request` scopes |
| project_file_name | no | `"pyproject.toml"` | File to update dependencies in |
| schedule_path | no | `"schedule.json"` | path to schedule json data. only relevant if you have it committed in your repo |
| target_branch | no | `"main"` | Branch to open PR against |
| create_pr | no | `true` | Open a PR with new versions |
| pr_title | no | `chore: Drop support for unsupported packages conform SPEC 0` | The title of the PR that will be opened |
| commit_msg | no | `chore: Drop support for unsupported packages conform SPEC 0` | Commit message of the commit to update the versions. |
| 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`) |

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).

## Limitations

1. Since this action simply parses the toml to do the upgrade and leaves any other bounds intact, it is possible that the environment of the PR becomes unsolvable.
For example if you have a numpy dependency like so: `numpy = ">=1.25.0,<2"` this will get updated in the PR to `numpy = ">=2.0.0,<2"` which is infeasible.
Keeping the resulting environment solvable is outside the scope of this action, so you might have to adjust them manually.
2. Currently only `pyproject.toml` is supported by this action, though other manifest files could be considered upon request.
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.
2. Only `pyproject.toml` is currently supported.

## 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.

```bash
git tag v1.x
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 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:

1. Go to **Actions → Update SPEC 0 schedule**
2. Click **Run workflow**

Subsequent releases are created automatically on the 1st of January, April, July, and October.
16 changes: 8 additions & 8 deletions run_spec0_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
parser = ArgumentParser(
description="A script to update your project dependencies to be in line with the scientific python SPEC 0 support schedule",
)

parser.add_argument(
"toml_path",
default="pyproject.toml",
Expand All @@ -18,24 +17,25 @@
default="schedule.json",
help="Path to the schedule json payload. defaults to 'schedule.json'",
)

parser.add_argument(
"--update-all",
type=float,
default=None,
metavar="YEARS",
help="Also update all non-SPEC0 dependencies to versions released within the last YEARS years (e.g., 2).",
)
args = parser.parse_args()

toml_path = Path(args.toml_path)
schedule_path = Path(args.schedule_path)

if not toml_path.exists():
raise ValueError(
f"{toml_path} was supplied as path to project file but it did not exist"
)

if not schedule_path.exists():
raise ValueError(
f"{schedule_path} was supplied as path to schedule file but it did not exist"
)

project_data = read_toml(toml_path)
schedule_data = read_schedule(schedule_path)
update_pyproject_toml(project_data, schedule_data)

update_pyproject_toml(project_data, schedule_data, update_all=args.update_all)
write_toml(toml_path, project_data)
Loading