Skip to content

Commit

Permalink
Support python find --script
Browse files Browse the repository at this point in the history
  • Loading branch information
InSyncWithFoo committed Mar 2, 2025
1 parent 461f4d9 commit 505faca
Show file tree
Hide file tree
Showing 7 changed files with 231 additions and 13 deletions.
10 changes: 10 additions & 0 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4615,6 +4615,16 @@ pub struct PythonFindArgs {

#[arg(long, overrides_with("system"), hide = true)]
pub no_system: bool,

/// Find the environment for a Python script, rather than the current project.
#[arg(
long,
conflicts_with = "request",
conflicts_with = "no_project",
conflicts_with = "system",
conflicts_with = "no_system"
)]
pub script: Option<PathBuf>,
}

#[derive(Args)]
Expand Down
1 change: 1 addition & 0 deletions crates/uv/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub(crate) use project::tree::tree;
pub(crate) use publish::publish;
pub(crate) use python::dir::dir as python_dir;
pub(crate) use python::find::find as python_find;
pub(crate) use python::find::find_script as python_find_script;
pub(crate) use python::install::install as python_install;
pub(crate) use python::list::list as python_list;
pub(crate) use python::pin::pin as python_pin;
Expand Down
44 changes: 42 additions & 2 deletions crates/uv/src/commands/python/find.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,20 @@ use std::path::Path;

use uv_cache::Cache;
use uv_fs::Simplified;
use uv_python::{EnvironmentPreference, PythonInstallation, PythonPreference, PythonRequest};
use uv_python::{
EnvironmentPreference, PythonDownloads, PythonInstallation, PythonPreference, PythonRequest,
};
use uv_scripts::Pep723ItemRef;
use uv_settings::PythonInstallMirrors;
use uv_warnings::{warn_user, warn_user_once};
use uv_workspace::{DiscoveryOptions, VirtualProject, WorkspaceError};

use crate::commands::{
project::{validate_project_requires_python, WorkspacePython},
project::{validate_project_requires_python, ScriptInterpreter, WorkspacePython},
ExitStatus,
};
use crate::printer::Printer;
use crate::settings::NetworkSettings;

/// Find a Python interpreter.
pub(crate) async fn find(
Expand Down Expand Up @@ -85,3 +91,37 @@ pub(crate) async fn find(

Ok(ExitStatus::Success)
}

pub(crate) async fn find_script(
script: Pep723ItemRef<'_>,
network_settings: &NetworkSettings,
python_preference: PythonPreference,
python_downloads: PythonDownloads,
no_config: bool,
cache: &Cache,
printer: Printer,
) -> Result<ExitStatus> {
match ScriptInterpreter::discover(
script,
None,
network_settings,
python_preference,
python_downloads,
&PythonInstallMirrors::default(),
no_config,
Some(false),
cache,
printer,
)
.await
{
Err(_) | Ok(ScriptInterpreter::Interpreter(_)) => Ok(ExitStatus::Failure),

Ok(ScriptInterpreter::Environment(environment)) => {
let path = environment.interpreter().sys_executable();
println!("{}", std::path::absolute(path)?.simplified_display());

Ok(ExitStatus::Success)
}
}
}
61 changes: 50 additions & 11 deletions crates/uv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use uv_cli::{PythonCommand, PythonNamespace, ToolCommand, ToolNamespace, TopLeve
use uv_cli::{SelfCommand, SelfNamespace, SelfUpdateArgs};
use uv_fs::{Simplified, CWD};
use uv_requirements::RequirementsSource;
use uv_scripts::{Pep723Error, Pep723Item, Pep723Metadata, Pep723Script};
use uv_scripts::{Pep723Error, Pep723Item, Pep723ItemRef, Pep723Metadata, Pep723Script};
use uv_settings::{Combine, FilesystemOptions, Options};
use uv_static::EnvVars;
use uv_warnings::{warn_user, warn_user_once};
Expand Down Expand Up @@ -241,6 +241,32 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
},
_ => None,
}
} else if let Commands::Python(uv_cli::PythonNamespace {
command:
PythonCommand::Find(uv_cli::PythonFindArgs {
script: Some(script),
..
}),
}) = &*cli.command
{
match Pep723Script::read(&script).await {
Ok(Some(script)) => Some(Pep723Item::Script(script)),
Ok(None) => {
bail!(
"`{}` does not contain a PEP 723 metadata tag; run `{}` to initialize the script",
script.user_display().cyan(),
format!("uv init --script {}", script.user_display()).green()
)
}
Err(Pep723Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => {
bail!(
"Failed to read `{}` (not found); run `{}` to create a PEP 723 script",
script.user_display().cyan(),
format!("uv init --script {}", script.user_display()).green()
)
}
Err(err) => return Err(err.into()),
}
} else {
None
};
Expand Down Expand Up @@ -1214,16 +1240,29 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
// Initialize the cache.
let cache = cache.init()?;

commands::python_find(
&project_dir,
args.request,
args.no_project,
cli.top_level.no_config,
args.system,
globals.python_preference,
&cache,
)
.await
if let Some(Pep723Item::Script(script)) = script {
commands::python_find_script(
Pep723ItemRef::Script(&script),
&globals.network_settings,
globals.python_preference,
globals.python_downloads,
cli.top_level.no_config,
&cache,
printer,
)
.await
} else {
commands::python_find(
&project_dir,
args.request,
args.no_project,
cli.top_level.no_config,
args.system,
globals.python_preference,
&cache,
)
.await
}
}
Commands::Python(PythonNamespace {
command: PythonCommand::Pin(args),
Expand Down
1 change: 1 addition & 0 deletions crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,7 @@ impl PythonFindSettings {
no_project,
system,
no_system,
script: _,
} = args;

Self {
Expand Down
125 changes: 125 additions & 0 deletions crates/uv/tests/it/python_find.rs
Original file line number Diff line number Diff line change
Expand Up @@ -663,3 +663,128 @@ fn python_find_venv_invalid() {
----- stderr -----
"###);
}

#[test]
fn python_find_script() {
let context = TestContext::new("3.13");
let filters = context
.filters()
.into_iter()
.chain(vec![(
r"environments-v2/[\w-]+",
"environments-v2/[HASHEDNAME]",
)])
.collect::<Vec<_>>();

uv_snapshot!(filters, context.init().arg("--script").arg("foo.py"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Initialized script at `foo.py`
"###);

uv_snapshot!(filters, context.sync().arg("--script").arg("foo.py"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Creating script environment at: [CACHE_DIR]/environments-v2/[HASHEDNAME]
"###);


if cfg!(windows) {
uv_snapshot!(filters, context.python_find().arg("--script").arg("foo.py"), @r###"
success: true
exit_code: 0
----- stdout -----
[CACHE_DIR]/environments-v2/[HASHEDNAME]/Scripts/python.exe
----- stderr -----
"###);
} else {
uv_snapshot!(filters, context.python_find().arg("--script").arg("foo.py"), @r###"
success: true
exit_code: 0
----- stdout -----
[CACHE_DIR]/environments-v2/[HASHEDNAME]/bin/python3
----- stderr -----
"###);
}
}

#[test]
fn python_find_script_failure() {
let context = TestContext::new_with_versions(&[]);

let script = context.temp_dir.child("foo.py");

script
.write_str(indoc! {r"
# /// script
# dependencies = []
# ///
"})
.unwrap();

uv_snapshot!(context.filters(), context.python_find().arg("--script").arg("foo.py"), @r###"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
"###);
}

#[test]
fn python_find_script_no_such_version() {
let context = TestContext::new("3.13");
let filters = context
.filters()
.into_iter()
.chain(vec![(
r"environments-v2/[\w-]+",
"environments-v2/[HASHEDNAME]",
)])
.collect::<Vec<_>>();

let script = context.temp_dir.child("foo.py");

script
.write_str(indoc! {r#"
# /// script
# requires-python = ">=3.13"
# dependencies = []
# ///
"#})
.unwrap();

uv_snapshot!(filters, context.sync().arg("--script").arg("foo.py"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Creating script environment at: [CACHE_DIR]/environments-v2/[HASHEDNAME]
"###);

script
.write_str(indoc! {r#"
# /// script
# requires-python = ">=3.14"
# dependencies = []
# ///
"#})
.unwrap();

uv_snapshot!(filters, context.python_find().arg("--script").arg("foo.py"), @r###"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
"###);
}
2 changes: 2 additions & 0 deletions docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -5065,6 +5065,8 @@ uv python find [OPTIONS] [REQUEST]
</ul>
</dd><dt id="uv-python-find--quiet"><a href="#uv-python-find--quiet"><code>--quiet</code></a>, <code>-q</code></dt><dd><p>Do not print any output</p>

</dd><dt id="uv-python-find--script"><a href="#uv-python-find--script"><code>--script</code></a> <i>script</i></dt><dd><p>Find the environment for a Python script, rather than the current project</p>

</dd><dt id="uv-python-find--system"><a href="#uv-python-find--system"><code>--system</code></a></dt><dd><p>Only find system Python interpreters.</p>

<p>By default, uv will report the first Python interpreter it would use, including those in an active virtual environment or a virtual environment in the current working directory or any parent directory.</p>
Expand Down

0 comments on commit 505faca

Please sign in to comment.