Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions crates/ty/docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,11 @@ ty check [OPTIONS] [PATH]...
</ul></dd><dt id="ty-check--project"><a href="#ty-check--project"><code>--project</code></a> <i>project</i></dt><dd><p>Run the command within the given project directory.</p>
<p>All <code>pyproject.toml</code> files will be discovered by walking up the directory tree from the given project directory, as will the project's virtual environment (<code>.venv</code>) unless the <code>venv-path</code> option is set.</p>
<p>Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.</p>
</dd><dt id="ty-check--python"><a href="#ty-check--python"><code>--python</code></a> <i>path</i></dt><dd><p>Path to the Python installation from which ty resolves type information and third-party dependencies.</p>
<p>If not specified, ty will look at the <code>VIRTUAL_ENV</code> environment variable.</p>
<p>ty will search in the path's <code>site-packages</code> directories for type information and third-party imports.</p>
<p>This option is commonly used to specify the path to a virtual environment.</p>
</dd><dt id="ty-check--python"><a href="#ty-check--python"><code>--python</code></a> <i>path</i></dt><dd><p>Path to the Python environment.</p>
<p>ty uses the Python environment to resolve type information and third-party dependencies.</p>
<p>If not specified, ty will attempt to infer it from the <code>VIRTUAL_ENV</code> environment variable or discover a <code>.venv</code> directory in the project root or working directory.</p>
<p>If a path to a Python interpreter is provided, e.g., <code>.venv/bin/python3</code>, ty will attempt to find an environment two directories up from the interpreter's path, e.g., <code>.venv</code>. At this time, ty does not invoke the interpreter to determine the location of the environment. This means that ty will not resolve dynamic executables such as a shim.</p>
<p>ty will search in the resolved environments's <code>site-packages</code> directories for type information and third-party imports.</p>
</dd><dt id="ty-check--python-platform"><a href="#ty-check--python-platform"><code>--python-platform</code></a>, <code>--platform</code> <i>platform</i></dt><dd><p>Target platform to assume when resolving types.</p>
<p>This is used to specialize the type of <code>sys.platform</code> and will affect the visibility of platform-specific functions and attributes. If the value is set to <code>all</code>, no assumptions are made about the target platform. If unspecified, the current system's platform will be used.</p>
</dd><dt id="ty-check--python-version"><a href="#ty-check--python-version"><code>--python-version</code></a>, <code>--target-version</code> <i>version</i></dt><dd><p>Python version to assume when resolving types</p>
Expand Down
16 changes: 11 additions & 5 deletions crates/ty/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,20 @@ pub(crate) struct CheckCommand {
#[arg(long, value_name = "PROJECT")]
pub(crate) project: Option<SystemPathBuf>,

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

Expand Down
27 changes: 27 additions & 0 deletions crates/ty_python_semantic/src/module_resolver/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,33 @@ impl SearchPaths {
.and_then(|env| env.site_packages_directories(system))?
}

PythonPath::Resolve(target, origin) => {
tracing::debug!("Resolving {origin}: {target}");

let root = system
// If given a file, assume it's a Python executable, e.g., `.venv/bin/python3`,
// and search for a virtual environment in the root directory. Ideally, we'd
// invoke the target to determine `sys.prefix` here, but that's more complicated
// and may be deferred to uv.
.is_file(target)
Copy link
Member

Choose a reason for hiding this comment

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

Should we also test if the file has the executable bit set (path_metadata instead of is_file)

Copy link
Member Author

@zanieb zanieb May 12, 2025

Choose a reason for hiding this comment

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

Seems reasonable, but is a little more painful cross-platform for little gain since we're just going to fail downstream anyway if we can't find site-packages

Copy link
Member Author

Choose a reason for hiding this comment

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

Let me know if you think it's worth pursuing regardless.

.then(|| target.as_path())
.take_if(|target| {
// Avoid using the target if it doesn't look like a Python executable, e.g.,
// to deny cases like `.venv/bin/foo`
target
.file_name()
.is_some_and(|name| name.starts_with("python"))
Copy link
Member Author

@zanieb zanieb May 8, 2025

Choose a reason for hiding this comment

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

We could be more strict here, and require python.exe on Windows and python | python3 | python3.XX on Unix. The last case is a bit annoying. We use a regular expression in uv, but I think that's probably not worth moving here yet? We might end up needing it here for nice error messages for other cases regardless though.

})
.and_then(SystemPath::parent)
.and_then(SystemPath::parent)
// If not a file, use the path as given and allow let `PythonEnvironment::new`
// handle the error.
.unwrap_or(target);

PythonEnvironment::new(root, *origin, system)
.and_then(|venv| venv.site_packages_directories(system))?
}

PythonPath::Discover(root) => {
tracing::debug!("Discovering virtual environment in `{root}`");
let virtual_env_path = discover_venv_in(db.system(), root);
Expand Down
5 changes: 4 additions & 1 deletion crates/ty_python_semantic/src/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ pub enum PythonPath {
/// [`sys.prefix`]: https://docs.python.org/3/library/sys.html#sys.prefix
SysPrefix(SystemPathBuf, SysPrefixPathOrigin),

/// Resolve a path to an executable (or environment directory) into a usable environment.
Resolve(SystemPathBuf, SysPrefixPathOrigin),

/// Tries to discover a virtual environment in the given path.
Discover(SystemPathBuf),

Expand All @@ -161,6 +164,6 @@ impl PythonPath {
}

pub fn from_cli_flag(path: SystemPathBuf) -> Self {
Self::SysPrefix(path, SysPrefixPathOrigin::PythonCliFlag)
Self::Resolve(path, SysPrefixPathOrigin::PythonCliFlag)
}
}
13 changes: 9 additions & 4 deletions crates/ty_test/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,17 @@ pub(crate) struct Environment {
/// Additional search paths to consider when resolving modules.
pub(crate) extra_paths: Option<Vec<SystemPathBuf>>,

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