From d31b995511ca328e396c0e78b4bc58d1c9329c9d Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 15 Oct 2024 16:22:45 -0700 Subject: [PATCH] Pin named indexes in `uv add` (#7747) ## Summary This PR adds an index pin with `uv add` when the user provides exactly one named index. We don't pin if the user provides an unnamed index, or if they provide multiple indexes. We probably _could_ pin on multiple indexes by writing the sources _after_ resolution, if that's desirable. But we have no idea which index the user _expects_ each package to come from. Possible extensions: - `uv add --no-pin` to avoid this pinning. - Warn if they provide a single, unnamed index? I'm not sure if that's worth a warn. Open to input. --- crates/uv-workspace/src/pyproject.rs | 15 ++++++++++- crates/uv/src/commands/project/add.rs | 20 ++++++++++++++- crates/uv/tests/it/edit.rs | 36 +++++++++++++++++++-------- 3 files changed, 58 insertions(+), 13 deletions(-) diff --git a/crates/uv-workspace/src/pyproject.rs b/crates/uv-workspace/src/pyproject.rs index bf551b299..c72a3d7d5 100644 --- a/crates/uv-workspace/src/pyproject.rs +++ b/crates/uv-workspace/src/pyproject.rs @@ -965,6 +965,7 @@ impl Source { source: RequirementSource, workspace: bool, editable: Option, + index: Option, rev: Option, tag: Option, branch: Option, @@ -1005,7 +1006,19 @@ impl Source { } let source = match source { - RequirementSource::Registry { .. } => return Ok(None), + RequirementSource::Registry { index: Some(_), .. } => { + return Ok(None); + } + RequirementSource::Registry { index: None, .. } => { + if let Some(index) = index { + Source::Registry { + index, + marker: MarkerTree::TRUE, + } + } else { + return Ok(None); + } + } RequirementSource::Path { install_path, .. } | RequirementSource::Directory { install_path, .. } => Source::Path { editable, diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index 029b409e0..84a0d5cd0 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -366,6 +366,16 @@ pub(crate) async fn add( } } + // If the user provides a single, named index, pin all requirements to that index. + let index = indexes + .first() + .as_ref() + .and_then(|index| index.name.as_ref()) + .filter(|_| indexes.len() == 1) + .inspect(|index| { + debug!("Pinning all requirements to index: `{index}`"); + }); + // Add the requirements to the `pyproject.toml` or script. let mut toml = match &target { Target::Script(script, _) => { @@ -394,6 +404,7 @@ pub(crate) async fn add( requirement, false, editable, + index.cloned(), rev.clone(), tag.clone(), branch.clone(), @@ -409,6 +420,7 @@ pub(crate) async fn add( requirement, workspace, editable, + index.cloned(), rev.clone(), tag.clone(), branch.clone(), @@ -691,7 +703,11 @@ async fn lock_and_sync( }; // Only set a minimum version for registry requirements. - if edit.source.is_some() { + if edit + .source + .as_ref() + .is_some_and(|source| !matches!(source, Source::Registry { .. })) + { continue; } @@ -891,6 +907,7 @@ fn resolve_requirement( requirement: uv_pypi_types::Requirement, workspace: bool, editable: Option, + index: Option, rev: Option, tag: Option, branch: Option, @@ -901,6 +918,7 @@ fn resolve_requirement( requirement.source.clone(), workspace, editable, + index, rev, tag, branch, diff --git a/crates/uv/tests/it/edit.rs b/crates/uv/tests/it/edit.rs index 6b51c7eda..591d03633 100644 --- a/crates/uv/tests/it/edit.rs +++ b/crates/uv/tests/it/edit.rs @@ -5129,7 +5129,7 @@ fn add_no_warn_index_url() -> Result<()> { /// Add an index provided via `--index`. #[test] -fn add_index_url() -> Result<()> { +fn add_index() -> Result<()> { let context = TestContext::new("3.12"); let pyproject_toml = context.temp_dir.child("pyproject.toml"); @@ -5214,6 +5214,7 @@ fn add_index_url() -> Result<()> { ----- stdout ----- ----- stderr ----- + warning: Missing version constraint (e.g., a lower bound) for `jinja2` Resolved 4 packages in [TIME] Prepared 2 packages in [TIME] Installed 2 packages in [TIME] @@ -5243,6 +5244,9 @@ fn add_index_url() -> Result<()> { [[tool.uv.index]] url = "https://pypi.org/simple" + + [tool.uv.sources] + jinja2 = { index = "pytorch" } "### ); }); @@ -5302,7 +5306,7 @@ fn add_index_url() -> Result<()> { [package.metadata] requires-dist = [ { name = "iniconfig", specifier = "==2.0.0" }, - { name = "jinja2", specifier = ">=3.1.3" }, + { name = "jinja2", specifier = ">=3.1.3", index = "https://download.pytorch.org/whl/cu121" }, ] "### ); @@ -5341,6 +5345,9 @@ fn add_index_url() -> Result<()> { [[tool.uv.index]] url = "https://pypi.org/simple" + + [tool.uv.sources] + jinja2 = { index = "pytorch" } "### ); }); @@ -5406,7 +5413,7 @@ fn add_index_url() -> Result<()> { [package.metadata] requires-dist = [ { name = "iniconfig", specifier = "==2.0.0" }, - { name = "jinja2", specifier = ">=3.1.3" }, + { name = "jinja2", specifier = ">=3.1.3", index = "https://test.pypi.org/simple" }, ] "### ); @@ -5419,6 +5426,7 @@ fn add_index_url() -> Result<()> { ----- stdout ----- ----- stderr ----- + warning: Missing version constraint (e.g., a lower bound) for `typing-extensions` Resolved 5 packages in [TIME] Prepared 1 package in [TIME] Installed 1 package in [TIME] @@ -5448,6 +5456,9 @@ fn add_index_url() -> Result<()> { [[tool.uv.index]] name = "pytorch" url = "https://test.pypi.org/simple" + + [tool.uv.sources] + jinja2 = { index = "pytorch" } "### ); }); @@ -5474,13 +5485,13 @@ fn add_index_url() -> Result<()> { [[package]] name = "jinja2" version = "3.1.3" - source = { registry = "https://pypi.org/simple" } + source = { registry = "https://test.pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] - sdist = { url = "https://files.pythonhosted.org/packages/b2/5e/3a21abf3cd467d7876045335e681d276ac32492febe6d98ad89562d1a7e1/Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90", size = 268261 } + sdist = { url = "https://test-files.pythonhosted.org/packages/3e/f0/69ae37cced6b277dc0419dbb1c6e4fb259e5e319a1a971061a2776316bec/Jinja2-3.1.3.tar.gz", hash = "sha256:27fb536952e578492fa66d8681d8967d8bdf1eb36368b1f842b53251c9f0bfe1", size = 268254 } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/6d/6de6be2d02603ab56e72997708809e8a5b0fbfee080735109b40a3564843/Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa", size = 133236 }, + { url = "https://test-files.pythonhosted.org/packages/47/dc/9d1c0f1ddbedb1e67f7d00e91819b5a9157056ad83bfa64c12ecef8a4f4e/Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:ddd11470e8a1dc4c30e3146400f0130fed7d85886c5f8082f309355b4b0c1128", size = 133236 }, ] [[package]] @@ -5514,7 +5525,7 @@ fn add_index_url() -> Result<()> { [package.metadata] requires-dist = [ { name = "iniconfig", specifier = "==2.0.0" }, - { name = "jinja2", specifier = ">=3.1.3" }, + { name = "jinja2", specifier = ">=3.1.3", index = "https://test.pypi.org/simple" }, { name = "typing-extensions", specifier = ">=4.12.2" }, ] @@ -5564,6 +5575,9 @@ fn add_index_url() -> Result<()> { [[tool.uv.index]] url = "https://pypi.org/simple" + + [tool.uv.sources] + jinja2 = { index = "pytorch" } "### ); }); @@ -5590,13 +5604,13 @@ fn add_index_url() -> Result<()> { [[package]] name = "jinja2" version = "3.1.3" - source = { registry = "https://pypi.org/simple" } + source = { registry = "https://test.pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] - sdist = { url = "https://files.pythonhosted.org/packages/b2/5e/3a21abf3cd467d7876045335e681d276ac32492febe6d98ad89562d1a7e1/Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90", size = 268261 } + sdist = { url = "https://test-files.pythonhosted.org/packages/3e/f0/69ae37cced6b277dc0419dbb1c6e4fb259e5e319a1a971061a2776316bec/Jinja2-3.1.3.tar.gz", hash = "sha256:27fb536952e578492fa66d8681d8967d8bdf1eb36368b1f842b53251c9f0bfe1", size = 268254 } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/6d/6de6be2d02603ab56e72997708809e8a5b0fbfee080735109b40a3564843/Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa", size = 133236 }, + { url = "https://test-files.pythonhosted.org/packages/47/dc/9d1c0f1ddbedb1e67f7d00e91819b5a9157056ad83bfa64c12ecef8a4f4e/Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:ddd11470e8a1dc4c30e3146400f0130fed7d85886c5f8082f309355b4b0c1128", size = 133236 }, ] [[package]] @@ -5630,7 +5644,7 @@ fn add_index_url() -> Result<()> { [package.metadata] requires-dist = [ { name = "iniconfig", specifier = "==2.0.0" }, - { name = "jinja2", specifier = ">=3.1.3" }, + { name = "jinja2", specifier = ">=3.1.3", index = "https://test.pypi.org/simple" }, { name = "typing-extensions", specifier = ">=4.12.2" }, ]