Skip to content

Read Python version specifier from hook repo pyproject.toml#1596

Merged
j178 merged 4 commits intoj178:masterfrom
shaanmajid:fix/pyproject-requires-python
Feb 9, 2026
Merged

Read Python version specifier from hook repo pyproject.toml#1596
j178 merged 4 commits intoj178:masterfrom
shaanmajid:fix/pyproject-requires-python

Conversation

@shaanmajid
Copy link
Collaborator

@shaanmajid shaanmajid commented Feb 7, 2026

Currently, when a Python hook has no language_version and its entry isn't a PEP 723 script, prek creates a venv with Any. uv will pick the Python version according to its default version resolution, which may be too old for the hook's dependencies in certain cases.

Now, in this case, prek will read requires-python from the hook repo's pyproject.toml as a fallback before defaulting to Any.

Precedence: PEP 723 > user config > pyproject.toml > default (Any)

Closes #1594

This only handles an explicit version specifier, but not possible narrower transitive dependencies. As a followup enhancement, we might want to retry uv pip install failures by extracting Python constraints from the resolver error. When uv pip install fails due to a Python version incompatibility, the error contains the constraint (e.g., does not satisfy Python>=3.10). prek could parse this, recreate the venv with --python ">=3.10", and retry. This handles transitive deps, (like zizmor-pre-commit today, where the wrapper has no requires-python but its dep zizmor requires >=3.10). The retry should be cheap; uv's HTTP cache means the second resolution is near instant since all package metadata was already fetched in the failed attempt.

Indeed, this is what uv itself already does in e.g. uvx or uv tool install. When initial resolution fails with NoSolution, uv calls refine_interpreter() which extracts a Python bound from the resolver error, then re-resolves with that inferred bound. This handles transitive constraints without any pre-resolution step. The uv code notes this is heuristic and can miss edge cases, but prek could adopt the same pattern for uv pip install failures.

All that being said, I figured that should probably be a follow-up PR, and is not immediately necessary.

When a Python hook has no `language_version` and its entry isn't a
PEP 723 script, prek creates a venv with `Any` — uv picks whatever
Python is available, which may be too old for the hook's dependencies.

Read `requires-python` from the hook repo's `pyproject.toml` as a
fallback before defaulting to `Any`.

Precedence: PEP 723 > user config > pyproject.toml > default (Any)

Closes j178#1594
@codecov
Copy link

codecov bot commented Feb 7, 2026

Codecov Report

❌ Patch coverage is 95.40816% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.62%. Comparing base (71790f3) to head (9493aa8).
⚠️ Report is 9 commits behind head on master.

Files with missing lines Patch % Lines
crates/prek/src/hook.rs 94.73% 5 Missing ⚠️
crates/prek/src/languages/python/pyproject.rs 95.78% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1596      +/-   ##
==========================================
- Coverage   91.66%   91.62%   -0.04%     
==========================================
  Files          93       96       +3     
  Lines       18312    18637     +325     
==========================================
+ Hits        16785    17077     +292     
- Misses       1527     1560      +33     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@prek-ci-bot
Copy link

prek-ci-bot bot commented Feb 7, 2026

📦 Cargo Bloat Comparison

Binary size change: +0.42% (23.7 MiB → 23.8 MiB)

Expand for cargo-bloat output

Head Branch Results

 File  .text    Size             Crate Name
 0.3%   0.7% 71.3KiB              prek prek::languages::<impl prek::config::Language>::run::{{closure}}::{{closure}}
 0.3%   0.7% 70.6KiB             prek? <prek::cli::Command as clap_builder::derive::Subcommand>::augment_subcommands
 0.3%   0.6% 65.6KiB              prek prek::languages::<impl prek::config::Language>::run::{{closure}}::{{closure}}
 0.2%   0.5% 51.2KiB annotate_snippets annotate_snippets::renderer::render::render
 0.2%   0.5% 50.5KiB              prek prek::languages::<impl prek::config::Language>::install::{{closure}}
 0.2%   0.4% 43.4KiB              prek prek::identify::by_extension::{{closure}}
 0.2%   0.4% 41.5KiB              prek prek::cli::run::run::run::{{closure}}
 0.2%   0.4% 38.8KiB              prek prek::run::{{closure}}
 0.1%   0.3% 32.0KiB             prek? <prek::cli::RunArgs as clap_builder::derive::Args>::augment_args
 0.1%   0.3% 28.4KiB      serde_saphyr saphyr_parser_bw::scanner::Scanner<T>::fetch_more_tokens
 0.1%   0.2% 24.8KiB             prek? <prek::config::_::<impl serde_core::de::Deserialize for prek::config::Config>::deserialize::__Visitor as serde_core::de::Visitor>::visit_map
 0.1%   0.2% 22.6KiB      serde_saphyr saphyr_parser_bw::scanner::Scanner<T>::fetch_more_tokens
 0.1%   0.2% 22.2KiB              prek prek::hooks::meta_hooks::MetaHooks::run::{{closure}}
 0.1%   0.2% 21.1KiB      clap_builder clap_builder::parser::parser::Parser::get_matches_with
 0.1%   0.2% 20.4KiB              prek prek::hooks::meta_hooks::MetaHooks::run::{{closure}}
 0.1%   0.2% 20.0KiB   cargo_metadata? <cargo_metadata::_::<impl serde_core::de::Deserialize for cargo_metadata::Package>::deserialize::__Visitor as serde_core::de::Visitor>::visit_map
 0.1%   0.2% 19.3KiB               std core::ptr::drop_in_place<prek::languages::<impl prek::config::Language>::install::{{closure}}>
 0.1%   0.2% 19.3KiB     serde_saphyr? <serde_saphyr::de::YamlDeserializer as serde_core::de::Deserializer>::deserialize_any
 0.1%   0.2% 19.2KiB              prek prek::archive::unzip::{{closure}}
 0.1%   0.2% 19.2KiB              prek <prek::languages::ruby::ruby::Ruby as prek::languages::LanguageImpl>::install::{{closure}}
38.5%  91.6%  9.2MiB                   And 20990 smaller methods. Use -n N to show more.
42.1% 100.0% 10.0MiB                   .text section size, the file size is 23.8MiB

Base Branch Results

 File  .text    Size             Crate Name
 0.3%   0.8% 80.9KiB             prek? <prek::cli::Command as clap_builder::derive::Subcommand>::augment_subcommands
 0.3%   0.7% 71.1KiB              prek prek::languages::<impl prek::config::Language>::run::{{closure}}::{{closure}}
 0.3%   0.6% 65.6KiB              prek prek::languages::<impl prek::config::Language>::run::{{closure}}::{{closure}}
 0.2%   0.5% 51.2KiB annotate_snippets annotate_snippets::renderer::render::render
 0.2%   0.5% 50.5KiB              prek prek::languages::<impl prek::config::Language>::install::{{closure}}
 0.2%   0.4% 41.2KiB              prek prek::cli::run::run::run::{{closure}}
 0.2%   0.4% 38.4KiB              prek prek::run::{{closure}}
 0.1%   0.3% 31.7KiB             prek? <prek::cli::RunArgs as clap_builder::derive::Args>::augment_args
 0.1%   0.3% 28.4KiB      serde_saphyr saphyr_parser_bw::scanner::Scanner<T>::fetch_more_tokens
 0.1%   0.3% 28.3KiB              prek prek::identify::by_extension::{{closure}}
 0.1%   0.2% 24.8KiB             prek? <prek::config::_::<impl serde_core::de::Deserialize for prek::config::Config>::deserialize::__Visitor as serde_core::de::Visitor>::visit_map
 0.1%   0.2% 22.6KiB      serde_saphyr saphyr_parser_bw::scanner::Scanner<T>::fetch_more_tokens
 0.1%   0.2% 22.1KiB              prek prek::hooks::meta_hooks::MetaHooks::run::{{closure}}
 0.1%   0.2% 22.1KiB              prek prek::hooks::meta_hooks::MetaHooks::run::{{closure}}
 0.1%   0.2% 21.1KiB      clap_builder clap_builder::parser::parser::Parser::get_matches_with
 0.1%   0.2% 20.0KiB   cargo_metadata? <cargo_metadata::_::<impl serde_core::de::Deserialize for cargo_metadata::Package>::deserialize::__Visitor as serde_core::de::Visitor>::visit_map
 0.1%   0.2% 19.8KiB              prek prek::archive::unzip::{{closure}}
 0.1%   0.2% 19.5KiB              prek prek::cli::run::filter::collect_files_from_args::{{closure}}
 0.1%   0.2% 19.3KiB               std core::ptr::drop_in_place<prek::languages::<impl prek::config::Language>::install::{{closure}}>
 0.1%   0.2% 19.2KiB              prek <prek::languages::ruby::ruby::Ruby as prek::languages::LanguageImpl>::install::{{closure}}
38.4%  91.6%  9.1MiB                   And 20956 smaller methods. Use -n N to show more.
41.9% 100.0%  9.9MiB                   .text section size, the file size is 23.7MiB

@jvllmr
Copy link

jvllmr commented Feb 7, 2026

Reading requires-python only does not resolve transitive dependencies. Wouldn't it be better to use uv sync to setup an envrionment and install packages if a pyproject.toml is present? uv sync can be used as a drop-in replacement to uv venv. There is no option to disable the lock generation though...

@shaanmajid
Copy link
Collaborator Author

Reading requires-python only does not resolve transitive dependencies. Wouldn't it be better to use uv sync to setup an envrionment and install packages if a pyproject.toml is present? uv sync can be used as a drop-in replacement to uv venv. There is no option to disable the lock generation though...

I think handling transitive dependencies is ideal, but a higher bar; ultimately, I think it's responsible for the hook maintainer to report a proper version bound.

I proposed a subsequent enhancement in the PR description inspired by uv's approach for e.g., uv tool install on how we might want to implement this.

Unfortunately, I don't think uv sync would work. It would require a refactor, and even then I don't think it's really possible for several reasons, including the lockfile issue you mention. (Also, many hooks don't provide a pyproject.toml, which uv sync requires.)

@shaanmajid shaanmajid marked this pull request as ready for review February 7, 2026 19:43
@shaanmajid shaanmajid requested a review from j178 as a code owner February 7, 2026 19:43
@shaanmajid shaanmajid changed the title fix: read Python version specifier from hook repo pyproject.toml Read Python version specifier from hook repo pyproject.toml Feb 8, 2026
@j178
Copy link
Owner

j178 commented Feb 8, 2026

uv sync needs a specific Python version to resolve dependencies, so the Python version has to be decided ahead of time. It can’t automatically figure out the right Python version before doing the resolution

Copy link
Owner

@j178 j178 left a comment

Choose a reason for hiding this comment

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

Great!

@j178 j178 merged commit 87b6a9d into j178:master Feb 9, 2026
48 of 49 checks passed
@shaanmajid shaanmajid deleted the fix/pyproject-requires-python branch February 9, 2026 04:46
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.

prek uses incorrect python version for zizmor

3 participants