Strip query parameters when parsing source URL (#14224)

## Summary

Closes https://github.com/astral-sh/uv/issues/14217.
This commit is contained in:
Charlie Marsh 2025-06-23 14:52:07 -04:00 committed by GitHub
parent d9351d52fc
commit aa2448ef83
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 63 additions and 6 deletions

View File

@ -2371,7 +2371,13 @@ impl Package {
let sdist = match &self.id.source { let sdist = match &self.id.source {
Source::Path(path) => { Source::Path(path) => {
// A direct path source can also be a wheel, so validate the extension. // A direct path source can also be a wheel, so validate the extension.
let DistExtension::Source(ext) = DistExtension::from_path(path)? else { let DistExtension::Source(ext) = DistExtension::from_path(path).map_err(|err| {
LockErrorKind::MissingExtension {
id: self.id.clone(),
err,
}
})?
else {
return Ok(None); return Ok(None);
}; };
let install_path = absolute_path(workspace_root, path)?; let install_path = absolute_path(workspace_root, path)?;
@ -2444,7 +2450,14 @@ impl Package {
} }
Source::Direct(url, direct) => { Source::Direct(url, direct) => {
// A direct URL source can also be a wheel, so validate the extension. // A direct URL source can also be a wheel, so validate the extension.
let DistExtension::Source(ext) = DistExtension::from_path(url.as_ref())? else { let DistExtension::Source(ext) =
DistExtension::from_path(url.base_str()).map_err(|err| {
LockErrorKind::MissingExtension {
id: self.id.clone(),
err,
}
})?
else {
return Ok(None); return Ok(None);
}; };
let location = url.to_url().map_err(LockErrorKind::InvalidUrl)?; let location = url.to_url().map_err(LockErrorKind::InvalidUrl)?;
@ -2483,7 +2496,12 @@ impl Package {
.ok_or_else(|| LockErrorKind::MissingFilename { .ok_or_else(|| LockErrorKind::MissingFilename {
id: self.id.clone(), id: self.id.clone(),
})?; })?;
let ext = SourceDistExtension::from_path(filename.as_ref())?; let ext = SourceDistExtension::from_path(filename.as_ref()).map_err(|err| {
LockErrorKind::MissingExtension {
id: self.id.clone(),
err,
}
})?;
let file = Box::new(uv_distribution_types::File { let file = Box::new(uv_distribution_types::File {
dist_info_metadata: false, dist_info_metadata: false,
filename: SmallString::from(filename), filename: SmallString::from(filename),
@ -2535,7 +2553,12 @@ impl Package {
.ok_or_else(|| LockErrorKind::MissingFilename { .ok_or_else(|| LockErrorKind::MissingFilename {
id: self.id.clone(), id: self.id.clone(),
})?; })?;
let ext = SourceDistExtension::from_path(filename.as_ref())?; let ext = SourceDistExtension::from_path(filename.as_ref()).map_err(|err| {
LockErrorKind::MissingExtension {
id: self.id.clone(),
err,
}
})?;
let file = Box::new(uv_distribution_types::File { let file = Box::new(uv_distribution_types::File {
dist_info_metadata: false, dist_info_metadata: false,
filename: SmallString::from(filename), filename: SmallString::from(filename),
@ -5222,8 +5245,13 @@ enum LockErrorKind {
), ),
/// An error that occurs when the extension can't be determined /// An error that occurs when the extension can't be determined
/// for a given wheel or source distribution. /// for a given wheel or source distribution.
#[error("Failed to parse file extension; expected one of: {0}")] #[error("Failed to parse file extension for `{id}`; expected one of: {err}", id = id.cyan())]
MissingExtension(#[from] ExtensionError), MissingExtension {
/// The filename that was expected to have an extension.
id: PackageId,
/// The list of valid extensions that were expected.
err: ExtensionError,
},
/// Failed to parse a Git source URL. /// Failed to parse a Git source URL.
#[error("Failed to parse Git URL")] #[error("Failed to parse Git URL")]
InvalidGitSourceUrl( InvalidGitSourceUrl(

View File

@ -9943,3 +9943,32 @@ fn sync_required_environment_hint() -> Result<()> {
Ok(()) Ok(())
} }
#[test]
fn sync_url_with_query_parameters() -> Result<()> {
let context = TestContext::new("3.13").with_exclude_newer("2025-03-24T19:00:00Z");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(r#"
[project]
name = "example"
version = "0.1.0"
requires-python = ">=3.13.2"
dependencies = ["source-distribution @ https://files.pythonhosted.org/packages/1f/e5/5b016c945d745f8b108e759d428341488a6aee8f51f07c6c4e33498bb91f/source_distribution-0.0.3.tar.gz?foo=bar"]
"#
)?;
uv_snapshot!(context.filters(), context.sync(), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ source-distribution==0.0.3 (from https://files.pythonhosted.org/packages/1f/e5/5b016c945d745f8b108e759d428341488a6aee8f51f07c6c4e33498bb91f/source_distribution-0.0.3.tar.gz?foo=bar)
");
Ok(())
}