diff --git a/.github/workflows/release_schedule.yaml b/.github/workflows/release_schedule.yaml new file mode 100644 index 0000000..dabdb70 --- /dev/null +++ b/.github/workflows/release_schedule.yaml @@ -0,0 +1,71 @@ +name: Generate release schedule artifacts +on: + schedule: + # At 00:00 on day-of-month 2 in every 3rd month. (i.e. every quarter) + # 2nd is chosen to avoid fencepost errors + - cron: "0 0 2 */3 *" + push: + branches: + - "main" + # On demand + workflow_dispatch: + +jobs: + create-artifacts: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + # We're going to make a tag that we can we release so we'll need the full history for that + fetch-depth: 0 + + - uses: prefix-dev/setup-pixi@v0.9.2 + with: + pixi-version: "v0.49.0" + + - name: Generate artifacts + run: | + pixi run generate-schedule --locked + + - name: Test json artifact + run: | + cat schedule.json | jq + + - name: setup git + run: | + # git will complain if we don't do this first + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: determine tag name + id: tag_name + run: | + echo "TAG_NAME=$(date '+%Y-Q%q')" >> "$GITHUB_OUTPUT" + - name: Commit artifacts and create tag + env: + GH_TOKEN: ${{ secrets.RELEASE_PAT }} + run: | + git add -f schedule.md chart.md schedule.json + git commit -m "generate schedule for ${{ steps.tag_name.outputs.TAG_NAME }} release" || echo "No changes to commit" + git tag ${{ steps.tag_name.outputs.TAG_NAME }} + git push origin main + git push origin tag ${{ steps.tag_name.outputs.TAG_NAME }} + + - name: Publish github release + uses: softprops/action-gh-release@v2 + env: + GH_TOKEN: ${{ secrets.RELEASE_PAT }} + with: + generate_release_notes: true + tag_name: ${{ steps.tag_name.outputs.TAG_NAME }} + make_latest: true + files: | + schedule.md + chart.md + schedule.json + +permissions: + contents: write diff --git a/.github/workflows/test_action.yaml b/.github/workflows/test_action.yaml index 20f5075..df1f64f 100644 --- a/.github/workflows/test_action.yaml +++ b/.github/workflows/test_action.yaml @@ -1,5 +1,18 @@ name: Test Action -on: [push, pull_request] +on: + push: + branches: + - "main" + - "*.*" + tags: + - "v*" + pull_request: + # Allow manual runs through the web UI + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: generate_data: diff --git a/.github/workflows/test_bench.yml b/.github/workflows/test_bench.yml index 824679a..79e2b28 100644 --- a/.github/workflows/test_bench.yml +++ b/.github/workflows/test_bench.yml @@ -1,10 +1,13 @@ name: Run the update test suite on: push: - branches: main + branches: + - "main" + - "*.*" + tags: + - "v*" pull_request: - branches: main - # On demand + # Allow manual runs through the web UI workflow_dispatch: jobs: @@ -13,7 +16,6 @@ jobs: steps: - name: Checkout uses: actions/checkout@v6 - - uses: prefix-dev/setup-pixi@v0.9.3 with: pixi-version: "v0.49.0" diff --git a/action.yaml b/action.yaml index 120ad0d..3cfa502 100644 --- a/action.yaml +++ b/action.yaml @@ -1,6 +1,8 @@ +# action.yml 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" @@ -34,63 +36,51 @@ runs: steps: - name: Checkout code uses: actions/checkout@v6 - - 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@v0.9.3 name: Setup Pixi with: pixi-version: v0.49.0 manifest-path: ${{ github.action_path }}/pyproject.toml - - - name: Regenerate schedule file if necessary - shell: bash - env: - SCHEDULE_FILE: ${{ inputs.schedule_path }} - GH_TOKEN: ${{ inputs.token }} - run: | - set -e - if [ ! -f "${{ github.workspace }}/$SCHEDULE_FILE" ]; then - echo "Regenerating schedule.json..." - pixi run generate-schedule --locked - if diff -q schedule.json "${{ github.workspace }}/$SCHEDULE_FILE" >/dev/null; then - echo "Source and destination have identical contents – nothing to move." - else - mv schedule.json "${{ github.workspace }}/$SCHEDULE_FILE" - fi - else - echo "Schedule file already exists at $SCHEDULE_FILE" - fi - + - name: Fetch Schedule from release + uses: robinraju/release-downloader@v1.3 + with: + repository: "savente93/SPEC0-schedule" + 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 }}" - - - name: Show changes (dry-run) - if: ${{ inputs.create_pr != 'true' }} + 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 }}" + - name: Changes + id: changes shell: bash run: | - echo "Dry run: showing changes that would be committed" + echo "Showing changes that would be committed" git --no-pager diff ${{ inputs.project_file_name }} - + if git diff --quiet ${{ inputs.project_file_name }}; then + echo "changes_detected=false" >> "$GITHUB_OUTPUT" + else + echo "changes_detected=true" >> "$GITHUB_OUTPUT" + fi - name: Create Pull Request - if: ${{ inputs.create_pr == 'true' }} + if: ${{ fromJSON(inputs.create_pr) && fromJSON(steps.changes.outputs.changes_detected) }} uses: peter-evans/create-pull-request@v7 with: token: ${{ inputs.token }} commit-message: ${{ inputs.commit_msg }} - path: ${{ inputs.project_file_name }} title: ${{ inputs.pr_title }} body: "This PR was created automatically" base: ${{ inputs.target_branch }} branch: update-spec0-dependencies-${{ github.run_id }} + add-paths: | + ${{ inputs.project_file_name }} branding: icon: "check-square" diff --git a/readme.md b/readme.md index 3c1532f..60fe253 100644 --- a/readme.md +++ b/readme.md @@ -41,7 +41,7 @@ It should update any of the packages listed in the `dependency`, or `tool.pixi.* 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/savente93/SPEC0-schedule](https://github.com/savente93/SPEC0-schedule) but you should not have to worry about this. +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. ### Parameters diff --git a/spec0_action/__init__.py b/spec0_action/__init__.py index 725c6cc..85307fb 100644 --- a/spec0_action/__init__.py +++ b/spec0_action/__init__.py @@ -112,7 +112,6 @@ def update_pyproject_toml( pyproject_data["project"]["requires-python"] = repr_spec_set( parse_version_spec(new_version["packages"]["python"]) ) - update_pyproject_dependencies( pyproject_data["project"]["dependencies"], new_version ) diff --git a/spec0_versions.py b/spec0_versions.py index 8786f45..4b7a0f8 100644 --- a/spec0_versions.py +++ b/spec0_versions.py @@ -4,7 +4,7 @@ from datetime import datetime, timedelta import pandas as pd -from packaging.version import Version +from packaging.version import Version, InvalidVersion PY_RELEASES = { @@ -14,6 +14,7 @@ "3.11": "Oct 24, 2022", "3.12": "Oct 2, 2023", "3.13": "Oct 7, 2024", + "3.14": "Oct 7, 2025", } CORE_PACKAGES = [ "ipython", @@ -56,11 +57,17 @@ def get_release_dates(package, support_time=PLUS_24_MONTHS): ver = f["filename"].split("-")[1] try: version = Version(ver) - except Exception: + except InvalidVersion as e: + print(f"Error: '{ver}' is an invalid version for '{package}'. Reason: {e}") continue if version.is_prerelease or version.micro != 0: continue - release_date = pd.Timestamp(f["upload-time"]).tz_localize(None) + release_date = None + for format in ["%Y-%m-%dT%H:%M:%S.%fZ", "%Y-%m-%dT%H:%M:%SZ"]: + try: + release_date = datetime.strptime(f["upload-time"], format) + except ValueError as e: + print(f"Error parsing invalid date: {e}") if not release_date: continue file_date[version].append(release_date) diff --git a/tests/test_data/pyproject.toml b/tests/test_data/pyproject.toml index b06e4c5..f289a6f 100644 --- a/tests/test_data/pyproject.toml +++ b/tests/test_data/pyproject.toml @@ -1,30 +1,16 @@ -[project] -authors = [{ name = "Scientific Python Developers"}] -name = "tests" -description = "This is just a dummy package for testing the spec 0 update github action and should not be used" -requires-python = ">=3.10" -version = "0.1.0" -dependencies = ["ipython>=8.7.0,<4", "numpy[foo,bar]>=1.10.0,<2"] - [build-system] -build-backend = "hatchling.build" -requires = ["hatchling"] - -[tool.pixi.workspace] -channels = ["conda-forge"] -platforms = ["linux-64"] - -[tool.pixi.pypi-dependencies] -tests = { path = ".", editable = true } -scikit-learn = ">=1.2.0" +requires = [ + "setuptools>=62.1", + "setuptools_scm[toml]>=8.0.0", + "wheel", +] +build-backend = "setuptools.build_meta" -[tool.pixi.tasks] - -[tool.pixi.feature.foo.dependencies] -xarray = "*" - -[tool.pixi.environments] -bar = ["foo"] - -[tool.pixi.dependencies] -numpy = ">=1.10.0,<2" +[project] +name = "setuptools_test" +requires-python = ">=3.11" +dependencies = [ + 'numpy>=1.20.0,<2', + 'pandas>=1.0.0,<3', + 'xarray>=2021.1.0', +] diff --git a/tests/test_data/pyproject_pixi.toml b/tests/test_data/pyproject_pixi.toml new file mode 100644 index 0000000..b06e4c5 --- /dev/null +++ b/tests/test_data/pyproject_pixi.toml @@ -0,0 +1,30 @@ +[project] +authors = [{ name = "Scientific Python Developers"}] +name = "tests" +description = "This is just a dummy package for testing the spec 0 update github action and should not be used" +requires-python = ">=3.10" +version = "0.1.0" +dependencies = ["ipython>=8.7.0,<4", "numpy[foo,bar]>=1.10.0,<2"] + +[build-system] +build-backend = "hatchling.build" +requires = ["hatchling"] + +[tool.pixi.workspace] +channels = ["conda-forge"] +platforms = ["linux-64"] + +[tool.pixi.pypi-dependencies] +tests = { path = ".", editable = true } +scikit-learn = ">=1.2.0" + +[tool.pixi.tasks] + +[tool.pixi.feature.foo.dependencies] +xarray = "*" + +[tool.pixi.environments] +bar = ["foo"] + +[tool.pixi.dependencies] +numpy = ">=1.10.0,<2" diff --git a/tests/test_data/pyproject_pixi_updated.toml b/tests/test_data/pyproject_pixi_updated.toml new file mode 100644 index 0000000..a780ef7 --- /dev/null +++ b/tests/test_data/pyproject_pixi_updated.toml @@ -0,0 +1,30 @@ +[project] +authors = [{ name = "Scientific Python Developers"}] +name = "tests" +description = "This is just a dummy package for testing the spec 0 update github action and should not be used" +requires-python = ">=3.11" +version = "0.1.0" +dependencies = ["ipython>=8.8.0,<4", "numpy[foo,bar]>=1.25.0,<2"] + +[build-system] +build-backend = "hatchling.build" +requires = ["hatchling"] + +[tool.pixi.workspace] +channels = ["conda-forge"] +platforms = ["linux-64"] + +[tool.pixi.pypi-dependencies] +tests = { path = ".", editable = true } +scikit-learn = ">=1.3.0" + +[tool.pixi.tasks] + +[tool.pixi.feature.foo.dependencies] +xarray = ">=2023.1.0" + +[tool.pixi.environments] +bar = ["foo"] + +[tool.pixi.dependencies] +numpy = ">=1.25.0,<2" diff --git a/tests/test_data/pyproject_updated.toml b/tests/test_data/pyproject_updated.toml index a780ef7..8698dbb 100644 --- a/tests/test_data/pyproject_updated.toml +++ b/tests/test_data/pyproject_updated.toml @@ -1,30 +1,16 @@ -[project] -authors = [{ name = "Scientific Python Developers"}] -name = "tests" -description = "This is just a dummy package for testing the spec 0 update github action and should not be used" -requires-python = ">=3.11" -version = "0.1.0" -dependencies = ["ipython>=8.8.0,<4", "numpy[foo,bar]>=1.25.0,<2"] - [build-system] -build-backend = "hatchling.build" -requires = ["hatchling"] - -[tool.pixi.workspace] -channels = ["conda-forge"] -platforms = ["linux-64"] - -[tool.pixi.pypi-dependencies] -tests = { path = ".", editable = true } -scikit-learn = ">=1.3.0" +requires = [ + "setuptools>=62.1", + "setuptools_scm[toml]>=8.0.0", + "wheel", +] +build-backend = "setuptools.build_meta" -[tool.pixi.tasks] - -[tool.pixi.feature.foo.dependencies] -xarray = ">=2023.1.0" - -[tool.pixi.environments] -bar = ["foo"] - -[tool.pixi.dependencies] -numpy = ">=1.25.0,<2" +[project] +name = "setuptools_test" +requires-python = ">=3.11" +dependencies = [ + 'numpy>=1.25.0,<2', + 'pandas>=1.0.0,<3', + 'xarray>=2023.1.0', +] diff --git a/tests/test_update_pyproject_toml.py b/tests/test_update_pyproject_toml.py index 40397fb..494f7a8 100644 --- a/tests/test_update_pyproject_toml.py +++ b/tests/test_update_pyproject_toml.py @@ -9,3 +9,12 @@ def test_update_pyproject_toml(): update_pyproject_toml(pyproject_data, test_schedule) assert pyproject_data == expected + + +def test_update_pyproject_toml_with_pixi(): + expected = read_toml("tests/test_data/pyproject_pixi_updated.toml") + pyproject_data = read_toml("tests/test_data/pyproject_pixi.toml") + test_schedule = read_schedule("tests/test_data/test_schedule.json") + update_pyproject_toml(pyproject_data, test_schedule) + + assert pyproject_data == expected