Reject non-PEP 751 filenames in `uv pip compile` and `uv export` (#13119)

We shouldn't let users create files that won't work in subsequent
commands.

Closes https://github.com/astral-sh/uv/issues/13117.
This commit is contained in:
Charlie Marsh 2025-04-28 16:42:03 -04:00 committed by Zanie Blue
parent e8524ebea4
commit 3ace372158
4 changed files with 92 additions and 2 deletions

View File

@ -1,5 +1,6 @@
use std::collections::{BTreeMap, BTreeSet};
use std::env;
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::Arc;
@ -33,7 +34,7 @@ use uv_python::{
};
use uv_requirements::upgrade::{read_pylock_toml_requirements, LockedRequirements};
use uv_requirements::{
upgrade::read_requirements_txt, RequirementsSource, RequirementsSpecification,
is_pylock_toml, upgrade::read_requirements_txt, RequirementsSource, RequirementsSpecification,
};
use uv_resolver::{
AnnotationStyle, DependencyMode, DisplayResolutionGraph, ExcludeNewer, FlatIndex, ForkStrategy,
@ -133,6 +134,20 @@ pub(crate) async fn pip_compile(
}
});
// If the user is exporting to PEP 751, ensure the filename matches the specification.
if matches!(format, ExportFormat::PylockToml) {
if let Some(file_name) = output_file
.and_then(Path::file_name)
.and_then(OsStr::to_str)
{
if !is_pylock_toml(file_name) {
return Err(anyhow!(
"Expected the output filename to start with `pylock.` and end with `.toml` (e.g., `pylock.toml`, `pylock.dev.toml`); `{file_name}` won't be recognized as a `pylock.toml` file in subsequent commands",
));
}
}
}
// Respect `UV_PYTHON`
if python.is_none() && python_version.is_none() {
if let Ok(request) = std::env::var("UV_PYTHON") {

View File

@ -2,7 +2,7 @@ use std::env;
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use anyhow::{anyhow, Context, Result};
use itertools::Itertools;
use owo_colors::OwoColorize;
@ -282,6 +282,21 @@ pub(crate) async fn export(
}
});
// If the user is exporting to PEP 751, ensure the filename matches the specification.
if matches!(format, ExportFormat::PylockToml) {
if let Some(file_name) = output_file
.as_deref()
.and_then(Path::file_name)
.and_then(OsStr::to_str)
{
if !is_pylock_toml(file_name) {
return Err(anyhow!(
"Expected the output filename to start with `pylock.` and end with `.toml` (e.g., `pylock.toml`, `pylock.dev.toml`); `{file_name}` won't be recognized as a `pylock.toml` file in subsequent commands",
));
}
}
}
// Generate the export.
match format {
ExportFormat::RequirementsTxt => {

View File

@ -4066,3 +4066,37 @@ fn pep_751_infer_output_format() -> Result<()> {
Ok(())
}
#[test]
fn pep_751_filename() -> Result<()> {
let context = TestContext::new("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["anyio==3.7.0"]
[build-system]
requires = ["setuptools>=42"]
build-backend = "setuptools.build_meta"
"#,
)?;
context.lock().assert().success();
uv_snapshot!(context.filters(), context.export().arg("--format").arg("pylock.toml").arg("-o").arg("test.toml"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
Resolved 4 packages in [TIME]
error: Expected the output filename to start with `pylock.` and end with `.toml` (e.g., `pylock.toml`, `pylock.dev.toml`); `test.toml` won't be recognized as a `pylock.toml` file in subsequent commands
");
Ok(())
}

View File

@ -16262,6 +16262,32 @@ fn compile_invalid_output_file() -> Result<()> {
Ok(())
}
#[test]
fn pep_751_filename() -> Result<()> {
let context = TestContext::new("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("iniconfig")?;
uv_snapshot!(context.filters(), context
.pip_compile()
.arg("requirements.txt")
.arg("--universal")
.arg("--format")
.arg("pylock.toml")
.arg("-o")
.arg("test.toml"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Expected the output filename to start with `pylock.` and end with `.toml` (e.g., `pylock.toml`, `pylock.dev.toml`); `test.toml` won't be recognized as a `pylock.toml` file in subsequent commands
");
Ok(())
}
#[test]
fn pep_751_compile_registry_wheel() -> Result<()> {
let context = TestContext::new("3.12");