mirror of https://github.com/astral-sh/uv
parent
63ab247765
commit
92c2bfcca0
|
|
@ -591,7 +591,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_is_known_url() {
|
fn test_is_known_url() {
|
||||||
let api_url = DisplaySafeUrl::from(Url::parse("https://api.pyx.dev").unwrap());
|
let api_url = DisplaySafeUrl::parse("https://api.pyx.dev").unwrap();
|
||||||
let cdn_domain = "astralhosted.com";
|
let cdn_domain = "astralhosted.com";
|
||||||
|
|
||||||
// Same realm as API.
|
// Same realm as API.
|
||||||
|
|
@ -646,7 +646,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_is_known_domain() {
|
fn test_is_known_domain() {
|
||||||
let api_url = DisplaySafeUrl::from(Url::parse("https://api.pyx.dev").unwrap());
|
let api_url = DisplaySafeUrl::parse("https://api.pyx.dev").unwrap();
|
||||||
let cdn_domain = "astralhosted.com";
|
let cdn_domain = "astralhosted.com";
|
||||||
|
|
||||||
// Same realm as API.
|
// Same realm as API.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::{fmt::Display, fmt::Formatter};
|
use std::{fmt::Display, fmt::Formatter};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
use uv_redacted::DisplaySafeUrl;
|
||||||
use uv_small_str::SmallString;
|
use uv_small_str::SmallString;
|
||||||
|
|
||||||
/// Used to determine if authentication information should be retained on a new URL.
|
/// Used to determine if authentication information should be retained on a new URL.
|
||||||
|
|
@ -29,6 +30,12 @@ pub struct Realm {
|
||||||
port: Option<u16>,
|
port: Option<u16>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<&DisplaySafeUrl> for Realm {
|
||||||
|
fn from(url: &DisplaySafeUrl) -> Self {
|
||||||
|
Self::from(&**url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<&Url> for Realm {
|
impl From<&Url> for Realm {
|
||||||
fn from(url: &Url) -> Self {
|
fn from(url: &Url) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ use fs_err as fs;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use url::Url;
|
|
||||||
use uv_fs::{LockedFile, with_added_extension};
|
use uv_fs::{LockedFile, with_added_extension};
|
||||||
use uv_preview::{Preview, PreviewFeatures};
|
use uv_preview::{Preview, PreviewFeatures};
|
||||||
use uv_redacted::DisplaySafeUrl;
|
use uv_redacted::DisplaySafeUrl;
|
||||||
|
|
@ -310,13 +309,17 @@ impl TextCredentialStore {
|
||||||
/// Get credentials for a given URL and username.
|
/// Get credentials for a given URL and username.
|
||||||
///
|
///
|
||||||
/// The most specific URL prefix match in the same [`Realm`] is returned, if any.
|
/// The most specific URL prefix match in the same [`Realm`] is returned, if any.
|
||||||
pub fn get_credentials(&self, url: &Url, username: Option<&str>) -> Option<&Credentials> {
|
pub fn get_credentials(
|
||||||
|
&self,
|
||||||
|
url: &DisplaySafeUrl,
|
||||||
|
username: Option<&str>,
|
||||||
|
) -> Option<&Credentials> {
|
||||||
let request_realm = Realm::from(url);
|
let request_realm = Realm::from(url);
|
||||||
|
|
||||||
// Perform an exact lookup first
|
// Perform an exact lookup first
|
||||||
// TODO(zanieb): Consider adding `DisplaySafeUrlRef` so we can avoid this clone
|
// TODO(zanieb): Consider adding `DisplaySafeUrlRef` so we can avoid this clone
|
||||||
// TODO(zanieb): We could also return early here if we can't normalize to a `Service`
|
// TODO(zanieb): We could also return early here if we can't normalize to a `Service`
|
||||||
if let Ok(url_service) = Service::try_from(DisplaySafeUrl::from(url.clone())) {
|
if let Ok(url_service) = Service::try_from(url.clone()) {
|
||||||
if let Some(credential) = self.credentials.get(&(
|
if let Some(credential) = self.credentials.get(&(
|
||||||
url_service.clone(),
|
url_service.clone(),
|
||||||
Username::from(username.map(str::to_string)),
|
Username::from(username.map(str::to_string)),
|
||||||
|
|
@ -430,10 +433,10 @@ mod tests {
|
||||||
|
|
||||||
let service = Service::from_str("https://example.com").unwrap();
|
let service = Service::from_str("https://example.com").unwrap();
|
||||||
store.insert(service.clone(), credentials.clone());
|
store.insert(service.clone(), credentials.clone());
|
||||||
let url = Url::parse("https://example.com/").unwrap();
|
let url = DisplaySafeUrl::parse("https://example.com/").unwrap();
|
||||||
assert!(store.get_credentials(&url, None).is_some());
|
assert!(store.get_credentials(&url, None).is_some());
|
||||||
|
|
||||||
let url = Url::parse("https://example.com/path").unwrap();
|
let url = DisplaySafeUrl::parse("https://example.com/path").unwrap();
|
||||||
let retrieved = store.get_credentials(&url, None).unwrap();
|
let retrieved = store.get_credentials(&url, None).unwrap();
|
||||||
assert_eq!(retrieved.username(), Some("user"));
|
assert_eq!(retrieved.username(), Some("user"));
|
||||||
assert_eq!(retrieved.password(), Some("pass"));
|
assert_eq!(retrieved.password(), Some("pass"));
|
||||||
|
|
@ -443,7 +446,7 @@ mod tests {
|
||||||
.remove(&service, Username::from(Some("user".to_string())))
|
.remove(&service, Username::from(Some("user".to_string())))
|
||||||
.is_some()
|
.is_some()
|
||||||
);
|
);
|
||||||
let url = Url::parse("https://example.com/").unwrap();
|
let url = DisplaySafeUrl::parse("https://example.com/").unwrap();
|
||||||
assert!(store.get_credentials(&url, None).is_none());
|
assert!(store.get_credentials(&url, None).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -469,12 +472,12 @@ password = "pass2"
|
||||||
|
|
||||||
let store = TextCredentialStore::from_file(temp_file.path()).unwrap();
|
let store = TextCredentialStore::from_file(temp_file.path()).unwrap();
|
||||||
|
|
||||||
let url = Url::parse("https://example.com/").unwrap();
|
let url = DisplaySafeUrl::parse("https://example.com/").unwrap();
|
||||||
assert!(store.get_credentials(&url, None).is_some());
|
assert!(store.get_credentials(&url, None).is_some());
|
||||||
let url = Url::parse("https://test.org/").unwrap();
|
let url = DisplaySafeUrl::parse("https://test.org/").unwrap();
|
||||||
assert!(store.get_credentials(&url, None).is_some());
|
assert!(store.get_credentials(&url, None).is_some());
|
||||||
|
|
||||||
let url = Url::parse("https://example.com").unwrap();
|
let url = DisplaySafeUrl::parse("https://example.com").unwrap();
|
||||||
let cred = store.get_credentials(&url, None).unwrap();
|
let cred = store.get_credentials(&url, None).unwrap();
|
||||||
assert_eq!(cred.username(), Some("testuser"));
|
assert_eq!(cred.username(), Some("testuser"));
|
||||||
assert_eq!(cred.password(), Some("testpass"));
|
assert_eq!(cred.password(), Some("testpass"));
|
||||||
|
|
@ -510,7 +513,7 @@ password = "pass2"
|
||||||
];
|
];
|
||||||
|
|
||||||
for url_str in matching_urls {
|
for url_str in matching_urls {
|
||||||
let url = Url::parse(url_str).unwrap();
|
let url = DisplaySafeUrl::parse(url_str).unwrap();
|
||||||
let cred = store.get_credentials(&url, None);
|
let cred = store.get_credentials(&url, None);
|
||||||
assert!(cred.is_some(), "Failed to match URL with prefix: {url_str}");
|
assert!(cred.is_some(), "Failed to match URL with prefix: {url_str}");
|
||||||
}
|
}
|
||||||
|
|
@ -523,7 +526,7 @@ password = "pass2"
|
||||||
];
|
];
|
||||||
|
|
||||||
for url_str in non_matching_urls {
|
for url_str in non_matching_urls {
|
||||||
let url = Url::parse(url_str).unwrap();
|
let url = DisplaySafeUrl::parse(url_str).unwrap();
|
||||||
let cred = store.get_credentials(&url, None);
|
let cred = store.get_credentials(&url, None);
|
||||||
assert!(cred.is_none(), "Should not match non-prefix URL: {url_str}");
|
assert!(cred.is_none(), "Should not match non-prefix URL: {url_str}");
|
||||||
}
|
}
|
||||||
|
|
@ -547,7 +550,7 @@ password = "pass2"
|
||||||
];
|
];
|
||||||
|
|
||||||
for url_str in matching_urls {
|
for url_str in matching_urls {
|
||||||
let url = Url::parse(url_str).unwrap();
|
let url = DisplaySafeUrl::parse(url_str).unwrap();
|
||||||
let cred = store.get_credentials(&url, None);
|
let cred = store.get_credentials(&url, None);
|
||||||
assert!(
|
assert!(
|
||||||
cred.is_some(),
|
cred.is_some(),
|
||||||
|
|
@ -563,7 +566,7 @@ password = "pass2"
|
||||||
];
|
];
|
||||||
|
|
||||||
for url_str in non_matching_urls {
|
for url_str in non_matching_urls {
|
||||||
let url = Url::parse(url_str).unwrap();
|
let url = DisplaySafeUrl::parse(url_str).unwrap();
|
||||||
let cred = store.get_credentials(&url, None);
|
let cred = store.get_credentials(&url, None);
|
||||||
assert!(
|
assert!(
|
||||||
cred.is_none(),
|
cred.is_none(),
|
||||||
|
|
@ -587,12 +590,12 @@ password = "pass2"
|
||||||
store.insert(specific_service.clone(), specific_cred);
|
store.insert(specific_service.clone(), specific_cred);
|
||||||
|
|
||||||
// Should match the most specific prefix
|
// Should match the most specific prefix
|
||||||
let url = Url::parse("https://example.com/api/v1/users").unwrap();
|
let url = DisplaySafeUrl::parse("https://example.com/api/v1/users").unwrap();
|
||||||
let cred = store.get_credentials(&url, None).unwrap();
|
let cred = store.get_credentials(&url, None).unwrap();
|
||||||
assert_eq!(cred.username(), Some("specific"));
|
assert_eq!(cred.username(), Some("specific"));
|
||||||
|
|
||||||
// Should match the general prefix for non-specific paths
|
// Should match the general prefix for non-specific paths
|
||||||
let url = Url::parse("https://example.com/api/v2").unwrap();
|
let url = DisplaySafeUrl::parse("https://example.com/api/v2").unwrap();
|
||||||
let cred = store.get_credentials(&url, None).unwrap();
|
let cred = store.get_credentials(&url, None).unwrap();
|
||||||
assert_eq!(cred.username(), Some("general"));
|
assert_eq!(cred.username(), Some("general"));
|
||||||
}
|
}
|
||||||
|
|
@ -600,7 +603,7 @@ password = "pass2"
|
||||||
#[test]
|
#[test]
|
||||||
fn test_username_exact_url_match() {
|
fn test_username_exact_url_match() {
|
||||||
let mut store = TextCredentialStore::default();
|
let mut store = TextCredentialStore::default();
|
||||||
let url = Url::parse("https://example.com").unwrap();
|
let url = DisplaySafeUrl::parse("https://example.com").unwrap();
|
||||||
let service = Service::from_str("https://example.com").unwrap();
|
let service = Service::from_str("https://example.com").unwrap();
|
||||||
let user1_creds = Credentials::basic(Some("user1".to_string()), Some("pass1".to_string()));
|
let user1_creds = Credentials::basic(Some("user1".to_string()), Some("pass1".to_string()));
|
||||||
store.insert(service.clone(), user1_creds.clone());
|
store.insert(service.clone(), user1_creds.clone());
|
||||||
|
|
@ -641,7 +644,7 @@ password = "pass2"
|
||||||
store.insert(general_service, general_creds);
|
store.insert(general_service, general_creds);
|
||||||
store.insert(specific_service, specific_creds);
|
store.insert(specific_service, specific_creds);
|
||||||
|
|
||||||
let url = Url::parse("https://example.com/api/v1/users").unwrap();
|
let url = DisplaySafeUrl::parse("https://example.com/api/v1/users").unwrap();
|
||||||
|
|
||||||
// Should match specific credentials when username matches
|
// Should match specific credentials when username matches
|
||||||
let result = store.get_credentials(&url, Some("specific_user"));
|
let result = store.get_credentials(&url, Some("specific_user"));
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ use std::str::FromStr;
|
||||||
use jiff::Timestamp;
|
use jiff::Timestamp;
|
||||||
use tl::HTMLTag;
|
use tl::HTMLTag;
|
||||||
use tracing::{debug, instrument, warn};
|
use tracing::{debug, instrument, warn};
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
use uv_pep440::VersionSpecifiers;
|
use uv_pep440::VersionSpecifiers;
|
||||||
|
|
@ -23,13 +22,13 @@ pub(crate) struct SimpleDetailHTML {
|
||||||
impl SimpleDetailHTML {
|
impl SimpleDetailHTML {
|
||||||
/// Parse the list of [`PypiFile`]s from the simple HTML page returned by the given URL.
|
/// Parse the list of [`PypiFile`]s from the simple HTML page returned by the given URL.
|
||||||
#[instrument(skip_all, fields(url = % url))]
|
#[instrument(skip_all, fields(url = % url))]
|
||||||
pub(crate) fn parse(text: &str, url: &Url) -> Result<Self, Error> {
|
pub(crate) fn parse(text: &str, url: &DisplaySafeUrl) -> Result<Self, Error> {
|
||||||
let dom = tl::parse(text, tl::ParserOptions::default())?;
|
let dom = tl::parse(text, tl::ParserOptions::default())?;
|
||||||
|
|
||||||
// Parse the first `<base>` tag, if any, to determine the base URL to which all
|
// Parse the first `<base>` tag, if any, to determine the base URL to which all
|
||||||
// relative URLs should be resolved. The HTML spec requires that the `<base>` tag
|
// relative URLs should be resolved. The HTML spec requires that the `<base>` tag
|
||||||
// appear before other tags with attribute values of URLs.
|
// appear before other tags with attribute values of URLs.
|
||||||
let base = BaseUrl::from(DisplaySafeUrl::from(
|
let base = BaseUrl::from(
|
||||||
dom.nodes()
|
dom.nodes()
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|node| node.as_tag())
|
.filter_map(|node| node.as_tag())
|
||||||
|
|
@ -39,7 +38,7 @@ impl SimpleDetailHTML {
|
||||||
.transpose()?
|
.transpose()?
|
||||||
.flatten()
|
.flatten()
|
||||||
.unwrap_or_else(|| url.clone()),
|
.unwrap_or_else(|| url.clone()),
|
||||||
));
|
);
|
||||||
|
|
||||||
// Parse each `<a>` tag, to extract the filename, hash, and URL.
|
// Parse each `<a>` tag, to extract the filename, hash, and URL.
|
||||||
let mut files: Vec<PypiFile> = dom
|
let mut files: Vec<PypiFile> = dom
|
||||||
|
|
@ -68,12 +67,13 @@ impl SimpleDetailHTML {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse the `href` from a `<base>` tag.
|
/// Parse the `href` from a `<base>` tag.
|
||||||
fn parse_base(base: &HTMLTag) -> Result<Option<Url>, Error> {
|
fn parse_base(base: &HTMLTag) -> Result<Option<DisplaySafeUrl>, Error> {
|
||||||
let Some(Some(href)) = base.attributes().get("href") else {
|
let Some(Some(href)) = base.attributes().get("href") else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
let href = std::str::from_utf8(href.as_bytes())?;
|
let href = std::str::from_utf8(href.as_bytes())?;
|
||||||
let url = Url::parse(href).map_err(|err| Error::UrlParse(href.to_string(), err))?;
|
let url =
|
||||||
|
DisplaySafeUrl::parse(href).map_err(|err| Error::UrlParse(href.to_string(), err))?;
|
||||||
Ok(Some(url))
|
Ok(Some(url))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -325,7 +325,7 @@ mod tests {
|
||||||
</html>
|
</html>
|
||||||
<!--TIMESTAMP 1703347410-->
|
<!--TIMESTAMP 1703347410-->
|
||||||
"#;
|
"#;
|
||||||
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
||||||
let result = SimpleDetailHTML::parse(text, &base).unwrap();
|
let result = SimpleDetailHTML::parse(text, &base).unwrap();
|
||||||
insta::assert_debug_snapshot!(result, @r#"
|
insta::assert_debug_snapshot!(result, @r#"
|
||||||
SimpleDetailHTML {
|
SimpleDetailHTML {
|
||||||
|
|
@ -382,7 +382,7 @@ mod tests {
|
||||||
</html>
|
</html>
|
||||||
<!--TIMESTAMP 1703347410-->
|
<!--TIMESTAMP 1703347410-->
|
||||||
"#;
|
"#;
|
||||||
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
||||||
let result = SimpleDetailHTML::parse(text, &base).unwrap();
|
let result = SimpleDetailHTML::parse(text, &base).unwrap();
|
||||||
insta::assert_debug_snapshot!(result, @r#"
|
insta::assert_debug_snapshot!(result, @r#"
|
||||||
SimpleDetailHTML {
|
SimpleDetailHTML {
|
||||||
|
|
@ -442,7 +442,7 @@ mod tests {
|
||||||
</html>
|
</html>
|
||||||
<!--TIMESTAMP 1703347410-->
|
<!--TIMESTAMP 1703347410-->
|
||||||
"#;
|
"#;
|
||||||
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
||||||
let result = SimpleDetailHTML::parse(text, &base).unwrap();
|
let result = SimpleDetailHTML::parse(text, &base).unwrap();
|
||||||
insta::assert_debug_snapshot!(result, @r#"
|
insta::assert_debug_snapshot!(result, @r#"
|
||||||
SimpleDetailHTML {
|
SimpleDetailHTML {
|
||||||
|
|
@ -499,7 +499,7 @@ mod tests {
|
||||||
</html>
|
</html>
|
||||||
<!--TIMESTAMP 1703347410-->
|
<!--TIMESTAMP 1703347410-->
|
||||||
"#;
|
"#;
|
||||||
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
||||||
let result = SimpleDetailHTML::parse(text, &base).unwrap();
|
let result = SimpleDetailHTML::parse(text, &base).unwrap();
|
||||||
insta::assert_debug_snapshot!(result, @r#"
|
insta::assert_debug_snapshot!(result, @r#"
|
||||||
SimpleDetailHTML {
|
SimpleDetailHTML {
|
||||||
|
|
@ -556,7 +556,7 @@ mod tests {
|
||||||
</html>
|
</html>
|
||||||
<!--TIMESTAMP 1703347410-->
|
<!--TIMESTAMP 1703347410-->
|
||||||
"#;
|
"#;
|
||||||
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
||||||
let result = SimpleDetailHTML::parse(text, &base).unwrap();
|
let result = SimpleDetailHTML::parse(text, &base).unwrap();
|
||||||
insta::assert_debug_snapshot!(result, @r#"
|
insta::assert_debug_snapshot!(result, @r#"
|
||||||
SimpleDetailHTML {
|
SimpleDetailHTML {
|
||||||
|
|
@ -613,7 +613,7 @@ mod tests {
|
||||||
</html>
|
</html>
|
||||||
<!--TIMESTAMP 1703347410-->
|
<!--TIMESTAMP 1703347410-->
|
||||||
"#;
|
"#;
|
||||||
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
||||||
let result = SimpleDetailHTML::parse(text, &base).unwrap();
|
let result = SimpleDetailHTML::parse(text, &base).unwrap();
|
||||||
insta::assert_debug_snapshot!(result, @r#"
|
insta::assert_debug_snapshot!(result, @r#"
|
||||||
SimpleDetailHTML {
|
SimpleDetailHTML {
|
||||||
|
|
@ -668,7 +668,7 @@ mod tests {
|
||||||
</html>
|
</html>
|
||||||
<!--TIMESTAMP 1703347410-->
|
<!--TIMESTAMP 1703347410-->
|
||||||
"#;
|
"#;
|
||||||
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
||||||
let result = SimpleDetailHTML::parse(text, &base).unwrap();
|
let result = SimpleDetailHTML::parse(text, &base).unwrap();
|
||||||
insta::assert_debug_snapshot!(result, @r#"
|
insta::assert_debug_snapshot!(result, @r#"
|
||||||
SimpleDetailHTML {
|
SimpleDetailHTML {
|
||||||
|
|
@ -723,7 +723,7 @@ mod tests {
|
||||||
</html>
|
</html>
|
||||||
<!--TIMESTAMP 1703347410-->
|
<!--TIMESTAMP 1703347410-->
|
||||||
";
|
";
|
||||||
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
||||||
let result = SimpleDetailHTML::parse(text, &base).unwrap();
|
let result = SimpleDetailHTML::parse(text, &base).unwrap();
|
||||||
insta::assert_debug_snapshot!(result, @r#"
|
insta::assert_debug_snapshot!(result, @r#"
|
||||||
SimpleDetailHTML {
|
SimpleDetailHTML {
|
||||||
|
|
@ -761,7 +761,7 @@ mod tests {
|
||||||
</html>
|
</html>
|
||||||
<!--TIMESTAMP 1703347410-->
|
<!--TIMESTAMP 1703347410-->
|
||||||
"#;
|
"#;
|
||||||
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
||||||
let result = SimpleDetailHTML::parse(text, &base).unwrap();
|
let result = SimpleDetailHTML::parse(text, &base).unwrap();
|
||||||
insta::assert_debug_snapshot!(result, @r#"
|
insta::assert_debug_snapshot!(result, @r#"
|
||||||
SimpleDetailHTML {
|
SimpleDetailHTML {
|
||||||
|
|
@ -799,7 +799,7 @@ mod tests {
|
||||||
</html>
|
</html>
|
||||||
<!--TIMESTAMP 1703347410-->
|
<!--TIMESTAMP 1703347410-->
|
||||||
"#;
|
"#;
|
||||||
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
||||||
let result = SimpleDetailHTML::parse(text, &base).unwrap();
|
let result = SimpleDetailHTML::parse(text, &base).unwrap();
|
||||||
insta::assert_debug_snapshot!(result, @r#"
|
insta::assert_debug_snapshot!(result, @r#"
|
||||||
SimpleDetailHTML {
|
SimpleDetailHTML {
|
||||||
|
|
@ -854,7 +854,7 @@ mod tests {
|
||||||
</html>
|
</html>
|
||||||
<!--TIMESTAMP 1703347410-->
|
<!--TIMESTAMP 1703347410-->
|
||||||
"#;
|
"#;
|
||||||
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
||||||
let result = SimpleDetailHTML::parse(text, &base).unwrap();
|
let result = SimpleDetailHTML::parse(text, &base).unwrap();
|
||||||
insta::assert_debug_snapshot!(result, @r#"
|
insta::assert_debug_snapshot!(result, @r#"
|
||||||
SimpleDetailHTML {
|
SimpleDetailHTML {
|
||||||
|
|
@ -909,7 +909,7 @@ mod tests {
|
||||||
</html>
|
</html>
|
||||||
<!--TIMESTAMP 1703347410-->
|
<!--TIMESTAMP 1703347410-->
|
||||||
"#;
|
"#;
|
||||||
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
||||||
let result = SimpleDetailHTML::parse(text, &base);
|
let result = SimpleDetailHTML::parse(text, &base);
|
||||||
insta::assert_debug_snapshot!(result, @r#"
|
insta::assert_debug_snapshot!(result, @r#"
|
||||||
Ok(
|
Ok(
|
||||||
|
|
@ -966,7 +966,7 @@ mod tests {
|
||||||
</html>
|
</html>
|
||||||
<!--TIMESTAMP 1703347410-->
|
<!--TIMESTAMP 1703347410-->
|
||||||
"#;
|
"#;
|
||||||
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
||||||
let result = SimpleDetailHTML::parse(text, &base);
|
let result = SimpleDetailHTML::parse(text, &base);
|
||||||
insta::assert_debug_snapshot!(result, @r#"
|
insta::assert_debug_snapshot!(result, @r#"
|
||||||
Ok(
|
Ok(
|
||||||
|
|
@ -1023,7 +1023,7 @@ mod tests {
|
||||||
</html>
|
</html>
|
||||||
<!--TIMESTAMP 1703347410-->
|
<!--TIMESTAMP 1703347410-->
|
||||||
"#;
|
"#;
|
||||||
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
||||||
let result = SimpleDetailHTML::parse(text, &base).unwrap_err();
|
let result = SimpleDetailHTML::parse(text, &base).unwrap_err();
|
||||||
insta::assert_snapshot!(result, @"Unsupported hash algorithm (expected one of: `md5`, `sha256`, `sha384`, `sha512`, or `blake2b`) on: `blake2=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61`");
|
insta::assert_snapshot!(result, @"Unsupported hash algorithm (expected one of: `md5`, `sha256`, `sha384`, `sha512`, or `blake2b`) on: `blake2=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61`");
|
||||||
}
|
}
|
||||||
|
|
@ -1040,8 +1040,10 @@ mod tests {
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"#;
|
"#;
|
||||||
let base = Url::parse("https://storage.googleapis.com/jax-releases/jax_cuda_releases.html")
|
let base = DisplaySafeUrl::parse(
|
||||||
.unwrap();
|
"https://storage.googleapis.com/jax-releases/jax_cuda_releases.html",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
let result = SimpleDetailHTML::parse(text, &base).unwrap();
|
let result = SimpleDetailHTML::parse(text, &base).unwrap();
|
||||||
insta::assert_debug_snapshot!(result, @r#"
|
insta::assert_debug_snapshot!(result, @r#"
|
||||||
SimpleDetailHTML {
|
SimpleDetailHTML {
|
||||||
|
|
@ -1122,7 +1124,7 @@ mod tests {
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"#;
|
"#;
|
||||||
let base = Url::parse("https://account.d.codeartifact.us-west-2.amazonaws.com/pypi/shared-packages-pypi/simple/flask/")
|
let base = DisplaySafeUrl::parse("https://account.d.codeartifact.us-west-2.amazonaws.com/pypi/shared-packages-pypi/simple/flask/")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let result = SimpleDetailHTML::parse(text, &base).unwrap();
|
let result = SimpleDetailHTML::parse(text, &base).unwrap();
|
||||||
insta::assert_debug_snapshot!(result, @r#"
|
insta::assert_debug_snapshot!(result, @r#"
|
||||||
|
|
@ -1226,7 +1228,7 @@ mod tests {
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"#;
|
"#;
|
||||||
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
||||||
let result = SimpleDetailHTML::parse(text, &base).unwrap();
|
let result = SimpleDetailHTML::parse(text, &base).unwrap();
|
||||||
insta::assert_debug_snapshot!(result, @r#"
|
insta::assert_debug_snapshot!(result, @r#"
|
||||||
SimpleDetailHTML {
|
SimpleDetailHTML {
|
||||||
|
|
@ -1298,7 +1300,7 @@ mod tests {
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"#;
|
"#;
|
||||||
let base = Url::parse("https://account.d.codeartifact.us-west-2.amazonaws.com/pypi/shared-packages-pypi/simple/flask/")
|
let base = DisplaySafeUrl::parse("https://account.d.codeartifact.us-west-2.amazonaws.com/pypi/shared-packages-pypi/simple/flask/")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let result = SimpleDetailHTML::parse(text, &base).unwrap();
|
let result = SimpleDetailHTML::parse(text, &base).unwrap();
|
||||||
insta::assert_debug_snapshot!(result, @r#"
|
insta::assert_debug_snapshot!(result, @r#"
|
||||||
|
|
|
||||||
|
|
@ -185,14 +185,14 @@ impl InstalledDist {
|
||||||
let build_info = Self::read_build_info(path)?;
|
let build_info = Self::read_build_info(path)?;
|
||||||
|
|
||||||
return if let Some(direct_url) = Self::read_direct_url(path)? {
|
return if let Some(direct_url) = Self::read_direct_url(path)? {
|
||||||
match Url::try_from(&direct_url) {
|
match DisplaySafeUrl::try_from(&direct_url) {
|
||||||
Ok(url) => Ok(Some(Self::from(InstalledDistKind::Url(
|
Ok(url) => Ok(Some(Self::from(InstalledDistKind::Url(
|
||||||
InstalledDirectUrlDist {
|
InstalledDirectUrlDist {
|
||||||
name,
|
name,
|
||||||
version,
|
version,
|
||||||
editable: matches!(&direct_url, DirectUrl::LocalDirectory { dir_info, .. } if dir_info.editable == Some(true)),
|
editable: matches!(&direct_url, DirectUrl::LocalDirectory { dir_info, .. } if dir_info.editable == Some(true)),
|
||||||
direct_url: Box::new(direct_url),
|
direct_url: Box::new(direct_url),
|
||||||
url: DisplaySafeUrl::from(url),
|
url,
|
||||||
path: path.to_path_buf().into_boxed_path(),
|
path: path.to_path_buf().into_boxed_path(),
|
||||||
cache_info,
|
cache_info,
|
||||||
build_info,
|
build_info,
|
||||||
|
|
@ -323,7 +323,7 @@ impl InstalledDist {
|
||||||
|
|
||||||
// Normalisation comes from `pkg_resources.to_filename`.
|
// Normalisation comes from `pkg_resources.to_filename`.
|
||||||
let egg_info = target.join(file_stem.replace('-', "_") + ".egg-info");
|
let egg_info = target.join(file_stem.replace('-', "_") + ".egg-info");
|
||||||
let url = Url::from_file_path(&target)
|
let url = DisplaySafeUrl::from_file_path(&target)
|
||||||
.map_err(|()| InstalledDistError::InvalidEggLinkTarget(path.to_path_buf()))?;
|
.map_err(|()| InstalledDistError::InvalidEggLinkTarget(path.to_path_buf()))?;
|
||||||
|
|
||||||
// Mildly unfortunate that we must read metadata to get the version.
|
// Mildly unfortunate that we must read metadata to get the version.
|
||||||
|
|
@ -337,7 +337,7 @@ impl InstalledDist {
|
||||||
version: Version::from_str(&egg_metadata.version)?,
|
version: Version::from_str(&egg_metadata.version)?,
|
||||||
egg_link: path.to_path_buf().into_boxed_path(),
|
egg_link: path.to_path_buf().into_boxed_path(),
|
||||||
target: target.into_boxed_path(),
|
target: target.into_boxed_path(),
|
||||||
target_url: DisplaySafeUrl::from(url),
|
target_url: url,
|
||||||
egg_info: egg_info.into_boxed_path(),
|
egg_info: egg_info.into_boxed_path(),
|
||||||
},
|
},
|
||||||
))));
|
))));
|
||||||
|
|
|
||||||
|
|
@ -555,7 +555,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
||||||
async fn url_metadata<'data>(
|
async fn url_metadata<'data>(
|
||||||
&self,
|
&self,
|
||||||
source: &BuildableSource<'data>,
|
source: &BuildableSource<'data>,
|
||||||
url: &'data Url,
|
url: &'data DisplaySafeUrl,
|
||||||
index: Option<&'data IndexUrl>,
|
index: Option<&'data IndexUrl>,
|
||||||
cache_shard: &CacheShard,
|
cache_shard: &CacheShard,
|
||||||
subdirectory: Option<&'data Path>,
|
subdirectory: Option<&'data Path>,
|
||||||
|
|
@ -637,7 +637,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
||||||
if let Some(subdirectory) = subdirectory {
|
if let Some(subdirectory) = subdirectory {
|
||||||
if !source_dist_entry.path().join(subdirectory).is_dir() {
|
if !source_dist_entry.path().join(subdirectory).is_dir() {
|
||||||
return Err(Error::MissingSubdirectory(
|
return Err(Error::MissingSubdirectory(
|
||||||
DisplaySafeUrl::from(url.clone()),
|
url.clone(),
|
||||||
subdirectory.to_path_buf(),
|
subdirectory.to_path_buf(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
@ -738,7 +738,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
||||||
&self,
|
&self,
|
||||||
source: &BuildableSource<'_>,
|
source: &BuildableSource<'_>,
|
||||||
ext: SourceDistExtension,
|
ext: SourceDistExtension,
|
||||||
url: &Url,
|
url: &DisplaySafeUrl,
|
||||||
index: Option<&IndexUrl>,
|
index: Option<&IndexUrl>,
|
||||||
cache_shard: &CacheShard,
|
cache_shard: &CacheShard,
|
||||||
hashes: HashPolicy<'_>,
|
hashes: HashPolicy<'_>,
|
||||||
|
|
@ -786,7 +786,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
||||||
.boxed_local()
|
.boxed_local()
|
||||||
.instrument(info_span!("download", source_dist = %source))
|
.instrument(info_span!("download", source_dist = %source))
|
||||||
};
|
};
|
||||||
let req = Self::request(DisplaySafeUrl::from(url.clone()), client.unmanaged)?;
|
let req = Self::request(url.clone(), client.unmanaged)?;
|
||||||
let revision = client
|
let revision = client
|
||||||
.managed(|client| {
|
.managed(|client| {
|
||||||
client.cached_client().get_serde_with_retry(
|
client.cached_client().get_serde_with_retry(
|
||||||
|
|
@ -811,7 +811,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
||||||
client
|
client
|
||||||
.cached_client()
|
.cached_client()
|
||||||
.skip_cache_with_retry(
|
.skip_cache_with_retry(
|
||||||
Self::request(DisplaySafeUrl::from(url.clone()), client)?,
|
Self::request(url.clone(), client)?,
|
||||||
&cache_entry,
|
&cache_entry,
|
||||||
cache_control,
|
cache_control,
|
||||||
download,
|
download,
|
||||||
|
|
@ -2146,7 +2146,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
||||||
&self,
|
&self,
|
||||||
source: &BuildableSource<'_>,
|
source: &BuildableSource<'_>,
|
||||||
ext: SourceDistExtension,
|
ext: SourceDistExtension,
|
||||||
url: &Url,
|
url: &DisplaySafeUrl,
|
||||||
index: Option<&IndexUrl>,
|
index: Option<&IndexUrl>,
|
||||||
entry: &CacheEntry,
|
entry: &CacheEntry,
|
||||||
revision: Revision,
|
revision: Revision,
|
||||||
|
|
@ -2208,7 +2208,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
||||||
client
|
client
|
||||||
.cached_client()
|
.cached_client()
|
||||||
.skip_cache_with_retry(
|
.skip_cache_with_retry(
|
||||||
Self::request(DisplaySafeUrl::from(url.clone()), client)?,
|
Self::request(url.clone(), client)?,
|
||||||
&cache_entry,
|
&cache_entry,
|
||||||
cache_control,
|
cache_control,
|
||||||
download,
|
download,
|
||||||
|
|
|
||||||
|
|
@ -58,11 +58,8 @@ impl VerbatimUrl {
|
||||||
|
|
||||||
/// Parse a URL from a string.
|
/// Parse a URL from a string.
|
||||||
pub fn parse_url(given: impl AsRef<str>) -> Result<Self, ParseError> {
|
pub fn parse_url(given: impl AsRef<str>) -> Result<Self, ParseError> {
|
||||||
let url = Url::parse(given.as_ref())?;
|
let url = DisplaySafeUrl::parse(given.as_ref())?;
|
||||||
Ok(Self {
|
Ok(Self { url, given: None })
|
||||||
url: DisplaySafeUrl::from(url),
|
|
||||||
given: None,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a [`VerbatimUrl`] from a path or a URL.
|
/// Convert a [`VerbatimUrl`] from a path or a URL.
|
||||||
|
|
@ -189,10 +186,8 @@ impl VerbatimUrl {
|
||||||
let (path, fragment) = split_fragment(path);
|
let (path, fragment) = split_fragment(path);
|
||||||
|
|
||||||
// Convert to a URL.
|
// Convert to a URL.
|
||||||
let mut url = DisplaySafeUrl::from(
|
let mut url = DisplaySafeUrl::from_file_path(path.clone())
|
||||||
Url::from_file_path(path.clone())
|
.unwrap_or_else(|()| panic!("path is absolute: {}", path.display()));
|
||||||
.unwrap_or_else(|()| panic!("path is absolute: {}", path.display())),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Set the fragment, if it exists.
|
// Set the fragment, if it exists.
|
||||||
if let Some(fragment) = fragment {
|
if let Some(fragment) = fragment {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use std::collections::BTreeMap;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use uv_redacted::DisplaySafeUrl;
|
||||||
|
|
||||||
/// Metadata for a distribution that was installed via a direct URL.
|
/// Metadata for a distribution that was installed via a direct URL.
|
||||||
///
|
///
|
||||||
|
|
@ -92,7 +92,7 @@ impl std::fmt::Display for VcsKind {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&DirectUrl> for Url {
|
impl TryFrom<&DirectUrl> for DisplaySafeUrl {
|
||||||
type Error = url::ParseError;
|
type Error = url::ParseError;
|
||||||
|
|
||||||
fn try_from(value: &DirectUrl) -> Result<Self, Self::Error> {
|
fn try_from(value: &DirectUrl) -> Result<Self, Self::Error> {
|
||||||
|
|
@ -126,9 +126,11 @@ impl TryFrom<&DirectUrl> for Url {
|
||||||
} => {
|
} => {
|
||||||
let mut url = Self::parse(&format!("{}+{}", vcs_info.vcs, url))?;
|
let mut url = Self::parse(&format!("{}+{}", vcs_info.vcs, url))?;
|
||||||
if let Some(commit_id) = &vcs_info.commit_id {
|
if let Some(commit_id) = &vcs_info.commit_id {
|
||||||
url.set_path(&format!("{}@{commit_id}", url.path()));
|
let path = format!("{}@{commit_id}", url.path());
|
||||||
|
url.set_path(&path);
|
||||||
} else if let Some(requested_revision) = &vcs_info.requested_revision {
|
} else if let Some(requested_revision) = &vcs_info.requested_revision {
|
||||||
url.set_path(&format!("{}@{requested_revision}", url.path()));
|
let path = format!("{}@{requested_revision}", url.path());
|
||||||
|
url.set_path(&path);
|
||||||
}
|
}
|
||||||
if let Some(subdirectory) = subdirectory {
|
if let Some(subdirectory) = subdirectory {
|
||||||
url.set_fragment(Some(&format!("subdirectory={}", subdirectory.display())));
|
url.set_fragment(Some(&format!("subdirectory={}", subdirectory.display())));
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ pub enum Error {
|
||||||
#[error("Invalid download URL")]
|
#[error("Invalid download URL")]
|
||||||
InvalidUrl(#[from] url::ParseError),
|
InvalidUrl(#[from] url::ParseError),
|
||||||
#[error("Invalid download URL: {0}")]
|
#[error("Invalid download URL: {0}")]
|
||||||
InvalidUrlFormat(Url),
|
InvalidUrlFormat(DisplaySafeUrl),
|
||||||
#[error("Invalid path in file URL: `{0}`")]
|
#[error("Invalid path in file URL: `{0}`")]
|
||||||
InvalidFileUrl(String),
|
InvalidFileUrl(String),
|
||||||
#[error("Failed to create download directory")]
|
#[error("Failed to create download directory")]
|
||||||
|
|
@ -109,7 +109,7 @@ pub enum Error {
|
||||||
#[error("An offline Python installation was requested, but {file} (from {url}) is missing in {}", python_builds_dir.user_display())]
|
#[error("An offline Python installation was requested, but {file} (from {url}) is missing in {}", python_builds_dir.user_display())]
|
||||||
OfflinePythonMissing {
|
OfflinePythonMissing {
|
||||||
file: Box<PythonInstallationKey>,
|
file: Box<PythonInstallationKey>,
|
||||||
url: Box<Url>,
|
url: Box<DisplaySafeUrl>,
|
||||||
python_builds_dir: PathBuf,
|
python_builds_dir: PathBuf,
|
||||||
},
|
},
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
|
@ -1198,7 +1198,7 @@ impl ManagedPythonDownload {
|
||||||
/// Download the managed Python archive into the cache directory.
|
/// Download the managed Python archive into the cache directory.
|
||||||
async fn download_archive(
|
async fn download_archive(
|
||||||
&self,
|
&self,
|
||||||
url: &Url,
|
url: &DisplaySafeUrl,
|
||||||
client: &BaseClient,
|
client: &BaseClient,
|
||||||
reporter: Option<&dyn Reporter>,
|
reporter: Option<&dyn Reporter>,
|
||||||
python_builds_dir: &Path,
|
python_builds_dir: &Path,
|
||||||
|
|
@ -1300,7 +1300,7 @@ impl ManagedPythonDownload {
|
||||||
&self,
|
&self,
|
||||||
python_install_mirror: Option<&str>,
|
python_install_mirror: Option<&str>,
|
||||||
pypy_install_mirror: Option<&str>,
|
pypy_install_mirror: Option<&str>,
|
||||||
) -> Result<Url, Error> {
|
) -> Result<DisplaySafeUrl, Error> {
|
||||||
match self.key.implementation {
|
match self.key.implementation {
|
||||||
LenientImplementationName::Known(ImplementationName::CPython) => {
|
LenientImplementationName::Known(ImplementationName::CPython) => {
|
||||||
if let Some(mirror) = python_install_mirror {
|
if let Some(mirror) = python_install_mirror {
|
||||||
|
|
@ -1312,7 +1312,7 @@ impl ManagedPythonDownload {
|
||||||
self.url.to_string(),
|
self.url.to_string(),
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
return Ok(Url::parse(
|
return Ok(DisplaySafeUrl::parse(
|
||||||
format!("{}/{}", mirror.trim_end_matches('/'), suffix).as_str(),
|
format!("{}/{}", mirror.trim_end_matches('/'), suffix).as_str(),
|
||||||
)?);
|
)?);
|
||||||
}
|
}
|
||||||
|
|
@ -1327,7 +1327,7 @@ impl ManagedPythonDownload {
|
||||||
self.url.to_string(),
|
self.url.to_string(),
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
return Ok(Url::parse(
|
return Ok(DisplaySafeUrl::parse(
|
||||||
format!("{}/{}", mirror.trim_end_matches('/'), suffix).as_str(),
|
format!("{}/{}", mirror.trim_end_matches('/'), suffix).as_str(),
|
||||||
)?);
|
)?);
|
||||||
}
|
}
|
||||||
|
|
@ -1336,7 +1336,7 @@ impl ManagedPythonDownload {
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Url::parse(&self.url)?)
|
Ok(DisplaySafeUrl::parse(&self.url)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1558,10 +1558,9 @@ where
|
||||||
|
|
||||||
/// Convert a [`Url`] into an [`AsyncRead`] stream.
|
/// Convert a [`Url`] into an [`AsyncRead`] stream.
|
||||||
async fn read_url(
|
async fn read_url(
|
||||||
url: &Url,
|
url: &DisplaySafeUrl,
|
||||||
client: &BaseClient,
|
client: &BaseClient,
|
||||||
) -> Result<(impl AsyncRead + Unpin, Option<u64>), Error> {
|
) -> Result<(impl AsyncRead + Unpin, Option<u64>), Error> {
|
||||||
let url = DisplaySafeUrl::from(url.clone());
|
|
||||||
if url.scheme() == "file" {
|
if url.scheme() == "file" {
|
||||||
// Loads downloaded distribution from the given `file://` URL.
|
// Loads downloaded distribution from the given `file://` URL.
|
||||||
let path = url
|
let path = url
|
||||||
|
|
@ -1574,7 +1573,7 @@ async fn read_url(
|
||||||
Ok((Either::Left(reader), Some(size)))
|
Ok((Either::Left(reader), Some(size)))
|
||||||
} else {
|
} else {
|
||||||
let response = client
|
let response = client
|
||||||
.for_host(&url)
|
.for_host(url)
|
||||||
.get(Url::from(url.clone()))
|
.get(Url::from(url.clone()))
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
|
|
@ -1588,7 +1587,7 @@ async fn read_url(
|
||||||
// Check the status code.
|
// Check the status code.
|
||||||
let response = response
|
let response = response
|
||||||
.error_for_status()
|
.error_for_status()
|
||||||
.map_err(|err| Error::from_reqwest(url, err, retry_count))?;
|
.map_err(|err| Error::from_reqwest(url.clone(), err, retry_count))?;
|
||||||
|
|
||||||
let size = response.content_length();
|
let size = response.content_length();
|
||||||
let stream = response
|
let stream = response
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ impl DisplaySafeUrl {
|
||||||
/// Parse a string as an URL, with this URL as the base URL.
|
/// Parse a string as an URL, with this URL as the base URL.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn join(&self, input: &str) -> Result<Self, url::ParseError> {
|
pub fn join(&self, input: &str) -> Result<Self, url::ParseError> {
|
||||||
self.0.join(input).map(Self::from)
|
self.0.join(input).map(Self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Serialize with Serde using the internal representation of the `Url` struct.
|
/// Serialize with Serde using the internal representation of the `Url` struct.
|
||||||
|
|
@ -78,7 +78,7 @@ impl DisplaySafeUrl {
|
||||||
where
|
where
|
||||||
D: serde::Deserializer<'de>,
|
D: serde::Deserializer<'de>,
|
||||||
{
|
{
|
||||||
Url::deserialize_internal(deserializer).map(Self::from)
|
Url::deserialize_internal(deserializer).map(Self)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::result_unit_err)]
|
#[allow(clippy::result_unit_err)]
|
||||||
|
|
@ -250,8 +250,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn from_url_no_credentials() {
|
fn from_url_no_credentials() {
|
||||||
let url_str = "https://pypi-proxy.fly.dev/basic-auth/simple";
|
let url_str = "https://pypi-proxy.fly.dev/basic-auth/simple";
|
||||||
let url = Url::parse(url_str).unwrap();
|
let url = DisplaySafeUrl::parse(url_str).unwrap();
|
||||||
let log_safe_url = DisplaySafeUrl::from(url);
|
let log_safe_url = url;
|
||||||
assert_eq!(log_safe_url.username(), "");
|
assert_eq!(log_safe_url.username(), "");
|
||||||
assert!(log_safe_url.password().is_none());
|
assert!(log_safe_url.password().is_none());
|
||||||
assert_eq!(log_safe_url.to_string(), url_str);
|
assert_eq!(log_safe_url.to_string(), url_str);
|
||||||
|
|
@ -260,8 +260,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn from_url_username_and_password() {
|
fn from_url_username_and_password() {
|
||||||
let url_str = "https://user:pass@pypi-proxy.fly.dev/basic-auth/simple";
|
let url_str = "https://user:pass@pypi-proxy.fly.dev/basic-auth/simple";
|
||||||
let url = Url::parse(url_str).unwrap();
|
let url = DisplaySafeUrl::parse(url_str).unwrap();
|
||||||
let log_safe_url = DisplaySafeUrl::from(url);
|
let log_safe_url = url;
|
||||||
assert_eq!(log_safe_url.username(), "user");
|
assert_eq!(log_safe_url.username(), "user");
|
||||||
assert!(log_safe_url.password().is_some_and(|p| p == "pass"));
|
assert!(log_safe_url.password().is_some_and(|p| p == "pass"));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -273,8 +273,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn from_url_just_password() {
|
fn from_url_just_password() {
|
||||||
let url_str = "https://:pass@pypi-proxy.fly.dev/basic-auth/simple";
|
let url_str = "https://:pass@pypi-proxy.fly.dev/basic-auth/simple";
|
||||||
let url = Url::parse(url_str).unwrap();
|
let url = DisplaySafeUrl::parse(url_str).unwrap();
|
||||||
let log_safe_url = DisplaySafeUrl::from(url);
|
let log_safe_url = url;
|
||||||
assert_eq!(log_safe_url.username(), "");
|
assert_eq!(log_safe_url.username(), "");
|
||||||
assert!(log_safe_url.password().is_some_and(|p| p == "pass"));
|
assert!(log_safe_url.password().is_some_and(|p| p == "pass"));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -286,8 +286,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn from_url_just_username() {
|
fn from_url_just_username() {
|
||||||
let url_str = "https://user@pypi-proxy.fly.dev/basic-auth/simple";
|
let url_str = "https://user@pypi-proxy.fly.dev/basic-auth/simple";
|
||||||
let url = Url::parse(url_str).unwrap();
|
let url = DisplaySafeUrl::parse(url_str).unwrap();
|
||||||
let log_safe_url = DisplaySafeUrl::from(url);
|
let log_safe_url = url;
|
||||||
assert_eq!(log_safe_url.username(), "user");
|
assert_eq!(log_safe_url.username(), "user");
|
||||||
assert!(log_safe_url.password().is_none());
|
assert!(log_safe_url.password().is_none());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -374,7 +374,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn log_safe_url_ref() {
|
fn log_safe_url_ref() {
|
||||||
let url_str = "https://user:pass@pypi-proxy.fly.dev/basic-auth/simple";
|
let url_str = "https://user:pass@pypi-proxy.fly.dev/basic-auth/simple";
|
||||||
let url = Url::parse(url_str).unwrap();
|
let url = DisplaySafeUrl::parse(url_str).unwrap();
|
||||||
let log_safe_url = DisplaySafeUrl::ref_cast(&url);
|
let log_safe_url = DisplaySafeUrl::ref_cast(&url);
|
||||||
assert_eq!(log_safe_url.username(), "user");
|
assert_eq!(log_safe_url.username(), "user");
|
||||||
assert!(log_safe_url.password().is_some_and(|p| p == "pass"));
|
assert!(log_safe_url.password().is_some_and(|p| p == "pass"));
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ use itertools::Itertools;
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
use rustc_hash::{FxBuildHasher, FxHashMap};
|
use rustc_hash::{FxBuildHasher, FxHashMap};
|
||||||
use tracing::{debug, warn};
|
use tracing::{debug, warn};
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
use uv_cache::Cache;
|
use uv_cache::Cache;
|
||||||
use uv_cache_key::RepositoryUrl;
|
use uv_cache_key::RepositoryUrl;
|
||||||
|
|
@ -1104,8 +1103,7 @@ async fn lock_and_sync(
|
||||||
|
|
||||||
// Invalidate the project metadata.
|
// Invalidate the project metadata.
|
||||||
if let AddTarget::Project(VirtualProject::Project(ref project), _) = target {
|
if let AddTarget::Project(VirtualProject::Project(ref project), _) = target {
|
||||||
let url = Url::from_file_path(project.project_root())
|
let url = DisplaySafeUrl::from_file_path(project.project_root())
|
||||||
.map(DisplaySafeUrl::from)
|
|
||||||
.expect("project root is a valid URL");
|
.expect("project root is a valid URL");
|
||||||
let version_id = VersionId::from_url(&url);
|
let version_id = VersionId::from_url(&url);
|
||||||
let existing = lock_state.index().distributions().remove(&version_id);
|
let existing = lock_state.index().distributions().remove(&version_id);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue