diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 20985b582828..1790d981c847 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -2457,7 +2457,7 @@ pub struct AddArgs { /// a new one will be created and added to the script. When executed via `uv run`, /// uv will create a temporary environment for the script with all inline /// dependencies installed. - #[arg(long)] + #[arg(long, conflicts_with = "dev", conflicts_with = "optional")] pub script: Option, /// The Python interpreter to use for resolving and syncing. diff --git a/crates/uv-workspace/src/pyproject_mut.rs b/crates/uv-workspace/src/pyproject_mut.rs index bdb5e5c7d807..c2596f08a6a4 100644 --- a/crates/uv-workspace/src/pyproject_mut.rs +++ b/crates/uv-workspace/src/pyproject_mut.rs @@ -16,7 +16,7 @@ use crate::pyproject::{DependencyType, Source}; /// preserving comments and other structure, such as `uv add` and `uv remove`. pub struct PyProjectTomlMut { doc: DocumentMut, - dependency_target: DependencyTarget, + target: DependencyTarget, } #[derive(Error, Debug)] @@ -57,10 +57,10 @@ pub enum DependencyTarget { impl PyProjectTomlMut { /// Initialize a [`PyProjectTomlMut`] from a [`str`]. - pub fn from_toml(raw: &str, dependency_target: DependencyTarget) -> Result { + pub fn from_toml(raw: &str, target: DependencyTarget) -> Result { Ok(Self { doc: raw.parse().map_err(Box::new)?, - dependency_target, + target, }) } @@ -93,8 +93,8 @@ impl PyProjectTomlMut { } /// Retrieves a mutable reference to the root `Table` of the TOML document, creating the `project` table if necessary. - fn doc(&mut self) -> Result<&mut toml_edit::Table, Error> { - let doc = match self.dependency_target { + fn doc(&mut self) -> Result<&mut Table, Error> { + let doc = match self.target { DependencyTarget::Script => self.doc.as_table_mut(), DependencyTarget::PyProjectToml => self .doc @@ -107,8 +107,8 @@ impl PyProjectTomlMut { } /// Retrieves an optional mutable reference to the `project` `Table`, returning `None` if it doesn't exist. - fn doc_mut(&mut self) -> Result, Error> { - let doc = match self.dependency_target { + fn doc_mut(&mut self) -> Result, Error> { + let doc = match self.target { DependencyTarget::Script => Some(self.doc.as_table_mut()), DependencyTarget::PyProjectToml => self .doc diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index 10e02dfe09d9..bda947bc7ae4 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -74,9 +74,6 @@ pub(crate) async fn add( let target = if let Some(script) = script { // If we found a PEP 723 script and the user provided a project-only setting, warn. - if !extras.is_empty() { - warn_user_once!("Extras are not supported for Python scripts with inline metadata"); - } if package.is_some() { warn_user_once!( "`--package` is a no-op for Python scripts with inline metadata, which always run in isolation" diff --git a/crates/uv/tests/edit.rs b/crates/uv/tests/edit.rs index 02bd97ddaa09..061dac21ee81 100644 --- a/crates/uv/tests/edit.rs +++ b/crates/uv/tests/edit.rs @@ -2913,7 +2913,7 @@ fn add_repeat() -> Result<()> { Ok(()) } -/// Add to a PEP732 script +/// Add to a PEP 732 script. #[test] fn add_script() -> Result<()> { let context = TestContext::new("3.12"); @@ -2973,7 +2973,7 @@ fn add_script() -> Result<()> { Ok(()) } -/// Add to a script without metadata table +/// Add to a script without an existing metadata table. #[test] fn add_script_without_metadata_table() -> Result<()> { let context = TestContext::new("3.12"); @@ -3023,7 +3023,7 @@ fn add_script_without_metadata_table() -> Result<()> { Ok(()) } -/// Add to a script without metadata table +/// Add to a script without an existing metadata table, but with a shebang. #[test] fn add_script_without_metadata_table_with_shebang() -> Result<()> { let context = TestContext::new("3.12"); @@ -3075,7 +3075,59 @@ fn add_script_without_metadata_table_with_shebang() -> Result<()> { Ok(()) } -/// Remove from a PEP732 script +/// Add to a script without a metadata table, but with a docstring. +#[test] +fn add_script_without_metadata_table_with_docstring() -> Result<()> { + let context = TestContext::new("3.12"); + + let script = context.temp_dir.child("script.py"); + script.write_str(indoc! {r#" + """This is a script.""" + import requests + from rich.pretty import pprint + + resp = requests.get("https://peps.python.org/api/peps.json") + data = resp.json() + pprint([(k, v["title"]) for k, v in data.items()][:10]) + "#})?; + + uv_snapshot!(context.filters(), context.add(&["rich", "requests<3"]).arg("--script").arg(script.path()), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: `uv add` is experimental and may change without warning + "###); + + let script_content = fs_err::read_to_string(context.temp_dir.join("script.py"))?; + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + script_content, @r###" + # /// script + # requires-python = ">=3.12" + # dependencies = [ + # "rich", + # "requests<3", + # ] + # /// + """This is a script.""" + import requests + from rich.pretty import pprint + + resp = requests.get("https://peps.python.org/api/peps.json") + data = resp.json() + pprint([(k, v["title"]) for k, v in data.items()][:10]) + "### + ); + }); + Ok(()) +} + +/// Remove from a PEP732 script, #[test] fn remove_script() -> Result<()> { let context = TestContext::new("3.12");