-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Normalize index URLs to remove trailing slash #14245
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,13 +37,22 @@ impl IndexUrl { | |
| /// | ||
| /// If no root directory is provided, relative paths are resolved against the current working | ||
| /// directory. | ||
| /// | ||
| /// Normalizes non-file URLs by removing trailing slashes for consistency. | ||
| pub fn parse(path: &str, root_dir: Option<&Path>) -> Result<Self, IndexUrlError> { | ||
| let url = match split_scheme(path) { | ||
| Some((scheme, ..)) => { | ||
| match Scheme::parse(scheme) { | ||
| Some(_) => { | ||
| // Ex) `https://pypi.org/simple` | ||
| VerbatimUrl::parse_url(path)? | ||
| Some(scheme) => { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could this be
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That was what I initially wanted to do, but there numerous other |
||
| if scheme.is_file() { | ||
| // Ex) `file:///path/to/something/` | ||
| VerbatimUrl::parse_url(path)? | ||
| } else { | ||
| // Ex) `https://pypi.org/simple/` | ||
| // Remove a trailing slash if it exists. | ||
| let normalized_path = path.strip_suffix('/').unwrap_or(path); | ||
| VerbatimUrl::parse_url(normalized_path)? | ||
| } | ||
| } | ||
| None => { | ||
| // Ex) `C:\Users\user\index` | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15479,7 +15479,7 @@ fn lock_trailing_slash() -> Result<()> { | |
| [[package]] | ||
| name = "anyio" | ||
| version = "3.7.0" | ||
| source = { registry = "https://pypi.org/simple/" } | ||
| source = { registry = "https://pypi.org/simple" } | ||
| dependencies = [ | ||
| { name = "idna" }, | ||
| { name = "sniffio" }, | ||
|
|
@@ -15492,7 +15492,7 @@ fn lock_trailing_slash() -> Result<()> { | |
| [[package]] | ||
| name = "idna" | ||
| version = "3.6" | ||
| source = { registry = "https://pypi.org/simple/" } | ||
| 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" }, | ||
|
|
@@ -15512,7 +15512,7 @@ fn lock_trailing_slash() -> Result<()> { | |
| [[package]] | ||
| name = "sniffio" | ||
| version = "1.3.1" | ||
| source = { registry = "https://pypi.org/simple/" } | ||
| 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" }, | ||
|
|
@@ -28257,3 +28257,198 @@ fn lock_conflict_for_disjoint_platform() -> Result<()> { | |
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| /// Add a package with an `--index` URL with no trailing slash. Run `uv lock --locked` | ||
| /// with a `pyproject.toml` with that same URL but with a trailing slash. | ||
| #[test] | ||
| fn lock_with_inconsistent_trailing_slash() -> Result<()> { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here's the new test |
||
| let context = TestContext::new("3.12"); | ||
|
|
||
| let pyproject_toml = context.temp_dir.child("pyproject.toml"); | ||
| pyproject_toml.write_str( | ||
| r#" | ||
| [project] | ||
| name = "project" | ||
| version = "0.1.0" | ||
| requires-python = ">=3.12" | ||
| dependencies = [] | ||
|
|
||
| [[tool.uv.index]] | ||
| name = "pypi-proxy" | ||
| url = "https://pypi-proxy.fly.dev/simple/" | ||
| "#, | ||
| )?; | ||
|
|
||
| let no_trailing_slash_url = "https://pypi-proxy.fly.dev/simple"; | ||
|
|
||
| uv_snapshot!(context.filters(), context.add().arg("anyio").arg("--index").arg(no_trailing_slash_url), @r" | ||
| success: true | ||
| exit_code: 0 | ||
| ----- stdout ----- | ||
|
|
||
| ----- stderr ----- | ||
| Resolved 4 packages in [TIME] | ||
| Prepared 3 packages in [TIME] | ||
| Installed 3 packages in [TIME] | ||
| + anyio==4.3.0 | ||
| + idna==3.6 | ||
| + sniffio==1.3.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 = "4.3.0" | ||
| source = { registry = "https://pypi-proxy.fly.dev/simple" } | ||
| dependencies = [ | ||
| { name = "idna" }, | ||
| { name = "sniffio" }, | ||
| ] | ||
| sdist = { url = "https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6", size = 159642, upload-time = "2024-02-19T08:36:28.641Z" } | ||
| wheels = [ | ||
| { url = "https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8", size = 85584, upload-time = "2024-02-19T08:36:26.842Z" }, | ||
| ] | ||
|
|
||
| [[package]] | ||
| name = "idna" | ||
| version = "3.6" | ||
| source = { registry = "https://pypi-proxy.fly.dev/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 = ">=4.3.0" }] | ||
|
|
||
| [[package]] | ||
| name = "sniffio" | ||
| version = "1.3.1" | ||
| source = { registry = "https://pypi-proxy.fly.dev/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" }, | ||
| ] | ||
| "# | ||
| ); | ||
| }); | ||
|
|
||
| // Re-run with `--locked`. | ||
| uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" | ||
| success: true | ||
| exit_code: 0 | ||
| ----- stdout ----- | ||
|
|
||
| ----- stderr ----- | ||
| Resolved 4 packages in [TIME] | ||
| "); | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| /// Run `uv lock --locked` with a lockfile with trailing slashes on index URLs. | ||
| #[test] | ||
| fn lock_with_index_trailing_slashes_in_lockfile() -> Result<()> { | ||
| let context = TestContext::new("3.12"); | ||
|
|
||
| let pyproject_toml = context.temp_dir.child("pyproject.toml"); | ||
| pyproject_toml.write_str( | ||
| r#" | ||
| [project] | ||
| name = "project" | ||
| version = "0.1.0" | ||
| requires-python = ">=3.12" | ||
| dependencies = ["anyio"] | ||
|
|
||
| [[tool.uv.index]] | ||
| name = "pypi-proxy" | ||
| url = "https://pypi-proxy.fly.dev/simple" | ||
| "#, | ||
| )?; | ||
|
|
||
| let lock = context.temp_dir.child("uv.lock"); | ||
| lock.write_str(r#" | ||
| version = 1 | ||
| revision = 2 | ||
| requires-python = ">=3.12" | ||
|
|
||
| [options] | ||
| exclude-newer = "2024-03-25T00:00:00Z" | ||
|
|
||
| [[package]] | ||
| name = "anyio" | ||
| version = "4.3.0" | ||
| source = { registry = "https://pypi-proxy.fly.dev/simple/" } | ||
| dependencies = [ | ||
| { name = "idna" }, | ||
| { name = "sniffio" }, | ||
| ] | ||
| sdist = { url = "https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6", size = 159642, upload-time = "2024-02-19T08:36:28.641Z" } | ||
| wheels = [ | ||
| { url = "https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8", size = 85584, upload-time = "2024-02-19T08:36:26.842Z" }, | ||
| ] | ||
|
|
||
| [[package]] | ||
| name = "idna" | ||
| version = "3.6" | ||
| source = { registry = "https://pypi-proxy.fly.dev/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" }] | ||
|
|
||
| [[package]] | ||
| name = "sniffio" | ||
| version = "1.3.1" | ||
| source = { registry = "https://pypi-proxy.fly.dev/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" }, | ||
| ] | ||
| "# | ||
| )?; | ||
|
|
||
| // Run `uv lock --locked`. | ||
| uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" | ||
| success: true | ||
| exit_code: 0 | ||
| ----- stdout ----- | ||
|
|
||
| ----- stderr ----- | ||
| Resolved 4 packages in [TIME] | ||
| "); | ||
|
|
||
| Ok(()) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this and
without_fragmentcould both returnCow? But that can be a separate PR if you want to try it.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense to me. I'll open as a separate PR