Skip to content

fix(pipx): filter yanked pypi releases#9607

Merged
jdx merged 3 commits intojdx:mainfrom
risu729:fix/pipx-yanked-versions
May 5, 2026
Merged

fix(pipx): filter yanked pypi releases#9607
jdx merged 3 commits intojdx:mainfrom
risu729:fix/pipx-yanked-versions

Conversation

@risu729
Copy link
Copy Markdown
Contributor

@risu729 risu729 commented May 5, 2026

Summary

  • ignore PyPI releases that have no non-yanked files when listing pipx versions
  • preserve exact pinned behavior by keeping the filter in fuzzy/latest version listing
  • add unit coverage for fully yanked, mixed, empty, and string-yanked PyPI releases

How it works

  • For PyPI packages, the pipx backend uses the PyPI JSON API, whose releases map contains each version and the files published for that version.
  • Each file can carry yanked metadata. The backend filters out a release only when there are no usable files: either the file list is empty or every file for that release is yanked.
  • Mixed releases are kept if at least one file is not yanked, and created_at is computed from non-yanked files so date filtering reflects usable artifacts.
  • The JSON latest_stable_version fast path now derives latest from the same filtered version list instead of returning info.version, because info.version can still point at a yanked release.
  • The yanked field is deserialized defensively because PyPI-compatible JSON can represent yanked metadata as either a bool or a string reason.

Why this is required

  • Yanked PyPI releases remain visible in JSON metadata, but pip avoids them unless the user asks for an exact pinned version. mise should follow that behavior for fuzzy/latest resolution.
  • Filtering only during version listing keeps exact pinned installs available while preventing latest or prefix resolution from choosing a yanked release by default.

Tests

  • cargo fmt --check
  • git diff --check
  • cargo test backend::pipx::tests

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 5, 2026

Greptile Summary

This PR filters out yanked PyPI releases from fuzzy/latest version resolution in the pipx backend, mirroring pip's default behavior. The info.version fast-path is replaced with a computed latest-stable derived from the same yanked-filtered list, and the yanked field is deserialized defensively to handle both bool and string-reason representations found in PyPI-compatible registries.

  • Adds versions_from_pypi_package and latest_stable_from_pypi_package helpers that skip releases with no usable (non-yanked) files, and compute created_at only from non-yanked files in mixed releases.
  • Replaces Ok(Some(pkg.info.version)) in the JSON fast path with Ok(Self::latest_stable_from_pypi_package(pkg)), returning Ok(None) when all stable versions are yanked so the caller can fall back gracefully.
  • Adds four focused unit tests covering fully-yanked, mixed, empty, and string-yanked-reason releases.

Confidence Score: 5/5

Safe to merge — the change is well-scoped to yanked-release filtering and is fully covered by new unit tests.

The logic is straightforward: a filter on file-level yanked flags, a re-derived latest-stable that goes through the same filter, and a defensive custom deserializer. The return type at the latest_stable_version call site is unchanged (Result<Option>), exact pinned installs are unaffected (filtering only happens in the list/latest path), and every edge case in the PR description is exercised by a test.

No files require special attention.

Reviews (3): Last reviewed commit: "Merge branch 'main' into fix/pipx-yanked..." | Re-trigger Greptile

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the PIPX backend to filter out yanked releases from PyPI by introducing a versions_from_pypi_package method and updating the PypiRelease struct. Feedback indicates that the latest_stable_version logic should also be updated to respect these filters to ensure consistency. Additionally, the yanked field should be handled as either a boolean or a string to comply with PEP 592 and avoid deserialization failures. Finally, a suggestion was made to exclude yanked files when determining the release timestamp.

Comment thread src/backend/pipx.rs
Comment thread src/backend/pipx.rs
Comment thread src/backend/pipx.rs
@risu729

This comment was marked as outdated.

@risu729

This comment was marked as outdated.

@risu729 risu729 marked this pull request as ready for review May 5, 2026 12:26
@jdx jdx merged commit 3648765 into jdx:main May 5, 2026
34 checks passed
@risu729 risu729 deleted the fix/pipx-yanked-versions branch May 5, 2026 20:17
mise-en-dev added a commit that referenced this pull request May 7, 2026
### 🚀 Features

- **(aqua)** support registry libc variants by @jdx in
[#9652](#9652)
- **(bin-paths)** add executable names output by @risu729 in
[#9617](#9617)

### 🐛 Bug Fixes

- **(aqua)** preserve configured file extensions by @risu729 in
[#9611](#9611)
- **(aqua)** support registry file links by @risu729 in
[#9610](#9610)
- **(backend)** reject bare package backend names by @risu729 in
[#9608](#9608)
- **(backend)** apply inline tool option overrides by @risu729 in
[#9306](#9306)
- **(backend)** skip versions host for local tool opts by @risu729 in
[#9568](#9568)
- **(github)** chmod explicit archive bin by @risu729 in
[#9609](#9609)
- **(install)** skip remote-versions refresh in prefer-offline mode by
@jdx in [#9627](#9627)
- **(lock)** scope targets to active project root by @risu729 in
[#9319](#9319)
- **(lockfile)** respect existing platforms during auto-lock by @jdx in
[#9621](#9621)
- **(pipx)** filter yanked pypi releases by @risu729 in
[#9607](#9607)
- **(pipx)** declare python as a backend dependency by @jdx in
[#9678](#9678)
- **(schema)** update refs to $defs in mise-registry-tool.json by
@risu729 in [#9671](#9671)
- **(task)** terminate parallel siblings on failure via process groups
by @jdx in [#9655](#9655)
- **(task)** stable MISE_PROJECT_ROOT for monorepo tasks, add
MISE_MONOREPO_ROOT by @jdx in
[#9657](#9657)
- **(trust)** run enter hooks after trusting config by @risu729 in
[#9634](#9634)
- **(ui)** stop clearing screen for prompts by @jdx in
[#9619](#9619)
- use /bin/cp on macos by @pdehlke in
[#9656](#9656)

### 🚜 Refactor

- **(aqua)** store aqua var defaults as strings by @risu729 in
[#9645](#9645)
- **(config)** support structured TOML values in registry backend
options by @risu729 in [#9584](#9584)
- **(deps)** remove serde_derive dependency by @risu729 in
[#9670](#9670)
- **(deps)** remove anyhow dependency by @risu729 in
[#9661](#9661)
- **(deps)** use std::sync::LazyLock instead of once_cell::Lazy by
@risu729 in [#9668](#9668)
- **(schema)** generate task schema from mise schema by @risu729 in
[#9581](#9581)
- **(schema)** reuse task props with unevaluatedProperties by @risu729
in [#9582](#9582)
- **(schema)** reuse registry common types by @risu729 in
[#9648](#9648)
- **(schema)** reuse plugin script config by @risu729 in
[#9647](#9647)
- **(schema)** use $defs in schema files by @risu729 in
[#9646](#9646)

### 📚 Documentation

- **(node)** add tips for enabling node idiomatic by @fu050409 in
[#9675](#9675)

### 🧪 Testing

- **(cli)** remove nondeterministic tool depends assertion by @risu729
in [#9633](#9633)
- **(e2e)** pin uv to 0.11.8 around astral-sh/uv#19278 by @jdx in
[#9618](#9618)
- **(e2e)** wait for docker env cleanup by @risu729 in
[#9631](#9631)
- **(zig)** use official zig instead of mach mirror by @jdx in
[#9659](#9659)

### 📦️ Dependency Updates

- fall through to hash check when providers have no outputs by @jdx in
[#9622](#9622)
- bump Cargo.lock by @jdx in
[#9625](#9625)

### 📦 Registry

- remove registry depends by @risu729 in
[#9571](#9571)
- add code-review-graph (pipx:code-review-graph) by @chautruonglong in
[#9673](#9673)

### Chore

- **(ci)** split large registry test-tool changes by @risu729 in
[#9628](#9628)
- **(ci)** make perf script robust to runner noise by @jdx in
[#9635](#9635)
- **(ci)** skip hyperfine comments without permission by @risu729 in
[#9629](#9629)

### New Contributors

- @chautruonglong made their first contribution in
[#9673](#9673)
- @pdehlke made their first contribution in
[#9656](#9656)

## 📦 Aqua Registry Updates

### New Packages (5)

-
[`anthropics/anthropic-cli`](https://github.com/anthropics/anthropic-cli)
- [`crates.io/wasmi_cli`](https://github.com/wasmi-labs/wasmi)
- [`openclaw/gogcli`](https://github.com/openclaw/gogcli)
- `racket-lang.org/racket-minimal`
- [`runs-on/cli`](https://github.com/runs-on/cli)

### Updated Packages (13)

- [`UpCloudLtd/upcloud-cli`](https://github.com/UpCloudLtd/upcloud-cli)
- [`aristocratos/btop`](https://github.com/aristocratos/btop)
- [`dprint/dprint`](https://github.com/dprint/dprint)
- [`j178/prek`](https://github.com/j178/prek)
- [`jdx/hk`](https://github.com/jdx/hk)
- [`jdx/mise`](https://github.com/jdx/mise)
- [`jdx/usage`](https://github.com/jdx/usage)
- [`jreleaser/jreleaser`](https://github.com/jreleaser/jreleaser)
-
[`jreleaser/jreleaser/standalone`](https://github.com/jreleaser/jreleaser)
- [`pnpm/pnpm`](https://github.com/pnpm/pnpm)
- [`suzuki-shunsuke/cmdx`](https://github.com/suzuki-shunsuke/cmdx)
- [`suzuki-shunsuke/ghir`](https://github.com/suzuki-shunsuke/ghir)
- [`twpayne/chezmoi`](https://github.com/twpayne/chezmoi)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants