diff --git a/src/fromager/__main__.py b/src/fromager/__main__.py index 32f1985b..e1ed7387 100644 --- a/src/fromager/__main__.py +++ b/src/fromager/__main__.py @@ -8,6 +8,7 @@ import click from . import ( + candidate, clickext, commands, context, @@ -258,7 +259,7 @@ def main( max_jobs=jobs, settings_dir=settings_dir, cooldown=( - context.Cooldown(min_age=datetime.timedelta(days=min_release_age)) + candidate.Cooldown(min_age=datetime.timedelta(days=min_release_age)) if min_release_age > 0 else None ), diff --git a/src/fromager/candidate.py b/src/fromager/candidate.py index 65c9f72f..36d5955a 100644 --- a/src/fromager/candidate.py +++ b/src/fromager/candidate.py @@ -15,6 +15,20 @@ logger = logging.getLogger(__name__) +@dataclasses.dataclass +class Cooldown: + """Policy for rejecting recently-published package versions. + + bootstrap_time is fixed at construction so all resolutions in a single run + share the same cutoff. + """ + + min_age: datetime.timedelta + bootstrap_time: datetime.datetime = dataclasses.field( + default_factory=lambda: datetime.datetime.now(datetime.UTC) + ) + + @dataclasses.dataclass(frozen=True, order=True, slots=True, repr=False, kw_only=True) class Candidate: name: str diff --git a/src/fromager/context.py b/src/fromager/context.py index 93aa3d59..1bc543ed 100644 --- a/src/fromager/context.py +++ b/src/fromager/context.py @@ -1,7 +1,6 @@ from __future__ import annotations import collections -import dataclasses import datetime import logging import os @@ -24,7 +23,7 @@ ) if typing.TYPE_CHECKING: - from . import build_environment + from . import build_environment, candidate logger = logging.getLogger(__name__) @@ -33,20 +32,6 @@ ROOT_BUILD_REQUIREMENT = canonicalize_name("", validate=False) -@dataclasses.dataclass -class Cooldown: - """Policy for rejecting recently-published package versions. - - bootstrap_time is fixed at construction so all resolutions in a single run - share the same cutoff. - """ - - min_age: datetime.timedelta - bootstrap_time: datetime.datetime = dataclasses.field( - default_factory=lambda: datetime.datetime.now(datetime.UTC) - ) - - class WorkContext: def __init__( self, @@ -62,7 +47,7 @@ def __init__( max_jobs: int | None = None, settings_dir: pathlib.Path | None = None, wheel_server_url: str = "", - cooldown: Cooldown | None = None, + cooldown: candidate.Cooldown | None = None, max_release_age: datetime.timedelta | None = None, ): if active_settings is None: @@ -113,7 +98,7 @@ def __init__( self._parallel_builds = False - self.cooldown: Cooldown | None = cooldown + self.cooldown: candidate.Cooldown | None = cooldown self._max_release_age: datetime.timedelta | None = max_release_age @property diff --git a/src/fromager/packagesettings/_pbi.py b/src/fromager/packagesettings/_pbi.py index ddf9af0d..ff31cd0c 100644 --- a/src/fromager/packagesettings/_pbi.py +++ b/src/fromager/packagesettings/_pbi.py @@ -257,7 +257,7 @@ def resolver_min_release_age(self) -> int | None: Returns None (inherit global), 0 (disabled), or a positive integer (override days). The caller is responsible for converting to a - :class:`~fromager.context.Cooldown` instance. + :class:`~fromager.candidate.Cooldown` instance. """ return self._ps.resolver_dist.min_release_age diff --git a/src/fromager/resolver.py b/src/fromager/resolver.py index 1b6754fd..d482d427 100644 --- a/src/fromager/resolver.py +++ b/src/fromager/resolver.py @@ -31,9 +31,8 @@ from resolvelib.resolvers import RequirementInformation from . import overrides -from .candidate import Candidate +from .candidate import Candidate, Cooldown from .constraints import Constraints -from .context import Cooldown from .extras_provider import ExtrasProvider from .http_retry import RETRYABLE_EXCEPTIONS, retry_on_exception from .request_session import session diff --git a/tests/test_cooldown.py b/tests/test_cooldown.py index 8fcca3db..6add8564 100644 --- a/tests/test_cooldown.py +++ b/tests/test_cooldown.py @@ -19,8 +19,7 @@ from packaging.requirements import Requirement from packaging.version import Version -from fromager import context, packagesettings, resolver, sources, wheels -from fromager.context import Cooldown +from fromager import candidate, context, packagesettings, resolver, sources, wheels _BOOTSTRAP_TIME = datetime.datetime(2026, 3, 26, 0, 0, 0, tzinfo=datetime.UTC) _COOLDOWN_7_DAYS = datetime.timedelta(days=7) @@ -77,7 +76,7 @@ ], } -_COOLDOWN = Cooldown( +_COOLDOWN = candidate.Cooldown( min_age=_COOLDOWN_7_DAYS, bootstrap_time=_BOOTSTRAP_TIME, ) @@ -327,7 +326,7 @@ def test_non_pypi_index_allows_without_upload_time( def _make_ctx( tmp_path: pathlib.Path, *, - cooldown: Cooldown | None, + cooldown: candidate.Cooldown | None, min_release_age: int | None = None, ) -> context.WorkContext: """Build a WorkContext with an optional per-package min_release_age setting.""" @@ -457,7 +456,7 @@ def test_per_package_cooldown_disable_via_ctx(tmp_path: pathlib.Path) -> None: # --------------------------------------------------------------------------- _GITLAB_BOOTSTRAP_TIME = datetime.datetime(2025, 5, 20, 0, 0, 0, tzinfo=datetime.UTC) -_GITLAB_COOLDOWN = Cooldown( +_GITLAB_COOLDOWN = candidate.Cooldown( min_age=datetime.timedelta(days=7), bootstrap_time=_GITLAB_BOOTSTRAP_TIME, ) @@ -507,7 +506,9 @@ def test_per_package_cooldown_disable_via_ctx(tmp_path: pathlib.Path) -> None: """ -def _make_gitlab_provider(cooldown: Cooldown | None) -> resolver.GitLabTagProvider: +def _make_gitlab_provider( + cooldown: candidate.Cooldown | None, +) -> resolver.GitLabTagProvider: return resolver.GitLabTagProvider( project_path="test/pkg", server_url="https://gitlab.com", @@ -832,7 +833,7 @@ def test_compute_max_age_cutoff_with_cooldown( tmp_context: context.WorkContext, ) -> None: """_compute_max_age_cutoff uses cooldown's bootstrap_time when available.""" - tmp_context.cooldown = Cooldown( + tmp_context.cooldown = candidate.Cooldown( min_age=datetime.timedelta(days=7), bootstrap_time=_BOOTSTRAP_TIME, )