Skip to content

feat(resolver): enforce PyPI quarantine check for all resolver types#1102

Open
pavank63 wants to merge 2 commits into
python-wheel-build:mainfrom
pavank63:enforce-quarantine-check-non-pypi-resolvers
Open

feat(resolver): enforce PyPI quarantine check for all resolver types#1102
pavank63 wants to merge 2 commits into
python-wheel-build:mainfrom
pavank63:enforce-quarantine-check-non-pypi-resolvers

Conversation

@pavank63
Copy link
Copy Markdown
Contributor

@pavank63 pavank63 commented May 1, 2026

The PEP 792 quarantine check previously only ran inside get_project_from_pypi(), which is only called by PyPIProvider. Packages resolved via GitHubTagProvider, GitLabTagProvider, or PyPIProvider with a custom index URL bypassed the check entirely.

Add a standalone check_pypi_quarantine_status() function that queries pypi.org for quarantine status and call it unconditionally from both resolve() and resolve_source() entry points. Remove the quarantine check from get_project_from_pypi() to avoid split responsibility.

Closes: #1101

@pavank63 pavank63 requested a review from a team as a code owner May 1, 2026 14:04
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 1, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

The changes extract PyPI quarantine status checking (PEP 792) into a standalone check_pypi_quarantine_status() function that queries the PyPI JSON API and raises ValueError if a project is quarantined. This function is now called unconditionally from both resolve() and resolve_source() entry points, ensuring quarantine validation occurs regardless of which resolver provider is used (PyPI, GitHub, GitLab, etc.). The original quarantine check is removed from get_project_from_pypi(), leaving only logging for non-fatal fetch errors.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main change: a new quarantine check function that runs unconditionally for all resolver types, centralizing enforcement.
Description check ✅ Passed The description clearly explains the problem (quarantine check only runs for PyPIProvider), the solution (standalone function called from entry points), and references the closed issue.
Linked Issues check ✅ Passed The PR fully implements the preferred Option A: a standalone check_pypi_quarantine_status() invoked unconditionally from resolve() and resolve_source(), with per-package opt-out support.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the quarantine check enforcement: new function, integration points, settings model, tests, and fixtures.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@mergify mergify Bot added the ci label May 1, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/fromager/resolver.py (1)

377-397: 💤 Low value

QUARANTINED falls through to catch-all, logging misleading "unknown status" warning.

If get_project_from_pypi queries PyPI directly (not via entry points) or if a custom index mirrors PyPI's quarantine status, QUARANTINED hits case _: and logs as "unknown status." Add explicit handling:

♻️ Suggested fix
         case pypi_simple.ProjectStatus.DEPRECATED | pypi_simple.ProjectStatus.ARCHIVED:
             logger.warning(
                 "project %r is no longer active: %r: %s",
                 project,
                 package.status,
                 package.status_reason,
             )
+        case pypi_simple.ProjectStatus.QUARANTINED:
+            # Quarantine is enforced at resolution entry points (resolve/resolve_source).
+            # If we reach here, the check either passed or was bypassed.
+            logger.debug(
+                "project %r has quarantine status in index response (checked at entry point)",
+                project,
+            )
         case _:
             logger.warning(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/fromager/resolver.py` around lines 377 - 397, The match on package.status
in get_project_from_pypi currently falls through QUARANTINED to the catch-all
and logs "unknown status"; add an explicit case for
pypi_simple.ProjectStatus.QUARANTINED in the match block (near the existing
cases for ACTIVE, DEPRECATED, ARCHIVED) and log a clear warning (e.g., "project
%r is quarantined: %s") using project and package.status_reason so quarantined
packages are not misreported as unknown; keep check_pypi_quarantine_status
semantics unchanged at the resolution entry points.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/fromager/resolver.py`:
- Around line 377-397: The match on package.status in get_project_from_pypi
currently falls through QUARANTINED to the catch-all and logs "unknown status";
add an explicit case for pypi_simple.ProjectStatus.QUARANTINED in the match
block (near the existing cases for ACTIVE, DEPRECATED, ARCHIVED) and log a clear
warning (e.g., "project %r is quarantined: %s") using project and
package.status_reason so quarantined packages are not misreported as unknown;
keep check_pypi_quarantine_status semantics unchanged at the resolution entry
points.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 72cf8f67-18ca-4c6c-8848-8044e83d0c4d

📥 Commits

Reviewing files that changed from the base of the PR and between 06e6317 and 445f9b6.

📒 Files selected for processing (3)
  • src/fromager/resolver.py
  • src/fromager/sources.py
  • tests/test_resolver.py

@tiran
Copy link
Copy Markdown
Collaborator

tiran commented May 4, 2026

I see two potential issues with this approach:

  • some downstream packages are not on PyPI (e.g. aotriton)
  • in some cases, a downstream package is typo-squatted by somebody else (e.g. deep-gemm) or may be a completely different package than on PyPI.

@pavank63
Copy link
Copy Markdown
Contributor Author

pavank63 commented May 4, 2026

@tiran thanks for flagging these.

some downstream packages are not on PyPI (e.g. aotriton)

This one should be handled already. The except Exception block in check_pypi_quarantine_status() catches the 404 and silently returns, so packages that don't exist on PyPI won't be blocked.

in some cases, a downstream package is typo-squatted by somebody else (e.g. deep-gemm) or may be a completely different package than on PyPI.

I can think of two options to handle this.

  1. Warn instead of block for non-PyPI resolvers — For non-PyPI resolvers (GitHub/GitLab), log a warning instead of raising an error when a package with the same name is quarantined on PyPI. This gives visibility without incorrectly blocking legitimate packages. If the package doesn't exist on PyPI at all, the check is silently skipped.

  2. Per-package opt-out setting — Add a package-level setting (e.g. skip_pypi_quarantine_check: true) that allows maintainers to disable the quarantine check for specific packages where the PyPI name is known to be a different project or doesn't exist.

@pavank63
Copy link
Copy Markdown
Contributor Author

Added a per-package opt-out: resolver_dist.skip_pypi_quarantine: true in the package settings YAML. Packages not on PyPI are already handled (404 is silently skipped). The opt-out covers the typo-squatting case where the PyPI name is an unrelated project.

@mergify
Copy link
Copy Markdown
Contributor

mergify Bot commented May 27, 2026

This pull request has merge conflicts that must be resolved before it can be merged.
@pavank63 please rebase your branch.

@tiran
Copy link
Copy Markdown
Collaborator

tiran commented May 27, 2026

There is a similar option use_pypi_org_metadata. How does the new option relate to the existing option?

@pavank63
Copy link
Copy Markdown
Contributor Author

I think they serve different purposes. use_pypi_org_metadata auto-defaults to false for any package that has a settings file, patches, or a plugin. That's a very broad set and reusing it for quarantine would silently skip the security check for most packages in a typical deployment, which is the opposite of what this PR is trying to achieve. skip_pypi_quarantine is an explicit opt-out for the narrow case where the PyPI name is a different project (e.g. typo-squatting).

pavank63 and others added 2 commits May 27, 2026 07:49
The PEP 792 quarantine check previously only ran inside
get_project_from_pypi(), which is only called by PyPIProvider.
Packages resolved via GitHubTagProvider, GitLabTagProvider, or
PyPIProvider with a custom index URL bypassed the check entirely.

Add a standalone check_pypi_quarantine_status() function that queries
pypi.org for quarantine status and call it unconditionally from both
resolve() and resolve_source() entry points. Remove the quarantine
check from get_project_from_pypi() to avoid split responsibility.

Signed-off-by: Pavan Kalyan Reddy Cherupally <pcherupa@redhat.com>
Co-Authored-By: Claude <claude@anthropic.com>
Packages resolved from non-PyPI sources (GitHub, GitLab) may share
a name with an unrelated PyPI project. If that PyPI project gets
quarantined, it would incorrectly block the legitimate package.

Add `resolver_dist.skip_pypi_quarantine` (default false) to allow
maintainers to disable the PEP 792 quarantine check for specific
packages.

Co-Authored-By: Claude <claude@anthropic.com>
Signed-off-by: Pavan Kalyan Reddy Cherupally <pcherupa@redhat.com>
@pavank63 pavank63 force-pushed the enforce-quarantine-check-non-pypi-resolvers branch from e5f6d30 to b092d8e Compare May 27, 2026 12:50
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/fromager/packagesettings/_pbi.py`:
- Around line 264-267: This new public property resolver_skip_pypi_quarantine
needs a Sphinx version marker in its docstring: update the property docstring on
resolver_skip_pypi_quarantine to include a versionadded directive (e.g.
:versionadded: X.Y) right after the one-line description so the new API is
documented properly; ensure you edit the docstring for the
resolver_skip_pypi_quarantine `@property` in the packagesettings/_pbi module.

In `@tests/test_resolver.py`:
- Around line 1341-1364: The test is tautological because it calls the mock
directly instead of exercising the resolver; update
test_check_pypi_quarantine_skipped_with_per_package_setting to call the real
resolver entrypoint (e.g., resolver.resolve or resolver.resolve_source) with the
mocked context/package_build_info and a Requirement("testpkg"), while patching
resolver.check_pypi_quarantine_status to a mock and using requests_mock to seed
the PyPI response; then assert the patched
check_pypi_quarantine_status.assert_not_called() after resolve returns to verify
the resolver respected
mock_ctx.package_build_info(...).resolver_skip_pypi_quarantine. Ensure you
reference the existing mocks (mock_ctx, mock_pbi) and the patched symbol
resolver.check_pypi_quarantine_status so the test exercises production wiring
rather than an inline conditional.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0d20fa96-3815-49d2-b0d6-37fb3affa866

📥 Commits

Reviewing files that changed from the base of the PR and between 445f9b6 and b092d8e.

📒 Files selected for processing (6)
  • src/fromager/packagesettings/_models.py
  • src/fromager/packagesettings/_pbi.py
  • src/fromager/resolver.py
  • src/fromager/sources.py
  • tests/test_packagesettings.py
  • tests/test_resolver.py
✅ Files skipped from review due to trivial changes (1)
  • tests/test_packagesettings.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/fromager/sources.py
  • src/fromager/resolver.py

Comment on lines +264 to +267
@property
def resolver_skip_pypi_quarantine(self) -> bool:
"""Skip PyPI quarantine status check for this package?"""
return self._ps.resolver_dist.skip_pypi_quarantine
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a Sphinx version marker for the new public property.

resolver_skip_pypi_quarantine is a new public API but its docstring lacks a versionadded directive.

Suggested patch
 `@property`
 def resolver_skip_pypi_quarantine(self) -> bool:
-    """Skip PyPI quarantine status check for this package?"""
+    """Skip PyPI quarantine status check for this package?
+
+    .. versionadded:: 0.86
+    """
     return self._ps.resolver_dist.skip_pypi_quarantine

As per coding guidelines: "**/*.{rst,py}: Use Sphinx versionadded, versionremoved, versionchanged directives for user-facing changes".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@property
def resolver_skip_pypi_quarantine(self) -> bool:
"""Skip PyPI quarantine status check for this package?"""
return self._ps.resolver_dist.skip_pypi_quarantine
`@property`
def resolver_skip_pypi_quarantine(self) -> bool:
"""Skip PyPI quarantine status check for this package?
.. versionadded:: 0.86
"""
return self._ps.resolver_dist.skip_pypi_quarantine
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/fromager/packagesettings/_pbi.py` around lines 264 - 267, This new public
property resolver_skip_pypi_quarantine needs a Sphinx version marker in its
docstring: update the property docstring on resolver_skip_pypi_quarantine to
include a versionadded directive (e.g. :versionadded: X.Y) right after the
one-line description so the new API is documented properly; ensure you edit the
docstring for the resolver_skip_pypi_quarantine `@property` in the
packagesettings/_pbi module.

Comment thread tests/test_resolver.py
Comment on lines +1341 to +1364
def test_check_pypi_quarantine_skipped_with_per_package_setting() -> None:
"""Quarantine check is skipped when skip_pypi_quarantine is True."""
from unittest.mock import MagicMock, patch

mock_ctx = MagicMock()
mock_pbi = MagicMock()
mock_pbi.resolver_skip_pypi_quarantine = True
mock_ctx.package_build_info.return_value = mock_pbi

req = Requirement("testpkg")

with (
requests_mock.Mocker() as m,
patch.object(resolver, "check_pypi_quarantine_status") as mock_check,
):
m.get(
"https://pypi.org/simple/testpkg/",
text=_quarantined_simple_response,
)
skip = mock_ctx.package_build_info(req).resolver_skip_pypi_quarantine
if not skip:
resolver.check_pypi_quarantine_status(req.name)

mock_check.assert_not_called()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

This test is tautological and doesn’t validate resolver wiring.

The current test manually checks skip and conditionally calls the mocked function, so assert_not_called() can pass even if production resolve() / resolve_source() ignores resolver_skip_pypi_quarantine.

Please invoke the real resolver entrypoint and assert check_pypi_quarantine_status was skipped there.

As per coding guidelines: "tests/**: Verify test actually tests the intended behavior."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_resolver.py` around lines 1341 - 1364, The test is tautological
because it calls the mock directly instead of exercising the resolver; update
test_check_pypi_quarantine_skipped_with_per_package_setting to call the real
resolver entrypoint (e.g., resolver.resolve or resolver.resolve_source) with the
mocked context/package_build_info and a Requirement("testpkg"), while patching
resolver.check_pypi_quarantine_status to a mock and using requests_mock to seed
the PyPI response; then assert the patched
check_pypi_quarantine_status.assert_not_called() after resolve returns to verify
the resolver respected
mock_ctx.package_build_info(...).resolver_skip_pypi_quarantine. Ensure you
reference the existing mocks (mock_ctx, mock_pbi) and the patched symbol
resolver.check_pypi_quarantine_status so the test exercises production wiring
rather than an inline conditional.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enforce PyPI quarantine check for non-PyPI resolvers (GitHub/GitLab)

2 participants