Skip to content

Conversation

@AlexWaygood
Copy link
Member

Summary

If a user passes --python=<virtual_environment> and doesn't specify a Python version on the CLI or in a configuration file, we currently use the metadata in that virtual environment's pyvenv.cfg file to infer the Python version we should use for resolving modules and types. We currently do not attempt to infer the Python version if a user passes --python=<system_installation>, but it's easy enough to do that as well, providing the user is not on a Windows machine. This PR implements that.

Test Plan

CLI integration tests, and manual testing.

Screenshot of what this looks like on the CLI:

image

@AlexWaygood AlexWaygood added the ty Multi-file analysis & type inference label Jun 8, 2025
@AlexWaygood AlexWaygood force-pushed the alex/system-python-version branch from 7a440de to 14ea91b Compare June 8, 2025 13:30
@AlexWaygood AlexWaygood requested review from zanieb and removed request for carljm, dcreager and sharkdp June 8, 2025 13:31
@github-actions
Copy link
Contributor

github-actions bot commented Jun 8, 2025

mypy_primer results

No ecosystem changes detected ✅

@github-actions
Copy link
Contributor

github-actions bot commented Jun 8, 2025

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

✅ ecosystem check detected no linter changes.

Formatter (stable)

✅ ecosystem check detected no format changes.

Formatter (preview)

✅ ecosystem check detected no format changes.

Copy link
Member

@MichaReiser MichaReiser left a comment

Choose a reason for hiding this comment

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

What makes it hard if the user is on a windows machine? I'd prefer if the CLI has consistent behavior across platforms.

@AlexWaygood
Copy link
Member Author

What makes it hard if the user is on a windows machine?

Apologies — I left several comments about this in the source code but forgot to mention it in my PR description.

On Unix, the site-packages directory is always located at <sys.prefix>/lib/pythonX.Y/site-packages (with minor variations if it's a PyPy installation or a free-threaded installation), so we can just look at the parent directory of the resolved site-packages directory to get the information we need. On Windows, the site-packages directory is always located at <sys.prefix>/Lib/site-packages, so we can't infer the information we need from the directory structure of the installation.

I'd prefer if the CLI has consistent behavior across platforms.

Yeah, I also wasn't totally sure if this was worth the inconsistency. I think being able to infer the Python version correctly from the minimum number of config options is very high-value, though, and chatting with @zanieb at PyCon they also thought that this was probably worth it

@zanieb
Copy link
Member

zanieb commented Jun 9, 2025

Yeah on Windows you'd need to query the interpreter to infer the version. Or.. maybe you could get away with reading the registry and matching paths (but that seems more complicated than it's worth here!)

@AlexWaygood
Copy link
Member Author

Or.. maybe you could get away with reading the registry and matching paths (but that seems more complicated than it's worth here!)

yeah I definitely don't want to do that 😆

Copy link
Member

@MichaReiser MichaReiser left a comment

Choose a reason for hiding this comment

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

Ty

Mainly a few comments around terminology.

Comment on lines 130 to 131
let (major, minor) = s.split_once('.').ok_or(())?;
Self::try_from((major, minor)).map_err(|_| ())
Copy link
Member

Choose a reason for hiding this comment

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

Nit: Not sure if it's worth it because it's so simple but this is somewhat duplicated now with the serde deserialization logic below.

Copy link
Member Author

Choose a reason for hiding this comment

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

This was bothering me too, especially because we also had duplicated logic in this third place:

fn python_version_from_versions_file_string(
s: &str,
) -> Result<PythonVersion, TypeshedVersionsParseErrorKind> {
let mut parts = s.split('.').map(str::trim);
let (Some(major), Some(minor), None) = (parts.next(), parts.next(), parts.next()) else {
return Err(TypeshedVersionsParseErrorKind::UnexpectedNumberOfPeriods(
s.to_string(),
));
};
PythonVersion::try_from((major, minor)).map_err(|int_parse_error| {
TypeshedVersionsParseErrorKind::IntegerParsingFailure {
version: s.to_string(),
err: int_parse_error,
}
})
}

I've unified the three implementations.

@AlexWaygood
Copy link
Member Author

AlexWaygood commented Jun 9, 2025

Apologies for the terrible image quality of this screenshot (taken on an ancient Windows machine I have hanging around that does not have -- and will never have -- Rust installed on it). But it does look like it is possible to query the metadata of a Python binary on Windows without actually invoking that binary in a subprocess. The "File version" and "Product version" fields here both contain the Python version in them (obtained here by right-clicking on the .exe file in Windows Explorer, clicking on "properties", and then nagivating to the "Details" tab).

FDF1B5F7-4BFE-408C-B0F3-4DF8645C621F_1_201_a

The metadata appears consistent regardless of whether the binary was installed using "official" python.org installers or using uv.

From what I can tell, though, querying these .exe-file properties from Rust might not be trivial. So I'll definitely pass on it for now.

@charliermarsh
Copy link
Member

Oh that's cool.

@zanieb
Copy link
Member

zanieb commented Jun 9, 2025

That is cool indeed. cc @konstin we could use this to filter Python binaries more aggressively on Windows without querying the interpreter!

@AlexWaygood
Copy link
Member Author

Here's a somewhat higher-quality screenshot since that last one appears to have generated some excitement 😆
ABF429DA-81BD-40D2-8B3E-13AFB1AE90B4_1_201_a

@AlexWaygood AlexWaygood force-pushed the alex/system-python-version branch from 14ea91b to a572b1e Compare June 11, 2025 10:59
@AlexWaygood AlexWaygood requested a review from MichaReiser June 11, 2025 11:15
@MichaReiser
Copy link
Member

Is there anything specific you want me to re-review?

@AlexWaygood
Copy link
Member Author

Is there anything specific you want me to re-review?

Are you okay with my response in #18550 (comment)?

crates/ruff_python_ast/src/python_version.rs and crates/ty_python_semantic/src/module_resolver/typeshed.rs have also seen significant changes since you last reviewed, as a result of your comment at #18550 (comment)!

@AlexWaygood AlexWaygood enabled auto-merge (squash) June 11, 2025 14:29
@AlexWaygood AlexWaygood merged commit e84406d into main Jun 11, 2025
31 checks passed
@AlexWaygood AlexWaygood deleted the alex/system-python-version branch June 11, 2025 14:32
@MichaReiser
Copy link
Member

Sorry, I was in meetings. Do you still want me to re-review or are you okay, considering that you merged the change 😆

@AlexWaygood
Copy link
Member Author

AlexWaygood commented Jun 11, 2025

Ohh sorry! Still might be good for you to give the changes in crates/ruff_python_ast/src/python_version.rs and crates/ty_python_semantic/src/module_resolver/typeshed.rs a quick once-over (unifying our various PythonVersion deserialization methods) -- happy to revert/modify any of those changes if you dislike them!

@MichaReiser
Copy link
Member

Okay, I'll do so tomorrow

dcreager added a commit that referenced this pull request Jun 12, 2025
* main:
  [ty] Add some "inside string" tests for `object.<CURSOR>` completions
  [ty] Pull types on synthesized Python files created by mdtest (#18539)
  Update Rust crate anstyle to v1.0.11 (#18583)
  [`pyupgrade`] Fix `super(__class__, self)` detection in UP008 (super-call-with-parameters) (#18478)
  [ty] Generate the top and bottom materialization of a type (#18594)
  `SourceOrderVisitor` should visit the `Identifier` part of the `PatternKeyword` node (#18635)
  Update salsa (#18636)
  [ty] Update mypy_primer doc (#18638)
  [ty] Improve support for `object.<CURSOR>` completions
  [ty] Add `CoveringNode::find_last`
  [ty] Refactor covering node representation
  [ty] Infer the Python version from `--python=<system installation>` on Unix (#18550)
  [`flake8-return`] Fix `RET504` autofix generating a syntax error (#18428)
  Fix incorrect salsa `return_ref` attribute (#18605)
  Move corpus tests to `ty_python_semantic` (#18609)
  [`pyupgrade`] Don't offer fix for `Optional[None]` in non-pep604-annotation-optional (`UP045)` or non-pep604-annotation-union (`UP007`) (#18545)
  [`pep8-naming`] Suppress fix for `N804` and `N805` if the recommend name is already used (#18472)
  [`ruff`] skip fix for `RUF059` if dummy name is already bound (unused-unpacked-variable) (#18509)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants