Skip to content

Commit c2f8c21

Browse files
committed
Update --python to accept paths to executables in virtual environments
1 parent c9031ce commit c2f8c21

File tree

4 files changed

+49
-10
lines changed

4 files changed

+49
-10
lines changed

crates/ty/src/args.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,19 @@ pub(crate) struct CheckCommand {
5151
#[arg(long, value_name = "PROJECT")]
5252
pub(crate) project: Option<SystemPathBuf>,
5353

54-
/// Path to the Python installation from which ty resolves type information and third-party dependencies.
54+
/// Path to the Python environment from which ty resolves type information and third-party
55+
/// dependencies.
5556
///
56-
/// If not specified, ty will look at the `VIRTUAL_ENV` environment variable.
57+
/// If not specified, ty will attempt to infer it from the `VIRTUAL_ENV` environment variable or
58+
/// discover a `.venv` directory in the project root or working directory.
5759
///
58-
/// ty will search in the path's `site-packages` directories for type information and
59-
/// third-party imports.
60+
/// If a path to a Python interpreter is provided, e.g., `.venv/bin/python3`, ty will attempt to
61+
/// find an environment two directories up from the interpreter's path, e.g., `.venv`. At this
62+
/// time, ty does not invoke the interpreter to determine the location of the environment. This
63+
/// means that ty will not resolve dynamic executables such as a shim.
6064
///
61-
/// This option is commonly used to specify the path to a virtual environment.
65+
/// ty will search in the given path's `site-packages` directories for type information and
66+
/// third-party imports.
6267
#[arg(long, value_name = "PATH")]
6368
pub(crate) python: Option<SystemPathBuf>,
6469

crates/ty_python_semantic/src/module_resolver/resolver.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,33 @@ impl SearchPaths {
247247
.and_then(|env| env.site_packages_directories(system))?
248248
}
249249

250+
PythonPath::Resolve(target, origin) => {
251+
tracing::debug!("Resolving {origin}: {target}");
252+
253+
let root = system
254+
// If given a file, assume it's a Python executable, e.g., `.venv/bin/python3`,
255+
// and search for a virtual environment in the root directory. Ideally, we'd
256+
// invoke the target to determine `sys.prefix` here, but that's more complicated
257+
// and may be deferred to uv.
258+
.is_file(target)
259+
.then(|| target.as_path())
260+
.take_if(|target| {
261+
// Avoid using the target if it doesn't look like a Python executable, e.g.,
262+
// to deny cases like `.venv/bin/foo`
263+
target
264+
.file_name()
265+
.is_some_and(|name| name.starts_with("python"))
266+
})
267+
.and_then(SystemPath::parent)
268+
.and_then(SystemPath::parent)
269+
// If not a file, use the path as given and allow let `PythonEnvironment::new`
270+
// handle the error.
271+
.unwrap_or(target);
272+
273+
PythonEnvironment::new(root, *origin, system)
274+
.and_then(|venv| venv.site_packages_directories(system))?
275+
}
276+
250277
PythonPath::Discover(root) => {
251278
tracing::debug!("Discovering virtual environment in `{root}`");
252279
let virtual_env_path = discover_venv_in(db.system(), root);

crates/ty_python_semantic/src/program.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,9 @@ pub enum PythonPath {
145145
/// [`sys.prefix`]: https://docs.python.org/3/library/sys.html#sys.prefix
146146
SysPrefix(SystemPathBuf, SysPrefixPathOrigin),
147147

148+
/// Resolve a path to an executable (or virtual environment) into a usable environment.
149+
Resolve(SystemPathBuf, SysPrefixPathOrigin),
150+
148151
/// Tries to discover a virtual environment in the given path.
149152
Discover(SystemPathBuf),
150153

@@ -161,6 +164,6 @@ impl PythonPath {
161164
}
162165

163166
pub fn from_cli_flag(path: SystemPathBuf) -> Self {
164-
Self::SysPrefix(path, SysPrefixPathOrigin::PythonCliFlag)
167+
Self::Resolve(path, SysPrefixPathOrigin::PythonCliFlag)
165168
}
166169
}

crates/ty_test/src/config.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,16 @@ pub(crate) struct Environment {
6868
/// Additional search paths to consider when resolving modules.
6969
pub(crate) extra_paths: Option<Vec<SystemPathBuf>>,
7070

71-
/// Path to the Python installation from which ty resolves type information and third-party dependencies.
71+
/// Path to the Python installation from which ty resolves type information and third-party
72+
/// dependencies.
7273
///
73-
/// ty will search in the path's `site-packages` directories for type information and
74-
/// third-party imports.
74+
/// If a path to a Python interpreter is provided, e.g., `.venv/bin/python3`, ty will attempt to
75+
/// find an environment two directories up from the interpreter's path, e.g., `.venv`. At this
76+
/// time, ty does not invoke the interpreter to determine the location of the environment. This
77+
/// means that ty will not resolve dynamic executables such as a shim.
7578
///
76-
/// This option is commonly used to specify the path to a virtual environment.
79+
/// ty will search in the given path's `site-packages` directories for type information and
80+
/// third-party imports.
7781
#[serde(skip_serializing_if = "Option::is_none")]
7882
pub python: Option<SystemPathBuf>,
7983
}

0 commit comments

Comments
 (0)