From 082ca11be1114533d533def6f9b83564c05ecea9 Mon Sep 17 00:00:00 2001 From: j178 <10510431+j178@users.noreply.github.com> Date: Sat, 19 Oct 2024 20:43:58 +0800 Subject: [PATCH 1/5] Add failing test --- crates/uv/tests/it/edit.rs | 126 +++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/crates/uv/tests/it/edit.rs b/crates/uv/tests/it/edit.rs index 33fc185f3cb9..049fb408d5ef 100644 --- a/crates/uv/tests/it/edit.rs +++ b/crates/uv/tests/it/edit.rs @@ -2720,6 +2720,132 @@ fn update_source_replace_url() -> Result<()> { Ok(()) } +/// If a source defined in `tool.uv.sources` but its name is not normalized, `uv add` should not +/// add the same source again. +#[test] +#[cfg(feature = "git")] +fn add_non_normalized_source() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [ + "uv-public-pypackage" + ] + + [build-system] + requires = ["setuptools>=42"] + build-backend = "setuptools.build_meta" + + [tool.uv.sources] + uv_public_pypackage = { git = "https://github.com/astral-test/uv-public-pypackage", tag = "0.0.1" } + "#})?; + + uv_snapshot!(context.filters(), context.add().arg("uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage@0.0.1"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + Prepared 2 packages in [TIME] + Installed 2 packages in [TIME] + + project==0.1.0 (from file://[TEMP_DIR]/) + + uv-public-pypackage==0.1.0 (from git+https://github.com/astral-test/uv-public-pypackage@0dacfd662c64cb4ceb16e6cf65a157a8b715b979) + "###); + + let pyproject_toml = context.read("pyproject.toml"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + pyproject_toml, @r###" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [ + "uv-public-pypackage", + ] + + [build-system] + requires = ["setuptools>=42"] + build-backend = "setuptools.build_meta" + + [tool.uv.sources] + uv_public_pypackage = { git = "https://github.com/astral-test/uv-public-pypackage", tag = "0.0.1" } + "### + ); + }); + + Ok(()) +} + +/// If a source defined in `tool.uv.sources` but its name is not normalized, `uv remove` should +/// remove the source. +#[test] +#[cfg(feature = "git")] +fn remove_non_normalized_source() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [ + "uv-public-pypackage" + ] + + [build-system] + requires = ["setuptools>=42"] + build-backend = "setuptools.build_meta" + + [tool.uv.sources] + uv_public_pypackage = { git = "https://github.com/astral-test/uv-public-pypackage", tag = "0.0.1" } + "#})?; + + uv_snapshot!(context.filters(), context.remove().arg("uv-public-pypackage"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + project==0.1.0 (from file://[TEMP_DIR]/) + "###); + + let pyproject_toml = context.read("pyproject.toml"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + pyproject_toml, @r###" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + + [build-system] + requires = ["setuptools>=42"] + build-backend = "setuptools.build_meta" + "### + ); + }); + + Ok(()) +} + /// Adding a dependency does not remove untracked dependencies from the environment. #[test] fn add_inexact() -> Result<()> { From 19ba56e247521d45ee63a7c57abe86d4515d35e1 Mon Sep 17 00:00:00 2001 From: j178 <10510431+j178@users.noreply.github.com> Date: Sat, 19 Oct 2024 20:52:08 +0800 Subject: [PATCH 2/5] Find by normalized name --- crates/uv-workspace/src/pyproject_mut.rs | 34 ++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/crates/uv-workspace/src/pyproject_mut.rs b/crates/uv-workspace/src/pyproject_mut.rs index 67b3f9ae3b61..dcd0c0824c79 100644 --- a/crates/uv-workspace/src/pyproject_mut.rs +++ b/crates/uv-workspace/src/pyproject_mut.rs @@ -131,6 +131,7 @@ impl PyProjectTomlMut { }; Ok(doc) } + /// Adds a dependency to `project.dependencies`. /// /// Returns `true` if the dependency was added, `false` if it was updated. @@ -431,7 +432,10 @@ impl PyProjectTomlMut { .as_table_mut() .ok_or(Error::MalformedSources)?; - add_source(name, source, sources)?; + if find_source(name, sources).is_none() { + add_source(name, source, sources)?; + } + Ok(()) } @@ -532,7 +536,23 @@ impl PyProjectTomlMut { .map(|sources| sources.as_table_mut().ok_or(Error::MalformedSources)) .transpose()? { - sources.remove(name.as_ref()); + if let Some(key) = find_source(name, sources) { + sources.remove(&key); + + // Remove the `tool.uv.sources` table if it is empty. + if sources.is_empty() { + self.doc + .entry("tool") + .or_insert(implicit()) + .as_table_mut() + .ok_or(Error::MalformedSources)? + .entry("uv") + .or_insert(implicit()) + .as_table_mut() + .ok_or(Error::MalformedSources)? + .remove("sources"); + } + } } Ok(()) @@ -766,6 +786,16 @@ fn find_dependencies( to_replace } +/// Returns the key in `tool.uv.sources` that matches the given package name. +fn find_source(name: &PackageName, sources: &Table) -> Option { + for (key, _) in sources { + if PackageName::from_str(key).is_ok_and(|ref key| key == name) { + return Some(key.to_string()); + } + } + None +} + // Add a source to `tool.uv.sources`. fn add_source(req: &PackageName, source: &Source, sources: &mut Table) -> Result<(), Error> { // Serialize as an inline table. From 09dd44376fcab67d7ff535a2df1dfa501810ba1d Mon Sep 17 00:00:00 2001 From: j178 <10510431+j178@users.noreply.github.com> Date: Sat, 19 Oct 2024 21:05:38 +0800 Subject: [PATCH 3/5] Remove then add --- crates/uv-workspace/src/pyproject_mut.rs | 5 +-- crates/uv/tests/it/edit.rs | 43 ++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/crates/uv-workspace/src/pyproject_mut.rs b/crates/uv-workspace/src/pyproject_mut.rs index dcd0c0824c79..44aaf7665247 100644 --- a/crates/uv-workspace/src/pyproject_mut.rs +++ b/crates/uv-workspace/src/pyproject_mut.rs @@ -432,9 +432,10 @@ impl PyProjectTomlMut { .as_table_mut() .ok_or(Error::MalformedSources)?; - if find_source(name, sources).is_none() { - add_source(name, source, sources)?; + if let Some(key) = find_source(name, sources) { + sources.remove(&key); } + add_source(name, source, sources)?; Ok(()) } diff --git a/crates/uv/tests/it/edit.rs b/crates/uv/tests/it/edit.rs index 049fb408d5ef..5f5ae68452b7 100644 --- a/crates/uv/tests/it/edit.rs +++ b/crates/uv/tests/it/edit.rs @@ -1687,8 +1687,6 @@ fn add_remove_workspace() -> Result<()> { [build-system] requires = ["setuptools>=42"] build-backend = "setuptools.build_meta" - - [tool.uv.sources] "### ); }); @@ -2717,6 +2715,47 @@ fn update_source_replace_url() -> Result<()> { ); }); + // Change the source again. The existing source should be replaced. + uv_snapshot!(context.filters(), context.add().arg("requests @ git+https://github.com/psf/requests").arg("--tag=v2.32.2"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 6 packages in [TIME] + Prepared 2 packages in [TIME] + Uninstalled 2 packages in [TIME] + Installed 2 packages in [TIME] + ~ project==0.1.0 (from file://[TEMP_DIR]/) + - requests==2.32.3 (from git+https://github.com/psf/requests@0e322af87745eff34caffe4df68456ebc20d9068) + + requests==2.32.2 (from git+https://github.com/psf/requests@88dce9d854797c05d0ff296b70e0430535ef8aaf) + "###); + + let pyproject_toml = context.read("pyproject.toml"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + pyproject_toml, @r###" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [ + "requests[security]", + ] + + [build-system] + requires = ["setuptools>=42"] + build-backend = "setuptools.build_meta" + + [tool.uv.sources] + requests = { git = "https://github.com/psf/requests", tag = "v2.32.2" } + "### + ); + }); + Ok(()) } From 79c486adb5aeb9327813b9ca7a4cbb8f751d1157 Mon Sep 17 00:00:00 2001 From: j178 <10510431+j178@users.noreply.github.com> Date: Sat, 19 Oct 2024 21:07:25 +0800 Subject: [PATCH 4/5] Remvove empty sources table in later PR --- crates/uv-workspace/src/pyproject_mut.rs | 14 -------------- crates/uv/tests/it/edit.rs | 4 ++++ 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/crates/uv-workspace/src/pyproject_mut.rs b/crates/uv-workspace/src/pyproject_mut.rs index 44aaf7665247..d87a0343da3a 100644 --- a/crates/uv-workspace/src/pyproject_mut.rs +++ b/crates/uv-workspace/src/pyproject_mut.rs @@ -539,20 +539,6 @@ impl PyProjectTomlMut { { if let Some(key) = find_source(name, sources) { sources.remove(&key); - - // Remove the `tool.uv.sources` table if it is empty. - if sources.is_empty() { - self.doc - .entry("tool") - .or_insert(implicit()) - .as_table_mut() - .ok_or(Error::MalformedSources)? - .entry("uv") - .or_insert(implicit()) - .as_table_mut() - .ok_or(Error::MalformedSources)? - .remove("sources"); - } } } diff --git a/crates/uv/tests/it/edit.rs b/crates/uv/tests/it/edit.rs index 5f5ae68452b7..cb64cd5ed824 100644 --- a/crates/uv/tests/it/edit.rs +++ b/crates/uv/tests/it/edit.rs @@ -1687,6 +1687,8 @@ fn add_remove_workspace() -> Result<()> { [build-system] requires = ["setuptools>=42"] build-backend = "setuptools.build_meta" + + [tool.uv.sources] "### ); }); @@ -2878,6 +2880,8 @@ fn remove_non_normalized_source() -> Result<()> { [build-system] requires = ["setuptools>=42"] build-backend = "setuptools.build_meta" + + [tool.uv.sources] "### ); }); From c9e44919ee9ab34c64cf5227b03aa265ef1737b6 Mon Sep 17 00:00:00 2001 From: j178 <10510431+j178@users.noreply.github.com> Date: Sat, 19 Oct 2024 21:19:03 +0800 Subject: [PATCH 5/5] Fix snapshot --- crates/uv/tests/it/edit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/uv/tests/it/edit.rs b/crates/uv/tests/it/edit.rs index cb64cd5ed824..080659d6973e 100644 --- a/crates/uv/tests/it/edit.rs +++ b/crates/uv/tests/it/edit.rs @@ -2819,7 +2819,7 @@ fn add_non_normalized_source() -> Result<()> { build-backend = "setuptools.build_meta" [tool.uv.sources] - uv_public_pypackage = { git = "https://github.com/astral-test/uv-public-pypackage", tag = "0.0.1" } + uv-public-pypackage = { git = "https://github.com/astral-test/uv-public-pypackage", rev = "0.0.1" } "### ); });