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 {
|
impl Pep723Script {
|
||||||
/// Read the PEP 723 `script` metadata from a Python file, if it exists.
|
/// 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/>
|
/// See: <https://peps.python.org/pep-0723/>
|
||||||
pub async fn read(file: impl AsRef<Path>) -> Result<Option<Self>, Pep723Error> {
|
pub async fn read(file: impl AsRef<Path>) -> Result<Option<Self>, Pep723Error> {
|
||||||
let contents = match fs_err::tokio::read(&file).await {
|
let contents = 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()),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Extract the `script` tag.
|
// Extract the `script` tag.
|
||||||
let ScriptTag {
|
let ScriptTag {
|
||||||
|
|
@ -286,13 +284,11 @@ impl Pep723Metadata {
|
||||||
|
|
||||||
/// Read the PEP 723 `script` metadata from a Python file, if it exists.
|
/// 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/>
|
/// See: <https://peps.python.org/pep-0723/>
|
||||||
pub async fn read(file: impl AsRef<Path>) -> Result<Option<Self>, Pep723Error> {
|
pub async fn read(file: impl AsRef<Path>) -> Result<Option<Self>, Pep723Error> {
|
||||||
let contents = match fs_err::tokio::read(&file).await {
|
let contents = 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()),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Extract the `script` tag.
|
// Extract the `script` tag.
|
||||||
let ScriptTag { metadata, .. } = match ScriptTag::parse(&contents) {
|
let ScriptTag { metadata, .. } = match ScriptTag::parse(&contents) {
|
||||||
|
|
@ -341,6 +337,8 @@ pub struct ToolUv {
|
||||||
pub enum Pep723Error {
|
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 `#`.")]
|
#[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,
|
UnclosedBlock,
|
||||||
|
#[error("The PEP 723 metadata block is missing from the script.")]
|
||||||
|
MissingTag,
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Io(#[from] io::Error),
|
Io(#[from] io::Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,9 @@ use uv_cli::{
|
||||||
use uv_cli::{PythonCommand, PythonNamespace, ToolCommand, ToolNamespace, TopLevelArgs};
|
use uv_cli::{PythonCommand, PythonNamespace, ToolCommand, ToolNamespace, TopLevelArgs};
|
||||||
#[cfg(feature = "self-update")]
|
#[cfg(feature = "self-update")]
|
||||||
use uv_cli::{SelfCommand, SelfNamespace, SelfUpdateArgs};
|
use uv_cli::{SelfCommand, SelfNamespace, SelfUpdateArgs};
|
||||||
use uv_fs::CWD;
|
use uv_fs::{Simplified, CWD};
|
||||||
use uv_requirements::RequirementsSource;
|
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_settings::{Combine, FilesystemOptions, Options};
|
||||||
use uv_static::EnvVars;
|
use uv_static::EnvVars;
|
||||||
use uv_warnings::{warn_user, warn_user_once};
|
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.
|
// If the target is a PEP 723 script, parse it.
|
||||||
let script = if let Commands::Project(command) = &*cli.command {
|
let script = if let Commands::Project(command) = &*cli.command {
|
||||||
if let ProjectCommand::Run(uv_cli::RunArgs { .. }) = &**command {
|
match &**command {
|
||||||
match run_command.as_ref() {
|
ProjectCommand::Run(uv_cli::RunArgs { .. }) => match run_command.as_ref() {
|
||||||
Some(
|
Some(
|
||||||
RunCommand::PythonScript(script, _) | RunCommand::PythonGuiScript(script, _),
|
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, _)) => {
|
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(
|
Some(
|
||||||
RunCommand::PythonStdin(contents, _) | RunCommand::PythonGuiStdin(contents, _),
|
RunCommand::PythonStdin(contents, _) | RunCommand::PythonGuiStdin(contents, _),
|
||||||
) => Pep723Metadata::parse(contents)?.map(Pep723Item::Stdin),
|
) => Pep723Metadata::parse(contents)?.map(Pep723Item::Stdin),
|
||||||
_ => None,
|
_ => 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()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else if let ProjectCommand::Remove(uv_cli::RemoveArgs {
|
Err(Pep723Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => {
|
||||||
script: Some(script),
|
bail!(
|
||||||
..
|
"Failed to read `{}` (not found); run `{}` to create a PEP 723 script",
|
||||||
}) = &**command
|
script.user_display().cyan(),
|
||||||
{
|
format!("uv init --script {}", script.user_display()).green()
|
||||||
Pep723Script::read(&script).await?.map(Pep723Item::Script)
|
)
|
||||||
} else if let ProjectCommand::Lock(uv_cli::LockArgs {
|
}
|
||||||
script: Some(script),
|
Err(err) => return Err(err.into()),
|
||||||
..
|
},
|
||||||
}) = &**command
|
_ => None,
|
||||||
{
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
|
||||||
|
|
@ -23092,6 +23092,37 @@ fn lock_script_path() -> Result<()> {
|
||||||
Ok(())
|
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]
|
#[test]
|
||||||
fn lock_pytorch_cpu() -> Result<()> {
|
fn lock_pytorch_cpu() -> Result<()> {
|
||||||
let context = TestContext::new("3.12").with_exclude_newer("2025-01-30T00:00:00Z");
|
let context = TestContext::new("3.12").with_exclude_newer("2025-01-30T00:00:00Z");
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue