From 53d19f8368154c201e5098678d82a4c49ee14e26 Mon Sep 17 00:00:00 2001 From: lipefree <43332207+lipefree@users.noreply.github.com> Date: Fri, 23 May 2025 20:00:42 +0200 Subject: [PATCH] [ty] Resolving Python path using `CONDA_PREFIX` variable to support Conda and Pixi (#18267) --- crates/ty/tests/cli.rs | 77 +++++++++++++++++++ crates/ty_project/src/metadata/options.rs | 5 ++ crates/ty_python_semantic/src/program.rs | 4 + .../ty_python_semantic/src/site_packages.rs | 4 +- 4 files changed, 89 insertions(+), 1 deletion(-) diff --git a/crates/ty/tests/cli.rs b/crates/ty/tests/cli.rs index d42ab07b45..6daa3d27d8 100644 --- a/crates/ty/tests/cli.rs +++ b/crates/ty/tests/cli.rs @@ -1555,6 +1555,83 @@ fn cli_config_args_invalid_option() -> anyhow::Result<()> { Ok(()) } +/// The `site-packages` directory is used by ty for external import. +/// Ty does the following checks to discover the `site-packages` directory in the order: +/// 1) If `VIRTUAL_ENV` environment variable is set +/// 2) If `CONDA_PREFIX` environment variable is set +/// 3) If a `.venv` directory exists at the project root +/// +/// This test is aiming at validating the logic around `CONDA_PREFIX`. +/// +/// A conda-like environment file structure is used +/// We test by first not setting the `CONDA_PREFIX` and expect a fail. +/// Then we test by setting `CONDA_PREFIX` to `conda-env` and expect a pass. +/// +/// ├── project +/// │ └── test.py +/// └── conda-env +/// └── lib +/// └── python3.13 +/// └── site-packages +/// └── package1 +/// └── __init__.py +/// +/// test.py imports package1 +/// And the command is run in the `project` directory. +#[test] +fn check_conda_prefix_var_to_resolve_path() -> anyhow::Result<()> { + let conda_package1_path = if cfg!(windows) { + "conda-env/Lib/site-packages/package1/__init__.py" + } else { + "conda-env/lib/python3.13/site-packages/package1/__init__.py" + }; + + let case = TestCase::with_files([ + ( + "project/test.py", + r#" + import package1 + "#, + ), + ( + conda_package1_path, + r#" + "#, + ), + ])?; + + assert_cmd_snapshot!(case.command().current_dir(case.root().join("project")), @r" + success: false + exit_code: 1 + ----- stdout ----- + error[unresolved-import]: Cannot resolve imported module `package1` + --> test.py:2:8 + | + 2 | import package1 + | ^^^^^^^^ + | + info: make sure your Python environment is properly configured: https://github.com/astral-sh/ty/blob/main/docs/README.md#python-environment + info: rule `unresolved-import` is enabled by default + + Found 1 diagnostic + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + + // do command : CONDA_PREFIX=/conda_env + assert_cmd_snapshot!(case.command().current_dir(case.root().join("project")).env("CONDA_PREFIX", case.root().join("conda-env")), @r" + success: true + exit_code: 0 + ----- stdout ----- + All checks passed! + + ----- stderr ----- + WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. + "); + Ok(()) +} + struct TestCase { _temp_dir: TempDir, _settings_scope: SettingsBindDropGuard, diff --git a/crates/ty_project/src/metadata/options.rs b/crates/ty_project/src/metadata/options.rs index 00bab6dd95..82cf45ab8b 100644 --- a/crates/ty_project/src/metadata/options.rs +++ b/crates/ty_project/src/metadata/options.rs @@ -186,6 +186,11 @@ impl Options { .ok() .map(PythonPath::from_virtual_env_var) }) + .or_else(|| { + std::env::var("CONDA_PREFIX") + .ok() + .map(PythonPath::from_conda_prefix_var) + }) .unwrap_or_else(|| PythonPath::Discover(project_root.to_path_buf())), } } diff --git a/crates/ty_python_semantic/src/program.rs b/crates/ty_python_semantic/src/program.rs index 74e662e355..ef059abff9 100644 --- a/crates/ty_python_semantic/src/program.rs +++ b/crates/ty_python_semantic/src/program.rs @@ -202,6 +202,10 @@ impl PythonPath { Self::SysPrefix(path.into(), SysPrefixPathOrigin::VirtualEnvVar) } + pub fn from_conda_prefix_var(path: impl Into) -> Self { + Self::Resolve(path.into(), SysPrefixPathOrigin::CondaPrefixVar) + } + pub fn from_cli_flag(path: SystemPathBuf) -> Self { Self::Resolve(path, SysPrefixPathOrigin::PythonCliFlag) } diff --git a/crates/ty_python_semantic/src/site_packages.rs b/crates/ty_python_semantic/src/site_packages.rs index e893c8817e..6204fff816 100644 --- a/crates/ty_python_semantic/src/site_packages.rs +++ b/crates/ty_python_semantic/src/site_packages.rs @@ -580,6 +580,7 @@ impl fmt::Display for SysPrefixPath { pub enum SysPrefixPathOrigin { PythonCliFlag, VirtualEnvVar, + CondaPrefixVar, Derived, LocalVenv, } @@ -590,7 +591,7 @@ impl SysPrefixPathOrigin { pub(crate) fn must_be_virtual_env(self) -> bool { match self { Self::LocalVenv | Self::VirtualEnvVar => true, - Self::PythonCliFlag | Self::Derived => false, + Self::PythonCliFlag | Self::Derived | Self::CondaPrefixVar => false, } } } @@ -600,6 +601,7 @@ impl Display for SysPrefixPathOrigin { match self { Self::PythonCliFlag => f.write_str("`--python` argument"), Self::VirtualEnvVar => f.write_str("`VIRTUAL_ENV` environment variable"), + Self::CondaPrefixVar => f.write_str("`CONDA_PREFIX` environment variable"), Self::Derived => f.write_str("derived `sys.prefix` path"), Self::LocalVenv => f.write_str("local virtual environment"), }