From ba2a2dbf2ed03f0d1541aaf57fc15c8ebd0a9177 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 17 Jun 2025 11:30:52 -0500 Subject: [PATCH] Serialize Python requests for tools as canonicalized strings --- crates/uv-python/src/discovery.rs | 20 ++++++++++++++++++++ crates/uv-tool/src/tool.rs | 17 ++++++++++++----- crates/uv/src/commands/tool/common.rs | 10 +++++++--- crates/uv/src/commands/tool/install.rs | 8 +++++--- crates/uv/src/commands/tool/upgrade.rs | 4 ++-- 5 files changed, 46 insertions(+), 13 deletions(-) diff --git a/crates/uv-python/src/discovery.rs b/crates/uv-python/src/discovery.rs index 3858fd525d5a3..27853e3db3020 100644 --- a/crates/uv-python/src/discovery.rs +++ b/crates/uv-python/src/discovery.rs @@ -67,6 +67,26 @@ pub enum PythonRequest { Key(PythonDownloadRequest), } +impl<'a> serde::Deserialize<'a> for PythonRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'a>, + { + let s = String::deserialize(deserializer)?; + Ok(PythonRequest::parse(&s)) + } +} + +impl serde::Serialize for PythonRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let s = self.to_canonical_string(); + serializer.serialize_str(&s) + } +} + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Deserialize)] #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "clap", derive(clap::ValueEnum))] diff --git a/crates/uv-tool/src/tool.rs b/crates/uv-tool/src/tool.rs index df8571c945d34..cce3a2f581cd8 100644 --- a/crates/uv-tool/src/tool.rs +++ b/crates/uv-tool/src/tool.rs @@ -7,6 +7,7 @@ use toml_edit::{Array, Item, Table, Value, value}; use uv_distribution_types::Requirement; use uv_fs::{PortablePath, Simplified}; use uv_pypi_types::VerbatimParsedUrl; +use uv_python::PythonRequest; use uv_settings::ToolOptions; /// A tool entry. @@ -22,7 +23,7 @@ pub struct Tool { /// The build constraints requested by the user during installation. build_constraints: Vec, /// The Python requested by the user during installation. - python: Option, + python: Option, /// A mapping of entry point names to their metadata. entrypoints: Vec, /// The [`ToolOptions`] used to install this tool. @@ -40,7 +41,7 @@ struct ToolWire { overrides: Vec, #[serde(default)] build_constraint_dependencies: Vec, - python: Option, + python: Option, entrypoints: Vec, #[serde(default)] options: ToolOptions, @@ -164,7 +165,7 @@ impl Tool { constraints: Vec, overrides: Vec, build_constraints: Vec, - python: Option, + python: Option, entrypoints: impl Iterator, options: ToolOptions, ) -> Self { @@ -280,7 +281,13 @@ impl Tool { } if let Some(ref python) = self.python { - table.insert("python", value(python)); + table.insert( + "python", + value(serde::Serialize::serialize( + &python, + toml_edit::ser::ValueSerializer::new(), + )?), + ); } table.insert("entrypoints", { @@ -327,7 +334,7 @@ impl Tool { &self.build_constraints } - pub fn python(&self) -> &Option { + pub fn python(&self) -> &Option { &self.python } diff --git a/crates/uv/src/commands/tool/common.rs b/crates/uv/src/commands/tool/common.rs index 77aba86191d48..807225cbc5d08 100644 --- a/crates/uv/src/commands/tool/common.rs +++ b/crates/uv/src/commands/tool/common.rs @@ -158,14 +158,18 @@ pub(crate) async fn refine_interpreter( Ok(Some(interpreter)) } -/// Installs tool executables for a given package and handles any conflicts. -pub(crate) fn install_executables( +/// Finalizes a tool installation, after creation of an environment. +/// +/// Installs tool executables for a given package, handling any conflicts. +/// +/// Adds a receipt for the tool. +pub(crate) fn finalize_tool_install( environment: &PythonEnvironment, name: &PackageName, installed_tools: &InstalledTools, options: ToolOptions, force: bool, - python: Option, + python: Option, requirements: Vec, constraints: Vec, overrides: Vec, diff --git a/crates/uv/src/commands/tool/install.rs b/crates/uv/src/commands/tool/install.rs index 86b0d4bc69625..a65ad3af2aad3 100644 --- a/crates/uv/src/commands/tool/install.rs +++ b/crates/uv/src/commands/tool/install.rs @@ -33,7 +33,9 @@ use crate::commands::project::{ EnvironmentSpecification, PlatformState, ProjectError, resolve_environment, resolve_names, sync_environment, update_environment, }; -use crate::commands::tool::common::{install_executables, refine_interpreter, remove_entrypoints}; +use crate::commands::tool::common::{ + finalize_tool_install, refine_interpreter, remove_entrypoints, +}; use crate::commands::tool::{Target, ToolRequest}; use crate::commands::{diagnostics, reporters::PythonDownloadReporter}; use crate::printer::Printer; @@ -592,13 +594,13 @@ pub(crate) async fn install( } }; - install_executables( + finalize_tool_install( &environment, &from.name, &installed_tools, options, force || invalid_tool_receipt, - python, + python_request, requirements, constraints, overrides, diff --git a/crates/uv/src/commands/tool/upgrade.rs b/crates/uv/src/commands/tool/upgrade.rs index 9f4d3bcabc258..c930ecada7492 100644 --- a/crates/uv/src/commands/tool/upgrade.rs +++ b/crates/uv/src/commands/tool/upgrade.rs @@ -29,7 +29,7 @@ use crate::commands::project::{ }; use crate::commands::reporters::PythonDownloadReporter; use crate::commands::tool::common::remove_entrypoints; -use crate::commands::{ExitStatus, conjunction, tool::common::install_executables}; +use crate::commands::{ExitStatus, conjunction, tool::common::finalize_tool_install}; use crate::printer::Printer; use crate::settings::{NetworkSettings, ResolverInstallerSettings}; @@ -375,7 +375,7 @@ async fn upgrade_tool( remove_entrypoints(&existing_tool_receipt); // If we modified the target tool, reinstall the entrypoints. - install_executables( + finalize_tool_install( &environment, name, installed_tools,