From 10426e15fe3e5a0172a35358f83e0140657b499a Mon Sep 17 00:00:00 2001 From: Chan Kang Date: Sun, 25 Aug 2024 18:42:26 -0400 Subject: [PATCH 01/18] fix tests --- crates/uv-workspace/src/pyproject_mut.rs | 30 +++- crates/uv/tests/edit.rs | 168 ++++++++++++++++++++++- 2 files changed, 188 insertions(+), 10 deletions(-) diff --git a/crates/uv-workspace/src/pyproject_mut.rs b/crates/uv-workspace/src/pyproject_mut.rs index cfb084fea45f..b5c67eda076f 100644 --- a/crates/uv-workspace/src/pyproject_mut.rs +++ b/crates/uv-workspace/src/pyproject_mut.rs @@ -3,10 +3,10 @@ use std::str::FromStr; use std::{fmt, mem}; use pep440_rs::{Version, VersionSpecifier, VersionSpecifiers}; -use pep508_rs::{ExtraName, MarkerTree, PackageName, Requirement, VersionOrUrl}; +use pep508_rs::{ExtraName, MarkerTree, PackageName, Requirement, VerbatimUrl, VersionOrUrl}; use thiserror::Error; use toml_edit::{Array, DocumentMut, Item, RawString, Table, TomlError, Value}; -use uv_fs::PortablePath; +use uv_fs::{PortablePath, CWD}; use crate::pyproject::{DependencyType, Source}; @@ -503,9 +503,20 @@ pub fn add_dependency( deps: &mut Array, has_source: bool, ) -> Result { - // Find matching dependencies. let mut to_replace = find_dependencies(&req.name, Some(&req.marker), deps); - match to_replace.as_slice() { + + // determine the dependency list is sorted prior to + // adding the new dependency; the new dependency list + // will be sorted only when the original list is sorted + // so that users' custom dependency ordering is preserved. + let sorted = deps + .clone() + .into_iter() + .collect::>() + .windows(2) + .all(|w| w[0].to_string() <= w[1].to_string()); + + let edit = match to_replace.as_slice() { [] => { deps.push(req.to_string()); reformat_array_multiline(deps); @@ -520,7 +531,18 @@ pub fn add_dependency( } // Cannot perform ambiguous updates. _ => Err(Error::Ambiguous), + }; + + if sorted { + deps.sort_by_key(|d| { + pep508_rs::Requirement::::parse(d.clone().as_str().unwrap(), &*CWD) + .unwrap() + .name + .clone() + }) } + + edit } /// Update an existing requirement. diff --git a/crates/uv/tests/edit.rs b/crates/uv/tests/edit.rs index c8425aa140c2..558f420a0c4f 100644 --- a/crates/uv/tests/edit.rs +++ b/crates/uv/tests/edit.rs @@ -3484,8 +3484,8 @@ fn add_requirements_file() -> Result<()> { # ... requires-python = ">=3.12" dependencies = [ - "flask==2.3.2", "anyio", + "flask==2.3.2", ] [tool.uv.sources] @@ -3564,9 +3564,9 @@ fn add_script() -> Result<()> { # /// script # requires-python = ">=3.11" # dependencies = [ + # "anyio", # "requests<3", # "rich", - # "anyio", # ] # /// @@ -3616,8 +3616,8 @@ fn add_script_without_metadata_table() -> Result<()> { # /// script # requires-python = ">=3.12" # dependencies = [ - # "rich", # "requests<3", + # "rich", # ] # /// import requests @@ -3668,8 +3668,8 @@ fn add_script_without_metadata_table_with_shebang() -> Result<()> { # /// script # requires-python = ">=3.12" # dependencies = [ - # "rich", # "requests<3", + # "rich", # ] # /// import requests @@ -3724,8 +3724,8 @@ fn add_script_with_metadata_table_and_shebang() -> Result<()> { # /// script # requires-python = ">=3.12" # dependencies = [ - # "rich", # "requests<3", + # "rich", # ] # /// import requests @@ -3775,8 +3775,8 @@ fn add_script_without_metadata_table_with_docstring() -> Result<()> { # /// script # requires-python = ">=3.12" # dependencies = [ - # "rich", # "requests<3", + # "rich", # ] # /// """This is a script.""" @@ -4038,3 +4038,159 @@ fn fail_to_add_revert_project() -> Result<()> { Ok(()) } + +/// Ensure that the added dependencies are sorted +/// if the dependency list was already sorted prior to adding the new one. +#[test] +fn sorted_dependencies() -> 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" + description = "Add your description here" + requires-python = ">=3.12" + dependencies = [ + "CacheControl[filecache]>=0.14,<0.15", + "mwparserfromhell", + "pywikibot", + "sentry-sdk", + "yarl", + ] + "#})?; + + uv_snapshot!(context.filters(), context.add(&["pydantic"]), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 19 packages in [TIME] + Prepared 19 packages in [TIME] + Installed 19 packages in [TIME] + + annotated-types==0.6.0 + + cachecontrol==0.14.0 + + certifi==2024.2.2 + + charset-normalizer==3.3.2 + + filelock==3.13.1 + + idna==3.6 + + msgpack==1.0.8 + + multidict==6.0.5 + + mwparserfromhell==0.6.6 + + packaging==24.0 + + project==0.1.0 (from file://[TEMP_DIR]/) + + pydantic==2.6.4 + + pydantic-core==2.16.3 + + pywikibot==9.0.0 + + requests==2.31.0 + + sentry-sdk==1.43.0 + + typing-extensions==4.10.0 + + urllib3==2.2.1 + + yarl==1.9.4 + "###); + + let pyproject_toml = fs_err::read_to_string(context.temp_dir.join("pyproject.toml"))?; + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + pyproject_toml, @r###" + [project] + name = "project" + version = "0.1.0" + description = "Add your description here" + requires-python = ">=3.12" + dependencies = [ + "CacheControl[filecache]>=0.14,<0.15", + "mwparserfromhell", + "pydantic", + "pywikibot", + "sentry-sdk", + "yarl>=2.6.4", + ] + "### + ); + }); + Ok(()) +} + +/// Ensure that the custom ordering of the dependencies is preserved +/// after adding a package. +#[test] +fn custom_dependencies() -> 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" + description = "Add your description here" + requires-python = ">=3.12" + dependencies = [ + "yarl", + "CacheControl[filecache]>=0.14,<0.15", + "mwparserfromhell", + "pywikibot", + "sentry-sdk", + ] + "#})?; + + uv_snapshot!(context.filters(), context.add(&["pydantic"]), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 19 packages in [TIME] + Prepared 19 packages in [TIME] + Installed 19 packages in [TIME] + + annotated-types==0.6.0 + + cachecontrol==0.14.0 + + certifi==2024.2.2 + + charset-normalizer==3.3.2 + + filelock==3.13.1 + + idna==3.6 + + msgpack==1.0.8 + + multidict==6.0.5 + + mwparserfromhell==0.6.6 + + packaging==24.0 + + project==0.1.0 (from file://[TEMP_DIR]/) + + pydantic==2.6.4 + + pydantic-core==2.16.3 + + pywikibot==9.0.0 + + requests==2.31.0 + + sentry-sdk==1.43.0 + + typing-extensions==4.10.0 + + urllib3==2.2.1 + + yarl==1.9.4 + "###); + + let pyproject_toml = fs_err::read_to_string(context.temp_dir.join("pyproject.toml"))?; + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + pyproject_toml, @r###" + [project] + name = "project" + version = "0.1.0" + description = "Add your description here" + requires-python = ">=3.12" + dependencies = [ + "yarl", + "CacheControl[filecache]>=0.14,<0.15", + "mwparserfromhell", + "pywikibot", + "sentry-sdk", + "pydantic>=2.6.4", + ] + "### + ); + }); + Ok(()) +} From 9c131a5c7583d4e4bef39e5eb68eb718fd2e3ea2 Mon Sep 17 00:00:00 2001 From: Chan Kang Date: Sun, 25 Aug 2024 18:47:25 -0400 Subject: [PATCH 02/18] clippy --- crates/uv-workspace/src/pyproject_mut.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/uv-workspace/src/pyproject_mut.rs b/crates/uv-workspace/src/pyproject_mut.rs index b5c67eda076f..1a535578227b 100644 --- a/crates/uv-workspace/src/pyproject_mut.rs +++ b/crates/uv-workspace/src/pyproject_mut.rs @@ -539,7 +539,7 @@ pub fn add_dependency( .unwrap() .name .clone() - }) + }); } edit From 279f653d1348b23fee5e5d1089f3f72657bb8cd6 Mon Sep 17 00:00:00 2001 From: Chan Kang Date: Sun, 25 Aug 2024 19:57:43 -0400 Subject: [PATCH 03/18] place the version constraint on the correct index --- crates/uv-workspace/src/pyproject_mut.rs | 56 ++++++++++++++---------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/crates/uv-workspace/src/pyproject_mut.rs b/crates/uv-workspace/src/pyproject_mut.rs index 1a535578227b..87f713ae1c5e 100644 --- a/crates/uv-workspace/src/pyproject_mut.rs +++ b/crates/uv-workspace/src/pyproject_mut.rs @@ -504,23 +504,44 @@ pub fn add_dependency( has_source: bool, ) -> Result { let mut to_replace = find_dependencies(&req.name, Some(&req.marker), deps); - - // determine the dependency list is sorted prior to - // adding the new dependency; the new dependency list - // will be sorted only when the original list is sorted - // so that users' custom dependency ordering is preserved. - let sorted = deps - .clone() - .into_iter() - .collect::>() - .windows(2) - .all(|w| w[0].to_string() <= w[1].to_string()); - let edit = match to_replace.as_slice() { [] => { + // determine the dependency list is sorted prior to + // adding the new dependency; the new dependency list + // will be sorted only when the original list is sorted + // so that users' custom dependency ordering is preserved. + let sorted = deps + .clone() + .into_iter() + .collect::>() + .windows(2) + .all(|w| w[0].to_string() <= w[1].to_string()); + deps.push(req.to_string()); + let mut index = deps.len() - 1; + if sorted { + deps.sort_by_key(|d| { + pep508_rs::Requirement::::parse(d.clone().as_str().unwrap(), &*CWD) + .unwrap() + .name + .clone() + }); + index = deps + .iter() + .position(|d| { + pep508_rs::Requirement::::parse( + d.clone().as_str().unwrap(), + &*CWD, + ) + .unwrap() + .name + == req.name + }) + .unwrap(); + } + reformat_array_multiline(deps); - Ok(ArrayEdit::Add(deps.len() - 1)) + Ok(ArrayEdit::Add(index)) } [_] => { let (i, mut old_req) = to_replace.remove(0); @@ -533,15 +554,6 @@ pub fn add_dependency( _ => Err(Error::Ambiguous), }; - if sorted { - deps.sort_by_key(|d| { - pep508_rs::Requirement::::parse(d.clone().as_str().unwrap(), &*CWD) - .unwrap() - .name - .clone() - }); - } - edit } From 01ecc17c8a8362d590b991c675df4ae5da66f268 Mon Sep 17 00:00:00 2001 From: Chan Kang Date: Sun, 25 Aug 2024 20:17:00 -0400 Subject: [PATCH 04/18] fix the test (should be failing) --- crates/uv-workspace/src/pyproject_mut.rs | 19 ++++--------------- crates/uv/tests/edit.rs | 4 ++-- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/crates/uv-workspace/src/pyproject_mut.rs b/crates/uv-workspace/src/pyproject_mut.rs index 87f713ae1c5e..1b003c749dab 100644 --- a/crates/uv-workspace/src/pyproject_mut.rs +++ b/crates/uv-workspace/src/pyproject_mut.rs @@ -504,6 +504,7 @@ pub fn add_dependency( has_source: bool, ) -> Result { let mut to_replace = find_dependencies(&req.name, Some(&req.marker), deps); + let edit = match to_replace.as_slice() { [] => { // determine the dependency list is sorted prior to @@ -518,7 +519,8 @@ pub fn add_dependency( .all(|w| w[0].to_string() <= w[1].to_string()); deps.push(req.to_string()); - let mut index = deps.len() - 1; + reformat_array_multiline(deps); + if sorted { deps.sort_by_key(|d| { pep508_rs::Requirement::::parse(d.clone().as_str().unwrap(), &*CWD) @@ -526,22 +528,9 @@ pub fn add_dependency( .name .clone() }); - index = deps - .iter() - .position(|d| { - pep508_rs::Requirement::::parse( - d.clone().as_str().unwrap(), - &*CWD, - ) - .unwrap() - .name - == req.name - }) - .unwrap(); } - reformat_array_multiline(deps); - Ok(ArrayEdit::Add(index)) + Ok(ArrayEdit::Add(deps.len() - 1)) } [_] => { let (i, mut old_req) = to_replace.remove(0); diff --git a/crates/uv/tests/edit.rs b/crates/uv/tests/edit.rs index 558f420a0c4f..bead26675dfa 100644 --- a/crates/uv/tests/edit.rs +++ b/crates/uv/tests/edit.rs @@ -4106,10 +4106,10 @@ fn sorted_dependencies() -> Result<()> { dependencies = [ "CacheControl[filecache]>=0.14,<0.15", "mwparserfromhell", - "pydantic", + "pydantic>=2.6.4", "pywikibot", "sentry-sdk", - "yarl>=2.6.4", + "yarl", ] "### ); From 7e3291eea8548c03596468cddcf7bd906c82227d Mon Sep 17 00:00:00 2001 From: Chan Kang Date: Sun, 25 Aug 2024 21:38:32 -0400 Subject: [PATCH 05/18] index fix + need to fix more tests --- crates/uv-workspace/src/pyproject_mut.rs | 38 +++++++++++++----------- crates/uv/tests/edit.rs | 2 +- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/crates/uv-workspace/src/pyproject_mut.rs b/crates/uv-workspace/src/pyproject_mut.rs index 1b003c749dab..fe257b30e3a7 100644 --- a/crates/uv-workspace/src/pyproject_mut.rs +++ b/crates/uv-workspace/src/pyproject_mut.rs @@ -3,10 +3,10 @@ use std::str::FromStr; use std::{fmt, mem}; use pep440_rs::{Version, VersionSpecifier, VersionSpecifiers}; -use pep508_rs::{ExtraName, MarkerTree, PackageName, Requirement, VerbatimUrl, VersionOrUrl}; +use pep508_rs::{ExtraName, MarkerTree, PackageName, Requirement, VersionOrUrl}; use thiserror::Error; use toml_edit::{Array, DocumentMut, Item, RawString, Table, TomlError, Value}; -use uv_fs::{PortablePath, CWD}; +use uv_fs::PortablePath; use crate::pyproject::{DependencyType, Source}; @@ -504,33 +504,35 @@ pub fn add_dependency( has_source: bool, ) -> Result { let mut to_replace = find_dependencies(&req.name, Some(&req.marker), deps); + // determine the dependency list is sorted prior to + // adding the new dependency; the new dependency list + // will be sorted only when the original list is sorted + // so that users' custom dependency ordering is preserved. + let sorted = deps + .clone() + .into_iter() + .collect::>() + .windows(2) + .all(|w| w[0].to_string() <= w[1].to_string()); let edit = match to_replace.as_slice() { [] => { - // determine the dependency list is sorted prior to - // adding the new dependency; the new dependency list - // will be sorted only when the original list is sorted - // so that users' custom dependency ordering is preserved. - let sorted = deps - .clone() - .into_iter() - .collect::>() - .windows(2) - .all(|w| w[0].to_string() <= w[1].to_string()); - deps.push(req.to_string()); reformat_array_multiline(deps); if sorted { deps.sort_by_key(|d| { - pep508_rs::Requirement::::parse(d.clone().as_str().unwrap(), &*CWD) + d.as_str() + .and_then(try_parse_requirement) .unwrap() - .name - .clone() + .to_string() }); } - - Ok(ArrayEdit::Add(deps.len() - 1)) + let index = deps + .iter() + .position(|d| d.as_str().and_then(try_parse_requirement).unwrap().name == req.name) + .unwrap(); + Ok(ArrayEdit::Add(index)) } [_] => { let (i, mut old_req) = to_replace.remove(0); diff --git a/crates/uv/tests/edit.rs b/crates/uv/tests/edit.rs index bead26675dfa..798928be6c64 100644 --- a/crates/uv/tests/edit.rs +++ b/crates/uv/tests/edit.rs @@ -2077,8 +2077,8 @@ fn add_update_marker() -> Result<()> { version = "0.1.0" requires-python = ">=3.8" dependencies = [ - "requests>=2.30; python_version >= '3.11'", "requests>=2.0,<2.29 ; python_full_version < '3.11'", + "requests>=2.30; python_version >= '3.11'", ] "### ); From c80112aa1d862ac1efd3956b67d2f29a49f0e818 Mon Sep 17 00:00:00 2001 From: Chan Kang Date: Sun, 25 Aug 2024 21:52:23 -0400 Subject: [PATCH 06/18] fix add_update_marker --- crates/uv/tests/edit.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/uv/tests/edit.rs b/crates/uv/tests/edit.rs index 798928be6c64..9e25ba942c06 100644 --- a/crates/uv/tests/edit.rs +++ b/crates/uv/tests/edit.rs @@ -2111,8 +2111,8 @@ fn add_update_marker() -> Result<()> { version = "0.1.0" requires-python = ">=3.8" dependencies = [ - "requests>=2.30; python_version >= '3.11'", "requests>=2.0,<2.20 ; python_full_version < '3.11'", + "requests>=2.30; python_version >= '3.11'", ] "### ); @@ -2149,8 +2149,8 @@ fn add_update_marker() -> Result<()> { version = "0.1.0" requires-python = ">=3.8" dependencies = [ - "requests>=2.30; python_version >= '3.11'", "requests>=2.0,<2.20 ; python_full_version < '3.11'", + "requests>=2.30; python_version >= '3.11'", "requests>=2.31 ; python_full_version >= '3.12' and sys_platform == 'win32'", ] "### @@ -2185,10 +2185,10 @@ fn add_update_marker() -> Result<()> { version = "0.1.0" requires-python = ">=3.8" dependencies = [ - "requests>=2.30; python_version >= '3.11'", "requests>=2.0,<2.20 ; python_full_version < '3.11'", - "requests>=2.31 ; python_full_version >= '3.12' and sys_platform == 'win32'", "requests>=2.10 ; sys_platform == 'win32'", + "requests>=2.30; python_version >= '3.11'", + "requests>=2.31 ; python_full_version >= '3.12' and sys_platform == 'win32'", ] "### ); From b98054f41aab5a6a9051002dc751cda2ab43d39d Mon Sep 17 00:00:00 2001 From: Chan Kang Date: Sun, 25 Aug 2024 23:14:11 -0400 Subject: [PATCH 07/18] compare both name and extras --- crates/uv-workspace/src/pyproject_mut.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/uv-workspace/src/pyproject_mut.rs b/crates/uv-workspace/src/pyproject_mut.rs index fe257b30e3a7..f5846e0000e2 100644 --- a/crates/uv-workspace/src/pyproject_mut.rs +++ b/crates/uv-workspace/src/pyproject_mut.rs @@ -530,7 +530,8 @@ pub fn add_dependency( } let index = deps .iter() - .position(|d| d.as_str().and_then(try_parse_requirement).unwrap().name == req.name) + .map(|d| d.as_str().and_then(try_parse_requirement).unwrap()) + .position(|r| r.name == req.name && r.extras == req.extras) .unwrap(); Ok(ArrayEdit::Add(index)) } From 1217cc65c29766d70d366a92fa2e2754f2cb29da Mon Sep 17 00:00:00 2001 From: Chan Kang Date: Sun, 25 Aug 2024 23:16:41 -0400 Subject: [PATCH 08/18] move 'sorted' into the conditional since it's only used inside --- crates/uv-workspace/src/pyproject_mut.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/crates/uv-workspace/src/pyproject_mut.rs b/crates/uv-workspace/src/pyproject_mut.rs index f5846e0000e2..50cd12942e57 100644 --- a/crates/uv-workspace/src/pyproject_mut.rs +++ b/crates/uv-workspace/src/pyproject_mut.rs @@ -504,19 +504,20 @@ pub fn add_dependency( has_source: bool, ) -> Result { let mut to_replace = find_dependencies(&req.name, Some(&req.marker), deps); - // determine the dependency list is sorted prior to - // adding the new dependency; the new dependency list - // will be sorted only when the original list is sorted - // so that users' custom dependency ordering is preserved. - let sorted = deps - .clone() - .into_iter() - .collect::>() - .windows(2) - .all(|w| w[0].to_string() <= w[1].to_string()); let edit = match to_replace.as_slice() { [] => { + // determine the dependency list is sorted prior to + // adding the new dependency; the new dependency list + // will be sorted only when the original list is sorted + // so that users' custom dependency ordering is preserved. + let sorted = deps + .clone() + .into_iter() + .collect::>() + .windows(2) + .all(|w| w[0].to_string() <= w[1].to_string()); + deps.push(req.to_string()); reformat_array_multiline(deps); From acaf70315a5b61bdeeeb5f9f5aa5105ed48d54da Mon Sep 17 00:00:00 2001 From: Chan Kang Date: Mon, 26 Aug 2024 10:29:12 -0400 Subject: [PATCH 09/18] remove unnecessary assignment --- crates/uv-workspace/src/pyproject_mut.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/uv-workspace/src/pyproject_mut.rs b/crates/uv-workspace/src/pyproject_mut.rs index 50cd12942e57..0357eeeaf515 100644 --- a/crates/uv-workspace/src/pyproject_mut.rs +++ b/crates/uv-workspace/src/pyproject_mut.rs @@ -505,7 +505,7 @@ pub fn add_dependency( ) -> Result { let mut to_replace = find_dependencies(&req.name, Some(&req.marker), deps); - let edit = match to_replace.as_slice() { + match to_replace.as_slice() { [] => { // determine the dependency list is sorted prior to // adding the new dependency; the new dependency list @@ -545,9 +545,7 @@ pub fn add_dependency( } // Cannot perform ambiguous updates. _ => Err(Error::Ambiguous), - }; - - edit + } } /// Update an existing requirement. From f454d23b018d40c80927a88a9ce93ac4663b75ff Mon Sep 17 00:00:00 2001 From: Chan Kang Date: Mon, 26 Aug 2024 10:33:29 -0400 Subject: [PATCH 10/18] omit syncing in the tests --- crates/uv/tests/edit.rs | 46 ++--------------------------------------- 1 file changed, 2 insertions(+), 44 deletions(-) diff --git a/crates/uv/tests/edit.rs b/crates/uv/tests/edit.rs index 9e25ba942c06..68b6c837116f 100644 --- a/crates/uv/tests/edit.rs +++ b/crates/uv/tests/edit.rs @@ -4061,34 +4061,13 @@ fn sorted_dependencies() -> Result<()> { ] "#})?; - uv_snapshot!(context.filters(), context.add(&["pydantic"]), @r###" + uv_snapshot!(context.filters(), context.add(&["pydantic"]).arg("--no-sync"), @r###" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 19 packages in [TIME] - Prepared 19 packages in [TIME] - Installed 19 packages in [TIME] - + annotated-types==0.6.0 - + cachecontrol==0.14.0 - + certifi==2024.2.2 - + charset-normalizer==3.3.2 - + filelock==3.13.1 - + idna==3.6 - + msgpack==1.0.8 - + multidict==6.0.5 - + mwparserfromhell==0.6.6 - + packaging==24.0 - + project==0.1.0 (from file://[TEMP_DIR]/) - + pydantic==2.6.4 - + pydantic-core==2.16.3 - + pywikibot==9.0.0 - + requests==2.31.0 - + sentry-sdk==1.43.0 - + typing-extensions==4.10.0 - + urllib3==2.2.1 - + yarl==1.9.4 "###); let pyproject_toml = fs_err::read_to_string(context.temp_dir.join("pyproject.toml"))?; @@ -4139,34 +4118,13 @@ fn custom_dependencies() -> Result<()> { ] "#})?; - uv_snapshot!(context.filters(), context.add(&["pydantic"]), @r###" + uv_snapshot!(context.filters(), context.add(&["pydantic"]).arg("--no-sync"), @r###" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 19 packages in [TIME] - Prepared 19 packages in [TIME] - Installed 19 packages in [TIME] - + annotated-types==0.6.0 - + cachecontrol==0.14.0 - + certifi==2024.2.2 - + charset-normalizer==3.3.2 - + filelock==3.13.1 - + idna==3.6 - + msgpack==1.0.8 - + multidict==6.0.5 - + mwparserfromhell==0.6.6 - + packaging==24.0 - + project==0.1.0 (from file://[TEMP_DIR]/) - + pydantic==2.6.4 - + pydantic-core==2.16.3 - + pywikibot==9.0.0 - + requests==2.31.0 - + sentry-sdk==1.43.0 - + typing-extensions==4.10.0 - + urllib3==2.2.1 - + yarl==1.9.4 "###); let pyproject_toml = fs_err::read_to_string(context.temp_dir.join("pyproject.toml"))?; From 2659dc72489f3b8401d8e198a5bacb311f2e3926 Mon Sep 17 00:00:00 2001 From: Chan Kang Date: Mon, 26 Aug 2024 13:10:16 -0400 Subject: [PATCH 11/18] add itertools to uv-workspace --- Cargo.lock | 1 + crates/uv-workspace/Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 31bd8ab4ea42..7d23463a69e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5319,6 +5319,7 @@ dependencies = [ "fs-err", "glob", "insta", + "itertools 0.13.0", "pep440_rs", "pep508_rs", "pypi-types", diff --git a/crates/uv-workspace/Cargo.toml b/crates/uv-workspace/Cargo.toml index 7b5a40473cfa..909e682b3712 100644 --- a/crates/uv-workspace/Cargo.toml +++ b/crates/uv-workspace/Cargo.toml @@ -35,6 +35,7 @@ toml = { workspace = true } toml_edit = { workspace = true } tracing = { workspace = true } url = { workspace = true } +itertools = { workspace = true } [dev-dependencies] insta = { version = "1.39.0", features = ["filters", "json", "redactions"] } From 99345448808f967b2b171fa6aafb12bb169e2bec Mon Sep 17 00:00:00 2001 From: Chan Kang Date: Tue, 27 Aug 2024 12:24:46 -0400 Subject: [PATCH 12/18] need to fix reformatting --- crates/uv-workspace/src/pyproject_mut.rs | 27 ++++++++++++------------ 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/crates/uv-workspace/src/pyproject_mut.rs b/crates/uv-workspace/src/pyproject_mut.rs index 0357eeeaf515..9df02ab10cee 100644 --- a/crates/uv-workspace/src/pyproject_mut.rs +++ b/crates/uv-workspace/src/pyproject_mut.rs @@ -2,6 +2,7 @@ use std::path::Path; use std::str::FromStr; use std::{fmt, mem}; +use itertools::partition; use pep440_rs::{Version, VersionSpecifier, VersionSpecifiers}; use pep508_rs::{ExtraName, MarkerTree, PackageName, Requirement, VersionOrUrl}; use thiserror::Error; @@ -518,22 +519,20 @@ pub fn add_dependency( .windows(2) .all(|w| w[0].to_string() <= w[1].to_string()); - deps.push(req.to_string()); + let mut index = deps.len(); + if sorted { + index = partition( + &mut deps.clone().into_iter().collect::>(), + |d: &Value| { + *d.to_string() + .trim_matches(|c| c == '\"' || c == ' ' || c == '\n') + < *req.to_string() + }, + ); + }; + deps.insert(index, req.to_string()); reformat_array_multiline(deps); - if sorted { - deps.sort_by_key(|d| { - d.as_str() - .and_then(try_parse_requirement) - .unwrap() - .to_string() - }); - } - let index = deps - .iter() - .map(|d| d.as_str().and_then(try_parse_requirement).unwrap()) - .position(|r| r.name == req.name && r.extras == req.extras) - .unwrap(); Ok(ArrayEdit::Add(index)) } [_] => { From fe7fbfe08c74e4f76cc26a795d04416369f4502f Mon Sep 17 00:00:00 2001 From: Chan Kang Date: Tue, 27 Aug 2024 16:15:58 -0400 Subject: [PATCH 13/18] handle the indentation edge case --- crates/uv-workspace/src/pyproject_mut.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/crates/uv-workspace/src/pyproject_mut.rs b/crates/uv-workspace/src/pyproject_mut.rs index 9df02ab10cee..2b5a0a570c0d 100644 --- a/crates/uv-workspace/src/pyproject_mut.rs +++ b/crates/uv-workspace/src/pyproject_mut.rs @@ -530,7 +530,25 @@ pub fn add_dependency( }, ); }; + deps.insert(index, req.to_string()); + // `reformat_array_multiline` uses the indentation of the first dependency entry. + // Therefore, we retrieve the indentation of the first dependency entry and apply it to the new entry. + // Note that it is only necessary if the newly added dependency is going to be the first in the list + // _and_ the dependency list was not empty prior to adding the new dependency. + if deps.len() > 1 && index == 0 { + let prefix = deps + .clone() + .get(index + 1) + .unwrap() + .decor() + .prefix() + .unwrap() + .clone(); + + deps.get_mut(index).unwrap().decor_mut().set_prefix(prefix); + } + reformat_array_multiline(deps); Ok(ArrayEdit::Add(index)) From e1e55f92bda9d501ef8b1e7b2dfc1ed48b15c442 Mon Sep 17 00:00:00 2001 From: Chan Kang Date: Tue, 27 Aug 2024 16:36:39 -0400 Subject: [PATCH 14/18] add check for str --- crates/uv-workspace/src/pyproject_mut.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/crates/uv-workspace/src/pyproject_mut.rs b/crates/uv-workspace/src/pyproject_mut.rs index 2b5a0a570c0d..38fa492e72c6 100644 --- a/crates/uv-workspace/src/pyproject_mut.rs +++ b/crates/uv-workspace/src/pyproject_mut.rs @@ -508,17 +508,19 @@ pub fn add_dependency( match to_replace.as_slice() { [] => { - // determine the dependency list is sorted prior to + // Determine the dependency list is sorted prior to // adding the new dependency; the new dependency list // will be sorted only when the original list is sorted // so that users' custom dependency ordering is preserved. - let sorted = deps - .clone() - .into_iter() - .collect::>() - .windows(2) - .all(|w| w[0].to_string() <= w[1].to_string()); - + // Additionallly, if the table is invalid (i.e. contains non-string values) + // we still treat it as unsorted for the sake of simplicity. + let sorted = deps.iter().all(|d| d.is_str()) + && deps + .clone() + .into_iter() + .collect::>() + .windows(2) + .all(|w| w[0].to_string() <= w[1].to_string()); let mut index = deps.len(); if sorted { index = partition( From 7f33e71ea30ed839ff95e84b7ff4fe8b839124be Mon Sep 17 00:00:00 2001 From: Chan Kang Date: Tue, 27 Aug 2024 18:06:30 -0400 Subject: [PATCH 15/18] clippy --- crates/uv-workspace/src/pyproject_mut.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/uv-workspace/src/pyproject_mut.rs b/crates/uv-workspace/src/pyproject_mut.rs index 38fa492e72c6..6a933e26d466 100644 --- a/crates/uv-workspace/src/pyproject_mut.rs +++ b/crates/uv-workspace/src/pyproject_mut.rs @@ -514,7 +514,7 @@ pub fn add_dependency( // so that users' custom dependency ordering is preserved. // Additionallly, if the table is invalid (i.e. contains non-string values) // we still treat it as unsorted for the sake of simplicity. - let sorted = deps.iter().all(|d| d.is_str()) + let sorted = deps.iter().all(toml_edit::Value::is_str) && deps .clone() .into_iter() From 4e3031dacca2d3474dabc344f8216a151f70492e Mon Sep 17 00:00:00 2001 From: Chan Kang Date: Tue, 27 Aug 2024 18:10:05 -0400 Subject: [PATCH 16/18] fix typo --- crates/uv-workspace/src/pyproject_mut.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/uv-workspace/src/pyproject_mut.rs b/crates/uv-workspace/src/pyproject_mut.rs index 6a933e26d466..e896c22e2295 100644 --- a/crates/uv-workspace/src/pyproject_mut.rs +++ b/crates/uv-workspace/src/pyproject_mut.rs @@ -512,7 +512,7 @@ pub fn add_dependency( // adding the new dependency; the new dependency list // will be sorted only when the original list is sorted // so that users' custom dependency ordering is preserved. - // Additionallly, if the table is invalid (i.e. contains non-string values) + // Additionally, if the table is invalid (i.e. contains non-string values) // we still treat it as unsorted for the sake of simplicity. let sorted = deps.iter().all(toml_edit::Value::is_str) && deps From db2af8537d1af8c3feb18d2653df83d35467f24c Mon Sep 17 00:00:00 2001 From: konstin Date: Thu, 29 Aug 2024 16:03:40 +0200 Subject: [PATCH 17/18] Don't lock on sortedness tests --- crates/uv/tests/edit.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/uv/tests/edit.rs b/crates/uv/tests/edit.rs index 68b6c837116f..75c3c67b3248 100644 --- a/crates/uv/tests/edit.rs +++ b/crates/uv/tests/edit.rs @@ -4061,13 +4061,12 @@ fn sorted_dependencies() -> Result<()> { ] "#})?; - uv_snapshot!(context.filters(), context.add(&["pydantic"]).arg("--no-sync"), @r###" + uv_snapshot!(context.filters(), context.add(&["pydantic"]).arg("--frozen"), @r###" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Resolved 19 packages in [TIME] "###); let pyproject_toml = fs_err::read_to_string(context.temp_dir.join("pyproject.toml"))?; @@ -4085,7 +4084,7 @@ fn sorted_dependencies() -> Result<()> { dependencies = [ "CacheControl[filecache]>=0.14,<0.15", "mwparserfromhell", - "pydantic>=2.6.4", + "pydantic", "pywikibot", "sentry-sdk", "yarl", @@ -4118,13 +4117,12 @@ fn custom_dependencies() -> Result<()> { ] "#})?; - uv_snapshot!(context.filters(), context.add(&["pydantic"]).arg("--no-sync"), @r###" + uv_snapshot!(context.filters(), context.add(&["pydantic"]).arg("--frozen"), @r###" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Resolved 19 packages in [TIME] "###); let pyproject_toml = fs_err::read_to_string(context.temp_dir.join("pyproject.toml"))?; @@ -4145,7 +4143,7 @@ fn custom_dependencies() -> Result<()> { "mwparserfromhell", "pywikibot", "sentry-sdk", - "pydantic>=2.6.4", + "pydantic", ] "### ); From c27bf33c1d212190e398b30342d191e7ea54fcde Mon Sep 17 00:00:00 2001 From: konstin Date: Thu, 29 Aug 2024 16:19:21 +0200 Subject: [PATCH 18/18] Simplify sorting logic --- crates/uv-workspace/src/pyproject_mut.rs | 41 +++++++++++------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/crates/uv-workspace/src/pyproject_mut.rs b/crates/uv-workspace/src/pyproject_mut.rs index e896c22e2295..8eb2a6ef854c 100644 --- a/crates/uv-workspace/src/pyproject_mut.rs +++ b/crates/uv-workspace/src/pyproject_mut.rs @@ -2,7 +2,7 @@ use std::path::Path; use std::str::FromStr; use std::{fmt, mem}; -use itertools::partition; +use itertools::Itertools; use pep440_rs::{Version, VersionSpecifier, VersionSpecifiers}; use pep508_rs::{ExtraName, MarkerTree, PackageName, Requirement, VersionOrUrl}; use thiserror::Error; @@ -508,36 +508,33 @@ pub fn add_dependency( match to_replace.as_slice() { [] => { - // Determine the dependency list is sorted prior to + // Determine if the dependency list is sorted prior to // adding the new dependency; the new dependency list // will be sorted only when the original list is sorted - // so that users' custom dependency ordering is preserved. + // so that user's custom dependency ordering is preserved. // Additionally, if the table is invalid (i.e. contains non-string values) // we still treat it as unsorted for the sake of simplicity. let sorted = deps.iter().all(toml_edit::Value::is_str) && deps - .clone() - .into_iter() - .collect::>() - .windows(2) - .all(|w| w[0].to_string() <= w[1].to_string()); - let mut index = deps.len(); - if sorted { - index = partition( - &mut deps.clone().into_iter().collect::>(), - |d: &Value| { - *d.to_string() - .trim_matches(|c| c == '\"' || c == ' ' || c == '\n') - < *req.to_string() - }, - ); + .iter() + .tuple_windows() + .all(|(a, b)| a.as_str() <= b.as_str()); + + let req_string = req.to_string(); + let index = if sorted { + deps.iter() + .position(|d: &Value| d.as_str() > Some(req_string.as_str())) + .unwrap_or(deps.len()) + } else { + deps.len() }; - deps.insert(index, req.to_string()); + deps.insert(index, req_string); // `reformat_array_multiline` uses the indentation of the first dependency entry. - // Therefore, we retrieve the indentation of the first dependency entry and apply it to the new entry. - // Note that it is only necessary if the newly added dependency is going to be the first in the list - // _and_ the dependency list was not empty prior to adding the new dependency. + // Therefore, we retrieve the indentation of the first dependency entry and apply it to + // the new entry. Note that it is only necessary if the newly added dependency is going + // to be the first in the list _and_ the dependency list was not empty prior to adding + // the new dependency. if deps.len() > 1 && index == 0 { let prefix = deps .clone()