Skip to content

Commit fa33c6c

Browse files
authored
Merge pull request #10 from savente93/main
2 parents e5e22da + fdda4dc commit fa33c6c

18 files changed

+623
-84
lines changed

.github/workflows/test_action.yaml

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,13 @@ on: [push, pull_request]
44
jobs:
55
generate_data:
66
runs-on: ubuntu-latest
7-
name: Generate version data
7+
name: Run action on test file in repo
88
steps:
99
- name: Checkout
1010
uses: actions/checkout@v5
1111
- name: Generate version data using local action
1212
uses: ./
13-
- name: Check file contents
14-
run: |
15-
printf "Contents of chart.md:\n"
16-
cat chart.md
17-
printf "\n\n"
18-
printf "Contents of schedule.json:\n"
19-
cat schedule.json
20-
printf "\n\n"
21-
printf "Contents of schedule.md:\n"
22-
cat schedule.md
23-
printf "\n\n"
24-
- name: Remove generated files
25-
run: |
26-
printf "Removing generated files...\n"
27-
rm -f chart.md schedule.json schedule.md
28-
ls -R
29-
- uses: actions/download-artifact@v6
30-
with:
31-
name: spec-zero-versions
32-
- name: Display structure of downloaded files
33-
run: ls -R
13+
with:
14+
project_file_name: tests/test_data/pyproject.toml
15+
create_pr: false
16+
schedule_path: tests/test_data/test_schedule.json

.github/workflows/test_bench.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
name: Run the update test suite
2+
on:
3+
push:
4+
branches: main
5+
pull_request:
6+
branches: main
7+
# On demand
8+
workflow_dispatch:
9+
10+
jobs:
11+
run-tests:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Checkout
15+
uses: actions/checkout@v5
16+
17+
- uses: prefix-dev/setup-pixi@v0.9.0
18+
with:
19+
pixi-version: "v0.49.0"
20+
- run: |
21+
pixi run test

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,6 @@
22
chart.md
33
schedule.md
44
schedule.json
5+
__pycache__
6+
*.pyc
7+
*.lock

action.yaml

Lines changed: 88 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,98 @@
1-
name: "Generate SPEC-0000 Data"
2-
description: "Based on the current SPEC 0 schedule, generate a tarball with the latest versions of all packages."
1+
name: "Update SPEC 0 dependencies"
2+
description: "Update the lower bounds of Python dependencies covered by the Scientific Python SPEC 0 support schedule"
3+
author: Scientific Python Developers
4+
inputs:
5+
target_branch:
6+
description: "Target branch for the pull request"
7+
required: true
8+
default: "main"
9+
project_file_name:
10+
description: "Path to the project file listing dependencies, relative to repository root. Defaults to 'pyproject.toml'. Currently only pyproject.toml is supported."
11+
required: true
12+
default: "pyproject.toml"
13+
create_pr:
14+
description: "Whether the action should open a PR or not. Set to false for dry-run/testing."
15+
required: true
16+
default: true
17+
commit_msg:
18+
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"
19+
required: false
20+
default: "chore: Drop support for unsupported packages conform SPEC 0"
21+
pr_title:
22+
description: "The title of the PR that will be opened. by default 'Drop support for unsupported packages conform SPEC 0'. has no effect if `create_pr` is set to false"
23+
required: false
24+
default: "chore: Drop support for unsupported packages conform SPEC 0"
25+
schedule_path:
26+
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"
27+
default: "schedule.json"
28+
token:
29+
description: "GitHub token with repo permissions to create pull requests"
30+
required: true
331

432
runs:
533
using: "composite"
634
steps:
7-
- name: Set up Python
8-
uses: actions/setup-python@v6
35+
- name: Checkout code
36+
uses: actions/checkout@v5
37+
38+
- name: Set up Git
39+
shell: bash
40+
run: |
41+
git config user.name "Scientific Python [bot]"
42+
git config user.email "scientific-python@users.noreply.github.com"
43+
44+
- uses: prefix-dev/setup-pixi@v0.9.0
45+
name: Setup Pixi
946
with:
10-
python-version: "3.13"
11-
- name: Install dependencies
47+
pixi-version: v0.49.0
48+
manifest-path: ${{ github.action_path }}/pyproject.toml
49+
50+
- name: Regenerate schedule file if necessary
51+
shell: bash
52+
env:
53+
SCHEDULE_FILE: ${{ inputs.schedule_path }}
54+
GH_TOKEN: ${{ inputs.token }}
55+
run: |
56+
set -e
57+
if [ ! -f "${{ github.workspace }}/$SCHEDULE_FILE" ]; then
58+
echo "Regenerating schedule.json..."
59+
pixi run generate-schedule --locked
60+
if diff -q schedule.json "${{ github.workspace }}/$SCHEDULE_FILE" >/dev/null; then
61+
echo "Source and destination have identical contents – nothing to move."
62+
else
63+
mv schedule.json "${{ github.workspace }}/$SCHEDULE_FILE"
64+
fi
65+
else
66+
echo "Schedule file already exists at $SCHEDULE_FILE"
67+
fi
68+
69+
70+
- name: Run update script
1271
shell: bash
1372
run: |
14-
pip install -r requirements.txt
15-
- name: Run spec_zero_versions.py
73+
set -e
74+
echo "Updating ${{inputs.project_file_name}} using schedule ${{inputs.schedule_path}}"
75+
pixi run --manifest-path ${{ github.action_path }}/pyproject.toml update-dependencies "${{ github.workspace }}/${{ inputs.project_file_name }}" "${{ github.workspace }}/${{ inputs.schedule_path }}"
76+
77+
- name: Show changes (dry-run)
78+
if: ${{ inputs.create_pr != 'true' }}
1679
shell: bash
1780
run: |
18-
python spec_zero_versions.py
19-
- name: Upload files as an artifact
20-
uses: actions/upload-artifact@v5
81+
echo "Dry run: showing changes that would be committed"
82+
git --no-pager diff ${{ inputs.project_file_name }}
83+
84+
- name: Create Pull Request
85+
if: ${{ inputs.create_pr == 'true' }}
86+
uses: peter-evans/create-pull-request@v7
2187
with:
22-
name: spec-zero-versions
23-
path: |
24-
schedule.json
25-
schedule.md
26-
chart.md
88+
token: ${{ inputs.token }}
89+
commit-message: ${{ inputs.commit_msg }}
90+
path: ${{ inputs.project_file_name }}
91+
title: ${{ inputs.pr_title }}
92+
body: "This PR was created automatically"
93+
base: ${{ inputs.target_branch }}
94+
branch: update-spec0-dependencies-${{ github.run_id }}
95+
96+
branding:
97+
icon: 'check-square'
98+
color: 'blue'

pyproject.toml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
[project]
2+
authors = [{ name = "Scientific Python Developers" }]
3+
name = "spec0-action"
4+
description = "Python code to update the lower bounds of Scientific Python libraries according to SPEC 0"
5+
requires-python = ">= 3.11"
6+
version = "1.0.0"
7+
dependencies = ["packaging>=25.0", "pandas>=2.3.3", "requests>=2.32.5,<3"]
8+
9+
[build-system]
10+
build-backend = "hatchling.build"
11+
requires = ["hatchling"]
12+
13+
[tool.pixi.workspace]
14+
channels = ["conda-forge"]
15+
platforms = ["linux-64"]
16+
17+
[tool.pixi.pypi-dependencies]
18+
spec0-action= { path = ".", editable = true }
19+
tomlkit = ">=0.13.3,<0.14"
20+
21+
[tool.pixi.tasks]
22+
update-dependencies = { cmd = ["python", "run_spec0_update.py"] }
23+
generate-schedule = { cmd = ["python", "spec0_versions.py"] }
24+
25+
[tool.pixi.feature.test.tasks]
26+
test = { cmd = ["pytest", "-vvv"] }
27+
28+
[tool.pixi.feature.test.dependencies]
29+
pytest = "*"
30+
31+
[tool.pixi.environments]
32+
test = ["test"]
33+

readme.md

Lines changed: 42 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,62 @@
1-
# SPEC-0 Versions Action
1+
# SPEC 0 Versions Action
22

3-
This repository contains a GitHub Action to generate the files required for the SPEC-0 documentation.
3+
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/)
44

55
## Using the action
66

7+
8+
### Example workflow
9+
10+
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).
11+
12+
713
```yaml
8-
name: Generate spec-zero data
14+
name: Update SPEC 0 dependencies
915

1016
on:
11-
push:
12-
branches:
13-
- main
17+
schedule:
18+
# At 00:00 on day-of-month 3 in every 3rd month. (i.e. every quarter)
19+
# Releases should happen on the second day of the quarter in savente93/SPEC0-schedule to
20+
# avoid fence post errors, so allow one day as a buffer to avoid timing issues here as well.
21+
- cron: "0 0 3 */3 *"
22+
# On demand:
23+
workflow_dispatch:
24+
25+
permissions:
26+
contents: write
27+
pull-requests: write
1428

1529
jobs:
16-
devstats-query:
30+
update:
1731
runs-on: ubuntu-latest
1832
steps:
19-
- uses: scientific-python/spec0-action@main
33+
- uses: savente93/update-spec0-dependencies@v1.0.0
34+
with:
35+
token: ${{ secrets.GH_PAT }} # <- GH_PAT you will have to configure in the repo as a secret
2036
```
2137
22-
The above would produce an artifact named `spec-zero-versions`, the following files: `schedule.yaml`,`schedule.md` and `chart.md`.
38+
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 I am open to feature requests.
2339

24-
To help projects stay compliant with SPEC-0, we provide a `schedule.json` file that can be used by CI systems to determine new version boundaries.
25-
The structure of the file is as follows:
40+
The newest lower bounds will be downloaded from [https://github.com/savente93/SPEC0-schedule](https://github.com/savente93/SPEC0-schedule) but you should not have to worry about this.
2641

27-
```json
28-
[
29-
{
30-
"start_date": "iso8601_timestamp",
31-
"packages": {
32-
"package_name": "version"
33-
}
34-
}
35-
]
36-
```
3742

38-
All information in the json file is in a string format that should be easy to use.
39-
The date is the first timestamp of the relevant quarter.
40-
Thus a workflow for using this file could be:
43+
### Parameters
4144

42-
1. Fetch `schedule.json`
43-
2. Determine maximum date that is smaller than current date
44-
3. Update packages listed with new minimum versions
45+
| Input | Required | Default | Description |
46+
| ------------------- | -------- | ------------------ | -------------------------------------------------------------------------------|
47+
| token | yes | — | Personal access token with `contents` & `pull-request` scopes |
48+
| project\_file\_name | no | `"pyproject.toml"` | File to update dependencies in |
49+
| schedule\_path | no | `"schedule.json"` | path to schedule json data. only relevant if you have it committed in your repo |
50+
| target\_branch | no | `"main"` | Branch to open PR against |
51+
| create_pr | no | `true` | Open a PR with new versions |
52+
| pr_title | no | `chore: Drop support for unsupported packages conform SPEC 0` | The title of the PR that will be opened |
53+
| commit_msg | no | `chore: Drop support for unsupported packages conform SPEC 0` | Commit message of the commit to update the versions. |
4554

46-
You can obtain the new versions you should set by using this `jq` expression:
4755

48-
```sh
49-
jq 'map(select(.start_date |fromdateiso8601 |tonumber < now))| sort_by("start_date") | reverse | .[0].packages ' schedule.json
50-
```
56+
## Limitations
57+
58+
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.
59+
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.
60+
Keeping the resulting environment solvable is outside the scope of this action, so you might have to adjust them manually.
61+
2. Currently only `pyproject.toml` is supported by this action, though other manifest files could be considered upon request.
5162

52-
If you use a package manager like pixi you could update the dependencies with a bash script like this (untested):
53-
54-
```sh
55-
curl -Ls -o schedule.json https://raw.githubusercontent.com/scientific-python/specs/main/spec-0000/schedule.json
56-
for line in $(jq 'map(select(.start_date |fromdateiso8601 |tonumber < now))| sort_by("start_date") | reverse | .[0].packages | to_entries | map(.key + ":" + .value)[]' --raw-output schedule.json); do
57-
package=$(echo "$line" | cut -d ':' -f 1)
58-
version=$(echo "$line" | cut -d ':' -f 2)
59-
if pixi list -x "^$package" &>/dev/null| grep "No packages" -q; then
60-
pixi add "$package>=$version";
61-
fi
62-
done
63-
```

requirements.txt

Lines changed: 0 additions & 3 deletions
This file was deleted.

run_spec0_update.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from spec0_action import update_pyproject_toml, read_toml, write_toml, read_schedule
2+
from pathlib import Path
3+
from argparse import ArgumentParser
4+
5+
6+
if __name__ == '__main__':
7+
parser = ArgumentParser(
8+
description='A script to update your project dependencies to be in line with the scientific python SPEC 0 support schedule',
9+
)
10+
11+
parser.add_argument('toml_path', default="pyproject.toml", help="Path to the project file that lists the dependencies. defaults to 'pyproject.toml'.")
12+
parser.add_argument('schedule_path', default="schedule.json", help="Path to the schedule json payload. defaults to 'schedule.json'")
13+
14+
args = parser.parse_args()
15+
16+
toml_path = Path(args.toml_path)
17+
schedule_path = Path(args.schedule_path)
18+
19+
if not toml_path.exists():
20+
raise ValueError(f"{toml_path} was supplied as path to project file but it did not exist")
21+
22+
if not schedule_path.exists():
23+
raise ValueError(f"{schedule_path} was supplied as path to schedule file but it did not exist")
24+
25+
project_data = read_toml(toml_path)
26+
schedule_data = read_schedule(schedule_path)
27+
update_pyproject_toml(project_data, schedule_data)
28+
29+
write_toml(toml_path, project_data)

0 commit comments

Comments
 (0)