From 3245de222458de1060cf19ed8b336c4b55c3eee7 Mon Sep 17 00:00:00 2001 From: Marcel Stimberg Date: Thu, 30 Oct 2025 12:04:50 +0100 Subject: [PATCH] fix: take past quarters into account for updates --- spec0_action/__init__.py | 53 ++++++++++----------- tests/test_data/pyproject.toml | 6 +-- tests/test_data/pyproject_pixi.toml | 6 +-- tests/test_data/pyproject_pixi_updated.toml | 16 +++---- tests/test_data/pyproject_updated.toml | 14 +++--- tests/test_update_pyproject_toml.py | 19 +++++++- 6 files changed, 65 insertions(+), 49 deletions(-) diff --git a/spec0_action/__init__.py b/spec0_action/__init__.py index c5a422b..7656721 100644 --- a/spec0_action/__init__.py +++ b/spec0_action/__init__.py @@ -1,5 +1,5 @@ from packaging.specifiers import SpecifierSet -from typing import Sequence, cast +from typing import Sequence, Dict import datetime from spec0_action.versions import repr_spec_set, tighten_lower_bound @@ -15,20 +15,18 @@ ) from packaging.version import Version - __all__ = ["read_schedule", "read_toml", "write_toml", "update_pyproject_toml"] - -def update_pyproject_dependencies(dependencies: dict, schedule: SupportSchedule): +def update_pyproject_dependencies(dependencies: dict, schedule: Dict[str, str]): # Iterate by idx because we want to update it inplace for i in range(len(dependencies)): dep_str = dependencies[i] pkg, extras, spec, env = parse_pep_dependency(dep_str) - if isinstance(spec, Url) or pkg not in schedule["packages"]: + if isinstance(spec, Url) or pkg not in schedule: continue - new_lower_bound = Version(schedule["packages"][pkg]) + new_lower_bound = Version(schedule[pkg]) try: spec = tighten_lower_bound(spec or SpecifierSet(), new_lower_bound) # Will raise a value error if bound is already tighter, in this case we just do nothing and continue @@ -75,42 +73,43 @@ def update_dependency_table(dep_table: dict, new_versions: dict): pkg_data["version"] = repr_spec_set(spec) -def update_pixi_dependencies(pixi_tables: dict, schedule: SupportSchedule): - if "pypi-dependencies" in pixi_tables: - update_dependency_table(pixi_tables["pypi-dependencies"], schedule["packages"]) - if "dependencies" in pixi_tables: - update_dependency_table(pixi_tables["dependencies"], schedule["packages"]) +def update_pixi_dependencies(pixi_tables: dict, schedule: Dict[str, str]): + if "pypi-dependencies" in pixi_tables: + update_dependency_table(pixi_tables["pypi-dependencies"], schedule) + if "dependencies" in pixi_tables: + update_dependency_table(pixi_tables["dependencies"], schedule) if "feature" in pixi_tables: for _, feature_data in pixi_tables["feature"].items(): if "dependencies" in feature_data: - update_dependency_table( - feature_data["dependencies"], schedule["packages"] - ) + update_dependency_table(feature_data["dependencies"], schedule) def update_pyproject_toml( pyproject_data: dict, schedule_data: Sequence[SupportSchedule] ): now = datetime.datetime.now(datetime.UTC) - try: - new_version = cast( - SupportSchedule, - next( - filter( - lambda s: now >= datetime.datetime.fromisoformat(s["start_date"]), - schedule_data, - ) - ), - ) - except StopIteration: + applicable = sorted( + filter( + lambda s: now >= datetime.datetime.fromisoformat(s["start_date"]), + schedule_data, + ), + key=lambda s: datetime.datetime.fromisoformat(s["start_date"]), + ) + new_version = {} + for schedule in applicable: + # Fill in the latest known requirement (schedule is sorted, newer entries overwrite older) + for pkg, version in schedule["packages"].items(): + new_version[pkg] = version + + if not new_version: raise RuntimeError( "Could not find schedule that applies to current time, perhaps your schedule is outdated." ) - if "python" in new_version["packages"]: + if "python" in new_version: pyproject_data["project"]["requires-python"] = repr_spec_set( - parse_version_spec(new_version["packages"]["python"]) + parse_version_spec(new_version["python"]) ) update_pyproject_dependencies( pyproject_data["project"]["dependencies"], new_version diff --git a/tests/test_data/pyproject.toml b/tests/test_data/pyproject.toml index 9198b2a..7af2767 100644 --- a/tests/test_data/pyproject.toml +++ b/tests/test_data/pyproject.toml @@ -13,8 +13,8 @@ requires-python = ">=3.11" dependencies = [ 'pandas>=1.0.0,<3', 'xarray>=2021.1.0', - "ipython>=8.7.0,<4", - "numpy[foo,bar]>=1.10.0,<2", - "scikit-learn>1.2,<1.4;sys_platform=='win32'", + "ipython>=8.7.0", + "numpy[foo,bar]>=1.10.0", + "scikit-learn>1.3,<1.5;sys_platform=='win32'", "scikit-learn>1.2;sys_platform!='win32'" ] diff --git a/tests/test_data/pyproject_pixi.toml b/tests/test_data/pyproject_pixi.toml index 959d181..6f1854e 100644 --- a/tests/test_data/pyproject_pixi.toml +++ b/tests/test_data/pyproject_pixi.toml @@ -5,9 +5,9 @@ description = "This is just a dummy package for testing the spec 0 update gi requires-python = ">=3.10" version = "0.1.0" dependencies = [ - "ipython>=8.7.0,<4", - "numpy[foo,bar]>=1.10.0,<2", - "scikit-learn>1.2,<1.4;sys_platform=='win32'", + "ipython>=8.7.0", + "numpy[foo,bar]>=1.10.0", + "scikit-learn>1.2,<1.5;sys_platform=='win32'", "scikit-learn>1.2;sys_platform!='win32'" ] diff --git a/tests/test_data/pyproject_pixi_updated.toml b/tests/test_data/pyproject_pixi_updated.toml index 21e724b..69cfc85 100644 --- a/tests/test_data/pyproject_pixi_updated.toml +++ b/tests/test_data/pyproject_pixi_updated.toml @@ -2,13 +2,13 @@ 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" +requires-python = ">=3.12" version = "0.1.0" dependencies = [ - "ipython>=8.8.0,<4", - "numpy[foo,bar]>=1.25.0,<2", - "scikit-learn>=1.3.0,<1.4;sys_platform=='win32'", - "scikit-learn>=1.3.0;sys_platform!='win32'" + "ipython>=8.20.0", + "numpy[foo,bar]>=2.0.0", + "scikit-learn>=1.4.0,<1.5;sys_platform=='win32'", + "scikit-learn>=1.4.0;sys_platform!='win32'" ] [build-system] build-backend = "hatchling.build" @@ -20,15 +20,15 @@ platforms = ["linux-64"] [tool.pixi.pypi-dependencies] tests = { path = ".", editable = true } -scikit-learn = ">=1.3.0" +scikit-learn = ">=1.4.0" [tool.pixi.tasks] [tool.pixi.feature.foo.dependencies] -xarray = ">=2023.1.0" +xarray = ">=2024.1.0" [tool.pixi.environments] bar = ["foo"] [tool.pixi.dependencies] -numpy = ">=1.25.0,<2" +numpy = ">=2.0.0,<2" diff --git a/tests/test_data/pyproject_updated.toml b/tests/test_data/pyproject_updated.toml index 83741ec..fbc559d 100644 --- a/tests/test_data/pyproject_updated.toml +++ b/tests/test_data/pyproject_updated.toml @@ -9,12 +9,12 @@ build-backend = "setuptools.build_meta" [project] name = "setuptools_test" version = "0.1.0" -requires-python = ">=3.11" +requires-python = ">=3.12" dependencies = [ - 'pandas>=1.0.0,<3', - 'xarray>=2023.1.0', - "ipython>=8.8.0,<4", - "numpy[foo,bar]>=1.25.0,<2", - "scikit-learn>=1.3.0,<1.4;sys_platform=='win32'", - "scikit-learn>=1.3.0;sys_platform!='win32'" + 'pandas>=2.2.0,<3', + 'xarray>=2024.1.0', + "ipython>=8.20.0", + "numpy[foo,bar]>=2.0.0", + "scikit-learn>=1.4.0,<1.5;sys_platform=='win32'", + "scikit-learn>=1.4.0;sys_platform!='win32'" ] diff --git a/tests/test_update_pyproject_toml.py b/tests/test_update_pyproject_toml.py index 494f7a8..f1dd998 100644 --- a/tests/test_update_pyproject_toml.py +++ b/tests/test_update_pyproject_toml.py @@ -1,8 +1,25 @@ +import datetime + +import pytest + from spec0_action.parsing import read_schedule, read_toml from spec0_action import update_pyproject_toml +# Fixed time to avoid test results changing over time... +FAKE_TIME = datetime.datetime(2025, 10, 30, 0, 0, 0, tzinfo=datetime.UTC) + +@pytest.fixture +def patch_datetime_now(monkeypatch): + + class mydatetime(datetime.datetime): + @classmethod + def now(cls, *args, **kwds): + return FAKE_TIME + + monkeypatch.setattr(datetime, 'datetime', mydatetime) + -def test_update_pyproject_toml(): +def test_update_pyproject_toml(patch_datetime_now): expected = read_toml("tests/test_data/pyproject_updated.toml") pyproject_data = read_toml("tests/test_data/pyproject.toml") test_schedule = read_schedule("tests/test_data/test_schedule.json")