-
Notifications
You must be signed in to change notification settings - Fork 50
fix(resolver): exempt top-level pinned versions from transitive cooldown #1157
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
base: main
Are you sure you want to change the base?
Changes from all commits
4b1515c
d0590ad
31be4d6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -147,42 +147,78 @@ def _has_equality_pin(req: Requirement) -> bool: | |
| return len(specs) == 1 and specs[0].operator == "==" and "*" not in specs[0].version | ||
|
|
||
|
|
||
| def _get_toplevel_pinned_versions( | ||
| ctx: context.WorkContext, req: Requirement | ||
| ) -> frozenset[Version]: | ||
| """Return versions of *req* that have a top-level exact ``==`` pin in the graph.""" | ||
| top_level_edges = ctx.dependency_graph.get_root_node().get_outgoing_edges( | ||
| req.name, RequirementType.TOP_LEVEL | ||
| ) | ||
| return frozenset( | ||
| edge.destination_node.version | ||
| for edge in top_level_edges | ||
| if _has_equality_pin(edge.req) | ||
| ) | ||
|
|
||
|
|
||
| def _resolve_cooldown_params( | ||
| ctx: context.WorkContext, | ||
| req: Requirement, | ||
| ) -> tuple[datetime.timedelta, datetime.datetime]: | ||
| """Resolve min_age and bootstrap_time for a package's cooldown. | ||
|
|
||
| Always returns a ``(min_age, bootstrap_time)`` pair. When cooldown is | ||
| disabled (per-package override of 0, or no global/per-package config), | ||
| ``min_age`` will be zero — every package age exceeds that threshold. | ||
| """ | ||
| min_age_override = ctx.package_build_info(req).resolver_min_release_age | ||
|
|
||
| if min_age_override == 0: | ||
| min_age = datetime.timedelta(0) | ||
| elif min_age_override is not None: | ||
| min_age = datetime.timedelta(days=min_age_override) | ||
| else: | ||
| min_age = ctx.cooldown.min_age | ||
|
|
||
| return min_age, ctx.cooldown.bootstrap_time | ||
|
|
||
|
|
||
| def resolve_package_cooldown( | ||
|
LalatenduMohanty marked this conversation as resolved.
|
||
| ctx: context.WorkContext, | ||
| req: Requirement, | ||
| req_type: RequirementType | None = None, | ||
| ) -> Cooldown | None: | ||
| ) -> Cooldown: | ||
| """Compute the effective cooldown for a single package. | ||
|
|
||
| Args: | ||
| ctx: The current work context (provides the global cooldown). | ||
| req: The package requirement being resolved. | ||
| req_type: The requirement type (top-level, install, etc.). | ||
| Always returns a ``Cooldown`` instance. A ``min_age`` of zero means | ||
| cooldown is effectively disabled — every package age exceeds zero. | ||
|
|
||
| Returns a *disabled* cooldown (min_age=0) when: | ||
|
|
||
| * The requirement is a top-level exact ``==`` pin — the user explicitly | ||
| approved that version. | ||
| * A per-package ``min_release_age=0`` override disables cooldown. | ||
| * No global cooldown is configured and no per-package override enables one. | ||
|
|
||
| Returns: | ||
| The cooldown to pass to the provider, or ``None`` if disabled. | ||
| Otherwise returns an *active* cooldown with: | ||
|
|
||
| * *min_age* from the per-package override (if set) or the global cooldown. | ||
| * *bootstrap_time* inherited from the global cooldown (for a consistent | ||
| cutoff across the entire run). | ||
| * *exempt_versions* populated from top-level exact-pinned entries in the | ||
| dependency graph, so transitive resolutions of the same package honour | ||
| the user's explicit pin. | ||
|
Comment on lines
191
to
+210
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add
As per coding guidelines, "Use Sphinx Also applies to: 290-293 🤖 Prompt for AI Agents |
||
| """ | ||
| if req_type == RequirementType.TOP_LEVEL and _has_equality_pin(req): | ||
| if ctx.cooldown is not None: | ||
| if ctx.cooldown.min_age > datetime.timedelta(0): | ||
| logger.info("cooldown bypassed as the top-level requirement uses == pin") | ||
| return None | ||
| return Cooldown.disabled() | ||
|
|
||
| per_package_days = ctx.package_build_info(req).resolver_min_release_age | ||
| global_cooldown = ctx.cooldown | ||
| if per_package_days is None: | ||
| return global_cooldown | ||
| if per_package_days == 0: | ||
| return None | ||
| # Per-package positive override: inherit bootstrap_time from global so all | ||
| # resolutions in a single run share the same fixed cutoff point. | ||
| bootstrap_time = ( | ||
| global_cooldown.bootstrap_time | ||
| if global_cooldown is not None | ||
| else datetime.datetime.now(datetime.UTC) | ||
| ) | ||
| min_age, bootstrap_time = _resolve_cooldown_params(ctx, req) | ||
| return Cooldown( | ||
| min_age=datetime.timedelta(days=per_package_days), | ||
| min_age=min_age, | ||
| bootstrap_time=bootstrap_time, | ||
| exempt_versions=_get_toplevel_pinned_versions(ctx, req), | ||
| ) | ||
|
|
||
|
|
||
|
|
@@ -196,12 +232,7 @@ def _compute_max_age_cutoff( | |
| """ | ||
| if ctx.max_release_age is None: | ||
| return None | ||
| bootstrap_time = ( | ||
| ctx.cooldown.bootstrap_time | ||
| if ctx.cooldown is not None | ||
| else datetime.datetime.now(datetime.UTC) | ||
| ) | ||
| return bootstrap_time - ctx.max_release_age | ||
| return ctx.cooldown.bootstrap_time - ctx.max_release_age | ||
|
|
||
|
|
||
| def extract_filename_from_url(url: str) -> str: | ||
|
|
@@ -576,8 +607,9 @@ def __init__( | |
| self.req_type = req_type | ||
| self.use_cache_candidates = use_resolver_cache | ||
|
|
||
| # cooldown specific settings | ||
| self.cooldown = cooldown | ||
| self.cooldown: Cooldown = ( | ||
| cooldown if cooldown is not None else Cooldown.disabled() | ||
| ) | ||
| # Does this provider supply upload timestamps for candidates? | ||
| # Defaults to False (safe/unknown). Subclasses that reliably populate | ||
| # upload_time on every candidate should set this to True in their __init__. | ||
|
|
@@ -699,11 +731,12 @@ def is_satisfied_by(self, requirement: Requirement, candidate: Candidate) -> boo | |
| def is_blocked_by_cooldown(self, candidate: Candidate) -> bool: | ||
| """Return True if the candidate is rejected by the release-age cooldown.""" | ||
|
|
||
| # a cooldown is not specified... | ||
| if self.cooldown is None: | ||
| if self.cooldown.min_age <= datetime.timedelta(0): | ||
| return False | ||
|
|
||
| if candidate.version in self.cooldown.exempt_versions: | ||
| return False | ||
|
|
||
| # the target candidate doesn't provide a valid upload timestamp | ||
| if candidate.upload_time is None: | ||
| if not self.supports_upload_time: | ||
| # this provider does not yet support timestamp retrieval (e.g. GitHub). | ||
|
|
@@ -947,7 +980,7 @@ def _get_no_match_error_message( | |
|
|
||
| # If a cooldown is active, check whether it's responsible for the | ||
| # failure so we can give a more actionable error message. | ||
| if self.cooldown is not None: | ||
| if self.cooldown.min_age > datetime.timedelta(0): | ||
| cutoff = self.cooldown.bootstrap_time - self.cooldown.min_age | ||
| all_candidates = list(self._find_cached_candidates(identifier)) | ||
| missing_time = [c for c in all_candidates if c.upload_time is None] | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.