Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support uv tool upgrade --python <PYTHON> #6448

Closed
wants to merge 2 commits into from
Closed
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
13 changes: 13 additions & 0 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2891,6 +2891,19 @@ pub struct ToolUpgradeArgs {

#[command(flatten)]
pub build: BuildArgs,

/// The Python interpreter to use to install the tool environment.
///
/// See `uv help python` for details on Python discovery and supported
/// request formats.
#[arg(
long,
short,
env = "UV_PYTHON",
verbatim_doc_comment,
help_heading = "Python options"
)]
pub python: Option<String>,
}

#[derive(Args)]
Expand Down
42 changes: 38 additions & 4 deletions crates/uv/src/commands/tool/upgrade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,19 @@ use owo_colors::OwoColorize;
use tracing::debug;

use uv_cache::Cache;
use uv_client::Connectivity;
use uv_client::{BaseClientBuilder, Connectivity};
use uv_configuration::Concurrency;
use uv_normalize::PackageName;
use uv_python::{
EnvironmentPreference, PythonDownloads, PythonInstallation, PythonPreference, PythonRequest,
};
use uv_requirements::RequirementsSpecification;
use uv_settings::{Combine, ResolverInstallerOptions, ToolOptions};
use uv_tool::InstalledTools;

use crate::commands::pip::loggers::{SummaryResolveLogger, UpgradeInstallLogger};
use crate::commands::project::{update_environment, EnvironmentUpdate};
use crate::commands::reporters::PythonDownloadReporter;
use crate::commands::tool::common::remove_entrypoints;
use crate::commands::{tool::common::install_executables, ExitStatus, SharedState};
use crate::printer::Printer;
Expand All @@ -22,18 +26,22 @@ use crate::settings::ResolverInstallerSettings;
/// Upgrade a tool.
pub(crate) async fn upgrade(
name: Option<PackageName>,
connectivity: Connectivity,
args: ResolverInstallerOptions,
python: Option<String>,
filesystem: ResolverInstallerOptions,
python_preference: PythonPreference,
python_downloads: PythonDownloads,
connectivity: Connectivity,
concurrency: Concurrency,
native_tls: bool,
cache: &Cache,

printer: Printer,
) -> Result<ExitStatus> {
// Initialize any shared state.
let state = SharedState::default();

let python_request = python.as_deref().map(PythonRequest::parse);

let installed_tools = InstalledTools::from_settings()?.init()?;
let _lock = installed_tools.acquire_lock()?;

Expand Down Expand Up @@ -84,7 +92,7 @@ pub(crate) async fn upgrade(
}
};

let existing_environment = match installed_tools.get_environment(&name, cache) {
let mut existing_environment = match installed_tools.get_environment(&name, cache) {
Ok(Some(environment)) => environment,
Ok(None) => {
let install_command = format!("uv tool install {name}");
Expand All @@ -108,6 +116,32 @@ pub(crate) async fn upgrade(
}
};

if let Some(python_request) = &python_request {
if !python_request.satisfied(existing_environment.interpreter(), cache) {
debug!("Requested `{python_request}` not satisfied; reinstalling");

let client_builder = BaseClientBuilder::new()
.connectivity(connectivity)
.native_tls(native_tls);

let reporter = PythonDownloadReporter::single(printer);

let interpreter = PythonInstallation::find_or_download(
Some(python_request.clone()),
EnvironmentPreference::OnlySystem,
python_preference,
python_downloads,
&client_builder,
cache,
Some(&reporter),
)
.await?
.into_interpreter();

existing_environment = installed_tools.create_environment(&name, interpreter)?;
}
}

// Resolve the appropriate settings, preferring: CLI > receipt > user.
let options = args.clone().combine(
ResolverInstallerOptions::from(existing_tool_receipt.options().clone())
Expand Down
5 changes: 4 additions & 1 deletion crates/uv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -830,9 +830,12 @@ async fn run(cli: Cli) -> Result<ExitStatus> {

commands::tool_upgrade(
args.name,
globals.connectivity,
args.args,
args.python,
args.filesystem,
globals.python_preference,
globals.python_downloads,
globals.connectivity,
globals.concurrency,
globals.native_tls,
&cache,
Expand Down
3 changes: 3 additions & 0 deletions crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ impl ToolInstallSettings {
pub(crate) struct ToolUpgradeSettings {
pub(crate) name: Option<PackageName>,
pub(crate) args: ResolverInstallerOptions,
pub(crate) python: Option<String>,
pub(crate) filesystem: ResolverInstallerOptions,
}

Expand All @@ -400,6 +401,7 @@ impl ToolUpgradeSettings {
pub(crate) fn resolve(args: ToolUpgradeArgs, filesystem: Option<FilesystemOptions>) -> Self {
let ToolUpgradeArgs {
name,
python,
all,
mut installer,
build,
Expand All @@ -422,6 +424,7 @@ impl ToolUpgradeSettings {
Self {
name: name.filter(|_| !all),
args,
python,
filesystem,
}
}
Expand Down
46 changes: 46 additions & 0 deletions crates/uv/tests/tool_upgrade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -423,3 +423,49 @@ fn test_tool_upgrade_with() {
+ pytz==2024.1
"###);
}

/// Upgrade a tool, with a different requested Python version.
#[test]
fn test_tool_upgrade_python_version() {
let context = TestContext::new_with_versions(&["3.11", "3.12"])
.with_filtered_counts()
.with_filtered_exe_suffix();
let tool_dir = context.temp_dir.child("tools");
let bin_dir = context.temp_dir.child("bin");

// Install `babel` with Python 3.11.
uv_snapshot!(context.filters(), context.tool_install()
.arg("babel")
.arg("--python=3.11")
.env("UV_TOOL_DIR", tool_dir.as_os_str())
.env("XDG_BIN_HOME", bin_dir.as_os_str())
.env("PATH", bin_dir.as_os_str()), @r###"
success: true
exit_code: 0
----- stdout -----

----- stderr -----
Resolved [N] packages in [TIME]
Prepared [N] packages in [TIME]
Installed [N] packages in [TIME]
+ babel==2.14.0
Installed 1 executable: pybabel
"###);

// Upgrade `babel` with Python 3.12.
uv_snapshot!(context.filters(), context.tool_upgrade()
.arg("babel")
.arg("--python=3.12")
.env("UV_TOOL_DIR", tool_dir.as_os_str())
.env("XDG_BIN_HOME", bin_dir.as_os_str())
.env("PATH", bin_dir.as_os_str()), @r###"
success: true
exit_code: 0
----- stdout -----

----- stderr -----
Added babel v2.14.0
+ babel==2.14.0
Installed 1 executable: pybabel
"###);
}
4 changes: 4 additions & 0 deletions docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -2469,6 +2469,10 @@ uv tool upgrade [OPTIONS] <NAME>

<li><code>if-necessary-or-explicit</code>: Allow pre-release versions if all versions of a package are pre-release, or if the package has an explicit pre-release marker in its version requirements</li>
</ul>
</dd><dt><code>--python</code>, <code>-p</code> <i>python</i></dt><dd><p>The Python interpreter to use to install the tool environment.</p>

<p>See <a href="#uv-python">uv python</a> for details on Python discovery and supported request formats.</p>

</dd><dt><code>--python-preference</code> <i>python-preference</i></dt><dd><p>Whether to prefer uv-managed or system Python installations.</p>

<p>By default, uv prefers using Python versions it manages. However, it will use system Python installations if a uv-managed Python is not installed. This option allows prioritizing or ignoring system Python installations.</p>
Expand Down
Loading