From e839aae435a9dc0c20cea6146369aac2c5b9a725 Mon Sep 17 00:00:00 2001 From: Liam Date: Sat, 15 Nov 2025 03:15:34 -0500 Subject: [PATCH 1/6] Prevent `uv export` from overwriting pyproject.toml --- crates/uv/src/commands/project/export.rs | 11 +++++++++++ crates/uv/tests/it/export.rs | 21 +++------------------ 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/crates/uv/src/commands/project/export.rs b/crates/uv/src/commands/project/export.rs index 1028feb85a0a3..d17445ece816f 100644 --- a/crates/uv/src/commands/project/export.rs +++ b/crates/uv/src/commands/project/export.rs @@ -293,6 +293,17 @@ pub(crate) async fn export( target.validate_extras(&extras)?; target.validate_groups(&groups)?; + if output_file + .as_deref() + .and_then(Path::file_name) + .is_some_and(|name| name.eq_ignore_ascii_case("pyproject.toml")) + { + return Err(anyhow!( + "`pyproject.toml` is reserved for project metadata; `{}` exports produce requirements files or `pylock.toml` lockfiles. Choose a different output filename.", + "uv export".green() + )); + } + // Write the resolved dependencies to the output channel. let mut writer = OutputWriter::new(!quiet || output_file.is_none(), output_file.as_deref()); diff --git a/crates/uv/tests/it/export.rs b/crates/uv/tests/it/export.rs index cb0ed4139daec..4e566efc267ba 100644 --- a/crates/uv/tests/it/export.rs +++ b/crates/uv/tests/it/export.rs @@ -4482,29 +4482,14 @@ fn pep_751_infer_output_format() -> Result<()> { Resolved 4 packages in [TIME] "#); - // TODO(charlie): Error on `pyproject.toml`. Right now, it's treated as `requirements.txt`. uv_snapshot!(context.filters(), context.export().arg("-o").arg("pyproject.toml"), @r" - success: true - exit_code: 0 + success: false + exit_code: 2 ----- stdout ----- - # This file was autogenerated by uv via the following command: - # uv export --cache-dir [CACHE_DIR] -o pyproject.toml - -e . - anyio==3.7.0 \ - --hash=sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce \ - --hash=sha256:eddca883c4175f14df8aedce21054bfca3adb70ffe76a9f607aef9d7fa2ea7f0 - # via project - idna==3.6 \ - --hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \ - --hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f - # via anyio - sniffio==1.3.1 \ - --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \ - --hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc - # via anyio ----- stderr ----- Resolved 4 packages in [TIME] + error: `pyproject.toml` is reserved for project metadata; `uv export` exports produce requirements files or `pylock.toml` lockfiles. Choose a different output filename. "); Ok(()) From 30d6409b0dd9e96dfe37c9f6a88815cbb95b68a6 Mon Sep 17 00:00:00 2001 From: Liam Date: Thu, 20 Nov 2025 12:22:14 -0500 Subject: [PATCH 2/6] Tweak message format --- crates/uv/src/commands/project/export.rs | 10 ++++++++-- crates/uv/tests/it/export.rs | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/uv/src/commands/project/export.rs b/crates/uv/src/commands/project/export.rs index d17445ece816f..536ccf2fe7396 100644 --- a/crates/uv/src/commands/project/export.rs +++ b/crates/uv/src/commands/project/export.rs @@ -3,6 +3,7 @@ use std::ffi::OsStr; use std::path::{Path, PathBuf}; use anyhow::{Context, Result, anyhow}; +use clap::ValueEnum; use itertools::Itertools; use owo_colors::OwoColorize; @@ -299,8 +300,13 @@ pub(crate) async fn export( .is_some_and(|name| name.eq_ignore_ascii_case("pyproject.toml")) { return Err(anyhow!( - "`pyproject.toml` is reserved for project metadata; `{}` exports produce requirements files or `pylock.toml` lockfiles. Choose a different output filename.", - "uv export".green() + "`pyproject.toml` is not a supported output format for {}. Supported formats: {}", + "uv export".green(), + ExportFormat::value_variants() + .iter() + .filter_map(|variant| variant.to_possible_value()) + .map(|value| value.get_name().to_string()) + .join(", ") )); } diff --git a/crates/uv/tests/it/export.rs b/crates/uv/tests/it/export.rs index 4e566efc267ba..15101bed809f6 100644 --- a/crates/uv/tests/it/export.rs +++ b/crates/uv/tests/it/export.rs @@ -4489,7 +4489,7 @@ fn pep_751_infer_output_format() -> Result<()> { ----- stderr ----- Resolved 4 packages in [TIME] - error: `pyproject.toml` is reserved for project metadata; `uv export` exports produce requirements files or `pylock.toml` lockfiles. Choose a different output filename. + error: `pyproject.toml` is not a supported output format for uv export. Supported formats: requirements.txt, pylock.toml "); Ok(()) From f0db0a94b28ce3dc538aded5a98259eda52b6024 Mon Sep 17 00:00:00 2001 From: konstin Date: Thu, 20 Nov 2025 19:10:32 +0100 Subject: [PATCH 3/6] Add backticks --- crates/uv/src/commands/project/export.rs | 18 +++++++++--------- crates/uv/tests/it/export.rs | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/uv/src/commands/project/export.rs b/crates/uv/src/commands/project/export.rs index 536ccf2fe7396..112e99742f5ba 100644 --- a/crates/uv/src/commands/project/export.rs +++ b/crates/uv/src/commands/project/export.rs @@ -98,7 +98,7 @@ pub(crate) async fn export( }, &workspace_cache, ) - .await? + .await? } else if let [name] = package.as_slice() { VirtualProject::Project( Workspace::discover(project_dir, &DiscoveryOptions::default(), &workspace_cache) @@ -112,7 +112,7 @@ pub(crate) async fn export( &DiscoveryOptions::default(), &workspace_cache, ) - .await?; + .await?; for name in &package { if !project.workspace().packages().contains_key(name) { @@ -159,8 +159,8 @@ pub(crate) async fn export( printer, preview, ) - .await? - .into_interpreter(), + .await? + .into_interpreter(), ExportTarget::Project(project) => ProjectInterpreter::discover( project.workspace(), project_dir, @@ -177,8 +177,8 @@ pub(crate) async fn export( printer, preview, ) - .await? - .into_interpreter(), + .await? + .into_interpreter(), }) }; @@ -213,9 +213,9 @@ pub(crate) async fn export( printer, preview, ) - .execute((&target).into()), + .execute((&target).into()), ) - .await + .await { Ok(result) => result.into_lock(), Err(ProjectError::Operation(err)) => { @@ -300,7 +300,7 @@ pub(crate) async fn export( .is_some_and(|name| name.eq_ignore_ascii_case("pyproject.toml")) { return Err(anyhow!( - "`pyproject.toml` is not a supported output format for {}. Supported formats: {}", + "`pyproject.toml` is not a supported output format for `{}`. Supported formats: {}", "uv export".green(), ExportFormat::value_variants() .iter() diff --git a/crates/uv/tests/it/export.rs b/crates/uv/tests/it/export.rs index 15101bed809f6..15e25bb19d968 100644 --- a/crates/uv/tests/it/export.rs +++ b/crates/uv/tests/it/export.rs @@ -4489,7 +4489,7 @@ fn pep_751_infer_output_format() -> Result<()> { ----- stderr ----- Resolved 4 packages in [TIME] - error: `pyproject.toml` is not a supported output format for uv export. Supported formats: requirements.txt, pylock.toml + error: `pyproject.toml` is not a supported output format for `uv export`. Supported formats: requirements.txt, pylock.toml "); Ok(()) From 85b961dfb09c8348ef8e752e95ba3fc024f9c390 Mon Sep 17 00:00:00 2001 From: konstin Date: Thu, 20 Nov 2025 19:12:09 +0100 Subject: [PATCH 4/6] Rustfmt sorry -.- I broke my rustup and then my IDE doesn't work properly. --- crates/uv/src/commands/project/export.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/uv/src/commands/project/export.rs b/crates/uv/src/commands/project/export.rs index 112e99742f5ba..7b3fa56b6dfc4 100644 --- a/crates/uv/src/commands/project/export.rs +++ b/crates/uv/src/commands/project/export.rs @@ -98,7 +98,7 @@ pub(crate) async fn export( }, &workspace_cache, ) - .await? + .await? } else if let [name] = package.as_slice() { VirtualProject::Project( Workspace::discover(project_dir, &DiscoveryOptions::default(), &workspace_cache) @@ -112,7 +112,7 @@ pub(crate) async fn export( &DiscoveryOptions::default(), &workspace_cache, ) - .await?; + .await?; for name in &package { if !project.workspace().packages().contains_key(name) { @@ -159,8 +159,8 @@ pub(crate) async fn export( printer, preview, ) - .await? - .into_interpreter(), + .await? + .into_interpreter(), ExportTarget::Project(project) => ProjectInterpreter::discover( project.workspace(), project_dir, @@ -177,8 +177,8 @@ pub(crate) async fn export( printer, preview, ) - .await? - .into_interpreter(), + .await? + .into_interpreter(), }) }; @@ -213,9 +213,9 @@ pub(crate) async fn export( printer, preview, ) - .execute((&target).into()), + .execute((&target).into()), ) - .await + .await { Ok(result) => result.into_lock(), Err(ProjectError::Operation(err)) => { From 5cd52d98b99f87812480aca1cc2a514856e85405 Mon Sep 17 00:00:00 2001 From: Liam Date: Thu, 20 Nov 2025 17:12:58 -0500 Subject: [PATCH 5/6] Fix clippy --- crates/uv/src/commands/project/export.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/uv/src/commands/project/export.rs b/crates/uv/src/commands/project/export.rs index 7b3fa56b6dfc4..5ba4a49b62713 100644 --- a/crates/uv/src/commands/project/export.rs +++ b/crates/uv/src/commands/project/export.rs @@ -304,7 +304,7 @@ pub(crate) async fn export( "uv export".green(), ExportFormat::value_variants() .iter() - .filter_map(|variant| variant.to_possible_value()) + .filter_map(clap::ValueEnum::to_possible_value) .map(|value| value.get_name().to_string()) .join(", ") )); From 8396e2274805873c0de3623b8b5c368b996cb951 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 20 Nov 2025 21:12:17 -0500 Subject: [PATCH 6/6] Fix test --- crates/uv/src/commands/project/export.rs | 2 +- crates/uv/tests/it/export.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/uv/src/commands/project/export.rs b/crates/uv/src/commands/project/export.rs index 1355adf3fa2f3..2d01d79248e7f 100644 --- a/crates/uv/src/commands/project/export.rs +++ b/crates/uv/src/commands/project/export.rs @@ -298,7 +298,7 @@ pub(crate) async fn export( .is_some_and(|name| name.eq_ignore_ascii_case("pyproject.toml")) { return Err(anyhow!( - "`pyproject.toml` is not a supported output format for `{}`. Supported formats: {}", + "`pyproject.toml` is not a supported output format for `{}` (supported formats: {})", "uv export".green(), ExportFormat::value_variants() .iter() diff --git a/crates/uv/tests/it/export.rs b/crates/uv/tests/it/export.rs index 6f6dfec93db01..c1b96bb47aee3 100644 --- a/crates/uv/tests/it/export.rs +++ b/crates/uv/tests/it/export.rs @@ -4489,7 +4489,7 @@ fn pep_751_infer_output_format() -> Result<()> { ----- stderr ----- Resolved 4 packages in [TIME] - error: `pyproject.toml` is not a supported output format for `uv export`. Supported formats: requirements.txt, pylock.toml + error: `pyproject.toml` is not a supported output format for `uv export` (supported formats: requirements.txt, pylock.toml, cyclonedx1.5) "); Ok(())