mirror of https://github.com/astral-sh/uv
Error when `--script` is passing a non-PEP 723 script (#11118)
## Summary We now show a custom error if (1) the file doesn't exist at all, or (2) it's not a PEP 723 script. In the future, `uv lock --script` should probably initialize the script, but that requires a more extensive refactor. At present, we just silently lock the project instead, which is pretty bad! Closes https://github.com/astral-sh/uv/issues/10979.
This commit is contained in:
parent
e0a19be825
commit
bf9fe1d36d
|
|
@ -121,13 +121,11 @@ pub struct Pep723Script {
|
|||
impl Pep723Script {
|
||||
/// Read the PEP 723 `script` metadata from a Python file, if it exists.
|
||||
///
|
||||
/// Returns `None` if the file is missing a PEP 723 metadata block.
|
||||
///
|
||||
/// See: <https://peps.python.org/pep-0723/>
|
||||
pub async fn read(file: impl AsRef<Path>) -> Result<Option<Self>, Pep723Error> {
|
||||
let contents = match fs_err::tokio::read(&file).await {
|
||||
Ok(contents) => contents,
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => return Ok(None),
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
let contents = fs_err::tokio::read(&file).await?;
|
||||
|
||||
// Extract the `script` tag.
|
||||
let ScriptTag {
|
||||
|
|
@ -286,13 +284,11 @@ impl Pep723Metadata {
|
|||
|
||||
/// Read the PEP 723 `script` metadata from a Python file, if it exists.
|
||||
///
|
||||
/// Returns `None` if the file is missing a PEP 723 metadata block.
|
||||
///
|
||||
/// See: <https://peps.python.org/pep-0723/>
|
||||
pub async fn read(file: impl AsRef<Path>) -> Result<Option<Self>, Pep723Error> {
|
||||
let contents = match fs_err::tokio::read(&file).await {
|
||||
Ok(contents) => contents,
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => return Ok(None),
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
let contents = fs_err::tokio::read(&file).await?;
|
||||
|
||||
// Extract the `script` tag.
|
||||
let ScriptTag { metadata, .. } = match ScriptTag::parse(&contents) {
|
||||
|
|
@ -341,6 +337,8 @@ pub struct ToolUv {
|
|||
pub enum Pep723Error {
|
||||
#[error("An opening tag (`# /// script`) was found without a closing tag (`# ///`). Ensure that every line between the opening and closing tags (including empty lines) starts with a leading `#`.")]
|
||||
UnclosedBlock,
|
||||
#[error("The PEP 723 metadata block is missing from the script.")]
|
||||
MissingTag,
|
||||
#[error(transparent)]
|
||||
Io(#[from] io::Error),
|
||||
#[error(transparent)]
|
||||
|
|
|
|||
|
|
@ -25,9 +25,9 @@ use uv_cli::{
|
|||
use uv_cli::{PythonCommand, PythonNamespace, ToolCommand, ToolNamespace, TopLevelArgs};
|
||||
#[cfg(feature = "self-update")]
|
||||
use uv_cli::{SelfCommand, SelfNamespace, SelfUpdateArgs};
|
||||
use uv_fs::CWD;
|
||||
use uv_fs::{Simplified, CWD};
|
||||
use uv_requirements::RequirementsSource;
|
||||
use uv_scripts::{Pep723Item, Pep723Metadata, Pep723Script};
|
||||
use uv_scripts::{Pep723Error, Pep723Item, Pep723Metadata, Pep723Script};
|
||||
use uv_settings::{Combine, FilesystemOptions, Options};
|
||||
use uv_static::EnvVars;
|
||||
use uv_warnings::{warn_user, warn_user_once};
|
||||
|
|
@ -168,45 +168,67 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
|||
|
||||
// If the target is a PEP 723 script, parse it.
|
||||
let script = if let Commands::Project(command) = &*cli.command {
|
||||
if let ProjectCommand::Run(uv_cli::RunArgs { .. }) = &**command {
|
||||
match run_command.as_ref() {
|
||||
match &**command {
|
||||
ProjectCommand::Run(uv_cli::RunArgs { .. }) => match run_command.as_ref() {
|
||||
Some(
|
||||
RunCommand::PythonScript(script, _) | RunCommand::PythonGuiScript(script, _),
|
||||
) => Pep723Script::read(&script).await?.map(Pep723Item::Script),
|
||||
) => match Pep723Script::read(&script).await {
|
||||
Ok(Some(script)) => Some(Pep723Item::Script(script)),
|
||||
Ok(None) => None,
|
||||
Err(Pep723Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => None,
|
||||
Err(err) => return Err(err.into()),
|
||||
},
|
||||
Some(RunCommand::PythonRemote(script, _)) => {
|
||||
Pep723Metadata::read(&script).await?.map(Pep723Item::Remote)
|
||||
match Pep723Metadata::read(&script).await {
|
||||
Ok(Some(metadata)) => Some(Pep723Item::Remote(metadata)),
|
||||
Ok(None) => None,
|
||||
Err(Pep723Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => {
|
||||
None
|
||||
}
|
||||
Err(err) => return Err(err.into()),
|
||||
}
|
||||
}
|
||||
Some(
|
||||
RunCommand::PythonStdin(contents, _) | RunCommand::PythonGuiStdin(contents, _),
|
||||
) => Pep723Metadata::parse(contents)?.map(Pep723Item::Stdin),
|
||||
_ => None,
|
||||
}
|
||||
} else if let ProjectCommand::Remove(uv_cli::RemoveArgs {
|
||||
script: Some(script),
|
||||
..
|
||||
}) = &**command
|
||||
{
|
||||
Pep723Script::read(&script).await?.map(Pep723Item::Script)
|
||||
} else if let ProjectCommand::Lock(uv_cli::LockArgs {
|
||||
script: Some(script),
|
||||
..
|
||||
}) = &**command
|
||||
{
|
||||
Pep723Script::read(&script).await?.map(Pep723Item::Script)
|
||||
} else if let ProjectCommand::Tree(uv_cli::TreeArgs {
|
||||
script: Some(script),
|
||||
..
|
||||
}) = &**command
|
||||
{
|
||||
Pep723Script::read(&script).await?.map(Pep723Item::Script)
|
||||
} else if let ProjectCommand::Export(uv_cli::ExportArgs {
|
||||
script: Some(script),
|
||||
..
|
||||
}) = &**command
|
||||
{
|
||||
Pep723Script::read(&script).await?.map(Pep723Item::Script)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
ProjectCommand::Remove(uv_cli::RemoveArgs {
|
||||
script: Some(script),
|
||||
..
|
||||
})
|
||||
| ProjectCommand::Lock(uv_cli::LockArgs {
|
||||
script: Some(script),
|
||||
..
|
||||
})
|
||||
| ProjectCommand::Tree(uv_cli::TreeArgs {
|
||||
script: Some(script),
|
||||
..
|
||||
})
|
||||
| ProjectCommand::Export(uv_cli::ExportArgs {
|
||||
script: Some(script),
|
||||
..
|
||||
}) => match Pep723Script::read(&script).await {
|
||||
Ok(Some(script)) => Some(Pep723Item::Script(script)),
|
||||
Ok(None) => {
|
||||
// TODO(charlie): `uv lock --script` should initialize the tag, if it doesn't
|
||||
// exist.
|
||||
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()),
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
|
|
|
|||
|
|
@ -23092,6 +23092,37 @@ fn lock_script_path() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lock_script_error() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
uv_snapshot!(context.filters(), context.lock().arg("--script").arg("script.py"), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: Failed to read `script.py` (not found); run `uv init --script script.py` to create a PEP 723 script
|
||||
"###);
|
||||
|
||||
let script = context.temp_dir.child("script.py");
|
||||
script.write_str(indoc! { r"
|
||||
import anyio
|
||||
"
|
||||
})?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.lock().arg("--script").arg("script.py"), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: `script.py` does not contain a PEP 723 metadata tag; run `uv init --script script.py` to initialize the script
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lock_pytorch_cpu() -> Result<()> {
|
||||
let context = TestContext::new("3.12").with_exclude_newer("2025-01-30T00:00:00Z");
|
||||
|
|
|
|||
Loading…
Reference in New Issue