Skip to content

dump lower bounds for packages in schedule.json #374

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
27 changes: 26 additions & 1 deletion spec-0000/SPEC0_versions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import requests
import json
import collections
from datetime import datetime, timedelta

Expand Down Expand Up @@ -148,8 +149,32 @@ def get_release_dates(package, support_time=plus24):
df = pd.DataFrame(data, columns=["package", "version", "release", "drop"])

df["quarter"] = df["drop"].dt.to_period("Q")
df["new_min_version"] = (
df[["package", "version", "quarter"]].groupby("package").shift(-1)["version"]
)
dq = df.set_index(["quarter", "package"]).sort_index().dropna()

new_min_versions = (
dq.groupby(["quarter", "package"]).agg({"new_min_version": "max"}).reset_index()
)


# we want to build a dict with the structure [{start_date: timestamp, packages: {package: lower_bound}}]
new_min_versions_list = []
for q, packages in new_min_versions.groupby("quarter"):
package_lower_bounds = {
p: str(v) for p, v in packages.drop("quarter", axis=1).itertuples(index=False)
}
# jq is really insistent the Z should be there
quarter_start_time_str = str(q.start_time.isoformat()) + "Z"
new_min_versions_list.append(
{"start_date": quarter_start_time_str, "packages": package_lower_bounds}
)

print("Saving drop schedule to schedule.json")

dq = df.set_index(["quarter", "package"]).sort_index()
with open("schedule.json", "w") as f:
f.write(json.dumps(new_min_versions_list, sort_keys=True))


print("Saving drop schedule to schedule.md")
Expand Down
83 changes: 83 additions & 0 deletions spec-0000/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,89 @@ You may want to delay the removal of support of an older Python version until yo

{{< include-md "schedule.md" >}}

### Automatically updating dependencies

To help projects stay compliant with this spec, we additionally provide a `schedule.json` file that can be used by CI systems to deterime new version boundaries. The structure of the file is as follows:

```json
[
{
"start_date": "iso8601_timestamp",
"packages": {
"package_name": "version"
}
}
]
```

All information in the json file is in a string format that should be easy to use. The date is the first timestamp of the relevant quarter. Thus a workflow for using this file could be:

1. fetch `schedule.json`
2. determine maximum date that is smaller than current date
3. update packages listed with new minimum versions

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

```sh

jq 'map(select(.start_date |fromdateiso8601 |tonumber < now))| sort_by("start_date") | reverse | .[0].packages ' schedule.json

```

If you use a package manager like pixi you could update the dependencies with a bash script like this:

```sh
curl -Ls -o schedule.json https://raw.githubusercontent.com/scientific-python/specs/main/spec-0000/schedule.json
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
package=$(echo "$line" | cut -d ':' -f 1)
version=$(echo "$line" | cut -d ':' -f 2)
if pixi list -x "^$package" &>/dev/null| grep "No packages" -q; then
pixi add "$package>=$version";
fi
done

```

You can create a GH action that runs every quarter like so

```yaml
name: Update SPEC 0 dependencies

on:
schedule:
# At 00:00 on day-of-month 1 in every 3rd month. (i.e. every quarter)
- cron: "0 0 1 */3 *"
# on demand
workflow_dispatch:

jobs:
auto-update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: update
run: |

curl -Ls -o schedule.json https://raw.githubusercontent.com/scientific-python/specs/main/spec-0000/schedule.json
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
package=$(echo "$line" | cut -d ':' -f 1)
version=$(echo "$line" | cut -d ':' -f 2)

# don't add packages that aren't already added.
if pixi list -x "^$package" &>/dev/null| grep "No packages" -q; then
pixi add "$package>=$version";
fi
done
- uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
branch: update/spec-0
title: Drop support for packages according to SPEC 0
commit-message: "Update dependencies"
body: Increase minimum version of .
author: "GitHub <[email protected]>"
```

## Notes

- This document builds on [NEP 29](https://numpy.org/neps/nep-0029-deprecation_policy.html), which describes several alternatives including ad hoc version support, all CPython supported versions, default version on Linux distribution, N minor versions of Python, and time window from the X.Y.1 Python release.
Expand Down
1 change: 1 addition & 0 deletions spec-0000/schedule.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"packages": {"ipython": "8.5.0", "networkx": "3.0", "numpy": "1.24.0", "scikit-learn": "1.2.0", "zarr": "2.13.0"}, "start_date": "2024-04-01T00:00:00Z"}, {"packages": {"ipython": "8.6.0", "matplotlib": "3.7.0", "pandas": "2.0.0", "scipy": "1.10.0", "xarray": "2022.10.0", "zarr": "2.14.0"}, "start_date": "2024-07-01T00:00:00Z"}, {"packages": {"ipython": "8.8.0", "numpy": "1.25.0", "python": "3.11", "scikit-learn": "1.3.0", "xarray": "2023.1.0"}, "start_date": "2024-10-01T00:00:00Z"}, {"packages": {"ipython": "8.13.0", "matplotlib": "3.8.0", "networkx": "3.1", "scikit-image": "0.21.0", "scipy": "1.11.0", "xarray": "2023.4.0", "zarr": "2.15.0"}, "start_date": "2025-01-01T00:00:00Z"}, {"packages": {"ipython": "8.15.0", "networkx": "3.2", "numpy": "1.26.0", "pandas": "2.1.0", "scikit-image": "0.22.0", "scikit-learn": "1.4.0", "scipy": "1.12.0", "xarray": "2023.7.0", "zarr": "2.16.0"}, "start_date": "2025-04-01T00:00:00Z"}, {"packages": {"ipython": "8.17.0", "matplotlib": "3.9.0", "numpy": "2.0.0", "pandas": "2.2.0", "xarray": "2023.10.0", "zarr": "2.17.0"}, "start_date": "2025-07-01T00:00:00Z"}, {"packages": {"ipython": "8.20.0", "networkx": "3.3", "python": "3.12", "scikit-image": "0.23.0", "xarray": "2024.1.0"}, "start_date": "2025-10-01T00:00:00Z"}, {"packages": {"ipython": "8.24.0", "scikit-learn": "1.5.0", "scipy": "1.13.0", "xarray": "2024.5.0", "zarr": "2.18.0"}, "start_date": "2026-01-01T00:00:00Z"}, {"packages": {"ipython": "8.27.0", "matplotlib": "3.10.0", "networkx": "3.4", "numpy": "2.1.0", "scikit-image": "0.25.0", "scikit-learn": "1.6.0", "scipy": "1.15.0", "xarray": "2024.7.0", "zarr": "3.0.0"}, "start_date": "2026-04-01T00:00:00Z"}, {"packages": {"ipython": "8.28.0", "numpy": "2.2.0", "xarray": "2024.10.0"}, "start_date": "2026-07-01T00:00:00Z"}, {"packages": {"ipython": "8.32.0", "python": "3.13", "xarray": "2025.1.0"}, "start_date": "2026-10-01T00:00:00Z"}]