#![allow(clippy::disallowed_types)] #[cfg(feature = "git")] mod conditional_imports { pub(crate) use crate::common::{READ_ONLY_GITHUB_TOKEN, decode_token}; } #[cfg(feature = "git")] use conditional_imports::*; use anyhow::Result; use assert_cmd::assert::OutputAssertExt; use assert_fs::prelude::*; use indoc::{formatdoc, indoc}; use insta::assert_snapshot; use std::path::Path; use uv_fs::Simplified; use wiremock::{Mock, MockServer, ResponseTemplate, matchers::method}; use uv_static::EnvVars; use crate::common::{TestContext, packse_index_url, uv_snapshot, venv_bin_path}; /// Add a PyPI requirement. #[test] fn add_registry() -> 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_snapshot!(context.filters(), context.add().arg("anyio==3.7.0"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 4 packages in [TIME] Prepared 3 packages in [TIME] Installed 3 packages in [TIME] + anyio==3.7.0 + idna==3.6 + sniffio==1.3.1 "); 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 = [ "anyio==3.7.0", ] "### ); }); let lock = context.read("uv.lock"); insta::with_settings!({ filters => context.filters(), }, { assert_snapshot!( lock, @r#" version = 1 revision = 2 requires-python = ">=3.12" [options] exclude-newer = "2024-03-25T00:00:00Z" [[package]] name = "anyio" version = "3.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "sniffio" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c6/b3/fefbf7e78ab3b805dec67d698dc18dd505af7a18a8dd08868c9b4fa736b5/anyio-3.7.0.tar.gz", hash = "sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce", size = 142737, upload-time = "2023-05-27T11:12:46.688Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/68/fe/7ce1926952c8a403b35029e194555558514b365ad77d75125f521a2bec62/anyio-3.7.0-py3-none-any.whl", hash = "sha256:eddca883c4175f14df8aedce21054bfca3adb70ffe76a9f607aef9d7fa2ea7f0", size = 80873, upload-time = "2023-05-27T11:12:44.474Z" }, ] [[package]] name = "idna" version = "3.6" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426, upload-time = "2023-11-25T15:40:54.902Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567, upload-time = "2023-11-25T15:40:52.604Z" }, ] [[package]] name = "project" version = "0.1.0" source = { virtual = "." } dependencies = [ { name = "anyio" }, ] [package.metadata] requires-dist = [{ name = "anyio", specifier = "==3.7.0" }] [[package]] name = "sniffio" version = "1.3.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] "# ); }); // Install from the lockfile. uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Audited 3 packages in [TIME] "); Ok(()) } /// Add a Git requirement. #[test] #[cfg(feature = "git")] fn add_git() -> 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 = ["anyio==3.7.0"] "#})?; uv_snapshot!(context.filters(), context.lock(), @r###" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 4 packages in [TIME] "###); uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Prepared 3 packages in [TIME] Installed 3 packages in [TIME] + anyio==3.7.0 + idna==3.6 + sniffio==1.3.1 "); // Adding with an ambiguous Git reference should treat it as a revision. 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 5 packages in [TIME] Prepared 1 package in [TIME] Installed 1 package in [TIME] + uv-public-pypackage==0.1.0 (from git+https://github.com/astral-test/uv-public-pypackage@0dacfd662c64cb4ceb16e6cf65a157a8b715b979) "); uv_snapshot!(context.filters(), context.add().arg("uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage").arg("--tag=0.0.1"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 5 packages in [TIME] Audited 4 packages in [TIME] "); 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 = [ "anyio==3.7.0", "uv-public-pypackage", ] [tool.uv.sources] uv-public-pypackage = { git = "https://github.com/astral-test/uv-public-pypackage", tag = "0.0.1" } "### ); }); let lock = context.read("uv.lock"); insta::with_settings!({ filters => context.filters(), }, { assert_snapshot!( lock, @r#" version = 1 revision = 2 requires-python = ">=3.12" [options] exclude-newer = "2024-03-25T00:00:00Z" [[package]] name = "anyio" version = "3.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "sniffio" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c6/b3/fefbf7e78ab3b805dec67d698dc18dd505af7a18a8dd08868c9b4fa736b5/anyio-3.7.0.tar.gz", hash = "sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce", size = 142737, upload-time = "2023-05-27T11:12:46.688Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/68/fe/7ce1926952c8a403b35029e194555558514b365ad77d75125f521a2bec62/anyio-3.7.0-py3-none-any.whl", hash = "sha256:eddca883c4175f14df8aedce21054bfca3adb70ffe76a9f607aef9d7fa2ea7f0", size = 80873, upload-time = "2023-05-27T11:12:44.474Z" }, ] [[package]] name = "idna" version = "3.6" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426, upload-time = "2023-11-25T15:40:54.902Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567, upload-time = "2023-11-25T15:40:52.604Z" }, ] [[package]] name = "project" version = "0.1.0" source = { virtual = "." } dependencies = [ { name = "anyio" }, { name = "uv-public-pypackage" }, ] [package.metadata] requires-dist = [ { name = "anyio", specifier = "==3.7.0" }, { name = "uv-public-pypackage", git = "https://github.com/astral-test/uv-public-pypackage?tag=0.0.1" }, ] [[package]] name = "sniffio" version = "1.3.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] [[package]] name = "uv-public-pypackage" version = "0.1.0" source = { git = "https://github.com/astral-test/uv-public-pypackage?tag=0.0.1#0dacfd662c64cb4ceb16e6cf65a157a8b715b979" } "# ); }); // Install from the lockfile. uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Audited 4 packages in [TIME] "); Ok(()) } /// Add a Git requirement from a private repository, with credentials. The resolution should /// succeed, but the `pyproject.toml` should omit the credentials. #[test] #[cfg(feature = "git")] fn add_git_private_source() -> Result<()> { let context = TestContext::new("3.12"); let token = decode_token(READ_ONLY_GITHUB_TOKEN); 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_snapshot!(context.filters(), context.add().arg(format!("uv-private-pypackage @ git+https://{token}@github.com/astral-test/uv-private-pypackage")), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 2 packages in [TIME] Prepared 1 package in [TIME] Installed 1 package in [TIME] + uv-private-pypackage==0.1.0 (from git+https://github.com/astral-test/uv-private-pypackage@d780faf0ac91257d4d5a4f0c5a0e4509608c0071) "); 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-private-pypackage", ] [tool.uv.sources] uv-private-pypackage = { git = "https://github.com/astral-test/uv-private-pypackage" } "### ); }); let lock = context.read("uv.lock"); insta::with_settings!({ filters => context.filters(), }, { assert_snapshot!( lock, @r#" version = 1 revision = 2 requires-python = ">=3.12" [options] exclude-newer = "2024-03-25T00:00:00Z" [[package]] name = "project" version = "0.1.0" source = { virtual = "." } dependencies = [ { name = "uv-private-pypackage" }, ] [package.metadata] requires-dist = [{ name = "uv-private-pypackage", git = "https://github.com/astral-test/uv-private-pypackage" }] [[package]] name = "uv-private-pypackage" version = "0.1.0" source = { git = "https://github.com/astral-test/uv-private-pypackage#d780faf0ac91257d4d5a4f0c5a0e4509608c0071" } "# ); }); // Install from the lockfile. uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Audited 1 package in [TIME] "); Ok(()) } /// Add a Git requirement from a private repository, with credentials. Since `--raw-sources` is /// specified, the `pyproject.toml` should retain the credentials. #[test] #[cfg(feature = "git")] fn add_git_private_raw() -> Result<()> { let context = TestContext::new("3.12"); let token = decode_token(READ_ONLY_GITHUB_TOKEN); let mut filters = context.filters(); filters.push((&token, "***")); 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_snapshot!(filters, context.add().arg(format!("uv-private-pypackage @ git+https://{token}@github.com/astral-test/uv-private-pypackage")).arg("--raw-sources"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 2 packages in [TIME] Prepared 1 package in [TIME] Installed 1 package in [TIME] + uv-private-pypackage==0.1.0 (from git+https://github.com/astral-test/uv-private-pypackage@d780faf0ac91257d4d5a4f0c5a0e4509608c0071) "); let pyproject_toml = context.read("pyproject.toml"); insta::with_settings!({ filters => filters.clone() }, { assert_snapshot!( pyproject_toml, @r#" [project] name = "project" version = "0.1.0" requires-python = ">=3.12" dependencies = [ "uv-private-pypackage @ git+https://***@github.com/astral-test/uv-private-pypackage", ] "# ); }); let lock = context.read("uv.lock"); insta::with_settings!({ filters => filters.clone(), }, { assert_snapshot!( lock, @r#" version = 1 revision = 2 requires-python = ">=3.12" [options] exclude-newer = "2024-03-25T00:00:00Z" [[package]] name = "project" version = "0.1.0" source = { virtual = "." } dependencies = [ { name = "uv-private-pypackage" }, ] [package.metadata] requires-dist = [{ name = "uv-private-pypackage", git = "https://github.com/astral-test/uv-private-pypackage" }] [[package]] name = "uv-private-pypackage" version = "0.1.0" source = { git = "https://github.com/astral-test/uv-private-pypackage#d780faf0ac91257d4d5a4f0c5a0e4509608c0071" } "# ); }); // Install from the lockfile. uv_snapshot!(filters, context.sync().arg("--frozen"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Audited 1 package in [TIME] "); Ok(()) } #[test] #[cfg(feature = "git")] fn add_git_error() -> 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_snapshot!(context.filters(), context.lock(), @r###" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 1 package in [TIME] "###); uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Audited in [TIME] "); // Provide a tag without a Git source. uv_snapshot!(context.filters(), context.add().arg("flask").arg("--tag").arg("0.0.1"), @r###" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- error: `flask` did not resolve to a Git repository, but a Git reference (`--tag 0.0.1`) was provided. "###); // Provide a tag with a non-Git source. uv_snapshot!(context.filters(), context.add().arg("flask @ https://files.pythonhosted.org/packages/61/80/ffe1da13ad9300f87c93af113edd0638c75138c42a0994becfacac078c06/flask-3.0.3-py3-none-any.whl").arg("--branch").arg("0.0.1"), @r###" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- error: `flask` did not resolve to a Git repository, but a Git reference (`--branch 0.0.1`) was provided. "###); Ok(()) } #[test] #[cfg(feature = "git")] fn add_git_branch() -> 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_snapshot!(context.filters(), context.add().arg("uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage").arg("--branch").arg("test-branch"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 2 packages in [TIME] Prepared 1 package in [TIME] Installed 1 package in [TIME] + uv-public-pypackage==0.1.0 (from git+https://github.com/astral-test/uv-public-pypackage@0dacfd662c64cb4ceb16e6cf65a157a8b715b979) "); Ok(()) } /// Add a Git requirement using the `--raw-sources` API. #[test] #[cfg(feature = "git")] fn add_git_raw() -> 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 = ["anyio==3.7.0"] "#})?; uv_snapshot!(context.filters(), context.lock(), @r###" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 4 packages in [TIME] "###); uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Prepared 3 packages in [TIME] Installed 3 packages in [TIME] + anyio==3.7.0 + idna==3.6 + sniffio==1.3.1 "); // Use an ambiguous tag reference, which would otherwise not resolve. uv_snapshot!(context.filters(), context.add().arg("uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage@0.0.1").arg("--raw-sources"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 5 packages in [TIME] Prepared 1 package in [TIME] Installed 1 package in [TIME] + 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 = [ "anyio==3.7.0", "uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage@0.0.1", ] "### ); }); let lock = context.read("uv.lock"); insta::with_settings!({ filters => context.filters(), }, { assert_snapshot!( lock, @r#" version = 1 revision = 2 requires-python = ">=3.12" [options] exclude-newer = "2024-03-25T00:00:00Z" [[package]] name = "anyio" version = "3.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "sniffio" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c6/b3/fefbf7e78ab3b805dec67d698dc18dd505af7a18a8dd08868c9b4fa736b5/anyio-3.7.0.tar.gz", hash = "sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce", size = 142737, upload-time = "2023-05-27T11:12:46.688Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/68/fe/7ce1926952c8a403b35029e194555558514b365ad77d75125f521a2bec62/anyio-3.7.0-py3-none-any.whl", hash = "sha256:eddca883c4175f14df8aedce21054bfca3adb70ffe76a9f607aef9d7fa2ea7f0", size = 80873, upload-time = "2023-05-27T11:12:44.474Z" }, ] [[package]] name = "idna" version = "3.6" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426, upload-time = "2023-11-25T15:40:54.902Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567, upload-time = "2023-11-25T15:40:52.604Z" }, ] [[package]] name = "project" version = "0.1.0" source = { virtual = "." } dependencies = [ { name = "anyio" }, { name = "uv-public-pypackage" }, ] [package.metadata] requires-dist = [ { name = "anyio", specifier = "==3.7.0" }, { name = "uv-public-pypackage", git = "https://github.com/astral-test/uv-public-pypackage?rev=0.0.1" }, ] [[package]] name = "sniffio" version = "1.3.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] [[package]] name = "uv-public-pypackage" version = "0.1.0" source = { git = "https://github.com/astral-test/uv-public-pypackage?rev=0.0.1#0dacfd662c64cb4ceb16e6cf65a157a8b715b979" } "# ); }); // Install from the lockfile. uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Audited 4 packages in [TIME] "); Ok(()) } /// Add a Git requirement without the `git+` prefix. #[test] #[cfg(feature = "git")] fn add_git_implicit() -> 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 = ["anyio==3.7.0"] "#})?; uv_snapshot!(context.filters(), context.lock(), @r###" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 4 packages in [TIME] "###); uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Prepared 3 packages in [TIME] Installed 3 packages in [TIME] + anyio==3.7.0 + idna==3.6 + sniffio==1.3.1 "); // Omit the `git+` prefix. uv_snapshot!(context.filters(), context.add().arg("uv-public-pypackage @ https://github.com/astral-test/uv-public-pypackage.git"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 5 packages in [TIME] Prepared 1 package in [TIME] Installed 1 package in [TIME] + uv-public-pypackage==0.1.0 (from git+https://github.com/astral-test/uv-public-pypackage.git@b270df1a2fb5d012294e9aaf05e7e0bab1e6a389) "); Ok(()) } /// `--raw-sources` should be considered conflicting with sources-specific arguments, like `--tag`. #[test] #[cfg(feature = "git")] fn add_raw_error() -> 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 = [] "#})?; // Provide a tag without a Git source. uv_snapshot!(context.filters(), context.add().arg("uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage").arg("--tag").arg("0.0.1").arg("--raw-sources"), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- error: the argument '--tag ' cannot be used with '--raw' Usage: uv add --cache-dir [CACHE_DIR] --tag --exclude-newer > For more information, try '--help'. "); Ok(()) } #[test] fn reinstall_local_source_trees() -> Result<()> { let context = TestContext::new("3.12"); let project_1 = context.temp_dir.child("project1"); project_1.child("pyproject.toml").write_str(indoc! {r#" [project] name = "project1" version = "0.1.0" requires-python = ">=3.12" dependencies = [] "#})?; let project_2 = context.temp_dir.child("project2"); project_2.child("pyproject.toml").write_str(indoc! {r#" [project] name = "project2" version = "0.1.0" requires-python = ">=3.12" dependencies = [] "#})?; uv_snapshot!(context.filters(), context.add().current_dir(&project_1).arg("../project2").arg("--editable"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] Creating virtual environment at: .venv Resolved 2 packages in [TIME] Prepared 1 package in [TIME] Installed 1 package in [TIME] + project2==0.1.0 (from file://[TEMP_DIR]/project2) "); // Running `uv add` should reinstall the project. uv_snapshot!(context.filters(), context.add().current_dir(&project_1).arg("../project2").arg("--editable"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 2 packages in [TIME] Prepared 1 package in [TIME] Uninstalled 1 package in [TIME] Installed 1 package in [TIME] ~ project2==0.1.0 (from file://[TEMP_DIR]/project2) "); Ok(()) } #[test] #[cfg(feature = "git")] fn add_editable_error() -> 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 = [] "#})?; // Provide `--editable` with a non-source tree. uv_snapshot!(context.filters(), context.add().arg("flask @ https://files.pythonhosted.org/packages/61/80/ffe1da13ad9300f87c93af113edd0638c75138c42a0994becfacac078c06/flask-3.0.3-py3-none-any.whl").arg("--editable"), @r###" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- error: `flask` did not resolve to a local directory, but the `--editable` flag was provided. Editable installs are only supported for local directories. "###); Ok(()) } /// Add an unnamed requirement. #[test] #[cfg(feature = "git")] fn add_unnamed() -> 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_snapshot!(context.filters(), context.add().arg("git+https://github.com/astral-test/uv-public-pypackage").arg("--tag=0.0.1"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 2 packages in [TIME] Prepared 1 package in [TIME] Installed 1 package in [TIME] + 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", ] [tool.uv.sources] uv-public-pypackage = { git = "https://github.com/astral-test/uv-public-pypackage", tag = "0.0.1" } "### ); }); let lock = context.read("uv.lock"); insta::with_settings!({ filters => context.filters(), }, { assert_snapshot!( lock, @r#" version = 1 revision = 2 requires-python = ">=3.12" [options] exclude-newer = "2024-03-25T00:00:00Z" [[package]] name = "project" version = "0.1.0" source = { virtual = "." } dependencies = [ { name = "uv-public-pypackage" }, ] [package.metadata] requires-dist = [{ name = "uv-public-pypackage", git = "https://github.com/astral-test/uv-public-pypackage?tag=0.0.1" }] [[package]] name = "uv-public-pypackage" version = "0.1.0" source = { git = "https://github.com/astral-test/uv-public-pypackage?tag=0.0.1#0dacfd662c64cb4ceb16e6cf65a157a8b715b979" } "# ); }); // Install from the lockfile. uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Audited 1 package in [TIME] "); Ok(()) } /// Add and remove a development dependency. #[test] fn add_remove_dev() -> 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_snapshot!(context.filters(), context.add().arg("anyio==3.7.0").arg("--dev"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 4 packages in [TIME] Prepared 3 packages in [TIME] Installed 3 packages in [TIME] + anyio==3.7.0 + idna==3.6 + sniffio==1.3.1 "); 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 = [] [dependency-groups] dev = [ "anyio==3.7.0", ] "### ); }); // `uv add` implies a full lock and sync, including development dependencies. let lock = context.read("uv.lock"); insta::with_settings!({ filters => context.filters(), }, { assert_snapshot!( lock, @r#" version = 1 revision = 2 requires-python = ">=3.12" [options] exclude-newer = "2024-03-25T00:00:00Z" [[package]] name = "anyio" version = "3.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "sniffio" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c6/b3/fefbf7e78ab3b805dec67d698dc18dd505af7a18a8dd08868c9b4fa736b5/anyio-3.7.0.tar.gz", hash = "sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce", size = 142737, upload-time = "2023-05-27T11:12:46.688Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/68/fe/7ce1926952c8a403b35029e194555558514b365ad77d75125f521a2bec62/anyio-3.7.0-py3-none-any.whl", hash = "sha256:eddca883c4175f14df8aedce21054bfca3adb70ffe76a9f607aef9d7fa2ea7f0", size = 80873, upload-time = "2023-05-27T11:12:44.474Z" }, ] [[package]] name = "idna" version = "3.6" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426, upload-time = "2023-11-25T15:40:54.902Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567, upload-time = "2023-11-25T15:40:52.604Z" }, ] [[package]] name = "project" version = "0.1.0" source = { virtual = "." } [package.dev-dependencies] dev = [ { name = "anyio" }, ] [package.metadata] [package.metadata.requires-dev] dev = [{ name = "anyio", specifier = "==3.7.0" }] [[package]] name = "sniffio" version = "1.3.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] "# ); }); // Install from the lockfile. uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Audited 3 packages in [TIME] "); // This should fail without --dev. uv_snapshot!(context.filters(), context.remove().arg("anyio"), @r###" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- hint: `anyio` is in the `dev` group (try: `uv remove anyio --group dev`) error: The dependency `anyio` could not be found in `project.dependencies` "###); // Remove the dependency. uv_snapshot!(context.filters(), context.remove().arg("anyio").arg("--dev"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 1 package in [TIME] Uninstalled 3 packages in [TIME] - anyio==3.7.0 - idna==3.6 - sniffio==1.3.1 "); 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 = [] [dependency-groups] dev = [] "### ); }); let lock = context.read("uv.lock"); insta::with_settings!({ filters => context.filters(), }, { assert_snapshot!( lock, @r#" version = 1 revision = 2 requires-python = ">=3.12" [options] exclude-newer = "2024-03-25T00:00:00Z" [[package]] name = "project" version = "0.1.0" source = { virtual = "." } [package.metadata] [package.metadata.requires-dev] dev = [] "# ); }); // Install from the lockfile. uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Audited in [TIME] "); Ok(()) } /// Add and remove an optional dependency. #[test] fn add_remove_optional() -> 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_snapshot!(context.filters(), context.add().arg("anyio==3.7.0").arg("--optional=io"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 4 packages in [TIME] Prepared 3 packages in [TIME] Installed 3 packages in [TIME] + anyio==3.7.0 + idna==3.6 + sniffio==1.3.1 "); 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 = [] [project.optional-dependencies] io = [ "anyio==3.7.0", ] "### ); }); // `uv add` implies a full lock and sync, including development dependencies. let lock = context.read("uv.lock"); insta::with_settings!({ filters => context.filters(), }, { assert_snapshot!( lock, @r#" version = 1 revision = 2 requires-python = ">=3.12" [options] exclude-newer = "2024-03-25T00:00:00Z" [[package]] name = "anyio" version = "3.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "sniffio" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c6/b3/fefbf7e78ab3b805dec67d698dc18dd505af7a18a8dd08868c9b4fa736b5/anyio-3.7.0.tar.gz", hash = "sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce", size = 142737, upload-time = "2023-05-27T11:12:46.688Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/68/fe/7ce1926952c8a403b35029e194555558514b365ad77d75125f521a2bec62/anyio-3.7.0-py3-none-any.whl", hash = "sha256:eddca883c4175f14df8aedce21054bfca3adb70ffe76a9f607aef9d7fa2ea7f0", size = 80873, upload-time = "2023-05-27T11:12:44.474Z" }, ] [[package]] name = "idna" version = "3.6" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426, upload-time = "2023-11-25T15:40:54.902Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567, upload-time = "2023-11-25T15:40:52.604Z" }, ] [[package]] name = "project" version = "0.1.0" source = { virtual = "." } [package.optional-dependencies] io = [ { name = "anyio" }, ] [package.metadata] requires-dist = [{ name = "anyio", marker = "extra == 'io'", specifier = "==3.7.0" }] provides-extras = ["io"] [[package]] name = "sniffio" version = "1.3.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] "# ); }); // Install from the lockfile. At present, this will _uninstall_ the packages since `sync` does // not include extras by default. uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Uninstalled 3 packages in [TIME] - anyio==3.7.0 - idna==3.6 - sniffio==1.3.1 "###); // This should fail without --optional. uv_snapshot!(context.filters(), context.remove().arg("anyio"), @r###" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- hint: `anyio` is an optional dependency (try: `uv remove anyio --optional io`) error: The dependency `anyio` could not be found in `project.dependencies` "###); // Remove the dependency. uv_snapshot!(context.filters(), context.remove().arg("anyio").arg("--optional=io"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 1 package in [TIME] Audited in [TIME] "); 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 = [] [project.optional-dependencies] io = [] "### ); }); let lock = context.read("uv.lock"); insta::with_settings!({ filters => context.filters(), }, { assert_snapshot!( lock, @r#" version = 1 revision = 2 requires-python = ">=3.12" [options] exclude-newer = "2024-03-25T00:00:00Z" [[package]] name = "project" version = "0.1.0" source = { virtual = "." } [package.metadata] provides-extras = ["io"] "# ); }); // Install from the lockfile. uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Audited in [TIME] "); Ok(()) } #[test] fn add_remove_inline_optional() -> 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 = [] optional-dependencies = { io = [ "anyio==3.7.0", ] } "#})?; uv_snapshot!(context.filters(), context.add().arg("typing-extensions").arg("--optional=types"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 5 packages in [TIME] Prepared 1 package in [TIME] Installed 1 package in [TIME] + typing-extensions==4.10.0 "); 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 = [] optional-dependencies = { io = [ "anyio==3.7.0", ], types = [ "typing-extensions>=4.10.0", ] } "### ); }); uv_snapshot!(context.filters(), context.remove().arg("typing-extensions").arg("--optional=types"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 4 packages in [TIME] Uninstalled 1 package in [TIME] - typing-extensions==4.10.0 "); 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 = [] optional-dependencies = { io = [ "anyio==3.7.0", ], types = [] } "### ); }); Ok(()) } /// Add and remove a workspace dependency. #[test] fn add_remove_workspace() -> Result<()> { let context = TestContext::new("3.12"); let workspace = context.temp_dir.child("pyproject.toml"); workspace.write_str(indoc! {r#" [tool.uv.workspace] members = ["child1", "child2"] "#})?; let pyproject_toml = context.temp_dir.child("child1/pyproject.toml"); pyproject_toml.write_str(indoc! {r#" [project] name = "child1" version = "0.1.0" requires-python = ">=3.12" dependencies = [] [build-system] requires = ["hatchling"] build-backend = "hatchling.build" "#})?; context .temp_dir .child("child1") .child("src") .child("child1") .child("__init__.py") .touch()?; let pyproject_toml = context.temp_dir.child("child2/pyproject.toml"); pyproject_toml.write_str(indoc! {r#" [project] name = "child2" version = "0.1.0" requires-python = ">=3.12" dependencies = [] [build-system] requires = ["hatchling"] build-backend = "hatchling.build" "#})?; context .temp_dir .child("child2") .child("src") .child("child2") .child("__init__.py") .touch()?; // Adding a workspace package with a mismatched source should error. let mut add_cmd = context.add(); add_cmd .arg("child2 @ git+https://github.com/astral-test/uv-public-pypackage") .arg("--package") .arg("child1") .current_dir(&context.temp_dir); uv_snapshot!(context.filters(), add_cmd, @r###" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- error: Workspace dependency `child2` must refer to local directory, not a Git repository "###); // Workspace packages should be detected automatically. let child1 = context.temp_dir.join("child1"); let mut add_cmd = context.add(); add_cmd .arg("child2") .arg("--package") .arg("child1") .current_dir(&context.temp_dir); uv_snapshot!(context.filters(), add_cmd, @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 2 packages in [TIME] Prepared 2 packages in [TIME] Installed 2 packages in [TIME] + child1==0.1.0 (from file://[TEMP_DIR]/child1) + child2==0.1.0 (from file://[TEMP_DIR]/child2) "); let pyproject_toml = fs_err::read_to_string(child1.join("pyproject.toml"))?; insta::with_settings!({ filters => context.filters(), }, { assert_snapshot!( pyproject_toml, @r#" [project] name = "child1" version = "0.1.0" requires-python = ">=3.12" dependencies = [ "child2", ] [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.uv.sources] child2 = { workspace = true } "# ); }); // `uv add` implies a full lock and sync, including development dependencies. let lock = context.read("uv.lock"); insta::with_settings!({ filters => context.filters(), }, { assert_snapshot!( lock, @r#" version = 1 revision = 2 requires-python = ">=3.12" [options] exclude-newer = "2024-03-25T00:00:00Z" [manifest] members = [ "child1", "child2", ] [[package]] name = "child1" version = "0.1.0" source = { editable = "child1" } dependencies = [ { name = "child2" }, ] [package.metadata] requires-dist = [{ name = "child2", editable = "child2" }] [[package]] name = "child2" version = "0.1.0" source = { editable = "child2" } "# ); }); // Install from the lockfile. uv_snapshot!(context.filters(), context.sync().arg("--frozen").current_dir(&child1), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Audited 2 packages in [TIME] "); // Remove the dependency. uv_snapshot!(context.filters(), context.remove().arg("child2").current_dir(&child1), @r###" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 2 packages in [TIME] Prepared 1 package in [TIME] Uninstalled 2 packages in [TIME] Installed 1 package in [TIME] ~ child1==0.1.0 (from file://[TEMP_DIR]/child1) - child2==0.1.0 (from file://[TEMP_DIR]/child2) "###); let pyproject_toml = fs_err::read_to_string(child1.join("pyproject.toml"))?; insta::with_settings!({ filters => context.filters(), }, { assert_snapshot!( pyproject_toml, @r###" [project] name = "child1" version = "0.1.0" requires-python = ">=3.12" dependencies = [] [build-system] requires = ["hatchling"] build-backend = "hatchling.build" "### ); }); let lock = context.read("uv.lock"); insta::with_settings!({ filters => context.filters(), }, { assert_snapshot!( lock, @r#" version = 1 revision = 2 requires-python = ">=3.12" [options] exclude-newer = "2024-03-25T00:00:00Z" [manifest] members = [ "child1", "child2", ] [[package]] name = "child1" version = "0.1.0" source = { editable = "child1" } [[package]] name = "child2" version = "0.1.0" source = { editable = "child2" } "# ); }); // Install from the lockfile. uv_snapshot!(context.filters(), context.sync().arg("--frozen").current_dir(&child1), @r###" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Audited 1 package in [TIME] "###); Ok(()) } /// `uv add --dev` should update `dev-dependencies` (rather than `dependency-groups.dev`) if a /// dependency already exists in `dev-dependencies`. #[test] fn update_existing_dev() -> 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 = [] [tool.uv] dev-dependencies = ["anyio"] [dependency-groups] dev = [] "#})?; uv_snapshot!(context.filters(), context.add().arg("anyio==3.7.0").arg("--dev"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 4 packages in [TIME] Prepared 3 packages in [TIME] Installed 3 packages in [TIME] + anyio==3.7.0 + idna==3.6 + sniffio==1.3.1 "); 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 = [] [tool.uv] dev-dependencies = [ "anyio==3.7.0", ] [dependency-groups] dev = [] "### ); }); Ok(()) } /// `uv add --dev` should add to `dev-dependencies` (rather than `dependency-groups.dev`) if it /// exists. #[test] fn add_existing_dev() -> 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 = [] [tool.uv] dev-dependencies = [] "#})?; uv_snapshot!(context.filters(), context.add().arg("anyio==3.7.0").arg("--dev"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 4 packages in [TIME] Prepared 3 packages in [TIME] Installed 3 packages in [TIME] + anyio==3.7.0 + idna==3.6 + sniffio==1.3.1 "); 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 = [] [tool.uv] dev-dependencies = [ "anyio==3.7.0", ] "### ); }); Ok(()) } /// `uv add --group dev` should update `dev-dependencies` (rather than `dependency-groups.dev`) if a /// dependency already exists. #[test] fn update_existing_dev_group() -> 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 = [] [tool.uv] dev-dependencies = ["anyio"] "#})?; uv_snapshot!(context.filters(), context.add().arg("anyio==3.7.0").arg("--group").arg("dev"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 4 packages in [TIME] Prepared 3 packages in [TIME] Installed 3 packages in [TIME] + anyio==3.7.0 + idna==3.6 + sniffio==1.3.1 "); 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 = [] [tool.uv] dev-dependencies = [ "anyio==3.7.0", ] "### ); }); Ok(()) } /// `uv add --group dev` should add to `dependency-groups` even if `dev-dependencies` exists. #[test] fn add_existing_dev_group() -> 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 = [] [tool.uv] dev-dependencies = [] "#})?; uv_snapshot!(context.filters(), context.add().arg("anyio==3.7.0").arg("--group").arg("dev"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 4 packages in [TIME] Prepared 3 packages in [TIME] Installed 3 packages in [TIME] + anyio==3.7.0 + idna==3.6 + sniffio==1.3.1 "); 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 = [] [tool.uv] dev-dependencies = [] [dependency-groups] dev = [ "anyio==3.7.0", ] "### ); }); Ok(()) } /// `uv remove --dev` should remove from both `dev-dependencies` and `dependency-groups.dev`. #[test] fn remove_both_dev() -> 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 = [] [tool.uv] dev-dependencies = ["anyio"] [dependency-groups] dev = ["anyio>=3.7.0"] "#})?; uv_snapshot!(context.filters(), context.remove().arg("anyio").arg("--dev"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 1 package in [TIME] Audited in [TIME] "); 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 = [] [tool.uv] dev-dependencies = [] [dependency-groups] dev = [] "### ); }); Ok(()) } /// Do not allow add for groups in scripts. #[test] fn disallow_group_script_add() -> Result<()> { let context = TestContext::new("3.12"); let script = context.temp_dir.child("main.py"); script.write_str(indoc! {r#" # /// script # requires-python = ">=3.13" # dependencies = [] # # /// "#})?; uv_snapshot!(context.filters(), context .add() .arg("--group") .arg("dev") .arg("anyio==3.7.0") .arg("--script") .arg("main.py"), @r###" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- error: the argument '--group ' cannot be used with '--script