mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 13:25:00 +00:00
Add DisplaySafeUrl
newtype to prevent leaking of credentials by default (#13560)
Prior to this PR, there were numerous places where uv would leak credentials in logs. We had a way to mask credentials by calling methods or a recently-added `redact_url` function, but this was not secure by default. There were a number of other types (like `GitUrl`) that would leak credentials on display. This PR adds a `DisplaySafeUrl` newtype to prevent leaking credentials when logging by default. It takes a maximalist approach, replacing the use of `Url` almost everywhere. This includes when first parsing config files, when storing URLs in types like `GitUrl`, and also when storing URLs in types that in practice will never contain credentials (like `DirectorySourceUrl`). The idea is to make it easy for developers to do the right thing and for the compiler to support this (and to minimize ever having to manually convert back and forth). Displaying credentials now requires an active step. Note that despite this maximalist approach, the use of the newtype should be zero cost. One conspicuous place this PR does not use `DisplaySafeUrl` is in the `uv-auth` crate. That would require new clones since there are calls to `request.url()` that return a `&Url`. One option would have been to make `DisplaySafeUrl` wrap a `Cow`, but this would lead to lifetime annotations all over the codebase. I've created a separate PR based on this one (#13576) that updates `uv-auth` to use `DisplaySafeUrl` with one new clone. We can discuss the tradeoffs there. Most of this PR just replaces `Url` with `DisplaySafeUrl`. The core is `uv_redacted/lib.rs`, where the newtype is implemented. To make it easier to review the rest, here are some points of note: * `DisplaySafeUrl` has a `Display` implementation that masks credentials. Currently, it will still display the username when there is both a username and password. If we think is the wrong choice, it can now be changed in one place. * `DisplaySafeUrl` has a `remove_credentials()` method and also a `.to_string_with_credentials()` method. This allows us to use it in a variety of scenarios. * `IndexUrl::redacted()` was renamed to `IndexUrl::removed_credentials()` to make it clearer that we are not masking. * We convert from a `DisplaySafeUrl` to a `Url` when calling `reqwest` methods like `.get()` and `.head()`. * We convert from a `DisplaySafeUrl` to a `Url` when creating a `uv_auth::Index`. That is because, as mentioned above, I will be updating the `uv_auth` crate to use this newtype in a separate PR. * A number of tests (e.g., in `pip_install.rs`) that formerly used filters to mask tokens in the test output no longer need those filters since tokens in URLs are now masked automatically. * The one place we are still knowingly writing credentials to `pyproject.toml` is when a URL with credentials is passed to `uv add` with `--raw`. Since displaying credentials is no longer automatic, I have added a `to_string_with_credentials()` method to the `Pep508Url` trait. This is used when `--raw` is passed. Adding it to that trait is a bit weird, but it's the simplest way to achieve the goal. I'm open to suggestions on how to improve this, but note that because of the way we're using generic bounds, it's not as simple as just creating a separate trait for that method.
This commit is contained in:
parent
b80cafd5e8
commit
c19a294a48
100 changed files with 1266 additions and 2249 deletions
24
Cargo.lock
generated
24
Cargo.lock
generated
|
@ -4671,9 +4671,9 @@ dependencies = [
|
|||
"test-log",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-test",
|
||||
"url",
|
||||
"uv-once-map",
|
||||
"uv-redacted",
|
||||
"uv-small-str",
|
||||
"uv-static",
|
||||
"uv-warnings",
|
||||
|
@ -4801,7 +4801,6 @@ dependencies = [
|
|||
"serde",
|
||||
"tempfile",
|
||||
"tracing",
|
||||
"url",
|
||||
"uv-cache-info",
|
||||
"uv-cache-key",
|
||||
"uv-dirs",
|
||||
|
@ -4809,6 +4808,7 @@ dependencies = [
|
|||
"uv-fs",
|
||||
"uv-normalize",
|
||||
"uv-pypi-types",
|
||||
"uv-redacted",
|
||||
"uv-static",
|
||||
"walkdir",
|
||||
]
|
||||
|
@ -4836,6 +4836,7 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
"seahash",
|
||||
"url",
|
||||
"uv-redacted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -4858,6 +4859,7 @@ dependencies = [
|
|||
"uv-pep508",
|
||||
"uv-pypi-types",
|
||||
"uv-python",
|
||||
"uv-redacted",
|
||||
"uv-resolver",
|
||||
"uv-settings",
|
||||
"uv-static",
|
||||
|
@ -5087,6 +5089,7 @@ dependencies = [
|
|||
"uv-pep508",
|
||||
"uv-platform-tags",
|
||||
"uv-pypi-types",
|
||||
"uv-redacted",
|
||||
"uv-types",
|
||||
"uv-workspace",
|
||||
"walkdir",
|
||||
|
@ -5144,6 +5147,7 @@ dependencies = [
|
|||
"uv-pep508",
|
||||
"uv-platform-tags",
|
||||
"uv-pypi-types",
|
||||
"uv-redacted",
|
||||
"uv-small-str",
|
||||
"version-ranges",
|
||||
]
|
||||
|
@ -5318,6 +5322,7 @@ dependencies = [
|
|||
"uv-platform-tags",
|
||||
"uv-pypi-types",
|
||||
"uv-python",
|
||||
"uv-redacted",
|
||||
"uv-static",
|
||||
"uv-types",
|
||||
"uv-warnings",
|
||||
|
@ -5413,6 +5418,7 @@ dependencies = [
|
|||
"uv-fs",
|
||||
"uv-normalize",
|
||||
"uv-pep440",
|
||||
"uv-redacted",
|
||||
"version-ranges",
|
||||
]
|
||||
|
||||
|
@ -5470,6 +5476,7 @@ dependencies = [
|
|||
"uv-fs",
|
||||
"uv-metadata",
|
||||
"uv-pypi-types",
|
||||
"uv-redacted",
|
||||
"uv-static",
|
||||
"uv-warnings",
|
||||
]
|
||||
|
@ -5501,6 +5508,7 @@ dependencies = [
|
|||
"uv-normalize",
|
||||
"uv-pep440",
|
||||
"uv-pep508",
|
||||
"uv-redacted",
|
||||
"uv-small-str",
|
||||
]
|
||||
|
||||
|
@ -5554,6 +5562,7 @@ dependencies = [
|
|||
"uv-pep508",
|
||||
"uv-platform-tags",
|
||||
"uv-pypi-types",
|
||||
"uv-redacted",
|
||||
"uv-state",
|
||||
"uv-static",
|
||||
"uv-trampoline-builder",
|
||||
|
@ -5568,6 +5577,8 @@ dependencies = [
|
|||
name = "uv-redacted"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
"url",
|
||||
]
|
||||
|
||||
|
@ -5598,6 +5609,7 @@ dependencies = [
|
|||
"uv-normalize",
|
||||
"uv-pep508",
|
||||
"uv-pypi-types",
|
||||
"uv-redacted",
|
||||
"uv-requirements-txt",
|
||||
"uv-resolver",
|
||||
"uv-types",
|
||||
|
@ -5633,6 +5645,7 @@ dependencies = [
|
|||
"uv-normalize",
|
||||
"uv-pep508",
|
||||
"uv-pypi-types",
|
||||
"uv-redacted",
|
||||
"uv-warnings",
|
||||
]
|
||||
|
||||
|
@ -5684,6 +5697,7 @@ dependencies = [
|
|||
"uv-platform-tags",
|
||||
"uv-pypi-types",
|
||||
"uv-python",
|
||||
"uv-redacted",
|
||||
"uv-requirements-txt",
|
||||
"uv-small-str",
|
||||
"uv-static",
|
||||
|
@ -5707,6 +5721,7 @@ dependencies = [
|
|||
"uv-pep440",
|
||||
"uv-pep508",
|
||||
"uv-pypi-types",
|
||||
"uv-redacted",
|
||||
"uv-settings",
|
||||
"uv-workspace",
|
||||
]
|
||||
|
@ -5736,6 +5751,7 @@ dependencies = [
|
|||
"uv-pep508",
|
||||
"uv-pypi-types",
|
||||
"uv-python",
|
||||
"uv-redacted",
|
||||
"uv-resolver",
|
||||
"uv-static",
|
||||
"uv-torch",
|
||||
|
@ -5851,7 +5867,6 @@ dependencies = [
|
|||
"anyhow",
|
||||
"rustc-hash",
|
||||
"thiserror 2.0.12",
|
||||
"url",
|
||||
"uv-cache",
|
||||
"uv-configuration",
|
||||
"uv-distribution-filename",
|
||||
|
@ -5863,6 +5878,7 @@ dependencies = [
|
|||
"uv-pep508",
|
||||
"uv-pypi-types",
|
||||
"uv-python",
|
||||
"uv-redacted",
|
||||
"uv-workspace",
|
||||
]
|
||||
|
||||
|
@ -5917,7 +5933,6 @@ dependencies = [
|
|||
"toml",
|
||||
"toml_edit",
|
||||
"tracing",
|
||||
"url",
|
||||
"uv-build-backend",
|
||||
"uv-cache-key",
|
||||
"uv-distribution-types",
|
||||
|
@ -5929,6 +5944,7 @@ dependencies = [
|
|||
"uv-pep440",
|
||||
"uv-pep508",
|
||||
"uv-pypi-types",
|
||||
"uv-redacted",
|
||||
"uv-static",
|
||||
"uv-warnings",
|
||||
]
|
||||
|
|
|
@ -11,6 +11,7 @@ workspace = true
|
|||
|
||||
[dependencies]
|
||||
uv-once-map = { workspace = true }
|
||||
uv-redacted = { workspace = true }
|
||||
uv-small-str = { workspace = true }
|
||||
uv-static = { workspace = true }
|
||||
uv-warnings = { workspace = true }
|
||||
|
@ -36,5 +37,4 @@ insta = { version = "1.40.0" }
|
|||
tempfile = { workspace = true }
|
||||
test-log = { version = "0.2.16", features = ["trace"], default-features = false }
|
||||
tokio = { workspace = true }
|
||||
tracing-test = { workspace = true }
|
||||
wiremock = { workspace = true }
|
||||
|
|
|
@ -9,6 +9,7 @@ use tracing::trace;
|
|||
use url::Url;
|
||||
|
||||
use uv_once_map::OnceMap;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
use crate::Realm;
|
||||
use crate::credentials::{Credentials, Username};
|
||||
|
@ -18,7 +19,7 @@ type FxOnceMap<K, V> = OnceMap<K, V, BuildHasherDefault<FxHasher>>;
|
|||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub(crate) enum FetchUrl {
|
||||
/// A full index URL
|
||||
Index(Url),
|
||||
Index(DisplaySafeUrl),
|
||||
/// A realm URL
|
||||
Realm(Realm),
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ use base64::read::DecoderReader;
|
|||
use base64::write::EncoderWriter;
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_redacted::DisplaySafeUrlRef;
|
||||
|
||||
use netrc::Netrc;
|
||||
use reqwest::Request;
|
||||
|
@ -141,7 +143,11 @@ impl Credentials {
|
|||
/// Return [`Credentials`] for a [`Url`] from a [`Netrc`] file, if any.
|
||||
///
|
||||
/// If a username is provided, it must match the login in the netrc file or [`None`] is returned.
|
||||
pub(crate) fn from_netrc(netrc: &Netrc, url: &Url, username: Option<&str>) -> Option<Self> {
|
||||
pub(crate) fn from_netrc(
|
||||
netrc: &Netrc,
|
||||
url: &DisplaySafeUrlRef<'_>,
|
||||
username: Option<&str>,
|
||||
) -> Option<Self> {
|
||||
let host = url.host_str()?;
|
||||
let entry = netrc
|
||||
.hosts
|
||||
|
@ -299,7 +305,7 @@ impl Credentials {
|
|||
///
|
||||
/// Any existing credentials will be overridden.
|
||||
#[must_use]
|
||||
pub fn apply(&self, mut url: Url) -> Url {
|
||||
pub fn apply(&self, mut url: DisplaySafeUrl) -> DisplaySafeUrl {
|
||||
if let Some(username) = self.username() {
|
||||
let _ = url.set_username(username);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ use std::fmt::{self, Display, Formatter};
|
|||
|
||||
use rustc_hash::FxHashSet;
|
||||
use url::Url;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
/// When to use authentication.
|
||||
#[derive(
|
||||
|
@ -53,10 +54,10 @@ impl Display for AuthPolicy {
|
|||
// could potentially make sense for a future refactor.
|
||||
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
|
||||
pub struct Index {
|
||||
pub url: Url,
|
||||
pub url: DisplaySafeUrl,
|
||||
/// The root endpoint where authentication is applied.
|
||||
/// For PEP 503 endpoints, this excludes `/simple`.
|
||||
pub root_url: Url,
|
||||
pub root_url: DisplaySafeUrl,
|
||||
pub auth_policy: AuthPolicy,
|
||||
}
|
||||
|
||||
|
@ -95,7 +96,7 @@ impl Indexes {
|
|||
}
|
||||
|
||||
/// Get the index URL prefix for a URL if one exists.
|
||||
pub fn index_url_for(&self, url: &Url) -> Option<&Url> {
|
||||
pub fn index_url_for(&self, url: &Url) -> Option<&DisplaySafeUrl> {
|
||||
self.find_prefix_index(url).map(|index| &index.url)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::{io::Write, process::Stdio};
|
||||
use tokio::process::Command;
|
||||
use tracing::{instrument, trace, warn};
|
||||
use url::Url;
|
||||
use uv_redacted::DisplaySafeUrlRef;
|
||||
use uv_warnings::warn_user_once;
|
||||
|
||||
use crate::credentials::Credentials;
|
||||
|
@ -36,7 +36,11 @@ impl KeyringProvider {
|
|||
/// Returns [`None`] if no password was found for the username or if any errors
|
||||
/// are encountered in the keyring backend.
|
||||
#[instrument(skip_all, fields(url = % url.to_string(), username))]
|
||||
pub async fn fetch(&self, url: &Url, username: Option<&str>) -> Option<Credentials> {
|
||||
pub async fn fetch(
|
||||
&self,
|
||||
url: &DisplaySafeUrlRef<'_>,
|
||||
username: Option<&str>,
|
||||
) -> Option<Credentials> {
|
||||
// Validate the request
|
||||
debug_assert!(
|
||||
url.host_str().is_some(),
|
||||
|
@ -217,15 +221,18 @@ impl KeyringProvider {
|
|||
mod tests {
|
||||
use super::*;
|
||||
use futures::FutureExt;
|
||||
use url::Url;
|
||||
|
||||
#[tokio::test]
|
||||
async fn fetch_url_no_host() {
|
||||
let url = Url::parse("file:/etc/bin/").unwrap();
|
||||
let keyring = KeyringProvider::empty();
|
||||
// Panics due to debug assertion; returns `None` in production
|
||||
let result = std::panic::AssertUnwindSafe(keyring.fetch(&url, Some("user")))
|
||||
.catch_unwind()
|
||||
.await;
|
||||
let result = std::panic::AssertUnwindSafe(
|
||||
keyring.fetch(&DisplaySafeUrlRef::from(&url), Some("user")),
|
||||
)
|
||||
.catch_unwind()
|
||||
.await;
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
|
@ -234,9 +241,11 @@ mod tests {
|
|||
let url = Url::parse("https://user:password@example.com").unwrap();
|
||||
let keyring = KeyringProvider::empty();
|
||||
// Panics due to debug assertion; returns `None` in production
|
||||
let result = std::panic::AssertUnwindSafe(keyring.fetch(&url, Some(url.username())))
|
||||
.catch_unwind()
|
||||
.await;
|
||||
let result = std::panic::AssertUnwindSafe(
|
||||
keyring.fetch(&DisplaySafeUrlRef::from(&url), Some(url.username())),
|
||||
)
|
||||
.catch_unwind()
|
||||
.await;
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
|
@ -245,15 +254,18 @@ mod tests {
|
|||
let url = Url::parse("https://example.com").unwrap();
|
||||
let keyring = KeyringProvider::empty();
|
||||
// Panics due to debug assertion; returns `None` in production
|
||||
let result = std::panic::AssertUnwindSafe(keyring.fetch(&url, Some(url.username())))
|
||||
.catch_unwind()
|
||||
.await;
|
||||
let result = std::panic::AssertUnwindSafe(
|
||||
keyring.fetch(&DisplaySafeUrlRef::from(&url), Some(url.username())),
|
||||
)
|
||||
.catch_unwind()
|
||||
.await;
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn fetch_url_no_auth() {
|
||||
let url = Url::parse("https://example.com").unwrap();
|
||||
let url = DisplaySafeUrlRef::from(&url);
|
||||
let keyring = KeyringProvider::empty();
|
||||
let credentials = keyring.fetch(&url, Some("user"));
|
||||
assert!(credentials.await.is_none());
|
||||
|
@ -264,7 +276,9 @@ mod tests {
|
|||
let url = Url::parse("https://example.com").unwrap();
|
||||
let keyring = KeyringProvider::dummy([(url.host_str().unwrap(), "user", "password")]);
|
||||
assert_eq!(
|
||||
keyring.fetch(&url, Some("user")).await,
|
||||
keyring
|
||||
.fetch(&DisplaySafeUrlRef::from(&url), Some("user"))
|
||||
.await,
|
||||
Some(Credentials::basic(
|
||||
Some("user".to_string()),
|
||||
Some("password".to_string())
|
||||
|
@ -272,7 +286,10 @@ mod tests {
|
|||
);
|
||||
assert_eq!(
|
||||
keyring
|
||||
.fetch(&url.join("test").unwrap(), Some("user"))
|
||||
.fetch(
|
||||
&DisplaySafeUrlRef::from(&url.join("test").unwrap()),
|
||||
Some("user")
|
||||
)
|
||||
.await,
|
||||
Some(Credentials::basic(
|
||||
Some("user".to_string()),
|
||||
|
@ -285,7 +302,9 @@ mod tests {
|
|||
async fn fetch_url_no_match() {
|
||||
let url = Url::parse("https://example.com").unwrap();
|
||||
let keyring = KeyringProvider::dummy([("other.com", "user", "password")]);
|
||||
let credentials = keyring.fetch(&url, Some("user")).await;
|
||||
let credentials = keyring
|
||||
.fetch(&DisplaySafeUrlRef::from(&url), Some("user"))
|
||||
.await;
|
||||
assert_eq!(credentials, None);
|
||||
}
|
||||
|
||||
|
@ -297,21 +316,33 @@ mod tests {
|
|||
(url.host_str().unwrap(), "user", "other-password"),
|
||||
]);
|
||||
assert_eq!(
|
||||
keyring.fetch(&url.join("foo").unwrap(), Some("user")).await,
|
||||
keyring
|
||||
.fetch(
|
||||
&DisplaySafeUrlRef::from(&url.join("foo").unwrap()),
|
||||
Some("user")
|
||||
)
|
||||
.await,
|
||||
Some(Credentials::basic(
|
||||
Some("user".to_string()),
|
||||
Some("password".to_string())
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
keyring.fetch(&url, Some("user")).await,
|
||||
keyring
|
||||
.fetch(&DisplaySafeUrlRef::from(&url), Some("user"))
|
||||
.await,
|
||||
Some(Credentials::basic(
|
||||
Some("user".to_string()),
|
||||
Some("other-password".to_string())
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
keyring.fetch(&url.join("bar").unwrap(), Some("user")).await,
|
||||
keyring
|
||||
.fetch(
|
||||
&DisplaySafeUrlRef::from(&url.join("bar").unwrap()),
|
||||
Some("user")
|
||||
)
|
||||
.await,
|
||||
Some(Credentials::basic(
|
||||
Some("user".to_string()),
|
||||
Some("other-password".to_string())
|
||||
|
@ -323,7 +354,9 @@ mod tests {
|
|||
async fn fetch_url_username() {
|
||||
let url = Url::parse("https://example.com").unwrap();
|
||||
let keyring = KeyringProvider::dummy([(url.host_str().unwrap(), "user", "password")]);
|
||||
let credentials = keyring.fetch(&url, Some("user")).await;
|
||||
let credentials = keyring
|
||||
.fetch(&DisplaySafeUrlRef::from(&url), Some("user"))
|
||||
.await;
|
||||
assert_eq!(
|
||||
credentials,
|
||||
Some(Credentials::basic(
|
||||
|
@ -337,7 +370,7 @@ mod tests {
|
|||
async fn fetch_url_no_username() {
|
||||
let url = Url::parse("https://example.com").unwrap();
|
||||
let keyring = KeyringProvider::dummy([(url.host_str().unwrap(), "user", "password")]);
|
||||
let credentials = keyring.fetch(&url, None).await;
|
||||
let credentials = keyring.fetch(&DisplaySafeUrlRef::from(&url), None).await;
|
||||
assert_eq!(
|
||||
credentials,
|
||||
Some(Credentials::basic(
|
||||
|
@ -351,12 +384,16 @@ mod tests {
|
|||
async fn fetch_url_username_no_match() {
|
||||
let url = Url::parse("https://example.com").unwrap();
|
||||
let keyring = KeyringProvider::dummy([(url.host_str().unwrap(), "foo", "password")]);
|
||||
let credentials = keyring.fetch(&url, Some("bar")).await;
|
||||
let credentials = keyring
|
||||
.fetch(&DisplaySafeUrlRef::from(&url), Some("bar"))
|
||||
.await;
|
||||
assert_eq!(credentials, None);
|
||||
|
||||
// Still fails if we have `foo` in the URL itself
|
||||
let url = Url::parse("https://foo@example.com").unwrap();
|
||||
let credentials = keyring.fetch(&url, Some("bar")).await;
|
||||
let credentials = keyring
|
||||
.fetch(&DisplaySafeUrlRef::from(&url), Some("bar"))
|
||||
.await;
|
||||
assert_eq!(credentials, None);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use std::sync::{Arc, LazyLock};
|
||||
|
||||
use tracing::trace;
|
||||
use url::Url;
|
||||
|
||||
use cache::CredentialsCache;
|
||||
pub use credentials::Credentials;
|
||||
|
@ -9,6 +8,7 @@ pub use index::{AuthPolicy, Index, Indexes};
|
|||
pub use keyring::KeyringProvider;
|
||||
pub use middleware::AuthMiddleware;
|
||||
use realm::Realm;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
mod cache;
|
||||
mod credentials;
|
||||
|
@ -28,7 +28,7 @@ pub(crate) static CREDENTIALS_CACHE: LazyLock<CredentialsCache> =
|
|||
/// Populate the global authentication store with credentials on a URL, if there are any.
|
||||
///
|
||||
/// Returns `true` if the store was updated.
|
||||
pub fn store_credentials_from_url(url: &Url) -> bool {
|
||||
pub fn store_credentials_from_url(url: &DisplaySafeUrl) -> bool {
|
||||
if let Some(credentials) = Credentials::from_url(url) {
|
||||
trace!("Caching credentials for {url}");
|
||||
CREDENTIALS_CACHE.insert(url, Arc::new(credentials));
|
||||
|
@ -41,7 +41,7 @@ pub fn store_credentials_from_url(url: &Url) -> bool {
|
|||
/// Populate the global authentication store with credentials on a URL, if there are any.
|
||||
///
|
||||
/// Returns `true` if the store was updated.
|
||||
pub fn store_credentials(url: &Url, credentials: Arc<Credentials>) {
|
||||
pub fn store_credentials(url: &DisplaySafeUrl, credentials: Arc<Credentials>) {
|
||||
trace!("Caching credentials for {url}");
|
||||
CREDENTIALS_CACHE.insert(url, credentials);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::sync::{Arc, LazyLock};
|
||||
|
||||
use http::{Extensions, StatusCode};
|
||||
use url::Url;
|
||||
use uv_redacted::{DisplaySafeUrl, DisplaySafeUrlRef};
|
||||
|
||||
use crate::{
|
||||
CREDENTIALS_CACHE, CredentialsCache, KeyringProvider,
|
||||
|
@ -274,6 +274,7 @@ impl Middleware for AuthMiddleware {
|
|||
trace!("Checking for credentials for {url}");
|
||||
(request, None)
|
||||
};
|
||||
let retry_request_url = DisplaySafeUrlRef::from(retry_request.url());
|
||||
|
||||
let username = credentials
|
||||
.as_ref()
|
||||
|
@ -282,13 +283,13 @@ impl Middleware for AuthMiddleware {
|
|||
let credentials = if let Some(index_url) = maybe_index_url {
|
||||
self.cache().get_url(index_url, &username).or_else(|| {
|
||||
self.cache()
|
||||
.get_realm(Realm::from(retry_request.url()), username)
|
||||
.get_realm(Realm::from(&*retry_request_url), username)
|
||||
})
|
||||
} else {
|
||||
// Since there is no known index for this URL, check if there are credentials in
|
||||
// the realm-level cache.
|
||||
self.cache()
|
||||
.get_realm(Realm::from(retry_request.url()), username)
|
||||
.get_realm(Realm::from(&*retry_request_url), username)
|
||||
}
|
||||
.or(credentials);
|
||||
|
||||
|
@ -307,7 +308,7 @@ impl Middleware for AuthMiddleware {
|
|||
if let Some(credentials) = self
|
||||
.fetch_credentials(
|
||||
credentials.as_deref(),
|
||||
retry_request.url(),
|
||||
retry_request_url,
|
||||
maybe_index_url,
|
||||
auth_policy,
|
||||
)
|
||||
|
@ -362,7 +363,7 @@ impl AuthMiddleware {
|
|||
// Nothing to insert into the cache if we don't have credentials
|
||||
return next.run(request, extensions).await;
|
||||
};
|
||||
let url = request.url().clone();
|
||||
let url = DisplaySafeUrl::from(request.url().clone());
|
||||
if matches!(auth_policy, AuthPolicy::Always) && credentials.password().is_none() {
|
||||
return Err(Error::Middleware(format_err!("Missing password for {url}")));
|
||||
}
|
||||
|
@ -387,8 +388,8 @@ impl AuthMiddleware {
|
|||
mut request: Request,
|
||||
extensions: &mut Extensions,
|
||||
next: Next<'_>,
|
||||
url: &str,
|
||||
index_url: Option<&Url>,
|
||||
url: &DisplaySafeUrl,
|
||||
index_url: Option<&DisplaySafeUrl>,
|
||||
auth_policy: AuthPolicy,
|
||||
) -> reqwest_middleware::Result<Response> {
|
||||
let credentials = Arc::new(credentials);
|
||||
|
@ -430,7 +431,12 @@ impl AuthMiddleware {
|
|||
// Do not insert already-cached credentials
|
||||
None
|
||||
} else if let Some(credentials) = self
|
||||
.fetch_credentials(Some(&credentials), request.url(), index_url, auth_policy)
|
||||
.fetch_credentials(
|
||||
Some(&credentials),
|
||||
DisplaySafeUrlRef::from(request.url()),
|
||||
index_url,
|
||||
auth_policy,
|
||||
)
|
||||
.await
|
||||
{
|
||||
request = credentials.authenticate(request);
|
||||
|
@ -462,8 +468,8 @@ impl AuthMiddleware {
|
|||
async fn fetch_credentials(
|
||||
&self,
|
||||
credentials: Option<&Credentials>,
|
||||
url: &Url,
|
||||
maybe_index_url: Option<&Url>,
|
||||
url: DisplaySafeUrlRef<'_>,
|
||||
maybe_index_url: Option<&DisplaySafeUrl>,
|
||||
auth_policy: AuthPolicy,
|
||||
) -> Option<Arc<Credentials>> {
|
||||
let username = Username::from(
|
||||
|
@ -475,7 +481,7 @@ impl AuthMiddleware {
|
|||
let key = if let Some(index_url) = maybe_index_url {
|
||||
(FetchUrl::Index(index_url.clone()), username)
|
||||
} else {
|
||||
(FetchUrl::Realm(Realm::from(url)), username)
|
||||
(FetchUrl::Realm(Realm::from(&*url)), username)
|
||||
};
|
||||
if !self.cache().fetches.register(key.clone()) {
|
||||
let credentials = self
|
||||
|
@ -502,7 +508,7 @@ impl AuthMiddleware {
|
|||
debug!("Checking netrc for credentials for {url}");
|
||||
Credentials::from_netrc(
|
||||
netrc,
|
||||
url,
|
||||
&url,
|
||||
credentials
|
||||
.as_ref()
|
||||
.and_then(|credentials| credentials.username()),
|
||||
|
@ -523,17 +529,17 @@ impl AuthMiddleware {
|
|||
if let Some(username) = credentials.and_then(|credentials| credentials.username()) {
|
||||
if let Some(index_url) = maybe_index_url {
|
||||
debug!("Checking keyring for credentials for index URL {}@{}", username, index_url);
|
||||
keyring.fetch(index_url, Some(username)).await
|
||||
keyring.fetch(&DisplaySafeUrlRef::from(index_url), Some(username)).await
|
||||
} else {
|
||||
debug!("Checking keyring for credentials for full URL {}@{}", username, url);
|
||||
keyring.fetch(url, Some(username)).await
|
||||
keyring.fetch(&url, Some(username)).await
|
||||
}
|
||||
} else if matches!(auth_policy, AuthPolicy::Always) {
|
||||
if let Some(index_url) = maybe_index_url {
|
||||
debug!(
|
||||
"Checking keyring for credentials for index URL {index_url} without username due to `authenticate = always`"
|
||||
);
|
||||
keyring.fetch(index_url, None).await
|
||||
keyring.fetch(&DisplaySafeUrlRef::from(index_url), None).await
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -558,24 +564,17 @@ impl AuthMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
fn tracing_url(request: &Request, credentials: Option<&Credentials>) -> String {
|
||||
if !tracing::enabled!(tracing::Level::DEBUG) {
|
||||
return request.url().to_string();
|
||||
}
|
||||
|
||||
let mut url = request.url().clone();
|
||||
fn tracing_url(request: &Request, credentials: Option<&Credentials>) -> DisplaySafeUrl {
|
||||
let mut url = DisplaySafeUrl::from(request.url().clone());
|
||||
if let Some(creds) = credentials {
|
||||
if creds.password().is_some() {
|
||||
if let Some(username) = creds.username() {
|
||||
let _ = url.set_username(username);
|
||||
}
|
||||
let _ = url.set_password(Some("****"));
|
||||
// A username on its own might be a secret token.
|
||||
} else if creds.username().is_some() {
|
||||
let _ = url.set_username("****");
|
||||
if let Some(username) = creds.username() {
|
||||
let _ = url.set_username(username);
|
||||
}
|
||||
if let Some(password) = creds.password() {
|
||||
let _ = url.set_password(Some(password));
|
||||
}
|
||||
}
|
||||
url.to_string()
|
||||
url
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -1749,13 +1748,13 @@ mod tests {
|
|||
let base_url_2 = base_url.join("prefix_2")?;
|
||||
let indexes = Indexes::from_indexes(vec![
|
||||
Index {
|
||||
url: base_url_1.clone(),
|
||||
root_url: base_url_1.clone(),
|
||||
url: DisplaySafeUrl::from(base_url_1.clone()),
|
||||
root_url: DisplaySafeUrl::from(base_url_1.clone()),
|
||||
auth_policy: AuthPolicy::Auto,
|
||||
},
|
||||
Index {
|
||||
url: base_url_2.clone(),
|
||||
root_url: base_url_2.clone(),
|
||||
url: DisplaySafeUrl::from(base_url_2.clone()),
|
||||
root_url: DisplaySafeUrl::from(base_url_2.clone()),
|
||||
auth_policy: AuthPolicy::Auto,
|
||||
},
|
||||
]);
|
||||
|
@ -1857,8 +1856,8 @@ mod tests {
|
|||
let base_url = Url::parse(&server.uri())?;
|
||||
let index_url = base_url.join("prefix_1")?;
|
||||
let indexes = Indexes::from_indexes(vec![Index {
|
||||
url: index_url.clone(),
|
||||
root_url: index_url.clone(),
|
||||
url: DisplaySafeUrl::from(index_url.clone()),
|
||||
root_url: DisplaySafeUrl::from(index_url.clone()),
|
||||
auth_policy: AuthPolicy::Auto,
|
||||
}]);
|
||||
|
||||
|
@ -1912,7 +1911,7 @@ mod tests {
|
|||
}
|
||||
|
||||
fn indexes_for(url: &Url, policy: AuthPolicy) -> Indexes {
|
||||
let mut url = url.clone();
|
||||
let mut url = DisplaySafeUrl::from(url.clone());
|
||||
url.set_password(None).ok();
|
||||
url.set_username("").ok();
|
||||
Indexes::from_indexes(vec![Index {
|
||||
|
@ -2104,16 +2103,14 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[tracing_test::traced_test(level = "debug")]
|
||||
fn test_tracing_url() {
|
||||
// No credentials
|
||||
let req = create_request("https://pypi-proxy.fly.dev/basic-auth/simple");
|
||||
assert_eq!(
|
||||
tracing_url(&req, None),
|
||||
"https://pypi-proxy.fly.dev/basic-auth/simple"
|
||||
DisplaySafeUrl::parse("https://pypi-proxy.fly.dev/basic-auth/simple").unwrap()
|
||||
);
|
||||
|
||||
// Mask username if there is a username but no password
|
||||
let creds = Credentials::Basic {
|
||||
username: Username::new(Some(String::from("user"))),
|
||||
password: None,
|
||||
|
@ -2121,10 +2118,9 @@ mod tests {
|
|||
let req = create_request("https://pypi-proxy.fly.dev/basic-auth/simple");
|
||||
assert_eq!(
|
||||
tracing_url(&req, Some(&creds)),
|
||||
"https://****@pypi-proxy.fly.dev/basic-auth/simple"
|
||||
DisplaySafeUrl::parse("https://user@pypi-proxy.fly.dev/basic-auth/simple").unwrap()
|
||||
);
|
||||
|
||||
// Log username but mask password if a password is present
|
||||
let creds = Credentials::Basic {
|
||||
username: Username::new(Some(String::from("user"))),
|
||||
password: Some(Password::new(String::from("password"))),
|
||||
|
@ -2132,7 +2128,8 @@ mod tests {
|
|||
let req = create_request("https://pypi-proxy.fly.dev/basic-auth/simple");
|
||||
assert_eq!(
|
||||
tracing_url(&req, Some(&creds)),
|
||||
"https://user:****@pypi-proxy.fly.dev/basic-auth/simple"
|
||||
DisplaySafeUrl::parse("https://user:password@pypi-proxy.fly.dev/basic-auth/simple")
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,8 @@ doctest = false
|
|||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
uv-redacted = { workspace = true }
|
||||
|
||||
hex = { workspace = true }
|
||||
memchr = { workspace = true }
|
||||
percent-encoding = { workspace = true }
|
||||
|
|
|
@ -4,6 +4,7 @@ use std::hash::{Hash, Hasher};
|
|||
use std::ops::Deref;
|
||||
|
||||
use url::Url;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
use crate::cache_key::{CacheKey, CacheKeyHasher};
|
||||
|
||||
|
@ -16,10 +17,10 @@ use crate::cache_key::{CacheKey, CacheKeyHasher};
|
|||
/// string value of the `Url` it contains. This is intentional, because all fetching should still
|
||||
/// happen within the context of the original URL.
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||
pub struct CanonicalUrl(Url);
|
||||
pub struct CanonicalUrl(DisplaySafeUrl);
|
||||
|
||||
impl CanonicalUrl {
|
||||
pub fn new(url: &Url) -> Self {
|
||||
pub fn new(url: &DisplaySafeUrl) -> Self {
|
||||
let mut url = url.clone();
|
||||
|
||||
// If the URL cannot be a base, then it's not a valid URL anyway.
|
||||
|
@ -42,8 +43,8 @@ impl CanonicalUrl {
|
|||
// almost certainly not using the same case conversion rules that GitHub
|
||||
// does. (See issue #84)
|
||||
if url.host_str() == Some("github.com") {
|
||||
url.set_scheme(url.scheme().to_lowercase().as_str())
|
||||
.unwrap();
|
||||
let scheme = url.scheme().to_lowercase();
|
||||
url.set_scheme(&scheme).unwrap();
|
||||
let path = url.path().to_lowercase();
|
||||
url.set_path(&path);
|
||||
}
|
||||
|
@ -56,7 +57,8 @@ impl CanonicalUrl {
|
|||
.is_some_and(|ext| ext.eq_ignore_ascii_case("git"));
|
||||
if needs_chopping {
|
||||
let prefix = &prefix[..prefix.len() - 4];
|
||||
url.set_path(&format!("{prefix}@{suffix}"));
|
||||
let path = format!("{prefix}@{suffix}");
|
||||
url.set_path(&path);
|
||||
}
|
||||
} else {
|
||||
// Ex) `git+https://github.com/pypa/sample-namespace-packages.git`
|
||||
|
@ -97,7 +99,7 @@ impl CanonicalUrl {
|
|||
}
|
||||
|
||||
pub fn parse(url: &str) -> Result<Self, url::ParseError> {
|
||||
Ok(Self::new(&Url::parse(url)?))
|
||||
Ok(Self::new(&DisplaySafeUrl::parse(url)?))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,7 +119,7 @@ impl Hash for CanonicalUrl {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<CanonicalUrl> for Url {
|
||||
impl From<CanonicalUrl> for DisplaySafeUrl {
|
||||
fn from(value: CanonicalUrl) -> Self {
|
||||
value.0
|
||||
}
|
||||
|
@ -138,10 +140,10 @@ impl std::fmt::Display for CanonicalUrl {
|
|||
/// [`CanonicalUrl`] values, but the same [`RepositoryUrl`], since they map to the same
|
||||
/// resource.
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||
pub struct RepositoryUrl(Url);
|
||||
pub struct RepositoryUrl(DisplaySafeUrl);
|
||||
|
||||
impl RepositoryUrl {
|
||||
pub fn new(url: &Url) -> Self {
|
||||
pub fn new(url: &DisplaySafeUrl) -> Self {
|
||||
let mut url = CanonicalUrl::new(url).0;
|
||||
|
||||
// If a Git URL ends in a reference (like a branch, tag, or commit), remove it.
|
||||
|
@ -163,7 +165,7 @@ impl RepositoryUrl {
|
|||
}
|
||||
|
||||
pub fn parse(url: &str) -> Result<Self, url::ParseError> {
|
||||
Ok(Self::new(&Url::parse(url)?))
|
||||
Ok(Self::new(&DisplaySafeUrl::parse(url)?))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ uv-distribution-types = { workspace = true }
|
|||
uv-fs = { workspace = true, features = ["tokio"] }
|
||||
uv-normalize = { workspace = true }
|
||||
uv-pypi-types = { workspace = true }
|
||||
uv-redacted = { workspace = true }
|
||||
uv-static = { workspace = true }
|
||||
|
||||
clap = { workspace = true, features = ["derive", "env"], optional = true }
|
||||
|
@ -35,5 +36,4 @@ same-file = { workspace = true }
|
|||
serde = { workspace = true, features = ["derive"] }
|
||||
tempfile = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
url = { workspace = true }
|
||||
walkdir = { workspace = true }
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use url::Url;
|
||||
|
||||
use uv_cache_key::{CanonicalUrl, cache_digest};
|
||||
use uv_distribution_types::IndexUrl;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
/// Cache wheels and their metadata, both from remote wheels and built from source distributions.
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -11,16 +10,16 @@ pub enum WheelCache<'a> {
|
|||
/// Either PyPI or an alternative index, which we key by index URL.
|
||||
Index(&'a IndexUrl),
|
||||
/// A direct URL dependency, which we key by URL.
|
||||
Url(&'a Url),
|
||||
Url(&'a DisplaySafeUrl),
|
||||
/// A path dependency, which we key by URL.
|
||||
Path(&'a Url),
|
||||
Path(&'a DisplaySafeUrl),
|
||||
/// An editable dependency, which we key by URL.
|
||||
Editable(&'a Url),
|
||||
Editable(&'a DisplaySafeUrl),
|
||||
/// A Git dependency, which we key by URL and SHA.
|
||||
///
|
||||
/// Note that this variant only exists for source distributions; wheels can't be delivered
|
||||
/// through Git.
|
||||
Git(&'a Url, &'a str),
|
||||
Git(&'a DisplaySafeUrl, &'a str),
|
||||
}
|
||||
|
||||
impl WheelCache<'_> {
|
||||
|
@ -30,7 +29,7 @@ impl WheelCache<'_> {
|
|||
WheelCache::Index(IndexUrl::Pypi(_)) => WheelCacheKind::Pypi.root(),
|
||||
WheelCache::Index(url) => WheelCacheKind::Index
|
||||
.root()
|
||||
.join(cache_digest(&CanonicalUrl::new(url))),
|
||||
.join(cache_digest(&CanonicalUrl::new(url.url()))),
|
||||
WheelCache::Url(url) => WheelCacheKind::Url
|
||||
.root()
|
||||
.join(cache_digest(&CanonicalUrl::new(url))),
|
||||
|
|
|
@ -25,6 +25,7 @@ uv-normalize = { workspace = true }
|
|||
uv-pep508 = { workspace = true }
|
||||
uv-pypi-types = { workspace = true }
|
||||
uv-python = { workspace = true, features = ["clap", "schemars"]}
|
||||
uv-redacted = { workspace = true }
|
||||
uv-resolver = { workspace = true, features = ["clap"] }
|
||||
uv-settings = { workspace = true, features = ["schemars"] }
|
||||
uv-static = { workspace = true }
|
||||
|
|
|
@ -8,7 +8,6 @@ use clap::builder::Styles;
|
|||
use clap::builder::styling::{AnsiColor, Effects, Style};
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
|
||||
use url::Url;
|
||||
use uv_cache::CacheArgs;
|
||||
use uv_configuration::{
|
||||
ConfigSettingEntry, ExportFormat, IndexStrategy, KeyringProviderType, PackageNameSpecifier,
|
||||
|
@ -19,6 +18,7 @@ use uv_normalize::{ExtraName, GroupName, PackageName, PipGroupName};
|
|||
use uv_pep508::{MarkerTree, Requirement};
|
||||
use uv_pypi_types::VerbatimParsedUrl;
|
||||
use uv_python::{PythonDownloads, PythonPreference, PythonVersion};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_resolver::{AnnotationStyle, ExcludeNewer, ForkStrategy, PrereleaseMode, ResolutionMode};
|
||||
use uv_static::EnvVars;
|
||||
use uv_torch::TorchMode;
|
||||
|
@ -5897,7 +5897,7 @@ pub struct PublishArgs {
|
|||
///
|
||||
/// Defaults to PyPI's publish URL (<https://upload.pypi.org/legacy/>).
|
||||
#[arg(long, env = EnvVars::UV_PUBLISH_URL)]
|
||||
pub publish_url: Option<Url>,
|
||||
pub publish_url: Option<DisplaySafeUrl>,
|
||||
|
||||
/// Check an index URL for existing files to skip duplicate uploads.
|
||||
///
|
||||
|
|
|
@ -21,6 +21,7 @@ use uv_configuration::{KeyringProviderType, TrustedHost};
|
|||
use uv_fs::Simplified;
|
||||
use uv_pep508::MarkerEnvironment;
|
||||
use uv_platform_tags::Platform;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_static::EnvVars;
|
||||
use uv_version::version;
|
||||
use uv_warnings::warn_user_once;
|
||||
|
@ -407,7 +408,7 @@ enum Security {
|
|||
|
||||
impl BaseClient {
|
||||
/// Selects the appropriate client based on the host's trustworthiness.
|
||||
pub fn for_host(&self, url: &Url) -> &ClientWithMiddleware {
|
||||
pub fn for_host(&self, url: &DisplaySafeUrl) -> &ClientWithMiddleware {
|
||||
if self.disable_ssl(url) {
|
||||
&self.dangerous_client
|
||||
} else {
|
||||
|
@ -416,7 +417,7 @@ impl BaseClient {
|
|||
}
|
||||
|
||||
/// Returns `true` if the host is trusted to use the insecure client.
|
||||
pub fn disable_ssl(&self, url: &Url) -> bool {
|
||||
pub fn disable_ssl(&self, url: &DisplaySafeUrl) -> bool {
|
||||
self.allow_insecure_host
|
||||
.iter()
|
||||
.any(|allow_insecure_host| allow_insecure_host.matches(url))
|
||||
|
|
|
@ -12,6 +12,7 @@ use tracing::{Instrument, debug, info_span, instrument, trace, warn};
|
|||
|
||||
use uv_cache::{CacheEntry, Freshness};
|
||||
use uv_fs::write_atomic;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
use crate::BaseClient;
|
||||
use crate::base_client::is_extended_transient_error;
|
||||
|
@ -481,11 +482,11 @@ impl CachedClient {
|
|||
cached: DataWithCachePolicy,
|
||||
new_cache_policy_builder: CachePolicyBuilder,
|
||||
) -> Result<CachedResponse, Error> {
|
||||
let url = req.url().clone();
|
||||
let url = DisplaySafeUrl::from(req.url().clone());
|
||||
debug!("Sending revalidation request for: {url}");
|
||||
let response = self
|
||||
.0
|
||||
.for_host(req.url())
|
||||
.for_host(&url)
|
||||
.execute(req)
|
||||
.instrument(info_span!("revalidation_request", url = url.as_str()))
|
||||
.await
|
||||
|
@ -521,7 +522,7 @@ impl CachedClient {
|
|||
&self,
|
||||
req: Request,
|
||||
) -> Result<(Response, Option<Box<CachePolicy>>), Error> {
|
||||
let url = req.url().clone();
|
||||
let url = DisplaySafeUrl::from(req.url().clone());
|
||||
trace!("Sending fresh {} request for {}", req.method(), url);
|
||||
let cache_policy_builder = CachePolicyBuilder::new(&req);
|
||||
let response = self
|
||||
|
|
|
@ -3,11 +3,10 @@ use std::ops::Deref;
|
|||
|
||||
use async_http_range_reader::AsyncHttpRangeReaderError;
|
||||
use async_zip::error::ZipError;
|
||||
use url::Url;
|
||||
|
||||
use uv_distribution_filename::{WheelFilename, WheelFilenameError};
|
||||
use uv_normalize::PackageName;
|
||||
use uv_redacted::redacted_url;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
use crate::middleware::OfflineError;
|
||||
use crate::{FlatIndexError, html};
|
||||
|
@ -30,12 +29,12 @@ impl Error {
|
|||
}
|
||||
|
||||
/// Create a new error from a JSON parsing error.
|
||||
pub(crate) fn from_json_err(err: serde_json::Error, url: Url) -> Self {
|
||||
pub(crate) fn from_json_err(err: serde_json::Error, url: DisplaySafeUrl) -> Self {
|
||||
ErrorKind::BadJson { source: err, url }.into()
|
||||
}
|
||||
|
||||
/// Create a new error from an HTML parsing error.
|
||||
pub(crate) fn from_html_err(err: html::Error, url: Url) -> Self {
|
||||
pub(crate) fn from_html_err(err: html::Error, url: DisplaySafeUrl) -> Self {
|
||||
ErrorKind::BadHtml { source: err, url }.into()
|
||||
}
|
||||
|
||||
|
@ -160,10 +159,10 @@ pub enum ErrorKind {
|
|||
Flat(#[from] FlatIndexError),
|
||||
|
||||
#[error("Expected a file URL, but received: {0}")]
|
||||
NonFileUrl(Url),
|
||||
NonFileUrl(DisplaySafeUrl),
|
||||
|
||||
#[error("Expected an index URL, but received non-base URL: {0}")]
|
||||
CannotBeABase(Url),
|
||||
CannotBeABase(DisplaySafeUrl),
|
||||
|
||||
#[error("Failed to read metadata: `{0}`")]
|
||||
Metadata(String, #[source] uv_metadata::Error),
|
||||
|
@ -196,16 +195,22 @@ pub enum ErrorKind {
|
|||
|
||||
/// An error that happened while making a request or in a reqwest middleware.
|
||||
#[error("Failed to fetch: `{0}`")]
|
||||
WrappedReqwestError(Url, #[source] WrappedReqwestError),
|
||||
WrappedReqwestError(DisplaySafeUrl, #[source] WrappedReqwestError),
|
||||
|
||||
#[error("Received some unexpected JSON from {}", redacted_url(url))]
|
||||
BadJson { source: serde_json::Error, url: Url },
|
||||
#[error("Received some unexpected JSON from {}", url)]
|
||||
BadJson {
|
||||
source: serde_json::Error,
|
||||
url: DisplaySafeUrl,
|
||||
},
|
||||
|
||||
#[error("Received some unexpected HTML from {}", redacted_url(url))]
|
||||
BadHtml { source: html::Error, url: Url },
|
||||
#[error("Received some unexpected HTML from {}", url)]
|
||||
BadHtml {
|
||||
source: html::Error,
|
||||
url: DisplaySafeUrl,
|
||||
},
|
||||
|
||||
#[error("Failed to read zip with range requests: `{0}`")]
|
||||
AsyncHttpRangeReader(Url, #[source] AsyncHttpRangeReaderError),
|
||||
AsyncHttpRangeReader(DisplaySafeUrl, #[source] AsyncHttpRangeReaderError),
|
||||
|
||||
#[error("{0} is not a valid wheel filename")]
|
||||
WheelFilename(#[source] WheelFilenameError),
|
||||
|
@ -232,13 +237,13 @@ pub enum ErrorKind {
|
|||
Encode(#[source] rmp_serde::encode::Error),
|
||||
|
||||
#[error("Missing `Content-Type` header for {0}")]
|
||||
MissingContentType(Url),
|
||||
MissingContentType(DisplaySafeUrl),
|
||||
|
||||
#[error("Invalid `Content-Type` header for {0}")]
|
||||
InvalidContentTypeHeader(Url, #[source] http::header::ToStrError),
|
||||
InvalidContentTypeHeader(DisplaySafeUrl, #[source] http::header::ToStrError),
|
||||
|
||||
#[error("Unsupported `Content-Type` \"{1}\" for {0}. Expected JSON or HTML.")]
|
||||
UnsupportedMediaType(Url, String),
|
||||
UnsupportedMediaType(DisplaySafeUrl, String),
|
||||
|
||||
#[error("Reading from cache archive failed: {0}")]
|
||||
ArchiveRead(String),
|
||||
|
@ -253,11 +258,14 @@ pub enum ErrorKind {
|
|||
}
|
||||
|
||||
impl ErrorKind {
|
||||
pub(crate) fn from_reqwest(url: Url, error: reqwest::Error) -> Self {
|
||||
pub(crate) fn from_reqwest(url: DisplaySafeUrl, error: reqwest::Error) -> Self {
|
||||
Self::WrappedReqwestError(url, WrappedReqwestError::from(error))
|
||||
}
|
||||
|
||||
pub(crate) fn from_reqwest_middleware(url: Url, err: reqwest_middleware::Error) -> Self {
|
||||
pub(crate) fn from_reqwest_middleware(
|
||||
url: DisplaySafeUrl,
|
||||
err: reqwest_middleware::Error,
|
||||
) -> Self {
|
||||
if let reqwest_middleware::Error::Middleware(ref underlying) = err {
|
||||
if let Some(err) = underlying.downcast_ref::<OfflineError>() {
|
||||
return Self::Offline(err.url().to_string());
|
||||
|
|
|
@ -10,7 +10,7 @@ use uv_cache_key::cache_digest;
|
|||
use uv_distribution_filename::DistFilename;
|
||||
use uv_distribution_types::{File, FileLocation, IndexUrl, UrlString};
|
||||
use uv_pypi_types::HashDigests;
|
||||
use uv_redacted::redacted_url;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_small_str::SmallString;
|
||||
|
||||
use crate::cached_client::{CacheControl, CachedClientError};
|
||||
|
@ -20,13 +20,13 @@ use crate::{CachedClient, Connectivity, Error, ErrorKind, OwnedArchive};
|
|||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum FlatIndexError {
|
||||
#[error("Expected a file URL, but received: {0}")]
|
||||
NonFileUrl(Url),
|
||||
NonFileUrl(DisplaySafeUrl),
|
||||
|
||||
#[error("Failed to read `--find-links` directory: {0}")]
|
||||
FindLinksDirectory(PathBuf, #[source] FindLinksDirectoryError),
|
||||
|
||||
#[error("Failed to read `--find-links` URL: {0}")]
|
||||
FindLinksUrl(Url, #[source] Error),
|
||||
FindLinksUrl(DisplaySafeUrl, #[source] Error),
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
|
@ -159,7 +159,7 @@ impl<'a> FlatIndexClient<'a> {
|
|||
/// Read a flat remote index from a `--find-links` URL.
|
||||
async fn read_from_url(
|
||||
&self,
|
||||
url: &Url,
|
||||
url: &DisplaySafeUrl,
|
||||
flat_index: &IndexUrl,
|
||||
) -> Result<FlatIndexEntries, Error> {
|
||||
let cache_entry = self.cache.entry(
|
||||
|
@ -180,7 +180,7 @@ impl<'a> FlatIndexClient<'a> {
|
|||
.client
|
||||
.uncached()
|
||||
.for_host(url)
|
||||
.get(url.clone())
|
||||
.get(Url::from(url.clone()))
|
||||
.header("Accept-Encoding", "gzip")
|
||||
.header("Accept", "text/html")
|
||||
.build()
|
||||
|
@ -189,7 +189,7 @@ impl<'a> FlatIndexClient<'a> {
|
|||
async {
|
||||
// Use the response URL, rather than the request URL, as the base for relative URLs.
|
||||
// This ensures that we handle redirects and other URL transformations correctly.
|
||||
let url = response.url().clone();
|
||||
let url = DisplaySafeUrl::from(response.url().clone());
|
||||
|
||||
let text = response
|
||||
.text()
|
||||
|
@ -208,7 +208,7 @@ impl<'a> FlatIndexClient<'a> {
|
|||
Ok(file) => Some(file),
|
||||
Err(err) => {
|
||||
// Ignore files with unparsable version specifiers.
|
||||
warn!("Skipping file in {}: {err}", redacted_url(&url));
|
||||
warn!("Skipping file in {}: {err}", &url);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -294,7 +294,7 @@ impl<'a> FlatIndexClient<'a> {
|
|||
};
|
||||
|
||||
// SAFETY: The index path is itself constructed from a URL.
|
||||
let url = Url::from_file_path(entry.path()).unwrap();
|
||||
let url = DisplaySafeUrl::from_file_path(entry.path()).unwrap();
|
||||
|
||||
let file = File {
|
||||
dist_info_metadata: false,
|
||||
|
@ -303,7 +303,7 @@ impl<'a> FlatIndexClient<'a> {
|
|||
requires_python: None,
|
||||
size: None,
|
||||
upload_time_utc_ms: None,
|
||||
url: FileLocation::AbsoluteUrl(UrlString::from(&url)),
|
||||
url: FileLocation::AbsoluteUrl(UrlString::from(url)),
|
||||
yanked: None,
|
||||
};
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ use url::Url;
|
|||
use uv_pep440::VersionSpecifiers;
|
||||
use uv_pypi_types::{BaseUrl, CoreMetadata, File, Hashes, Yanked};
|
||||
use uv_pypi_types::{HashError, LenientVersionSpecifiers};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
/// A parsed structure from PyPI "HTML" index format for a single package.
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -27,7 +28,7 @@ impl SimpleHtml {
|
|||
// 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
|
||||
// appear before other tags with attribute values of URLs.
|
||||
let base = BaseUrl::from(
|
||||
let base = BaseUrl::from(DisplaySafeUrl::from(
|
||||
dom.nodes()
|
||||
.iter()
|
||||
.filter_map(|node| node.as_tag())
|
||||
|
@ -37,7 +38,7 @@ impl SimpleHtml {
|
|||
.transpose()?
|
||||
.flatten()
|
||||
.unwrap_or_else(|| url.clone()),
|
||||
);
|
||||
));
|
||||
|
||||
// Parse each `<a>` tag, to extract the filename, hash, and URL.
|
||||
let mut files: Vec<File> = dom
|
||||
|
@ -278,21 +279,7 @@ mod tests {
|
|||
insta::assert_debug_snapshot!(result, @r#"
|
||||
SimpleHtml {
|
||||
base: BaseUrl(
|
||||
Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"download.pytorch.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/whl/jinja2/",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
https://download.pytorch.org/whl/jinja2/,
|
||||
),
|
||||
files: [
|
||||
File {
|
||||
|
@ -335,21 +322,7 @@ mod tests {
|
|||
insta::assert_debug_snapshot!(result, @r#"
|
||||
SimpleHtml {
|
||||
base: BaseUrl(
|
||||
Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"download.pytorch.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/whl/jinja2/",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
https://download.pytorch.org/whl/jinja2/,
|
||||
),
|
||||
files: [
|
||||
File {
|
||||
|
@ -395,21 +368,7 @@ mod tests {
|
|||
insta::assert_debug_snapshot!(result, @r#"
|
||||
SimpleHtml {
|
||||
base: BaseUrl(
|
||||
Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"index.python.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
https://index.python.org/,
|
||||
),
|
||||
files: [
|
||||
File {
|
||||
|
@ -452,21 +411,7 @@ mod tests {
|
|||
insta::assert_debug_snapshot!(result, @r#"
|
||||
SimpleHtml {
|
||||
base: BaseUrl(
|
||||
Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"download.pytorch.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/whl/jinja2/",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
https://download.pytorch.org/whl/jinja2/,
|
||||
),
|
||||
files: [
|
||||
File {
|
||||
|
@ -509,21 +454,7 @@ mod tests {
|
|||
insta::assert_debug_snapshot!(result, @r#"
|
||||
SimpleHtml {
|
||||
base: BaseUrl(
|
||||
Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"download.pytorch.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/whl/jinja2/",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
https://download.pytorch.org/whl/jinja2/,
|
||||
),
|
||||
files: [
|
||||
File {
|
||||
|
@ -566,21 +497,7 @@ mod tests {
|
|||
insta::assert_debug_snapshot!(result, @r#"
|
||||
SimpleHtml {
|
||||
base: BaseUrl(
|
||||
Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"download.pytorch.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/whl/jinja2/",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
https://download.pytorch.org/whl/jinja2/,
|
||||
),
|
||||
files: [
|
||||
File {
|
||||
|
@ -621,21 +538,7 @@ mod tests {
|
|||
insta::assert_debug_snapshot!(result, @r#"
|
||||
SimpleHtml {
|
||||
base: BaseUrl(
|
||||
Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"download.pytorch.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/whl/jinja2/",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
https://download.pytorch.org/whl/jinja2/,
|
||||
),
|
||||
files: [
|
||||
File {
|
||||
|
@ -673,28 +576,14 @@ mod tests {
|
|||
";
|
||||
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
||||
let result = SimpleHtml::parse(text, &base).unwrap();
|
||||
insta::assert_debug_snapshot!(result, @r###"
|
||||
insta::assert_debug_snapshot!(result, @r"
|
||||
SimpleHtml {
|
||||
base: BaseUrl(
|
||||
Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"download.pytorch.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/whl/jinja2/",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
https://download.pytorch.org/whl/jinja2/,
|
||||
),
|
||||
files: [],
|
||||
}
|
||||
"###);
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -711,28 +600,14 @@ mod tests {
|
|||
"#;
|
||||
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
||||
let result = SimpleHtml::parse(text, &base).unwrap();
|
||||
insta::assert_debug_snapshot!(result, @r###"
|
||||
insta::assert_debug_snapshot!(result, @r"
|
||||
SimpleHtml {
|
||||
base: BaseUrl(
|
||||
Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"download.pytorch.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/whl/jinja2/",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
https://download.pytorch.org/whl/jinja2/,
|
||||
),
|
||||
files: [],
|
||||
}
|
||||
"###);
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -752,21 +627,7 @@ mod tests {
|
|||
insta::assert_debug_snapshot!(result, @r#"
|
||||
SimpleHtml {
|
||||
base: BaseUrl(
|
||||
Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"download.pytorch.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/whl/jinja2/",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
https://download.pytorch.org/whl/jinja2/,
|
||||
),
|
||||
files: [
|
||||
File {
|
||||
|
@ -807,21 +668,7 @@ mod tests {
|
|||
insta::assert_debug_snapshot!(result, @r#"
|
||||
SimpleHtml {
|
||||
base: BaseUrl(
|
||||
Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"download.pytorch.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/whl/jinja2/",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
https://download.pytorch.org/whl/jinja2/,
|
||||
),
|
||||
files: [
|
||||
File {
|
||||
|
@ -863,21 +710,7 @@ mod tests {
|
|||
Ok(
|
||||
SimpleHtml {
|
||||
base: BaseUrl(
|
||||
Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"download.pytorch.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/whl/jinja2/",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
https://download.pytorch.org/whl/jinja2/,
|
||||
),
|
||||
files: [
|
||||
File {
|
||||
|
@ -920,21 +753,7 @@ mod tests {
|
|||
Ok(
|
||||
SimpleHtml {
|
||||
base: BaseUrl(
|
||||
Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"download.pytorch.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/whl/jinja2/",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
https://download.pytorch.org/whl/jinja2/,
|
||||
),
|
||||
files: [
|
||||
File {
|
||||
|
@ -994,21 +813,7 @@ mod tests {
|
|||
insta::assert_debug_snapshot!(result, @r#"
|
||||
SimpleHtml {
|
||||
base: BaseUrl(
|
||||
Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"storage.googleapis.com",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/jax-releases/jax_cuda_releases.html",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
https://storage.googleapis.com/jax-releases/jax_cuda_releases.html,
|
||||
),
|
||||
files: [
|
||||
File {
|
||||
|
@ -1076,21 +881,7 @@ mod tests {
|
|||
insta::assert_debug_snapshot!(result, @r#"
|
||||
SimpleHtml {
|
||||
base: BaseUrl(
|
||||
Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"account.d.codeartifact.us-west-2.amazonaws.com",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/pypi/shared-packages-pypi/simple/flask/",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
https://account.d.codeartifact.us-west-2.amazonaws.com/pypi/shared-packages-pypi/simple/flask/,
|
||||
),
|
||||
files: [
|
||||
File {
|
||||
|
@ -1179,21 +970,7 @@ mod tests {
|
|||
insta::assert_debug_snapshot!(result, @r#"
|
||||
SimpleHtml {
|
||||
base: BaseUrl(
|
||||
Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"download.pytorch.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/whl/jinja2/",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
https://download.pytorch.org/whl/jinja2/,
|
||||
),
|
||||
files: [
|
||||
File {
|
||||
|
@ -1252,21 +1029,7 @@ mod tests {
|
|||
insta::assert_debug_snapshot!(result, @r#"
|
||||
SimpleHtml {
|
||||
base: BaseUrl(
|
||||
Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"account.d.codeartifact.us-west-2.amazonaws.com",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/pypi/shared-packages-pypi/simple/flask/",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
https://account.d.codeartifact.us-west-2.amazonaws.com/pypi/shared-packages-pypi/simple/flask/,
|
||||
),
|
||||
files: [
|
||||
File {
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
use http::Extensions;
|
||||
use std::fmt::Debug;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
use reqwest::{Request, Response};
|
||||
use reqwest_middleware::{Middleware, Next};
|
||||
use url::Url;
|
||||
|
||||
/// A custom error type for the offline middleware.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct OfflineError {
|
||||
url: Url,
|
||||
url: DisplaySafeUrl,
|
||||
}
|
||||
|
||||
impl OfflineError {
|
||||
/// Returns the URL that caused the error.
|
||||
pub(crate) fn url(&self) -> &Url {
|
||||
pub(crate) fn url(&self) -> &DisplaySafeUrl {
|
||||
&self.url
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ impl Middleware for OfflineMiddleware {
|
|||
) -> reqwest_middleware::Result<Response> {
|
||||
Err(reqwest_middleware::Error::Middleware(
|
||||
OfflineError {
|
||||
url: req.url().clone(),
|
||||
url: DisplaySafeUrl::from(req.url().clone()),
|
||||
}
|
||||
.into(),
|
||||
))
|
||||
|
|
|
@ -31,7 +31,7 @@ use uv_pep440::Version;
|
|||
use uv_pep508::MarkerEnvironment;
|
||||
use uv_platform_tags::Platform;
|
||||
use uv_pypi_types::{ResolutionMetadata, SimpleJson};
|
||||
use uv_redacted::redacted_url;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_small_str::SmallString;
|
||||
use uv_torch::TorchStrategy;
|
||||
|
||||
|
@ -251,12 +251,12 @@ impl RegistryClient {
|
|||
}
|
||||
|
||||
/// Return the [`BaseClient`] used by this client.
|
||||
pub fn uncached_client(&self, url: &Url) -> &ClientWithMiddleware {
|
||||
pub fn uncached_client(&self, url: &DisplaySafeUrl) -> &ClientWithMiddleware {
|
||||
self.client.uncached().for_host(url)
|
||||
}
|
||||
|
||||
/// Returns `true` if SSL verification is disabled for the given URL.
|
||||
pub fn disable_ssl(&self, url: &Url) -> bool {
|
||||
pub fn disable_ssl(&self, url: &DisplaySafeUrl) -> bool {
|
||||
self.client.uncached().disable_ssl(url)
|
||||
}
|
||||
|
||||
|
@ -485,10 +485,7 @@ impl RegistryClient {
|
|||
// ref https://github.com/servo/rust-url/issues/333
|
||||
.push("");
|
||||
|
||||
trace!(
|
||||
"Fetching metadata for {package_name} from {}",
|
||||
redacted_url(&url)
|
||||
);
|
||||
trace!("Fetching metadata for {package_name} from {url}");
|
||||
|
||||
let cache_entry = self.cache.entry(
|
||||
CacheBucket::Simple,
|
||||
|
@ -554,13 +551,13 @@ impl RegistryClient {
|
|||
async fn fetch_remote_index(
|
||||
&self,
|
||||
package_name: &PackageName,
|
||||
url: &Url,
|
||||
url: &DisplaySafeUrl,
|
||||
cache_entry: &CacheEntry,
|
||||
cache_control: CacheControl,
|
||||
) -> Result<OwnedArchive<SimpleMetadata>, Error> {
|
||||
let simple_request = self
|
||||
.uncached_client(url)
|
||||
.get(url.clone())
|
||||
.get(Url::from(url.clone()))
|
||||
.header("Accept-Encoding", "gzip")
|
||||
.header("Accept", MediaType::accepts())
|
||||
.build()
|
||||
|
@ -569,7 +566,7 @@ impl RegistryClient {
|
|||
async {
|
||||
// Use the response URL, rather than the request URL, as the base for relative URLs.
|
||||
// This ensures that we handle redirects and other URL transformations correctly.
|
||||
let url = response.url().clone();
|
||||
let url = DisplaySafeUrl::from(response.url().clone());
|
||||
|
||||
let content_type = response
|
||||
.headers()
|
||||
|
@ -629,7 +626,7 @@ impl RegistryClient {
|
|||
async fn fetch_local_index(
|
||||
&self,
|
||||
package_name: &PackageName,
|
||||
url: &Url,
|
||||
url: &DisplaySafeUrl,
|
||||
) -> Result<OwnedArchive<SimpleMetadata>, Error> {
|
||||
let path = url
|
||||
.to_file_path()
|
||||
|
@ -669,7 +666,7 @@ impl RegistryClient {
|
|||
/// A local file path.
|
||||
Path(PathBuf),
|
||||
/// A remote URL.
|
||||
Url(Url),
|
||||
Url(DisplaySafeUrl),
|
||||
}
|
||||
|
||||
let wheel = wheels.best_wheel();
|
||||
|
@ -770,14 +767,15 @@ impl RegistryClient {
|
|||
&self,
|
||||
index: &IndexUrl,
|
||||
file: &File,
|
||||
url: &Url,
|
||||
url: &DisplaySafeUrl,
|
||||
capabilities: &IndexCapabilities,
|
||||
) -> Result<ResolutionMetadata, Error> {
|
||||
// If the metadata file is available at its own url (PEP 658), download it from there.
|
||||
let filename = WheelFilename::from_str(&file.filename).map_err(ErrorKind::WheelFilename)?;
|
||||
if file.dist_info_metadata {
|
||||
let mut url = url.clone();
|
||||
url.set_path(&format!("{}.metadata", url.path()));
|
||||
let path = format!("{}.metadata", url.path());
|
||||
url.set_path(&path);
|
||||
|
||||
let cache_entry = self.cache.entry(
|
||||
CacheBucket::Wheels,
|
||||
|
@ -818,7 +816,7 @@ impl RegistryClient {
|
|||
};
|
||||
let req = self
|
||||
.uncached_client(&url)
|
||||
.get(url.clone())
|
||||
.get(Url::from(url.clone()))
|
||||
.build()
|
||||
.map_err(|err| ErrorKind::from_reqwest(url.clone(), err))?;
|
||||
Ok(self
|
||||
|
@ -844,7 +842,7 @@ impl RegistryClient {
|
|||
async fn wheel_metadata_no_pep658<'data>(
|
||||
&self,
|
||||
filename: &'data WheelFilename,
|
||||
url: &'data Url,
|
||||
url: &'data DisplaySafeUrl,
|
||||
index: Option<&'data IndexUrl>,
|
||||
cache_shard: WheelCache<'data>,
|
||||
capabilities: &'data IndexCapabilities,
|
||||
|
@ -874,7 +872,7 @@ impl RegistryClient {
|
|||
if index.is_none_or(|index| capabilities.supports_range_requests(index)) {
|
||||
let req = self
|
||||
.uncached_client(url)
|
||||
.head(url.clone())
|
||||
.head(Url::from(url.clone()))
|
||||
.header(
|
||||
"accept-encoding",
|
||||
http::HeaderValue::from_static("identity"),
|
||||
|
@ -895,7 +893,7 @@ impl RegistryClient {
|
|||
let mut reader = AsyncHttpRangeReader::from_head_response(
|
||||
self.uncached_client(url).clone(),
|
||||
response,
|
||||
url.clone(),
|
||||
Url::from(url.clone()),
|
||||
headers.clone(),
|
||||
)
|
||||
.await
|
||||
|
@ -949,7 +947,7 @@ impl RegistryClient {
|
|||
// Create a request to stream the file.
|
||||
let req = self
|
||||
.uncached_client(url)
|
||||
.get(url.clone())
|
||||
.get(Url::from(url.clone()))
|
||||
.header(
|
||||
// `reqwest` defaults to accepting compressed responses.
|
||||
// Specify identity encoding to get consistent .whl downloading
|
||||
|
@ -1141,7 +1139,11 @@ impl SimpleMetadata {
|
|||
}
|
||||
|
||||
/// Read the [`SimpleMetadata`] from an HTML index.
|
||||
fn from_html(text: &str, package_name: &PackageName, url: &Url) -> Result<Self, Error> {
|
||||
fn from_html(
|
||||
text: &str,
|
||||
package_name: &PackageName,
|
||||
url: &DisplaySafeUrl,
|
||||
) -> Result<Self, Error> {
|
||||
let SimpleHtml { base, files } =
|
||||
SimpleHtml::parse(text, url).map_err(|err| Error::from_html_err(err, url.clone()))?;
|
||||
|
||||
|
@ -1220,10 +1222,9 @@ impl Connectivity {
|
|||
mod tests {
|
||||
use std::str::FromStr;
|
||||
|
||||
use url::Url;
|
||||
|
||||
use uv_normalize::PackageName;
|
||||
use uv_pypi_types::{JoinRelativeError, SimpleJson};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
use crate::{SimpleMetadata, SimpleMetadatum, html::SimpleHtml};
|
||||
|
||||
|
@ -1263,7 +1264,7 @@ mod tests {
|
|||
}
|
||||
"#;
|
||||
let data: SimpleJson = serde_json::from_str(response).unwrap();
|
||||
let base = Url::parse("https://pypi.org/simple/pyflyby/").unwrap();
|
||||
let base = DisplaySafeUrl::parse("https://pypi.org/simple/pyflyby/").unwrap();
|
||||
let simple_metadata = SimpleMetadata::from_files(
|
||||
data.files,
|
||||
&PackageName::from_str("pyflyby").unwrap(),
|
||||
|
@ -1300,7 +1301,7 @@ mod tests {
|
|||
"#;
|
||||
|
||||
// Note the lack of a trailing `/` here is important for coverage of url-join behavior
|
||||
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();
|
||||
let SimpleHtml { base, files } = SimpleHtml::parse(text, &base).unwrap();
|
||||
|
||||
|
@ -1309,7 +1310,10 @@ mod tests {
|
|||
.iter()
|
||||
.map(|file| uv_pypi_types::base_url_join_relative(base.as_url().as_str(), &file.url))
|
||||
.collect::<Result<Vec<_>, JoinRelativeError>>()?;
|
||||
let urls = urls.iter().map(Url::as_str).collect::<Vec<_>>();
|
||||
let urls = urls
|
||||
.iter()
|
||||
.map(DisplaySafeUrl::to_string)
|
||||
.collect::<Vec<_>>();
|
||||
insta::assert_debug_snapshot!(urls, @r#"
|
||||
[
|
||||
"https://account.d.codeartifact.us-west-2.amazonaws.com/pypi/shared-packages-pypi/simple/0.1/Flask-0.1.tar.gz",
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use anyhow::Result;
|
||||
use url::Url;
|
||||
|
||||
use uv_cache::Cache;
|
||||
use uv_client::RegistryClientBuilder;
|
||||
use uv_distribution_filename::WheelFilename;
|
||||
use uv_distribution_types::{BuiltDist, DirectUrlBuiltDist, IndexCapabilities};
|
||||
use uv_pep508::VerbatimUrl;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
#[tokio::test]
|
||||
async fn remote_metadata_with_and_without_cache() -> Result<()> {
|
||||
|
@ -21,7 +21,7 @@ async fn remote_metadata_with_and_without_cache() -> Result<()> {
|
|||
let filename = WheelFilename::from_str(url.rsplit_once('/').unwrap().1)?;
|
||||
let dist = BuiltDist::DirectUrl(DirectUrlBuiltDist {
|
||||
filename,
|
||||
location: Box::new(Url::parse(url).unwrap()),
|
||||
location: Box::new(DisplaySafeUrl::parse(url).unwrap()),
|
||||
url: VerbatimUrl::from_str(url).unwrap(),
|
||||
});
|
||||
let capabilities = IndexCapabilities::default();
|
||||
|
|
|
@ -16,6 +16,7 @@ use uv_client::LineHaul;
|
|||
use uv_client::RegistryClientBuilder;
|
||||
use uv_pep508::{MarkerEnvironment, MarkerEnvironmentBuilder};
|
||||
use uv_platform_tags::{Arch, Os, Platform};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_version::version;
|
||||
|
||||
#[tokio::test]
|
||||
|
@ -54,12 +55,12 @@ async fn test_user_agent_has_version() -> Result<()> {
|
|||
let client = RegistryClientBuilder::new(cache).build();
|
||||
|
||||
// Send request to our dummy server
|
||||
let url = Url::from_str(&format!("http://{addr}"))?;
|
||||
let url = DisplaySafeUrl::from_str(&format!("http://{addr}"))?;
|
||||
let res = client
|
||||
.cached_client()
|
||||
.uncached()
|
||||
.for_host(&url)
|
||||
.get(url)
|
||||
.get(Url::from(url))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
|
@ -151,12 +152,12 @@ async fn test_user_agent_has_linehaul() -> Result<()> {
|
|||
let client = builder.build();
|
||||
|
||||
// Send request to our dummy server
|
||||
let url = Url::from_str(&format!("http://{addr}"))?;
|
||||
let url = DisplaySafeUrl::from_str(&format!("http://{addr}"))?;
|
||||
let res = client
|
||||
.cached_client()
|
||||
.uncached()
|
||||
.for_host(&url)
|
||||
.get(url)
|
||||
.get(Url::from(url))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ uv-pep440 = { workspace = true }
|
|||
uv-pep508 = { workspace = true }
|
||||
uv-platform-tags = { workspace = true }
|
||||
uv-pypi-types = { workspace = true }
|
||||
uv-redacted = { workspace = true }
|
||||
uv-small-str = { workspace = true }
|
||||
|
||||
arcstr = { workspace = true }
|
||||
|
@ -50,3 +51,6 @@ version-ranges = { workspace = true }
|
|||
|
||||
[dev-dependencies]
|
||||
toml = { workspace = true }
|
||||
|
||||
[features]
|
||||
schemars = ["dep:schemars", "uv-redacted/schemars"]
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
use std::borrow::Cow;
|
||||
use std::path::Path;
|
||||
|
||||
use url::Url;
|
||||
use uv_distribution_filename::SourceDistExtension;
|
||||
use uv_git_types::GitUrl;
|
||||
use uv_pep440::{Version, VersionSpecifiers};
|
||||
use uv_pep508::VerbatimUrl;
|
||||
|
||||
use uv_normalize::PackageName;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
use crate::{DirectorySourceDist, GitSourceDist, Name, PathSourceDist, SourceDist};
|
||||
|
||||
|
@ -102,8 +102,8 @@ pub enum SourceUrl<'a> {
|
|||
}
|
||||
|
||||
impl SourceUrl<'_> {
|
||||
/// Return the [`Url`] of the source.
|
||||
pub fn url(&self) -> &Url {
|
||||
/// Return the [`DisplaySafeUrl`] of the source.
|
||||
pub fn url(&self) -> &DisplaySafeUrl {
|
||||
match self {
|
||||
Self::Direct(dist) => dist.url,
|
||||
Self::Git(dist) => dist.url,
|
||||
|
@ -147,7 +147,7 @@ impl std::fmt::Display for SourceUrl<'_> {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DirectSourceUrl<'a> {
|
||||
pub url: &'a Url,
|
||||
pub url: &'a DisplaySafeUrl,
|
||||
pub subdirectory: Option<&'a Path>,
|
||||
pub ext: SourceDistExtension,
|
||||
}
|
||||
|
@ -185,7 +185,7 @@ impl<'a> From<&'a GitSourceDist> for GitSourceUrl<'a> {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PathSourceUrl<'a> {
|
||||
pub url: &'a Url,
|
||||
pub url: &'a DisplaySafeUrl,
|
||||
pub path: Cow<'a, Path>,
|
||||
pub ext: SourceDistExtension,
|
||||
}
|
||||
|
@ -208,7 +208,7 @@ impl<'a> From<&'a PathSourceDist> for PathSourceUrl<'a> {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DirectorySourceUrl<'a> {
|
||||
pub url: &'a Url,
|
||||
pub url: &'a DisplaySafeUrl,
|
||||
pub install_path: Cow<'a, Path>,
|
||||
pub editable: bool,
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use url::Url;
|
||||
|
||||
use uv_normalize::PackageName;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Error {
|
||||
|
@ -17,7 +16,7 @@ pub enum Error {
|
|||
MissingPathSegments(String),
|
||||
|
||||
#[error("Distribution not found at: {0}")]
|
||||
NotFound(Url),
|
||||
NotFound(DisplaySafeUrl),
|
||||
|
||||
#[error("Requested package name `{0}` does not match `{1}` in the distribution filename: {2}")]
|
||||
PackageNameMismatch(PackageName, PackageName, String),
|
||||
|
|
|
@ -3,11 +3,11 @@ use std::str::FromStr;
|
|||
|
||||
use jiff::Timestamp;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
use uv_pep440::{VersionSpecifiers, VersionSpecifiersParseError};
|
||||
use uv_pep508::split_scheme;
|
||||
use uv_pypi_types::{CoreMetadata, HashDigests, Yanked};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_small_str::SmallString;
|
||||
|
||||
/// Error converting [`uv_pypi_types::File`] to [`distribution_type::File`].
|
||||
|
@ -87,13 +87,14 @@ impl FileLocation {
|
|||
/// This returns an error if any of the URL parsing fails, or if, for
|
||||
/// example, the location is a path and the path isn't valid UTF-8.
|
||||
/// (Because URLs must be valid UTF-8.)
|
||||
pub fn to_url(&self) -> Result<Url, ToUrlError> {
|
||||
pub fn to_url(&self) -> Result<DisplaySafeUrl, ToUrlError> {
|
||||
match *self {
|
||||
FileLocation::RelativeUrl(ref base, ref path) => {
|
||||
let base_url = Url::parse(base).map_err(|err| ToUrlError::InvalidBase {
|
||||
base: base.to_string(),
|
||||
err,
|
||||
})?;
|
||||
let base_url =
|
||||
DisplaySafeUrl::parse(base).map_err(|err| ToUrlError::InvalidBase {
|
||||
base: base.to_string(),
|
||||
err,
|
||||
})?;
|
||||
let joined = base_url.join(path).map_err(|err| ToUrlError::InvalidJoin {
|
||||
base: base.to_string(),
|
||||
path: path.to_string(),
|
||||
|
@ -142,9 +143,9 @@ impl UrlString {
|
|||
Self(url)
|
||||
}
|
||||
|
||||
/// Converts a [`UrlString`] to a [`Url`].
|
||||
pub fn to_url(&self) -> Result<Url, ToUrlError> {
|
||||
Url::from_str(&self.0).map_err(|err| ToUrlError::InvalidAbsolute {
|
||||
/// Converts a [`UrlString`] to a [`DisplaySafeUrl`].
|
||||
pub fn to_url(&self) -> Result<DisplaySafeUrl, ToUrlError> {
|
||||
DisplaySafeUrl::from_str(&self.0).map_err(|err| ToUrlError::InvalidAbsolute {
|
||||
absolute: self.0.to_string(),
|
||||
err,
|
||||
})
|
||||
|
@ -178,14 +179,14 @@ impl AsRef<str> for UrlString {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Url> for UrlString {
|
||||
fn from(value: Url) -> Self {
|
||||
impl From<DisplaySafeUrl> for UrlString {
|
||||
fn from(value: DisplaySafeUrl) -> Self {
|
||||
Self(value.as_str().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Url> for UrlString {
|
||||
fn from(value: &Url) -> Self {
|
||||
impl From<&DisplaySafeUrl> for UrlString {
|
||||
fn from(value: &DisplaySafeUrl) -> Self {
|
||||
Self(value.as_str().into())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
use std::fmt::{Display, Formatter};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use url::Url;
|
||||
use uv_cache_key::{CanonicalUrl, RepositoryUrl};
|
||||
|
||||
use uv_normalize::PackageName;
|
||||
use uv_pep440::Version;
|
||||
use uv_pypi_types::HashDigest;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
/// A unique identifier for a package. A package can either be identified by a name (e.g., `black`)
|
||||
/// or a URL (e.g., `git+https://github.com/psf/black`).
|
||||
|
@ -25,7 +25,7 @@ impl PackageId {
|
|||
}
|
||||
|
||||
/// Create a new [`PackageId`] from a URL.
|
||||
pub fn from_url(url: &Url) -> Self {
|
||||
pub fn from_url(url: &DisplaySafeUrl) -> Self {
|
||||
Self::Url(CanonicalUrl::new(url))
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ impl VersionId {
|
|||
}
|
||||
|
||||
/// Create a new [`VersionId`] from a URL.
|
||||
pub fn from_url(url: &Url) -> Self {
|
||||
pub fn from_url(url: &DisplaySafeUrl) -> Self {
|
||||
Self::Url(CanonicalUrl::new(url))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,9 @@ use std::str::FromStr;
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
|
||||
use uv_auth::{AuthPolicy, Credentials};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
use crate::index_name::{IndexName, IndexNameError};
|
||||
use crate::origin::Origin;
|
||||
|
@ -82,7 +82,7 @@ pub struct Index {
|
|||
/// url = "https://pypi.org/simple"
|
||||
/// publish-url = "https://upload.pypi.org/legacy/"
|
||||
/// ```
|
||||
pub publish_url: Option<Url>,
|
||||
pub publish_url: Option<DisplaySafeUrl>,
|
||||
/// When uv should use authentication for requests to the index.
|
||||
///
|
||||
/// ```toml
|
||||
|
@ -193,7 +193,7 @@ impl Index {
|
|||
}
|
||||
|
||||
/// Return the raw [`Url`] of the index.
|
||||
pub fn raw_url(&self) -> &Url {
|
||||
pub fn raw_url(&self) -> &DisplaySafeUrl {
|
||||
self.url.url()
|
||||
}
|
||||
|
||||
|
@ -201,7 +201,7 @@ impl Index {
|
|||
///
|
||||
/// For indexes with a `/simple` endpoint, this is simply the URL with the final segment
|
||||
/// removed. This is useful, e.g., for credential propagation to other endpoints on the index.
|
||||
pub fn root_url(&self) -> Option<Url> {
|
||||
pub fn root_url(&self) -> Option<DisplaySafeUrl> {
|
||||
self.url.root()
|
||||
}
|
||||
|
||||
|
|
|
@ -11,10 +11,12 @@ use thiserror::Error;
|
|||
use url::{ParseError, Url};
|
||||
|
||||
use uv_pep508::{Scheme, VerbatimUrl, VerbatimUrlError, split_scheme};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
use crate::{Index, IndexStatusCodeStrategy, Verbatim};
|
||||
|
||||
static PYPI_URL: LazyLock<Url> = LazyLock::new(|| Url::parse("https://pypi.org/simple").unwrap());
|
||||
static PYPI_URL: LazyLock<DisplaySafeUrl> =
|
||||
LazyLock::new(|| DisplaySafeUrl::parse("https://pypi.org/simple").unwrap());
|
||||
|
||||
static DEFAULT_INDEX: LazyLock<Index> = LazyLock::new(|| {
|
||||
Index::from_index_url(IndexUrl::Pypi(Arc::new(VerbatimUrl::from_url(
|
||||
|
@ -69,7 +71,7 @@ impl IndexUrl {
|
|||
///
|
||||
/// For indexes with a `/simple` endpoint, this is simply the URL with the final segment
|
||||
/// removed. This is useful, e.g., for credential propagation to other endpoints on the index.
|
||||
pub fn root(&self) -> Option<Url> {
|
||||
pub fn root(&self) -> Option<DisplaySafeUrl> {
|
||||
let mut segments = self.url().path_segments()?;
|
||||
let last = match segments.next_back()? {
|
||||
// If the last segment is empty due to a trailing `/`, skip it (as in `pop_if_empty`)
|
||||
|
@ -108,7 +110,7 @@ impl schemars::JsonSchema for IndexUrl {
|
|||
|
||||
impl IndexUrl {
|
||||
/// Return the raw URL for the index.
|
||||
pub fn url(&self) -> &Url {
|
||||
pub fn url(&self) -> &DisplaySafeUrl {
|
||||
match self {
|
||||
Self::Pypi(url) => url.raw(),
|
||||
Self::Url(url) => url.raw(),
|
||||
|
@ -116,8 +118,8 @@ impl IndexUrl {
|
|||
}
|
||||
}
|
||||
|
||||
/// Convert the index URL into a [`Url`].
|
||||
pub fn into_url(self) -> Url {
|
||||
/// Convert the index URL into a [`DisplaySafeUrl`].
|
||||
pub fn into_url(self) -> DisplaySafeUrl {
|
||||
match self {
|
||||
Self::Pypi(url) => url.to_url(),
|
||||
Self::Url(url) => url.to_url(),
|
||||
|
@ -126,7 +128,7 @@ impl IndexUrl {
|
|||
}
|
||||
|
||||
/// Return the redacted URL for the index, omitting any sensitive credentials.
|
||||
pub fn redacted(&self) -> Cow<'_, Url> {
|
||||
pub fn without_credentials(&self) -> Cow<'_, DisplaySafeUrl> {
|
||||
let url = self.url();
|
||||
if url.username().is_empty() && url.password().is_none() {
|
||||
Cow::Borrowed(url)
|
||||
|
@ -222,7 +224,7 @@ impl From<VerbatimUrl> for IndexUrl {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<IndexUrl> for Url {
|
||||
impl From<IndexUrl> for DisplaySafeUrl {
|
||||
fn from(index: IndexUrl) -> Self {
|
||||
match index {
|
||||
IndexUrl::Pypi(url) => url.to_url(),
|
||||
|
|
|
@ -14,6 +14,7 @@ use uv_fs::Simplified;
|
|||
use uv_normalize::PackageName;
|
||||
use uv_pep440::Version;
|
||||
use uv_pypi_types::{DirectUrl, MetadataError};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
use crate::{DistributionMetadata, InstalledMetadata, InstalledVersion, Name, VersionOrUrlRef};
|
||||
|
||||
|
@ -86,7 +87,7 @@ pub struct InstalledDirectUrlDist {
|
|||
pub name: PackageName,
|
||||
pub version: Version,
|
||||
pub direct_url: Box<DirectUrl>,
|
||||
pub url: Url,
|
||||
pub url: DisplaySafeUrl,
|
||||
pub editable: bool,
|
||||
pub path: Box<Path>,
|
||||
pub cache_info: Option<CacheInfo>,
|
||||
|
@ -112,7 +113,7 @@ pub struct InstalledLegacyEditable {
|
|||
pub version: Version,
|
||||
pub egg_link: Box<Path>,
|
||||
pub target: Box<Path>,
|
||||
pub target_url: Url,
|
||||
pub target_url: DisplaySafeUrl,
|
||||
pub egg_info: Box<Path>,
|
||||
}
|
||||
|
||||
|
@ -144,7 +145,7 @@ impl InstalledDist {
|
|||
version,
|
||||
editable: matches!(&direct_url, DirectUrl::LocalDirectory { dir_info, .. } if dir_info.editable == Some(true)),
|
||||
direct_url: Box::new(direct_url),
|
||||
url,
|
||||
url: DisplaySafeUrl::from(url),
|
||||
path: path.to_path_buf().into_boxed_path(),
|
||||
cache_info,
|
||||
}))),
|
||||
|
@ -272,7 +273,7 @@ impl InstalledDist {
|
|||
version: Version::from_str(&egg_metadata.version)?,
|
||||
egg_link: path.to_path_buf().into_boxed_path(),
|
||||
target: target.into_boxed_path(),
|
||||
target_url: url,
|
||||
target_url: DisplaySafeUrl::from(url),
|
||||
egg_info: egg_info.into_boxed_path(),
|
||||
})));
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ use uv_pep508::{Pep508Url, VerbatimUrl};
|
|||
use uv_pypi_types::{
|
||||
ParsedArchiveUrl, ParsedDirectoryUrl, ParsedGitUrl, ParsedPathUrl, ParsedUrl, VerbatimParsedUrl,
|
||||
};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
pub use crate::annotation::*;
|
||||
pub use crate::any::*;
|
||||
|
@ -147,12 +148,12 @@ pub enum InstalledVersion<'a> {
|
|||
Version(&'a Version),
|
||||
/// A URL, used to identify a distribution at an arbitrary location, along with the version
|
||||
/// specifier to which it resolved.
|
||||
Url(&'a Url, &'a Version),
|
||||
Url(&'a DisplaySafeUrl, &'a Version),
|
||||
}
|
||||
|
||||
impl InstalledVersion<'_> {
|
||||
/// If it is a URL, return its value.
|
||||
pub fn url(&self) -> Option<&Url> {
|
||||
pub fn url(&self) -> Option<&DisplaySafeUrl> {
|
||||
match self {
|
||||
InstalledVersion::Version(_) => None,
|
||||
InstalledVersion::Url(url, _) => Some(url),
|
||||
|
@ -258,7 +259,7 @@ pub struct DirectUrlBuiltDist {
|
|||
/// `https://example.org/packages/flask-3.0.0-py3-none-any.whl`
|
||||
pub filename: WheelFilename,
|
||||
/// The URL without the subdirectory fragment.
|
||||
pub location: Box<Url>,
|
||||
pub location: Box<DisplaySafeUrl>,
|
||||
/// The URL as it was provided by the user.
|
||||
pub url: VerbatimUrl,
|
||||
}
|
||||
|
@ -299,7 +300,7 @@ pub struct DirectUrlSourceDist {
|
|||
/// like using e.g. `foo @ https://github.com/org/repo/archive/master.zip`
|
||||
pub name: PackageName,
|
||||
/// The URL without the subdirectory fragment.
|
||||
pub location: Box<Url>,
|
||||
pub location: Box<DisplaySafeUrl>,
|
||||
/// The subdirectory within the archive in which the source distribution is located.
|
||||
pub subdirectory: Option<Box<Path>>,
|
||||
/// The file extension, e.g. `tar.gz`, `zip`, etc.
|
||||
|
@ -353,7 +354,7 @@ impl Dist {
|
|||
pub fn from_http_url(
|
||||
name: PackageName,
|
||||
url: VerbatimUrl,
|
||||
location: Url,
|
||||
location: DisplaySafeUrl,
|
||||
subdirectory: Option<Box<Path>>,
|
||||
ext: DistExtension,
|
||||
) -> Result<Dist, Error> {
|
||||
|
@ -1168,7 +1169,7 @@ impl RemoteSource for Dist {
|
|||
}
|
||||
}
|
||||
|
||||
impl Identifier for Url {
|
||||
impl Identifier for DisplaySafeUrl {
|
||||
fn distribution_id(&self) -> DistributionId {
|
||||
DistributionId::Url(uv_cache_key::CanonicalUrl::new(self))
|
||||
}
|
||||
|
@ -1461,7 +1462,7 @@ impl Identifier for BuildableSource<'_> {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{BuiltDist, Dist, RemoteSource, SourceDist, UrlString};
|
||||
use url::Url;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
/// Ensure that we don't accidentally grow the `Dist` sizes.
|
||||
#[test]
|
||||
|
@ -1485,7 +1486,7 @@ mod test {
|
|||
"https://example.com/foo-0.1.0.tar.gz?query=1/2#fragment",
|
||||
"https://example.com/foo-0.1.0.tar.gz?query=1/2#fragment/3",
|
||||
] {
|
||||
let url = Url::parse(url).unwrap();
|
||||
let url = DisplaySafeUrl::parse(url).unwrap();
|
||||
assert_eq!(url.filename().unwrap(), "foo-0.1.0.tar.gz", "{url}");
|
||||
let url = UrlString::from(url.clone());
|
||||
assert_eq!(url.filename().unwrap(), "foo-0.1.0.tar.gz", "{url}");
|
||||
|
|
|
@ -4,7 +4,6 @@ use std::path::Path;
|
|||
use std::str::FromStr;
|
||||
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
|
||||
use uv_distribution_filename::DistExtension;
|
||||
use uv_fs::{CWD, PortablePath, PortablePathBuf, relative_to};
|
||||
|
@ -14,6 +13,7 @@ use uv_pep440::VersionSpecifiers;
|
|||
use uv_pep508::{
|
||||
MarkerEnvironment, MarkerTree, RequirementOrigin, VerbatimUrl, VersionOrUrl, marker,
|
||||
};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
use crate::{IndexMetadata, IndexUrl};
|
||||
|
||||
|
@ -391,7 +391,7 @@ pub enum RequirementSource {
|
|||
/// e.g.`foo @ https://example.org/foo-1.0.zip`.
|
||||
Url {
|
||||
/// The remote location of the archive file, without subdirectory fragment.
|
||||
location: Url,
|
||||
location: DisplaySafeUrl,
|
||||
/// For source distributions, the path to the distribution if it is not in the archive
|
||||
/// root.
|
||||
subdirectory: Option<Box<Path>>,
|
||||
|
@ -682,7 +682,7 @@ enum RequirementSourceWire {
|
|||
Git { git: String },
|
||||
/// Ex) `source = { url = "<https://example.org/foo-1.0.zip>" }`
|
||||
Direct {
|
||||
url: Url,
|
||||
url: DisplaySafeUrl,
|
||||
subdirectory: Option<PortablePathBuf>,
|
||||
},
|
||||
/// Ex) `source = { path = "/home/ferris/iniconfig-2.0.0-py3-none-any.whl" }`
|
||||
|
@ -697,7 +697,7 @@ enum RequirementSourceWire {
|
|||
Registry {
|
||||
#[serde(skip_serializing_if = "VersionSpecifiers::is_empty", default)]
|
||||
specifier: VersionSpecifiers,
|
||||
index: Option<Url>,
|
||||
index: Option<DisplaySafeUrl>,
|
||||
conflict: Option<ConflictItem>,
|
||||
},
|
||||
}
|
||||
|
@ -711,7 +711,7 @@ impl From<RequirementSource> for RequirementSourceWire {
|
|||
conflict,
|
||||
} => {
|
||||
let index = index.map(|index| index.url.into_url()).map(|mut index| {
|
||||
redact_credentials(&mut index);
|
||||
index.remove_credentials();
|
||||
index
|
||||
});
|
||||
Self::Registry {
|
||||
|
@ -736,8 +736,8 @@ impl From<RequirementSource> for RequirementSourceWire {
|
|||
} => {
|
||||
let mut url = git.repository().clone();
|
||||
|
||||
// Redact the credentials.
|
||||
redact_credentials(&mut url);
|
||||
// Remove the credentials.
|
||||
url.remove_credentials();
|
||||
|
||||
// Clear out any existing state.
|
||||
url.set_fragment(None);
|
||||
|
@ -826,7 +826,7 @@ impl TryFrom<RequirementSourceWire> for RequirementSource {
|
|||
conflict,
|
||||
}),
|
||||
RequirementSourceWire::Git { git } => {
|
||||
let mut repository = Url::parse(&git)?;
|
||||
let mut repository = DisplaySafeUrl::parse(&git)?;
|
||||
|
||||
let mut reference = GitReference::DefaultBranch;
|
||||
let mut subdirectory: Option<PortablePathBuf> = None;
|
||||
|
@ -848,13 +848,14 @@ impl TryFrom<RequirementSourceWire> for RequirementSource {
|
|||
repository.set_fragment(None);
|
||||
repository.set_query(None);
|
||||
|
||||
// Redact the credentials.
|
||||
redact_credentials(&mut repository);
|
||||
// Remove the credentials.
|
||||
repository.remove_credentials();
|
||||
|
||||
// Create a PEP 508-compatible URL.
|
||||
let mut url = Url::parse(&format!("git+{repository}"))?;
|
||||
let mut url = DisplaySafeUrl::parse(&format!("git+{repository}"))?;
|
||||
if let Some(rev) = reference.as_str() {
|
||||
url.set_path(&format!("{}@{}", url.path(), rev));
|
||||
let path = format!("{}@{}", url.path(), rev);
|
||||
url.set_path(&path);
|
||||
}
|
||||
if let Some(subdirectory) = subdirectory.as_ref() {
|
||||
url.set_fragment(Some(&format!("subdirectory={subdirectory}")));
|
||||
|
@ -940,18 +941,6 @@ impl TryFrom<RequirementSourceWire> for RequirementSource {
|
|||
}
|
||||
}
|
||||
|
||||
/// Remove the credentials from a URL, allowing the generic `git` username (without a password)
|
||||
/// in SSH URLs, as in, `ssh://git@github.com/...`.
|
||||
pub fn redact_credentials(url: &mut Url) {
|
||||
// For URLs that use the `git` convention (i.e., `ssh://git@github.com/...`), avoid dropping the
|
||||
// username.
|
||||
if url.scheme() == "ssh" && url.username() == "git" && url.password().is_none() {
|
||||
return;
|
||||
}
|
||||
let _ = url.set_password(None);
|
||||
let _ = url.set_username("");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::PathBuf;
|
||||
|
|
|
@ -33,6 +33,7 @@ uv-pep440 = { workspace = true }
|
|||
uv-pep508 = { workspace = true }
|
||||
uv-platform-tags = { workspace = true }
|
||||
uv-pypi-types = { workspace = true }
|
||||
uv-redacted = { workspace = true }
|
||||
uv-types = { workspace = true }
|
||||
uv-workspace = { workspace = true }
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ use uv_extract::hash::Hasher;
|
|||
use uv_fs::write_atomic;
|
||||
use uv_platform_tags::Tags;
|
||||
use uv_pypi_types::{HashDigest, HashDigests};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_types::{BuildContext, BuildStack};
|
||||
|
||||
use crate::archive::Archive;
|
||||
|
@ -529,7 +530,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
|
|||
/// Stream a wheel from a URL, unzipping it into the cache as it's downloaded.
|
||||
async fn stream_wheel(
|
||||
&self,
|
||||
url: Url,
|
||||
url: DisplaySafeUrl,
|
||||
filename: &WheelFilename,
|
||||
size: Option<u64>,
|
||||
wheel_entry: &CacheEntry,
|
||||
|
@ -666,7 +667,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
|
|||
/// Download a wheel from a URL, then unzip it into the cache.
|
||||
async fn download_wheel(
|
||||
&self,
|
||||
url: Url,
|
||||
url: DisplaySafeUrl,
|
||||
filename: &WheelFilename,
|
||||
size: Option<u64>,
|
||||
wheel_entry: &CacheEntry,
|
||||
|
@ -980,11 +981,11 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
|
|||
}
|
||||
|
||||
/// Returns a GET [`reqwest::Request`] for the given URL.
|
||||
fn request(&self, url: Url) -> Result<reqwest::Request, reqwest::Error> {
|
||||
fn request(&self, url: DisplaySafeUrl) -> Result<reqwest::Request, reqwest::Error> {
|
||||
self.client
|
||||
.unmanaged
|
||||
.uncached_client(&url)
|
||||
.get(url)
|
||||
.get(Url::from(url))
|
||||
.header(
|
||||
// `reqwest` defaults to accepting compressed responses.
|
||||
// Specify identity encoding to get consistent .whl downloading
|
||||
|
|
|
@ -2,7 +2,6 @@ use std::path::PathBuf;
|
|||
|
||||
use owo_colors::OwoColorize;
|
||||
use tokio::task::JoinError;
|
||||
use url::Url;
|
||||
use zip::result::ZipError;
|
||||
|
||||
use crate::metadata::MetadataError;
|
||||
|
@ -13,6 +12,7 @@ use uv_fs::Simplified;
|
|||
use uv_normalize::PackageName;
|
||||
use uv_pep440::{Version, VersionSpecifiers};
|
||||
use uv_pypi_types::{HashAlgorithm, HashDigest};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_types::AnyErrorBuild;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
|
@ -28,7 +28,7 @@ pub enum Error {
|
|||
#[error(transparent)]
|
||||
JoinRelativeUrl(#[from] uv_pypi_types::JoinRelativeError),
|
||||
#[error("Expected a file URL, but received: {0}")]
|
||||
NonFileUrl(Url),
|
||||
NonFileUrl(DisplaySafeUrl),
|
||||
#[error(transparent)]
|
||||
Git(#[from] uv_git::GitResolverError),
|
||||
#[error(transparent)]
|
||||
|
@ -89,7 +89,7 @@ pub enum Error {
|
|||
#[error("The source distribution is missing a `PKG-INFO` file")]
|
||||
MissingPkgInfo,
|
||||
#[error("The source distribution `{}` has no subdirectory `{}`", _0, _1.display())]
|
||||
MissingSubdirectory(Url, PathBuf),
|
||||
MissingSubdirectory(DisplaySafeUrl, PathBuf),
|
||||
#[error("Failed to extract static metadata from `PKG-INFO`")]
|
||||
PkgInfo(#[source] uv_pypi_types::MetadataError),
|
||||
#[error("Failed to extract metadata from `requires.txt`")]
|
||||
|
@ -103,7 +103,7 @@ pub enum Error {
|
|||
#[error(transparent)]
|
||||
MetadataLowering(#[from] MetadataError),
|
||||
#[error("Distribution not found at: {0}")]
|
||||
NotFound(Url),
|
||||
NotFound(DisplaySafeUrl),
|
||||
#[error("Attempted to re-extract the source distribution for `{}`, but the {} hash didn't match. Run `{}` to clear the cache.", _0, _1, "uv cache clean".green())]
|
||||
CacheHeal(String, HashAlgorithm),
|
||||
#[error("The source distribution requires Python {0}, but {1} is installed")]
|
||||
|
|
|
@ -4,7 +4,6 @@ use std::path::{Path, PathBuf};
|
|||
|
||||
use either::Either;
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
|
||||
use uv_distribution_filename::DistExtension;
|
||||
use uv_distribution_types::{
|
||||
|
@ -15,6 +14,7 @@ use uv_normalize::{ExtraName, GroupName, PackageName};
|
|||
use uv_pep440::VersionSpecifiers;
|
||||
use uv_pep508::{MarkerTree, VerbatimUrl, VersionOrUrl, looks_like_git_repository};
|
||||
use uv_pypi_types::{ConflictItem, ParsedUrlError, VerbatimParsedUrl};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_workspace::Workspace;
|
||||
use uv_workspace::pyproject::{PyProjectToml, Source, Sources};
|
||||
|
||||
|
@ -528,11 +528,11 @@ pub enum LoweringError {
|
|||
#[error(transparent)]
|
||||
InvalidVerbatimUrl(#[from] uv_pep508::VerbatimUrlError),
|
||||
#[error("Fragments are not allowed in URLs: `{0}`")]
|
||||
ForbiddenFragment(Url),
|
||||
ForbiddenFragment(DisplaySafeUrl),
|
||||
#[error(
|
||||
"`{0}` is associated with a URL source, but references a Git repository. Consider using a Git source instead (e.g., `{0} = {{ git = \"{1}\" }}`)"
|
||||
)]
|
||||
MissingGitSource(PackageName, Url),
|
||||
MissingGitSource(PackageName, DisplaySafeUrl),
|
||||
#[error("`workspace = false` is not yet supported")]
|
||||
WorkspaceFalse,
|
||||
#[error("Source with `editable = true` must refer to a local directory, not a file: `{0}`")]
|
||||
|
@ -572,7 +572,7 @@ impl std::fmt::Display for SourceKind {
|
|||
|
||||
/// Convert a Git source into a [`RequirementSource`].
|
||||
fn git_source(
|
||||
git: &Url,
|
||||
git: &DisplaySafeUrl,
|
||||
subdirectory: Option<Box<Path>>,
|
||||
rev: Option<String>,
|
||||
tag: Option<String>,
|
||||
|
@ -587,9 +587,10 @@ fn git_source(
|
|||
};
|
||||
|
||||
// Create a PEP 508-compatible URL.
|
||||
let mut url = Url::parse(&format!("git+{git}"))?;
|
||||
let mut url = DisplaySafeUrl::parse(&format!("git+{git}"))?;
|
||||
if let Some(rev) = reference.as_str() {
|
||||
url.set_path(&format!("{}@{}", url.path(), rev));
|
||||
let path = format!("{}@{}", url.path(), rev);
|
||||
url.set_path(&path);
|
||||
}
|
||||
if let Some(subdirectory) = subdirectory.as_ref() {
|
||||
let subdirectory = subdirectory
|
||||
|
@ -611,7 +612,7 @@ fn git_source(
|
|||
/// Convert a URL source into a [`RequirementSource`].
|
||||
fn url_source(
|
||||
requirement: &uv_pep508::Requirement<VerbatimParsedUrl>,
|
||||
url: Url,
|
||||
url: DisplaySafeUrl,
|
||||
subdirectory: Option<Box<Path>>,
|
||||
) -> Result<RequirementSource, LoweringError> {
|
||||
let mut verbatim_url = url.clone();
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use url::Url;
|
||||
|
||||
use uv_distribution_types::BuildableSource;
|
||||
use uv_pep508::PackageName;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
pub trait Reporter: Send + Sync {
|
||||
/// Callback to invoke when a source distribution build is kicked off.
|
||||
|
@ -13,10 +12,10 @@ pub trait Reporter: Send + Sync {
|
|||
fn on_build_complete(&self, source: &BuildableSource, id: usize);
|
||||
|
||||
/// Callback to invoke when a repository checkout begins.
|
||||
fn on_checkout_start(&self, url: &Url, rev: &str) -> usize;
|
||||
fn on_checkout_start(&self, url: &DisplaySafeUrl, rev: &str) -> usize;
|
||||
|
||||
/// Callback to invoke when a repository checkout completes.
|
||||
fn on_checkout_complete(&self, url: &Url, rev: &str, id: usize);
|
||||
fn on_checkout_complete(&self, url: &DisplaySafeUrl, rev: &str, id: usize);
|
||||
|
||||
/// Callback to invoke when a download is kicked off.
|
||||
fn on_download_start(&self, name: &PackageName, size: Option<u64>) -> usize;
|
||||
|
@ -44,11 +43,11 @@ struct Facade {
|
|||
}
|
||||
|
||||
impl uv_git::Reporter for Facade {
|
||||
fn on_checkout_start(&self, url: &Url, rev: &str) -> usize {
|
||||
fn on_checkout_start(&self, url: &DisplaySafeUrl, rev: &str) -> usize {
|
||||
self.reporter.on_checkout_start(url, rev)
|
||||
}
|
||||
|
||||
fn on_checkout_complete(&self, url: &Url, rev: &str, id: usize) {
|
||||
fn on_checkout_complete(&self, url: &DisplaySafeUrl, rev: &str, id: usize) {
|
||||
self.reporter.on_checkout_complete(url, rev, id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ use reqwest::{Response, StatusCode};
|
|||
use tokio_util::compat::FuturesAsyncReadCompatExt;
|
||||
use tracing::{Instrument, debug, info_span, instrument, warn};
|
||||
use url::Url;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use zip::ZipArchive;
|
||||
|
||||
use uv_cache::{Cache, CacheBucket, CacheEntry, CacheShard, Removal, WheelCache};
|
||||
|
@ -386,7 +387,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
async fn url<'data>(
|
||||
&self,
|
||||
source: &BuildableSource<'data>,
|
||||
url: &'data Url,
|
||||
url: &'data DisplaySafeUrl,
|
||||
cache_shard: &CacheShard,
|
||||
subdirectory: Option<&'data Path>,
|
||||
ext: SourceDistExtension,
|
||||
|
@ -582,7 +583,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
if let Some(subdirectory) = subdirectory {
|
||||
if !source_dist_entry.path().join(subdirectory).is_dir() {
|
||||
return Err(Error::MissingSubdirectory(
|
||||
url.clone(),
|
||||
DisplaySafeUrl::from(url.clone()),
|
||||
subdirectory.to_path_buf(),
|
||||
));
|
||||
}
|
||||
|
@ -715,7 +716,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
.boxed_local()
|
||||
.instrument(info_span!("download", source_dist = %source))
|
||||
};
|
||||
let req = Self::request(url.clone(), client.unmanaged)?;
|
||||
let req = Self::request(DisplaySafeUrl::from(url.clone()), client.unmanaged)?;
|
||||
let revision = client
|
||||
.managed(|client| {
|
||||
client.cached_client().get_serde_with_retry(
|
||||
|
@ -740,7 +741,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
client
|
||||
.cached_client()
|
||||
.skip_cache_with_retry(
|
||||
Self::request(url.clone(), client)?,
|
||||
Self::request(DisplaySafeUrl::from(url.clone()), client)?,
|
||||
&cache_entry,
|
||||
download,
|
||||
)
|
||||
|
@ -2077,7 +2078,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
client
|
||||
.cached_client()
|
||||
.skip_cache_with_retry(
|
||||
Self::request(url.clone(), client)?,
|
||||
Self::request(DisplaySafeUrl::from(url.clone()), client)?,
|
||||
&cache_entry,
|
||||
download,
|
||||
)
|
||||
|
@ -2402,10 +2403,13 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
}
|
||||
|
||||
/// Returns a GET [`reqwest::Request`] for the given URL.
|
||||
fn request(url: Url, client: &RegistryClient) -> Result<reqwest::Request, reqwest::Error> {
|
||||
fn request(
|
||||
url: DisplaySafeUrl,
|
||||
client: &RegistryClient,
|
||||
) -> Result<reqwest::Request, reqwest::Error> {
|
||||
client
|
||||
.uncached_client(&url)
|
||||
.get(url)
|
||||
.get(Url::from(url))
|
||||
.header(
|
||||
// `reqwest` defaults to accepting compressed responses.
|
||||
// Specify identity encoding to get consistent .whl downloading
|
||||
|
|
|
@ -16,9 +16,9 @@ doctest = false
|
|||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
uv-redacted = { workspace = true }
|
||||
|
||||
serde = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
url = { workspace = true }
|
||||
|
||||
uv-redacted = { workspace = true }
|
||||
|
|
|
@ -3,9 +3,7 @@ pub use crate::oid::{GitOid, OidParseError};
|
|||
pub use crate::reference::GitReference;
|
||||
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
|
||||
use uv_redacted::redacted_url;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
mod github;
|
||||
mod oid;
|
||||
|
@ -16,7 +14,7 @@ pub enum GitUrlParseError {
|
|||
#[error(
|
||||
"Unsupported Git URL scheme `{0}:` in `{1}` (expected one of `https:`, `ssh:`, or `file:`)"
|
||||
)]
|
||||
UnsupportedGitScheme(String, Url),
|
||||
UnsupportedGitScheme(String, DisplaySafeUrl),
|
||||
}
|
||||
|
||||
/// A URL reference to a Git repository.
|
||||
|
@ -24,7 +22,7 @@ pub enum GitUrlParseError {
|
|||
pub struct GitUrl {
|
||||
/// The URL of the Git repository, with any query parameters, fragments, and leading `git+`
|
||||
/// removed.
|
||||
repository: Url,
|
||||
repository: DisplaySafeUrl,
|
||||
/// The reference to the commit to use, which could be a branch, tag or revision.
|
||||
reference: GitReference,
|
||||
/// The precise commit to use, if known.
|
||||
|
@ -34,7 +32,7 @@ pub struct GitUrl {
|
|||
impl GitUrl {
|
||||
/// Create a new [`GitUrl`] from a repository URL and a reference.
|
||||
pub fn from_reference(
|
||||
repository: Url,
|
||||
repository: DisplaySafeUrl,
|
||||
reference: GitReference,
|
||||
) -> Result<Self, GitUrlParseError> {
|
||||
Self::from_fields(repository, reference, None)
|
||||
|
@ -42,7 +40,7 @@ impl GitUrl {
|
|||
|
||||
/// Create a new [`GitUrl`] from a repository URL and a precise commit.
|
||||
pub fn from_commit(
|
||||
repository: Url,
|
||||
repository: DisplaySafeUrl,
|
||||
reference: GitReference,
|
||||
precise: GitOid,
|
||||
) -> Result<Self, GitUrlParseError> {
|
||||
|
@ -51,7 +49,7 @@ impl GitUrl {
|
|||
|
||||
/// Create a new [`GitUrl`] from a repository URL and a precise commit, if known.
|
||||
pub fn from_fields(
|
||||
repository: Url,
|
||||
repository: DisplaySafeUrl,
|
||||
reference: GitReference,
|
||||
precise: Option<GitOid>,
|
||||
) -> Result<Self, GitUrlParseError> {
|
||||
|
@ -86,7 +84,7 @@ impl GitUrl {
|
|||
}
|
||||
|
||||
/// Return the [`Url`] of the Git repository.
|
||||
pub fn repository(&self) -> &Url {
|
||||
pub fn repository(&self) -> &DisplaySafeUrl {
|
||||
&self.repository
|
||||
}
|
||||
|
||||
|
@ -101,11 +99,11 @@ impl GitUrl {
|
|||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Url> for GitUrl {
|
||||
impl TryFrom<DisplaySafeUrl> for GitUrl {
|
||||
type Error = GitUrlParseError;
|
||||
|
||||
/// Initialize a [`GitUrl`] source from a URL.
|
||||
fn try_from(mut url: Url) -> Result<Self, Self::Error> {
|
||||
fn try_from(mut url: DisplaySafeUrl) -> Result<Self, Self::Error> {
|
||||
// Remove any query parameters and fragments.
|
||||
url.set_fragment(None);
|
||||
url.set_query(None);
|
||||
|
@ -126,13 +124,14 @@ impl TryFrom<Url> for GitUrl {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<GitUrl> for Url {
|
||||
impl From<GitUrl> for DisplaySafeUrl {
|
||||
fn from(git: GitUrl) -> Self {
|
||||
let mut url = git.repository;
|
||||
|
||||
// If we have a precise commit, add `@` and the commit hash to the URL.
|
||||
if let Some(precise) = git.precise {
|
||||
url.set_path(&format!("{}@{}", url.path(), precise));
|
||||
let path = format!("{}@{}", url.path(), precise);
|
||||
url.set_path(&path);
|
||||
} else {
|
||||
// Otherwise, add the branch or tag name.
|
||||
match git.reference {
|
||||
|
@ -141,7 +140,8 @@ impl From<GitUrl> for Url {
|
|||
| GitReference::BranchOrTag(rev)
|
||||
| GitReference::NamedRef(rev)
|
||||
| GitReference::BranchOrTagOrCommit(rev) => {
|
||||
url.set_path(&format!("{}@{}", url.path(), rev));
|
||||
let path = format!("{}@{}", url.path(), rev);
|
||||
url.set_path(&path);
|
||||
}
|
||||
GitReference::DefaultBranch => {}
|
||||
}
|
||||
|
@ -153,6 +153,6 @@ impl From<GitUrl> for Url {
|
|||
|
||||
impl std::fmt::Display for GitUrl {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", redacted_url(&self.repository))
|
||||
write!(f, "{}", &self.repository)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, LazyLock, RwLock};
|
||||
use tracing::trace;
|
||||
use url::Url;
|
||||
use uv_auth::Credentials;
|
||||
use uv_cache_key::RepositoryUrl;
|
||||
use uv_redacted::redacted_url;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
/// Global authentication cache for a uv invocation.
|
||||
///
|
||||
|
@ -30,9 +29,9 @@ impl GitStore {
|
|||
/// Populate the global authentication store with credentials on a Git URL, if there are any.
|
||||
///
|
||||
/// Returns `true` if the store was updated.
|
||||
pub fn store_credentials_from_url(url: &Url) -> bool {
|
||||
pub fn store_credentials_from_url(url: &DisplaySafeUrl) -> bool {
|
||||
if let Some(credentials) = Credentials::from_url(url) {
|
||||
trace!("Caching credentials for {}", redacted_url(url));
|
||||
trace!("Caching credentials for {url}");
|
||||
GIT_STORE.insert(RepositoryUrl::new(url), credentials);
|
||||
true
|
||||
} else {
|
||||
|
|
|
@ -16,6 +16,7 @@ use url::Url;
|
|||
|
||||
use uv_fs::Simplified;
|
||||
use uv_git_types::{GitHubRepository, GitOid, GitReference};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_static::EnvVars;
|
||||
use uv_version::version;
|
||||
|
||||
|
@ -132,7 +133,7 @@ impl Display for ReferenceOrOid<'_> {
|
|||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub(crate) struct GitRemote {
|
||||
/// URL to a remote repository.
|
||||
url: Url,
|
||||
url: DisplaySafeUrl,
|
||||
}
|
||||
|
||||
/// A local clone of a remote repository's database. Multiple [`GitCheckout`]s
|
||||
|
@ -205,12 +206,12 @@ impl GitRepository {
|
|||
|
||||
impl GitRemote {
|
||||
/// Creates an instance for a remote repository URL.
|
||||
pub(crate) fn new(url: &Url) -> Self {
|
||||
pub(crate) fn new(url: &DisplaySafeUrl) -> Self {
|
||||
Self { url: url.clone() }
|
||||
}
|
||||
|
||||
/// Gets the remote repository URL.
|
||||
pub(crate) fn url(&self) -> &Url {
|
||||
pub(crate) fn url(&self) -> &DisplaySafeUrl {
|
||||
&self.url
|
||||
}
|
||||
|
||||
|
|
|
@ -9,11 +9,10 @@ use std::sync::Arc;
|
|||
use anyhow::Result;
|
||||
use reqwest_middleware::ClientWithMiddleware;
|
||||
use tracing::{debug, instrument};
|
||||
use url::Url;
|
||||
|
||||
use uv_cache_key::{RepositoryUrl, cache_digest};
|
||||
use uv_git_types::GitUrl;
|
||||
use uv_redacted::redacted_url;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
use crate::GIT_STORE;
|
||||
use crate::git::GitRemote;
|
||||
|
@ -101,10 +100,7 @@ impl GitSource {
|
|||
// situation that we have a locked revision but the database
|
||||
// doesn't have it.
|
||||
(locked_rev, db) => {
|
||||
debug!(
|
||||
"Updating Git source `{}`",
|
||||
redacted_url(self.git.repository())
|
||||
);
|
||||
debug!("Updating Git source `{}`", self.git.repository());
|
||||
|
||||
// Report the checkout operation to the reporter.
|
||||
let task = self.reporter.as_ref().map(|reporter| {
|
||||
|
@ -181,8 +177,8 @@ impl Fetch {
|
|||
|
||||
pub trait Reporter: Send + Sync {
|
||||
/// Callback to invoke when a repository checkout begins.
|
||||
fn on_checkout_start(&self, url: &Url, rev: &str) -> usize;
|
||||
fn on_checkout_start(&self, url: &DisplaySafeUrl, rev: &str) -> usize;
|
||||
|
||||
/// Callback to invoke when a repository checkout completes.
|
||||
fn on_checkout_complete(&self, url: &Url, rev: &str, index: usize);
|
||||
fn on_checkout_complete(&self, url: &DisplaySafeUrl, rev: &str, index: usize);
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ uv-pep508 = { workspace = true }
|
|||
uv-platform-tags = { workspace = true }
|
||||
uv-pypi-types = { workspace = true }
|
||||
uv-python = { workspace = true }
|
||||
uv-redacted = { workspace = true }
|
||||
uv-static = { workspace = true }
|
||||
uv-types = { workspace = true }
|
||||
uv-warnings = { workspace = true }
|
||||
|
|
|
@ -3,7 +3,6 @@ use std::sync::Arc;
|
|||
|
||||
use futures::{FutureExt, Stream, TryFutureExt, TryStreamExt, stream::FuturesUnordered};
|
||||
use tracing::{debug, instrument};
|
||||
use url::Url;
|
||||
|
||||
use uv_cache::Cache;
|
||||
use uv_configuration::BuildOptions;
|
||||
|
@ -14,6 +13,7 @@ use uv_distribution_types::{
|
|||
};
|
||||
use uv_pep508::PackageName;
|
||||
use uv_platform_tags::Tags;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_types::{BuildContext, HashStrategy, InFlight};
|
||||
|
||||
/// Prepare distributions for installation.
|
||||
|
@ -268,10 +268,10 @@ pub trait Reporter: Send + Sync {
|
|||
fn on_build_complete(&self, source: &BuildableSource, id: usize);
|
||||
|
||||
/// Callback to invoke when a repository checkout begins.
|
||||
fn on_checkout_start(&self, url: &Url, rev: &str) -> usize;
|
||||
fn on_checkout_start(&self, url: &DisplaySafeUrl, rev: &str) -> usize;
|
||||
|
||||
/// Callback to invoke when a repository checkout completes.
|
||||
fn on_checkout_complete(&self, url: &Url, rev: &str, index: usize);
|
||||
fn on_checkout_complete(&self, url: &DisplaySafeUrl, rev: &str, index: usize);
|
||||
}
|
||||
|
||||
impl dyn Reporter {
|
||||
|
@ -299,11 +299,11 @@ impl uv_distribution::Reporter for Facade {
|
|||
self.reporter.on_build_complete(source, id);
|
||||
}
|
||||
|
||||
fn on_checkout_start(&self, url: &Url, rev: &str) -> usize {
|
||||
fn on_checkout_start(&self, url: &DisplaySafeUrl, rev: &str) -> usize {
|
||||
self.reporter.on_checkout_start(url, rev)
|
||||
}
|
||||
|
||||
fn on_checkout_complete(&self, url: &Url, rev: &str, index: usize) {
|
||||
fn on_checkout_complete(&self, url: &DisplaySafeUrl, rev: &str, index: usize) {
|
||||
self.reporter.on_checkout_complete(url, rev, index);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ use std::path::PathBuf;
|
|||
use anyhow::{Context, Result};
|
||||
use fs_err as fs;
|
||||
use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
|
||||
use url::Url;
|
||||
|
||||
use uv_distribution_types::{
|
||||
Diagnostic, InstalledDist, Name, NameRequirementSpecification, Requirement,
|
||||
|
@ -18,6 +17,7 @@ use uv_pep440::{Version, VersionSpecifiers};
|
|||
use uv_pep508::VersionOrUrl;
|
||||
use uv_pypi_types::{ResolverMarkerEnvironment, VerbatimParsedUrl};
|
||||
use uv_python::{Interpreter, PythonEnvironment};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_types::InstalledPackagesProvider;
|
||||
use uv_warnings::warn_user;
|
||||
|
||||
|
@ -38,7 +38,7 @@ pub struct SitePackages {
|
|||
/// virtual environment, which we handle gracefully.
|
||||
by_name: FxHashMap<PackageName, Vec<usize>>,
|
||||
/// The installed editable distributions, keyed by URL.
|
||||
by_url: FxHashMap<Url, Vec<usize>>,
|
||||
by_url: FxHashMap<DisplaySafeUrl, Vec<usize>>,
|
||||
}
|
||||
|
||||
impl SitePackages {
|
||||
|
@ -174,7 +174,7 @@ impl SitePackages {
|
|||
}
|
||||
|
||||
/// Returns the distributions installed from the given URL, if any.
|
||||
pub fn get_urls(&self, url: &Url) -> Vec<&InstalledDist> {
|
||||
pub fn get_urls(&self, url: &DisplaySafeUrl) -> Vec<&InstalledDist> {
|
||||
let Some(indexes) = self.by_url.get(url) else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
|
|
@ -22,6 +22,7 @@ workspace = true
|
|||
uv-fs = { workspace = true }
|
||||
uv-normalize = { workspace = true }
|
||||
uv-pep440 = { workspace = true }
|
||||
uv-redacted = { workspace = true }
|
||||
|
||||
arcstr = { workspace = true}
|
||||
boxcar = { workspace = true }
|
||||
|
|
|
@ -144,23 +144,50 @@ impl<T: Pep508Url> Requirement<T> {
|
|||
self.version_or_url = None;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a [`Display`] implementation that doesn't mask credentials.
|
||||
pub fn displayable_with_credentials(&self) -> impl Display {
|
||||
RequirementDisplay {
|
||||
requirement: self,
|
||||
display_credentials: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Pep508Url + Display> Display for Requirement<T> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.name)?;
|
||||
if !self.extras.is_empty() {
|
||||
RequirementDisplay {
|
||||
requirement: self,
|
||||
display_credentials: false,
|
||||
}
|
||||
.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
struct RequirementDisplay<'a, T>
|
||||
where
|
||||
T: Pep508Url + Display,
|
||||
{
|
||||
requirement: &'a Requirement<T>,
|
||||
display_credentials: bool,
|
||||
}
|
||||
|
||||
impl<T: Pep508Url + Display> Display for RequirementDisplay<'_, T> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.requirement.name)?;
|
||||
if !self.requirement.extras.is_empty() {
|
||||
write!(
|
||||
f,
|
||||
"[{}]",
|
||||
self.extras
|
||||
self.requirement
|
||||
.extras
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<_>>()
|
||||
.join(",")
|
||||
)?;
|
||||
}
|
||||
if let Some(version_or_url) = &self.version_or_url {
|
||||
if let Some(version_or_url) = &self.requirement.version_or_url {
|
||||
match version_or_url {
|
||||
VersionOrUrl::VersionSpecifier(version_specifier) => {
|
||||
let version_specifier: Vec<String> =
|
||||
|
@ -168,12 +195,17 @@ impl<T: Pep508Url + Display> Display for Requirement<T> {
|
|||
write!(f, "{}", version_specifier.join(","))?;
|
||||
}
|
||||
VersionOrUrl::Url(url) => {
|
||||
let url_string = if self.display_credentials {
|
||||
url.displayable_with_credentials().to_string()
|
||||
} else {
|
||||
url.to_string()
|
||||
};
|
||||
// We add the space for markers later if necessary
|
||||
write!(f, " @ {url}")?;
|
||||
write!(f, " @ {url_string}")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(marker) = self.marker.contents() {
|
||||
if let Some(marker) = self.requirement.marker.contents() {
|
||||
write!(f, " ; {marker}")?;
|
||||
}
|
||||
Ok(())
|
||||
|
@ -255,6 +287,9 @@ pub trait Pep508Url: Display + Debug + Sized {
|
|||
|
||||
/// Parse a url from `name @ <url>`. Defaults to [`Url::parse_url`].
|
||||
fn parse_url(url: &str, working_dir: Option<&Path>) -> Result<Self, Self::Err>;
|
||||
|
||||
/// Returns a [`Display`] implementation that doesn't mask credentials.
|
||||
fn displayable_with_credentials(&self) -> impl Display;
|
||||
}
|
||||
|
||||
impl Pep508Url for Url {
|
||||
|
@ -263,6 +298,10 @@ impl Pep508Url for Url {
|
|||
fn parse_url(url: &str, _working_dir: Option<&Path>) -> Result<Self, Self::Err> {
|
||||
Url::parse(url)
|
||||
}
|
||||
|
||||
fn displayable_with_credentials(&self) -> impl Display {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A reporter for warnings that occur during marker parsing or evaluation.
|
||||
|
|
|
@ -66,9 +66,9 @@ impl UnnamedRequirementUrl for VerbatimUrl {
|
|||
/// dependencies. This isn't compliant with PEP 508, but is common in `requirements.txt`, which
|
||||
/// is implementation-defined.
|
||||
#[derive(Hash, Debug, Clone, Eq, PartialEq)]
|
||||
pub struct UnnamedRequirement<Url: UnnamedRequirementUrl = VerbatimUrl> {
|
||||
pub struct UnnamedRequirement<ReqUrl: UnnamedRequirementUrl = VerbatimUrl> {
|
||||
/// The direct URL that defines the version specifier.
|
||||
pub url: Url,
|
||||
pub url: ReqUrl,
|
||||
/// The list of extras such as `security`, `tests` in
|
||||
/// `requests [security,tests] >= 2.8.1, == 2.8.* ; python_version > "3.8"`.
|
||||
pub extras: Box<[ExtraName]>,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::borrow::Cow;
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Debug;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::hash::Hash;
|
||||
use std::ops::Deref;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
@ -13,6 +13,7 @@ use url::{ParseError, Url};
|
|||
|
||||
#[cfg_attr(not(feature = "non-pep508-extensions"), allow(unused_imports))]
|
||||
use uv_fs::{normalize_absolute_path, normalize_url_path};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
use crate::Pep508Url;
|
||||
|
||||
|
@ -20,7 +21,7 @@ use crate::Pep508Url;
|
|||
#[derive(Debug, Clone, Eq)]
|
||||
pub struct VerbatimUrl {
|
||||
/// The parsed URL.
|
||||
url: Url,
|
||||
url: DisplaySafeUrl,
|
||||
/// The URL as it was provided by the user.
|
||||
given: Option<ArcStr>,
|
||||
}
|
||||
|
@ -39,14 +40,17 @@ impl PartialEq for VerbatimUrl {
|
|||
|
||||
impl VerbatimUrl {
|
||||
/// Create a [`VerbatimUrl`] from a [`Url`].
|
||||
pub fn from_url(url: Url) -> Self {
|
||||
pub fn from_url(url: DisplaySafeUrl) -> Self {
|
||||
Self { url, given: None }
|
||||
}
|
||||
|
||||
/// Parse a URL from a string.
|
||||
pub fn parse_url(given: impl AsRef<str>) -> Result<Self, ParseError> {
|
||||
let url = Url::parse(given.as_ref())?;
|
||||
Ok(Self { url, given: None })
|
||||
Ok(Self {
|
||||
url: DisplaySafeUrl::from(url),
|
||||
given: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse a URL from an absolute or relative path.
|
||||
|
@ -72,8 +76,10 @@ impl VerbatimUrl {
|
|||
let (path, fragment) = split_fragment(&path);
|
||||
|
||||
// Convert to a URL.
|
||||
let mut url = Url::from_file_path(path.clone())
|
||||
.map_err(|()| VerbatimUrlError::UrlConversion(path.to_path_buf()))?;
|
||||
let mut url = DisplaySafeUrl::from(
|
||||
Url::from_file_path(path.clone())
|
||||
.map_err(|()| VerbatimUrlError::UrlConversion(path.to_path_buf()))?,
|
||||
);
|
||||
|
||||
// Set the fragment, if it exists.
|
||||
if let Some(fragment) = fragment {
|
||||
|
@ -102,8 +108,10 @@ impl VerbatimUrl {
|
|||
let (path, fragment) = split_fragment(&path);
|
||||
|
||||
// Convert to a URL.
|
||||
let mut url = Url::from_file_path(path.clone())
|
||||
.unwrap_or_else(|()| panic!("path is absolute: {}", path.display()));
|
||||
let mut url = DisplaySafeUrl::from(
|
||||
Url::from_file_path(path.clone())
|
||||
.unwrap_or_else(|()| panic!("path is absolute: {}", path.display())),
|
||||
);
|
||||
|
||||
// Set the fragment, if it exists.
|
||||
if let Some(fragment) = fragment {
|
||||
|
@ -130,8 +138,10 @@ impl VerbatimUrl {
|
|||
let (path, fragment) = split_fragment(path);
|
||||
|
||||
// Convert to a URL.
|
||||
let mut url = Url::from_file_path(path.clone())
|
||||
.unwrap_or_else(|()| panic!("path is absolute: {}", path.display()));
|
||||
let mut url = DisplaySafeUrl::from(
|
||||
Url::from_file_path(path.clone())
|
||||
.unwrap_or_else(|()| panic!("path is absolute: {}", path.display())),
|
||||
);
|
||||
|
||||
// Set the fragment, if it exists.
|
||||
if let Some(fragment) = fragment {
|
||||
|
@ -155,18 +165,18 @@ impl VerbatimUrl {
|
|||
self.given.as_deref()
|
||||
}
|
||||
|
||||
/// Return the underlying [`Url`].
|
||||
pub fn raw(&self) -> &Url {
|
||||
/// Return the underlying [`DisplaySafeUrl`].
|
||||
pub fn raw(&self) -> &DisplaySafeUrl {
|
||||
&self.url
|
||||
}
|
||||
|
||||
/// Convert a [`VerbatimUrl`] into a [`Url`].
|
||||
pub fn to_url(&self) -> Url {
|
||||
/// Convert a [`VerbatimUrl`] into a [`DisplaySafeUrl`].
|
||||
pub fn to_url(&self) -> DisplaySafeUrl {
|
||||
self.url.clone()
|
||||
}
|
||||
|
||||
/// Convert a [`VerbatimUrl`] into a [`Url`].
|
||||
pub fn into_url(self) -> Url {
|
||||
/// Convert a [`VerbatimUrl`] into a [`DisplaySafeUrl`].
|
||||
pub fn into_url(self) -> DisplaySafeUrl {
|
||||
self.url
|
||||
}
|
||||
|
||||
|
@ -206,7 +216,7 @@ impl std::fmt::Display for VerbatimUrl {
|
|||
}
|
||||
|
||||
impl Deref for VerbatimUrl {
|
||||
type Target = Url;
|
||||
type Target = DisplaySafeUrl;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.url
|
||||
|
@ -215,10 +225,22 @@ impl Deref for VerbatimUrl {
|
|||
|
||||
impl From<Url> for VerbatimUrl {
|
||||
fn from(url: Url) -> Self {
|
||||
VerbatimUrl::from_url(DisplaySafeUrl::from(url))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DisplaySafeUrl> for VerbatimUrl {
|
||||
fn from(url: DisplaySafeUrl) -> Self {
|
||||
VerbatimUrl::from_url(url)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VerbatimUrl> for Url {
|
||||
fn from(url: VerbatimUrl) -> Self {
|
||||
Url::from(url.url)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl serde::Serialize for VerbatimUrl {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
|
@ -235,7 +257,7 @@ impl<'de> serde::Deserialize<'de> for VerbatimUrl {
|
|||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let url = Url::deserialize(deserializer)?;
|
||||
let url = DisplaySafeUrl::deserialize(deserializer)?;
|
||||
Ok(VerbatimUrl::from_url(url))
|
||||
}
|
||||
}
|
||||
|
@ -314,6 +336,10 @@ impl Pep508Url for VerbatimUrl {
|
|||
Err(Self::Err::NotAUrl(expanded.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
fn displayable_with_credentials(&self) -> impl Display {
|
||||
self.url.to_string_with_credentials()
|
||||
}
|
||||
}
|
||||
|
||||
/// An error that can occur when parsing a [`VerbatimUrl`].
|
||||
|
|
|
@ -23,6 +23,7 @@ uv-extract = { workspace = true }
|
|||
uv-fs = { workspace = true }
|
||||
uv-metadata = { workspace = true }
|
||||
uv-pypi-types = { workspace = true }
|
||||
uv-redacted = { workspace = true }
|
||||
uv-static = { workspace = true }
|
||||
uv-warnings = { workspace = true }
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ use uv_extract::hash::{HashReader, Hasher};
|
|||
use uv_fs::{ProgressReader, Simplified};
|
||||
use uv_metadata::read_metadata_async_seek;
|
||||
use uv_pypi_types::{HashAlgorithm, HashDigest, Metadata23, MetadataError};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_static::EnvVars;
|
||||
use uv_warnings::{warn_user, warn_user_once};
|
||||
|
||||
|
@ -59,7 +60,7 @@ pub enum PublishError {
|
|||
#[error("Failed to publish: `{}`", _0.user_display())]
|
||||
PublishPrepare(PathBuf, #[source] Box<PublishPrepareError>),
|
||||
#[error("Failed to publish `{}` to {}", _0.user_display(), _1)]
|
||||
PublishSend(PathBuf, Url, #[source] PublishSendError),
|
||||
PublishSend(PathBuf, DisplaySafeUrl, #[source] PublishSendError),
|
||||
#[error("Failed to obtain token for trusted publishing")]
|
||||
TrustedPublishing(#[from] TrustedPublishingError),
|
||||
#[error("{0} are not allowed when using trusted publishing")]
|
||||
|
@ -308,7 +309,7 @@ pub async fn check_trusted_publishing(
|
|||
password: Option<&str>,
|
||||
keyring_provider: KeyringProviderType,
|
||||
trusted_publishing: TrustedPublishing,
|
||||
registry: &Url,
|
||||
registry: &DisplaySafeUrl,
|
||||
client: &BaseClient,
|
||||
) -> Result<TrustedPublishResult, PublishError> {
|
||||
match trusted_publishing {
|
||||
|
@ -379,7 +380,7 @@ pub async fn upload(
|
|||
file: &Path,
|
||||
raw_filename: &str,
|
||||
filename: &DistFilename,
|
||||
registry: &Url,
|
||||
registry: &DisplaySafeUrl,
|
||||
client: &BaseClient,
|
||||
credentials: &Credentials,
|
||||
check_url_client: Option<&CheckUrlClient<'_>>,
|
||||
|
@ -751,7 +752,7 @@ async fn build_request(
|
|||
file: &Path,
|
||||
raw_filename: &str,
|
||||
filename: &DistFilename,
|
||||
registry: &Url,
|
||||
registry: &DisplaySafeUrl,
|
||||
client: &BaseClient,
|
||||
credentials: &Credentials,
|
||||
form_metadata: &[(&'static str, String)],
|
||||
|
@ -790,7 +791,7 @@ async fn build_request(
|
|||
|
||||
let mut request = client
|
||||
.for_host(&url)
|
||||
.post(url)
|
||||
.post(Url::from(url))
|
||||
.multipart(form)
|
||||
// Ask PyPI for a structured error messages instead of HTML-markup error messages.
|
||||
// For other registries, we ask them to return plain text over HTML. See
|
||||
|
@ -889,10 +890,10 @@ mod tests {
|
|||
use itertools::Itertools;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use url::Url;
|
||||
use uv_auth::Credentials;
|
||||
use uv_client::BaseClientBuilder;
|
||||
use uv_distribution_filename::DistFilename;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
struct DummyReporter;
|
||||
|
||||
|
@ -972,7 +973,7 @@ mod tests {
|
|||
&file,
|
||||
raw_filename,
|
||||
&filename,
|
||||
&Url::parse("https://example.org/upload").unwrap(),
|
||||
&DisplaySafeUrl::parse("https://example.org/upload").unwrap(),
|
||||
&BaseClientBuilder::new().build(),
|
||||
&Credentials::basic(Some("ferris".to_string()), Some("F3RR!S".to_string())),
|
||||
&form_metadata,
|
||||
|
@ -1121,7 +1122,7 @@ mod tests {
|
|||
&file,
|
||||
raw_filename,
|
||||
&filename,
|
||||
&Url::parse("https://example.org/upload").unwrap(),
|
||||
&DisplaySafeUrl::parse("https://example.org/upload").unwrap(),
|
||||
&BaseClientBuilder::new().build(),
|
||||
&Credentials::basic(Some("ferris".to_string()), Some("F3RR!S".to_string())),
|
||||
&form_metadata,
|
||||
|
|
|
@ -12,6 +12,7 @@ use std::fmt::Display;
|
|||
use thiserror::Error;
|
||||
use tracing::{debug, trace};
|
||||
use url::Url;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_static::EnvVars;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
@ -23,9 +24,9 @@ pub enum TrustedPublishingError {
|
|||
#[error(transparent)]
|
||||
Url(#[from] url::ParseError),
|
||||
#[error("Failed to fetch: `{0}`")]
|
||||
Reqwest(Url, #[source] reqwest::Error),
|
||||
Reqwest(DisplaySafeUrl, #[source] reqwest::Error),
|
||||
#[error("Failed to fetch: `{0}`")]
|
||||
ReqwestMiddleware(Url, #[source] reqwest_middleware::Error),
|
||||
ReqwestMiddleware(DisplaySafeUrl, #[source] reqwest_middleware::Error),
|
||||
#[error(transparent)]
|
||||
SerdeJson(#[from] serde_json::error::Error),
|
||||
#[error(
|
||||
|
@ -94,7 +95,7 @@ pub struct OidcTokenClaims {
|
|||
|
||||
/// Returns the short-lived token to use for uploading.
|
||||
pub(crate) async fn get_token(
|
||||
registry: &Url,
|
||||
registry: &DisplaySafeUrl,
|
||||
client: &ClientWithMiddleware,
|
||||
) -> Result<TrustedPublishingToken, TrustedPublishingError> {
|
||||
// If this fails, we can skip the audience request.
|
||||
|
@ -124,15 +125,16 @@ pub(crate) async fn get_token(
|
|||
}
|
||||
|
||||
async fn get_audience(
|
||||
registry: &Url,
|
||||
registry: &DisplaySafeUrl,
|
||||
client: &ClientWithMiddleware,
|
||||
) -> Result<String, TrustedPublishingError> {
|
||||
// `pypa/gh-action-pypi-publish` uses `netloc` (RFC 1808), which is deprecated for authority
|
||||
// (RFC 3986).
|
||||
let audience_url = Url::parse(&format!("https://{}/_/oidc/audience", registry.authority()))?;
|
||||
let audience_url =
|
||||
DisplaySafeUrl::parse(&format!("https://{}/_/oidc/audience", registry.authority()))?;
|
||||
debug!("Querying the trusted publishing audience from {audience_url}");
|
||||
let response = client
|
||||
.get(audience_url.clone())
|
||||
.get(Url::from(audience_url.clone()))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|err| TrustedPublishingError::ReqwestMiddleware(audience_url.clone(), err))?;
|
||||
|
@ -154,14 +156,14 @@ async fn get_oidc_token(
|
|||
let oidc_token_url = env::var(EnvVars::ACTIONS_ID_TOKEN_REQUEST_URL).map_err(|err| {
|
||||
TrustedPublishingError::from_var_err(EnvVars::ACTIONS_ID_TOKEN_REQUEST_URL, err)
|
||||
})?;
|
||||
let mut oidc_token_url = Url::parse(&oidc_token_url)?;
|
||||
let mut oidc_token_url = DisplaySafeUrl::parse(&oidc_token_url)?;
|
||||
oidc_token_url
|
||||
.query_pairs_mut()
|
||||
.append_pair("audience", audience);
|
||||
debug!("Querying the trusted publishing OIDC token from {oidc_token_url}");
|
||||
let authorization = format!("bearer {oidc_token_request_token}");
|
||||
let response = client
|
||||
.get(oidc_token_url.clone())
|
||||
.get(Url::from(oidc_token_url.clone()))
|
||||
.header(header::AUTHORIZATION, authorization)
|
||||
.send()
|
||||
.await
|
||||
|
@ -188,11 +190,11 @@ fn decode_oidc_token(oidc_token: &str) -> Option<OidcTokenClaims> {
|
|||
}
|
||||
|
||||
async fn get_publish_token(
|
||||
registry: &Url,
|
||||
registry: &DisplaySafeUrl,
|
||||
oidc_token: &str,
|
||||
client: &ClientWithMiddleware,
|
||||
) -> Result<TrustedPublishingToken, TrustedPublishingError> {
|
||||
let mint_token_url = Url::parse(&format!(
|
||||
let mint_token_url = DisplaySafeUrl::parse(&format!(
|
||||
"https://{}/_/oidc/mint-token",
|
||||
registry.authority()
|
||||
))?;
|
||||
|
@ -201,7 +203,7 @@ async fn get_publish_token(
|
|||
token: oidc_token.to_string(),
|
||||
};
|
||||
let response = client
|
||||
.post(mint_token_url.clone())
|
||||
.post(Url::from(mint_token_url.clone()))
|
||||
.body(serde_json::to_vec(&mint_token_payload)?)
|
||||
.send()
|
||||
.await
|
||||
|
|
|
@ -21,6 +21,7 @@ uv-git-types = { workspace = true }
|
|||
uv-normalize = { workspace = true }
|
||||
uv-pep440 = { workspace = true }
|
||||
uv-pep508 = { workspace = true }
|
||||
uv-redacted = { workspace = true }
|
||||
uv-small-str = { workspace = true }
|
||||
|
||||
hashbrown = { workspace = true }
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
/// Join a relative URL to a base URL.
|
||||
pub fn base_url_join_relative(base: &str, relative: &str) -> Result<Url, JoinRelativeError> {
|
||||
let base_url = Url::parse(base).map_err(|err| JoinRelativeError::ParseError {
|
||||
pub fn base_url_join_relative(
|
||||
base: &str,
|
||||
relative: &str,
|
||||
) -> Result<DisplaySafeUrl, JoinRelativeError> {
|
||||
let base_url = DisplaySafeUrl::parse(base).map_err(|err| JoinRelativeError::ParseError {
|
||||
original: base.to_string(),
|
||||
source: err,
|
||||
})?;
|
||||
|
@ -32,26 +35,26 @@ pub enum JoinRelativeError {
|
|||
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct BaseUrl(
|
||||
#[serde(
|
||||
serialize_with = "Url::serialize_internal",
|
||||
deserialize_with = "Url::deserialize_internal"
|
||||
serialize_with = "DisplaySafeUrl::serialize_internal",
|
||||
deserialize_with = "DisplaySafeUrl::deserialize_internal"
|
||||
)]
|
||||
Url,
|
||||
DisplaySafeUrl,
|
||||
);
|
||||
|
||||
impl BaseUrl {
|
||||
/// Return the underlying [`Url`].
|
||||
pub fn as_url(&self) -> &Url {
|
||||
/// Return the underlying [`DisplaySafeUrl`].
|
||||
pub fn as_url(&self) -> &DisplaySafeUrl {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Return the underlying [`Url`] as a serialized string.
|
||||
/// Return the underlying [`DisplaySafeUrl`] as a serialized string.
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.0.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Url> for BaseUrl {
|
||||
fn from(url: Url) -> Self {
|
||||
impl From<DisplaySafeUrl> for BaseUrl {
|
||||
fn from(url: DisplaySafeUrl) -> Self {
|
||||
Self(url)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ use uv_git_types::{GitUrl, GitUrlParseError};
|
|||
use uv_pep508::{
|
||||
Pep508Url, UnnamedRequirementUrl, VerbatimUrl, VerbatimUrlError, looks_like_git_repository,
|
||||
};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
use crate::{ArchiveInfo, DirInfo, DirectUrl, VcsInfo, VcsKind};
|
||||
|
||||
|
@ -61,6 +62,10 @@ impl Pep508Url for VerbatimParsedUrl {
|
|||
verbatim,
|
||||
})
|
||||
}
|
||||
|
||||
fn displayable_with_credentials(&self) -> impl Display {
|
||||
self.verbatim.displayable_with_credentials()
|
||||
}
|
||||
}
|
||||
|
||||
impl UnnamedRequirementUrl for VerbatimParsedUrl {
|
||||
|
@ -194,7 +199,7 @@ impl ParsedUrl {
|
|||
/// * `file:///home/ferris/my_project/my_project-0.1.0-py3-none-any.whl`
|
||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Hash, Ord)]
|
||||
pub struct ParsedPathUrl {
|
||||
pub url: Url,
|
||||
pub url: DisplaySafeUrl,
|
||||
/// The absolute path to the distribution which we use for installing.
|
||||
pub install_path: Box<Path>,
|
||||
/// The file extension, e.g. `tar.gz`, `zip`, etc.
|
||||
|
@ -203,7 +208,7 @@ pub struct ParsedPathUrl {
|
|||
|
||||
impl ParsedPathUrl {
|
||||
/// Construct a [`ParsedPathUrl`] from a path requirement source.
|
||||
pub fn from_source(install_path: Box<Path>, ext: DistExtension, url: Url) -> Self {
|
||||
pub fn from_source(install_path: Box<Path>, ext: DistExtension, url: DisplaySafeUrl) -> Self {
|
||||
Self {
|
||||
url,
|
||||
install_path,
|
||||
|
@ -218,7 +223,7 @@ impl ParsedPathUrl {
|
|||
/// * `file:///home/ferris/my_project`
|
||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Hash, Ord)]
|
||||
pub struct ParsedDirectoryUrl {
|
||||
pub url: Url,
|
||||
pub url: DisplaySafeUrl,
|
||||
/// The absolute path to the distribution which we use for installing.
|
||||
pub install_path: Box<Path>,
|
||||
pub editable: bool,
|
||||
|
@ -227,7 +232,12 @@ pub struct ParsedDirectoryUrl {
|
|||
|
||||
impl ParsedDirectoryUrl {
|
||||
/// Construct a [`ParsedDirectoryUrl`] from a path requirement source.
|
||||
pub fn from_source(install_path: Box<Path>, editable: bool, r#virtual: bool, url: Url) -> Self {
|
||||
pub fn from_source(
|
||||
install_path: Box<Path>,
|
||||
editable: bool,
|
||||
r#virtual: bool,
|
||||
url: DisplaySafeUrl,
|
||||
) -> Self {
|
||||
Self {
|
||||
url,
|
||||
install_path,
|
||||
|
@ -255,21 +265,22 @@ impl ParsedGitUrl {
|
|||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Url> for ParsedGitUrl {
|
||||
impl TryFrom<DisplaySafeUrl> for ParsedGitUrl {
|
||||
type Error = ParsedUrlError;
|
||||
|
||||
/// Supports URLs with and without the `git+` prefix.
|
||||
///
|
||||
/// When the URL includes a prefix, it's presumed to come from a PEP 508 requirement; when it's
|
||||
/// excluded, it's presumed to come from `tool.uv.sources`.
|
||||
fn try_from(url_in: Url) -> Result<Self, Self::Error> {
|
||||
fn try_from(url_in: DisplaySafeUrl) -> Result<Self, Self::Error> {
|
||||
let subdirectory = get_subdirectory(&url_in).map(PathBuf::into_boxed_path);
|
||||
|
||||
let url = url_in
|
||||
.as_str()
|
||||
.strip_prefix("git+")
|
||||
.unwrap_or(url_in.as_str());
|
||||
let url = Url::parse(url).map_err(|err| ParsedUrlError::UrlParse(url.to_string(), err))?;
|
||||
let url = DisplaySafeUrl::parse(url)
|
||||
.map_err(|err| ParsedUrlError::UrlParse(url.to_string(), err))?;
|
||||
let url = GitUrl::try_from(url)?;
|
||||
Ok(Self { url, subdirectory })
|
||||
}
|
||||
|
@ -283,14 +294,18 @@ impl TryFrom<Url> for ParsedGitUrl {
|
|||
/// * A source dist with a recognizable extension but invalid name: `https://github.com/foo-labs/foo/archive/master.zip#egg=pkg&subdirectory=packages/bar`
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
|
||||
pub struct ParsedArchiveUrl {
|
||||
pub url: Url,
|
||||
pub url: DisplaySafeUrl,
|
||||
pub subdirectory: Option<Box<Path>>,
|
||||
pub ext: DistExtension,
|
||||
}
|
||||
|
||||
impl ParsedArchiveUrl {
|
||||
/// Construct a [`ParsedArchiveUrl`] from a URL requirement source.
|
||||
pub fn from_source(location: Url, subdirectory: Option<Box<Path>>, ext: DistExtension) -> Self {
|
||||
pub fn from_source(
|
||||
location: DisplaySafeUrl,
|
||||
subdirectory: Option<Box<Path>>,
|
||||
ext: DistExtension,
|
||||
) -> Self {
|
||||
Self {
|
||||
url: location,
|
||||
subdirectory,
|
||||
|
@ -299,10 +314,10 @@ impl ParsedArchiveUrl {
|
|||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Url> for ParsedArchiveUrl {
|
||||
impl TryFrom<DisplaySafeUrl> for ParsedArchiveUrl {
|
||||
type Error = ParsedUrlError;
|
||||
|
||||
fn try_from(mut url: Url) -> Result<Self, Self::Error> {
|
||||
fn try_from(mut url: DisplaySafeUrl) -> Result<Self, Self::Error> {
|
||||
// Extract the `#subdirectory` fragment, if present.
|
||||
let subdirectory = get_subdirectory(&url).map(PathBuf::into_boxed_path);
|
||||
url.set_fragment(None);
|
||||
|
@ -338,10 +353,10 @@ fn get_subdirectory(url: &Url) -> Option<PathBuf> {
|
|||
Some(PathBuf::from(subdirectory))
|
||||
}
|
||||
|
||||
impl TryFrom<Url> for ParsedUrl {
|
||||
impl TryFrom<DisplaySafeUrl> for ParsedUrl {
|
||||
type Error = ParsedUrlError;
|
||||
|
||||
fn try_from(url: Url) -> Result<Self, Self::Error> {
|
||||
fn try_from(url: DisplaySafeUrl) -> Result<Self, Self::Error> {
|
||||
if let Some((prefix, ..)) = url.scheme().split_once('+') {
|
||||
match prefix {
|
||||
"git" => Ok(Self::Git(ParsedGitUrl::try_from(url)?)),
|
||||
|
@ -464,7 +479,7 @@ impl From<&ParsedGitUrl> for DirectUrl {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<ParsedUrl> for Url {
|
||||
impl From<ParsedUrl> for DisplaySafeUrl {
|
||||
fn from(value: ParsedUrl) -> Self {
|
||||
match value {
|
||||
ParsedUrl::Path(value) => value.into(),
|
||||
|
@ -475,19 +490,19 @@ impl From<ParsedUrl> for Url {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<ParsedPathUrl> for Url {
|
||||
impl From<ParsedPathUrl> for DisplaySafeUrl {
|
||||
fn from(value: ParsedPathUrl) -> Self {
|
||||
value.url
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParsedDirectoryUrl> for Url {
|
||||
impl From<ParsedDirectoryUrl> for DisplaySafeUrl {
|
||||
fn from(value: ParsedDirectoryUrl) -> Self {
|
||||
value.url
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParsedArchiveUrl> for Url {
|
||||
impl From<ParsedArchiveUrl> for DisplaySafeUrl {
|
||||
fn from(value: ParsedArchiveUrl) -> Self {
|
||||
let mut url = value.url;
|
||||
if let Some(subdirectory) = value.subdirectory {
|
||||
|
@ -497,7 +512,7 @@ impl From<ParsedArchiveUrl> for Url {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<ParsedGitUrl> for Url {
|
||||
impl From<ParsedGitUrl> for DisplaySafeUrl {
|
||||
fn from(value: ParsedGitUrl) -> Self {
|
||||
let mut url = Self::parse(&format!("{}{}", "git+", Self::from(value.url).as_str()))
|
||||
.expect("Git URL is invalid");
|
||||
|
@ -511,33 +526,36 @@ impl From<ParsedGitUrl> for Url {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
use url::Url;
|
||||
|
||||
use crate::parsed_url::ParsedUrl;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
#[test]
|
||||
fn direct_url_from_url() -> Result<()> {
|
||||
let expected = Url::parse("git+https://github.com/pallets/flask.git")?;
|
||||
let actual = Url::from(ParsedUrl::try_from(expected.clone())?);
|
||||
assert_eq!(expected, actual);
|
||||
|
||||
let expected = Url::parse("git+https://github.com/pallets/flask.git#subdirectory=pkg_dir")?;
|
||||
let actual = Url::from(ParsedUrl::try_from(expected.clone())?);
|
||||
assert_eq!(expected, actual);
|
||||
|
||||
let expected = Url::parse("git+https://github.com/pallets/flask.git@2.0.0")?;
|
||||
let actual = Url::from(ParsedUrl::try_from(expected.clone())?);
|
||||
let expected = DisplaySafeUrl::parse("git+https://github.com/pallets/flask.git")?;
|
||||
let actual = DisplaySafeUrl::from(ParsedUrl::try_from(expected.clone())?);
|
||||
assert_eq!(expected, actual);
|
||||
|
||||
let expected =
|
||||
Url::parse("git+https://github.com/pallets/flask.git@2.0.0#subdirectory=pkg_dir")?;
|
||||
let actual = Url::from(ParsedUrl::try_from(expected.clone())?);
|
||||
DisplaySafeUrl::parse("git+https://github.com/pallets/flask.git#subdirectory=pkg_dir")?;
|
||||
let actual = DisplaySafeUrl::from(ParsedUrl::try_from(expected.clone())?);
|
||||
assert_eq!(expected, actual);
|
||||
|
||||
let expected = DisplaySafeUrl::parse("git+https://github.com/pallets/flask.git@2.0.0")?;
|
||||
let actual = DisplaySafeUrl::from(ParsedUrl::try_from(expected.clone())?);
|
||||
assert_eq!(expected, actual);
|
||||
|
||||
let expected = DisplaySafeUrl::parse(
|
||||
"git+https://github.com/pallets/flask.git@2.0.0#subdirectory=pkg_dir",
|
||||
)?;
|
||||
let actual = DisplaySafeUrl::from(ParsedUrl::try_from(expected.clone())?);
|
||||
assert_eq!(expected, actual);
|
||||
|
||||
// TODO(charlie): Preserve other fragments.
|
||||
let expected =
|
||||
Url::parse("git+https://github.com/pallets/flask.git#egg=flask&subdirectory=pkg_dir")?;
|
||||
let actual = Url::from(ParsedUrl::try_from(expected.clone())?);
|
||||
let expected = DisplaySafeUrl::parse(
|
||||
"git+https://github.com/pallets/flask.git#egg=flask&subdirectory=pkg_dir",
|
||||
)?;
|
||||
let actual = DisplaySafeUrl::from(ParsedUrl::try_from(expected.clone())?);
|
||||
assert_ne!(expected, actual);
|
||||
|
||||
Ok(())
|
||||
|
@ -546,8 +564,8 @@ mod tests {
|
|||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn direct_url_from_url_absolute() -> Result<()> {
|
||||
let expected = Url::parse("file:///path/to/directory")?;
|
||||
let actual = Url::from(ParsedUrl::try_from(expected.clone())?);
|
||||
let expected = DisplaySafeUrl::parse("file:///path/to/directory")?;
|
||||
let actual = DisplaySafeUrl::from(ParsedUrl::try_from(expected.clone())?);
|
||||
assert_eq!(expected, actual);
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ uv-pep440 = { workspace = true }
|
|||
uv-pep508 = { workspace = true }
|
||||
uv-platform-tags = { workspace = true }
|
||||
uv-pypi-types = { workspace = true }
|
||||
uv-redacted = { workspace = true }
|
||||
uv-state = { workspace = true }
|
||||
uv-static = { workspace = true }
|
||||
uv-trampoline-builder = { workspace = true }
|
||||
|
|
|
@ -26,6 +26,7 @@ use uv_distribution_filename::{ExtensionError, SourceDistExtension};
|
|||
use uv_extract::hash::Hasher;
|
||||
use uv_fs::{Simplified, rename_with_retry};
|
||||
use uv_pypi_types::{HashAlgorithm, HashDigest};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_static::EnvVars;
|
||||
|
||||
use crate::PythonVariant;
|
||||
|
@ -51,9 +52,9 @@ pub enum Error {
|
|||
#[error("Invalid request key (too many parts): {0}")]
|
||||
TooManyParts(String),
|
||||
#[error("Failed to download {0}")]
|
||||
NetworkError(Url, #[source] WrappedReqwestError),
|
||||
NetworkError(DisplaySafeUrl, #[source] WrappedReqwestError),
|
||||
#[error("Failed to download {0}")]
|
||||
NetworkMiddlewareError(Url, #[source] anyhow::Error),
|
||||
NetworkMiddlewareError(DisplaySafeUrl, #[source] anyhow::Error),
|
||||
#[error("Failed to extract archive: {0}")]
|
||||
ExtractError(String, #[source] uv_extract::Error),
|
||||
#[error("Failed to hash installation")]
|
||||
|
@ -1060,11 +1061,14 @@ fn parse_json_downloads(
|
|||
}
|
||||
|
||||
impl Error {
|
||||
pub(crate) fn from_reqwest(url: Url, err: reqwest::Error) -> Self {
|
||||
pub(crate) fn from_reqwest(url: DisplaySafeUrl, err: reqwest::Error) -> Self {
|
||||
Self::NetworkError(url, WrappedReqwestError::from(err))
|
||||
}
|
||||
|
||||
pub(crate) fn from_reqwest_middleware(url: Url, err: reqwest_middleware::Error) -> Self {
|
||||
pub(crate) fn from_reqwest_middleware(
|
||||
url: DisplaySafeUrl,
|
||||
err: reqwest_middleware::Error,
|
||||
) -> Self {
|
||||
match err {
|
||||
reqwest_middleware::Error::Middleware(error) => {
|
||||
Self::NetworkMiddlewareError(url, error)
|
||||
|
@ -1155,6 +1159,7 @@ async fn read_url(
|
|||
url: &Url,
|
||||
client: &BaseClient,
|
||||
) -> Result<(impl AsyncRead + Unpin, Option<u64>), Error> {
|
||||
let url = DisplaySafeUrl::from(url.clone());
|
||||
if url.scheme() == "file" {
|
||||
// Loads downloaded distribution from the given `file://` URL.
|
||||
let path = url
|
||||
|
@ -1167,8 +1172,8 @@ async fn read_url(
|
|||
Ok((Either::Left(reader), Some(size)))
|
||||
} else {
|
||||
let response = client
|
||||
.for_host(url)
|
||||
.get(url.clone())
|
||||
.for_host(&url)
|
||||
.get(Url::from(url.clone()))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|err| Error::from_reqwest_middleware(url.clone(), err))?;
|
||||
|
@ -1176,7 +1181,7 @@ async fn read_url(
|
|||
// Ensure the request was successful.
|
||||
response
|
||||
.error_for_status_ref()
|
||||
.map_err(|err| Error::from_reqwest(url.clone(), err))?;
|
||||
.map_err(|err| Error::from_reqwest(url, err))?;
|
||||
|
||||
let size = response.content_length();
|
||||
let stream = response
|
||||
|
|
|
@ -16,4 +16,9 @@ doctest = false
|
|||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
schemars = { workspace = true, optional = true }
|
||||
serde = { workspace = true }
|
||||
url = { workspace = true }
|
||||
|
||||
[features]
|
||||
schemars = ["dep:schemars"]
|
||||
|
|
|
@ -1,21 +1,241 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Write;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::str::FromStr;
|
||||
use url::Url;
|
||||
|
||||
/// Return a version of the URL with redacted credentials, allowing the generic `git` username (without a password)
|
||||
/// in SSH URLs, as in, `ssh://git@github.com/...`.
|
||||
pub fn redacted_url(url: &Url) -> Cow<'_, Url> {
|
||||
if url.username().is_empty() && url.password().is_none() {
|
||||
return Cow::Borrowed(url);
|
||||
}
|
||||
if url.scheme() == "ssh" && url.username() == "git" && url.password().is_none() {
|
||||
return Cow::Borrowed(url);
|
||||
/// A [`Url`] wrapper that redacts credentials when displaying the URL.
|
||||
///
|
||||
/// `DisplaySafeUrl` wraps the standard [`url::Url`] type, providing functionality to mask
|
||||
/// secrets by default when the URL is displayed or logged. This helps prevent accidental
|
||||
/// exposure of sensitive information in logs and debug output.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use uv_redacted::DisplaySafeUrl;
|
||||
/// use std::str::FromStr;
|
||||
///
|
||||
/// // Create a `DisplaySafeUrl` from a `&str`
|
||||
/// let mut url = DisplaySafeUrl::parse("https://user:password@example.com").unwrap();
|
||||
///
|
||||
/// // Display will mask secrets
|
||||
/// assert_eq!(url.to_string(), "https://user:****@example.com/");
|
||||
///
|
||||
/// // You can still access the username and password
|
||||
/// assert_eq!(url.username(), "user");
|
||||
/// assert_eq!(url.password(), Some("password"));
|
||||
///
|
||||
/// // And you can still update the username and password
|
||||
/// let _ = url.set_username("new_user");
|
||||
/// let _ = url.set_password(Some("new_password"));
|
||||
/// assert_eq!(url.username(), "new_user");
|
||||
/// assert_eq!(url.password(), Some("new_password"));
|
||||
///
|
||||
/// // It is also possible to remove the credentials entirely
|
||||
/// url.remove_credentials();
|
||||
/// assert_eq!(url.username(), "");
|
||||
/// assert_eq!(url.password(), None);
|
||||
/// ```
|
||||
#[derive(Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[cfg_attr(feature = "schemars", schemars(transparent))]
|
||||
pub struct DisplaySafeUrl(Url);
|
||||
|
||||
impl DisplaySafeUrl {
|
||||
#[inline]
|
||||
pub fn parse(input: &str) -> Result<Self, url::ParseError> {
|
||||
Ok(Self(Url::parse(input)?))
|
||||
}
|
||||
|
||||
let mut url = url.clone();
|
||||
let _ = url.set_username("");
|
||||
let _ = url.set_password(None);
|
||||
Cow::Owned(url)
|
||||
/// Parse a string as an URL, with this URL as the base URL.
|
||||
#[inline]
|
||||
pub fn join(&self, input: &str) -> Result<Self, url::ParseError> {
|
||||
self.0.join(input).map(DisplaySafeUrl::from)
|
||||
}
|
||||
|
||||
/// Serialize with Serde using the internal representation of the `Url` struct.
|
||||
#[inline]
|
||||
pub fn serialize_internal<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
self.0.serialize_internal(serializer)
|
||||
}
|
||||
|
||||
/// Serialize with Serde using the internal representation of the `Url` struct.
|
||||
#[inline]
|
||||
pub fn deserialize_internal<'de, D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
Url::deserialize_internal(deserializer).map(DisplaySafeUrl::from)
|
||||
}
|
||||
|
||||
#[allow(clippy::result_unit_err)]
|
||||
pub fn from_file_path<P: AsRef<std::path::Path>>(path: P) -> Result<DisplaySafeUrl, ()> {
|
||||
Url::from_file_path(path).map(DisplaySafeUrl::from)
|
||||
}
|
||||
|
||||
/// Remove the credentials from a URL, allowing the generic `git` username (without a password)
|
||||
/// in SSH URLs, as in, `ssh://git@github.com/...`.
|
||||
#[inline]
|
||||
pub fn remove_credentials(&mut self) {
|
||||
// For URLs that use the `git` convention (i.e., `ssh://git@github.com/...`), avoid dropping the
|
||||
// username.
|
||||
if self.0.scheme() == "ssh" && self.0.username() == "git" && self.0.password().is_none() {
|
||||
return;
|
||||
}
|
||||
let _ = self.0.set_username("");
|
||||
let _ = self.0.set_password(None);
|
||||
}
|
||||
|
||||
/// Returns string representation without masking credentials.
|
||||
#[inline]
|
||||
pub fn to_string_with_credentials(&self) -> String {
|
||||
self.0.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for DisplaySafeUrl {
|
||||
type Target = Url;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for DisplaySafeUrl {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for DisplaySafeUrl {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
fmt_with_obfuscated_credentials(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for DisplaySafeUrl {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{self}")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Url> for DisplaySafeUrl {
|
||||
fn from(url: Url) -> Self {
|
||||
DisplaySafeUrl(url)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DisplaySafeUrl> for Url {
|
||||
fn from(url: DisplaySafeUrl) -> Self {
|
||||
url.0
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for DisplaySafeUrl {
|
||||
type Err = url::ParseError;
|
||||
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self(Url::from_str(input)?))
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_with_obfuscated_credentials<W: Write>(url: &Url, mut f: W) -> std::fmt::Result {
|
||||
if url.password().is_none() && url.username() == "" {
|
||||
return write!(f, "{url}");
|
||||
}
|
||||
|
||||
write!(f, "{}://", url.scheme())?;
|
||||
|
||||
if url.username() != "" && url.password().is_some() {
|
||||
write!(f, "{}", url.username())?;
|
||||
write!(f, ":****@")?;
|
||||
} else if url.username() != "" {
|
||||
write!(f, "****@")?;
|
||||
} else if url.password().is_some() {
|
||||
write!(f, ":****@")?;
|
||||
}
|
||||
|
||||
write!(f, "{}", url.host_str().unwrap_or(""))?;
|
||||
|
||||
if let Some(port) = url.port() {
|
||||
write!(f, ":{port}")?;
|
||||
}
|
||||
|
||||
write!(f, "{}", url.path())?;
|
||||
if let Some(query) = url.query() {
|
||||
write!(f, "?{query}")?;
|
||||
}
|
||||
if let Some(fragment) = url.fragment() {
|
||||
write!(f, "#{fragment}")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A wrapper around a [`url::Url`] ref that safely handles credentials for
|
||||
/// logging purposes.
|
||||
///
|
||||
/// Uses the same underlying [`Display`] implementation as [`DisplaySafeUrl`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use uv_redacted::DisplaySafeUrl;
|
||||
/// use std::str::FromStr;
|
||||
///
|
||||
/// // Create from a `url::Url` ref
|
||||
/// let url = Url::parse("https://user:password@example.com").unwrap();
|
||||
/// let log_safe_url = DisplaySafeUrlRef::from(&url);
|
||||
///
|
||||
/// // Display will mask secrets
|
||||
/// assert_eq!(url.to_string(), "https://user:****@example.com/");
|
||||
///
|
||||
/// // Since `DisplaySafeUrlRef` provides full access to the underlying `Url` through a
|
||||
/// // `Deref` implementation, you can still access the username and password
|
||||
/// assert_eq!(url.username(), "user");
|
||||
/// assert_eq!(url.password(), Some("password"));
|
||||
pub struct DisplaySafeUrlRef<'a>(&'a Url);
|
||||
|
||||
impl<'a> Deref for DisplaySafeUrlRef<'a> {
|
||||
type Target = Url;
|
||||
|
||||
fn deref(&self) -> &'a Self::Target {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for DisplaySafeUrlRef<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
fmt_with_obfuscated_credentials(self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for DisplaySafeUrlRef<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{self}")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Url> for DisplaySafeUrlRef<'a> {
|
||||
fn from(url: &'a Url) -> Self {
|
||||
DisplaySafeUrlRef(url)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a DisplaySafeUrl> for DisplaySafeUrlRef<'a> {
|
||||
fn from(url: &'a DisplaySafeUrl) -> Self {
|
||||
DisplaySafeUrlRef(url)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<DisplaySafeUrlRef<'a>> for DisplaySafeUrl {
|
||||
fn from(url: DisplaySafeUrlRef<'a>) -> Self {
|
||||
DisplaySafeUrl(url.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -24,49 +244,103 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn from_url_no_credentials() {
|
||||
let url = Url::parse("https://pypi-proxy.fly.dev/basic-auth/simple").unwrap();
|
||||
let redacted = redacted_url(&url);
|
||||
assert_eq!(redacted.username(), "");
|
||||
assert!(redacted.password().is_none());
|
||||
assert_eq!(
|
||||
format!("{redacted}"),
|
||||
"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 log_safe_url = DisplaySafeUrl::from(url);
|
||||
assert_eq!(log_safe_url.username(), "");
|
||||
assert!(log_safe_url.password().is_none());
|
||||
assert_eq!(format!("{log_safe_url}"), url_str);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_url_username_and_password() {
|
||||
let url = Url::parse("https://user:pass@pypi-proxy.fly.dev/basic-auth/simple").unwrap();
|
||||
let redacted = redacted_url(&url);
|
||||
assert_eq!(redacted.username(), "");
|
||||
assert!(redacted.password().is_none());
|
||||
let url_str = "https://user:pass@pypi-proxy.fly.dev/basic-auth/simple";
|
||||
let url = Url::parse(url_str).unwrap();
|
||||
let log_safe_url = DisplaySafeUrl::from(url);
|
||||
assert_eq!(log_safe_url.username(), "user");
|
||||
assert!(log_safe_url.password().is_some_and(|p| p == "pass"));
|
||||
assert_eq!(
|
||||
format!("{redacted}"),
|
||||
"https://pypi-proxy.fly.dev/basic-auth/simple"
|
||||
format!("{log_safe_url}"),
|
||||
"https://user:****@pypi-proxy.fly.dev/basic-auth/simple"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_url_just_password() {
|
||||
let url = Url::parse("https://:pass@pypi-proxy.fly.dev/basic-auth/simple").unwrap();
|
||||
let redacted = redacted_url(&url);
|
||||
assert_eq!(redacted.username(), "");
|
||||
assert!(redacted.password().is_none());
|
||||
let url_str = "https://:pass@pypi-proxy.fly.dev/basic-auth/simple";
|
||||
let url = Url::parse(url_str).unwrap();
|
||||
let log_safe_url = DisplaySafeUrl::from(url);
|
||||
assert_eq!(log_safe_url.username(), "");
|
||||
assert!(log_safe_url.password().is_some_and(|p| p == "pass"));
|
||||
assert_eq!(
|
||||
format!("{redacted}"),
|
||||
"https://pypi-proxy.fly.dev/basic-auth/simple"
|
||||
format!("{log_safe_url}"),
|
||||
"https://:****@pypi-proxy.fly.dev/basic-auth/simple"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_url_just_username() {
|
||||
let url = Url::parse("https://user@pypi-proxy.fly.dev/basic-auth/simple").unwrap();
|
||||
let redacted = redacted_url(&url);
|
||||
assert_eq!(redacted.username(), "");
|
||||
assert!(redacted.password().is_none());
|
||||
let url_str = "https://user@pypi-proxy.fly.dev/basic-auth/simple";
|
||||
let url = Url::parse(url_str).unwrap();
|
||||
let log_safe_url = DisplaySafeUrl::from(url);
|
||||
assert_eq!(log_safe_url.username(), "user");
|
||||
assert!(log_safe_url.password().is_none());
|
||||
assert_eq!(
|
||||
format!("{redacted}"),
|
||||
format!("{log_safe_url}"),
|
||||
"https://****@pypi-proxy.fly.dev/basic-auth/simple"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_url_string() {
|
||||
let url_str = "https://user:pass@pypi-proxy.fly.dev/basic-auth/simple";
|
||||
let log_safe_url = DisplaySafeUrl::parse(url_str).unwrap();
|
||||
assert_eq!(log_safe_url.username(), "user");
|
||||
assert!(log_safe_url.password().is_some_and(|p| p == "pass"));
|
||||
assert_eq!(
|
||||
format!("{log_safe_url}"),
|
||||
"https://user:****@pypi-proxy.fly.dev/basic-auth/simple"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_credentials() {
|
||||
let url_str = "https://user:pass@pypi-proxy.fly.dev/basic-auth/simple";
|
||||
let mut log_safe_url = DisplaySafeUrl::parse(url_str).unwrap();
|
||||
log_safe_url.remove_credentials();
|
||||
assert_eq!(log_safe_url.username(), "");
|
||||
assert!(log_safe_url.password().is_none());
|
||||
assert_eq!(
|
||||
format!("{log_safe_url}"),
|
||||
"https://pypi-proxy.fly.dev/basic-auth/simple"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_string_with_credentials() {
|
||||
let url_str = "https://user:pass@pypi-proxy.fly.dev/basic-auth/simple";
|
||||
let log_safe_url = DisplaySafeUrl::parse(url_str).unwrap();
|
||||
assert_eq!(&log_safe_url.to_string_with_credentials(), url_str);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn url_join() {
|
||||
let url_str = "https://token@example.com/abc/";
|
||||
let log_safe_url = DisplaySafeUrl::parse(url_str).unwrap();
|
||||
let foo_url = log_safe_url.join("foo").unwrap();
|
||||
assert_eq!(format!("{foo_url}"), "https://****@example.com/abc/foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn log_safe_url_ref() {
|
||||
let url_str = "https://user:pass@pypi-proxy.fly.dev/basic-auth/simple";
|
||||
let url = Url::parse(url_str).unwrap();
|
||||
let log_safe_url = DisplaySafeUrlRef::from(&url);
|
||||
assert_eq!(log_safe_url.username(), "user");
|
||||
assert!(log_safe_url.password().is_some_and(|p| p == "pass"));
|
||||
assert_eq!(
|
||||
format!("{log_safe_url}"),
|
||||
"https://user:****@pypi-proxy.fly.dev/basic-auth/simple"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ uv-fs = { workspace = true }
|
|||
uv-normalize = { workspace = true }
|
||||
uv-pep508 = { workspace = true }
|
||||
uv-pypi-types = { workspace = true }
|
||||
uv-redacted = { workspace = true }
|
||||
uv-warnings = { workspace = true }
|
||||
|
||||
fs-err = { workspace = true }
|
||||
|
|
|
@ -54,6 +54,8 @@ use uv_distribution_types::{
|
|||
use uv_fs::Simplified;
|
||||
use uv_pep508::{Pep508Error, RequirementOrigin, VerbatimUrl, expand_env_vars};
|
||||
use uv_pypi_types::VerbatimParsedUrl;
|
||||
#[cfg(feature = "http")]
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
use crate::requirement::EditableError;
|
||||
pub use crate::requirement::RequirementsTxtRequirement;
|
||||
|
@ -949,11 +951,11 @@ async fn read_url_to_string(
|
|||
url: path.as_ref().to_owned(),
|
||||
})?;
|
||||
|
||||
let url = Url::from_str(path_utf8)
|
||||
let url = DisplaySafeUrl::from_str(path_utf8)
|
||||
.map_err(|err| RequirementsTxtParserError::InvalidUrl(path_utf8.to_string(), err))?;
|
||||
let response = client
|
||||
.for_host(&url)
|
||||
.get(url.clone())
|
||||
.get(Url::from(url.clone()))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|err| RequirementsTxtParserError::from_reqwest_middleware(url.clone(), err))?;
|
||||
|
@ -1047,7 +1049,7 @@ pub enum RequirementsTxtParserError {
|
|||
url: PathBuf,
|
||||
},
|
||||
#[cfg(feature = "http")]
|
||||
Reqwest(Url, reqwest_middleware::Error),
|
||||
Reqwest(DisplaySafeUrl, reqwest_middleware::Error),
|
||||
#[cfg(feature = "http")]
|
||||
InvalidUrl(String, url::ParseError),
|
||||
}
|
||||
|
@ -1301,11 +1303,11 @@ impl From<io::Error> for RequirementsTxtParserError {
|
|||
|
||||
#[cfg(feature = "http")]
|
||||
impl RequirementsTxtParserError {
|
||||
fn from_reqwest(url: Url, err: reqwest::Error) -> Self {
|
||||
fn from_reqwest(url: DisplaySafeUrl, err: reqwest::Error) -> Self {
|
||||
Self::Reqwest(url, reqwest_middleware::Error::Reqwest(err))
|
||||
}
|
||||
|
||||
fn from_reqwest_middleware(url: Url, err: reqwest_middleware::Error) -> Self {
|
||||
fn from_reqwest_middleware(url: DisplaySafeUrl, err: reqwest_middleware::Error) -> Self {
|
||||
Self::Reqwest(url, err)
|
||||
}
|
||||
}
|
||||
|
@ -2039,7 +2041,7 @@ mod test {
|
|||
insta::with_settings!({
|
||||
filters => path_filters(&path_filter(temp_dir.path())),
|
||||
}, {
|
||||
insta::assert_debug_snapshot!(requirements, @r###"
|
||||
insta::assert_debug_snapshot!(requirements, @r#"
|
||||
RequirementsTxt {
|
||||
requirements: [],
|
||||
constraints: [],
|
||||
|
@ -2050,34 +2052,14 @@ mod test {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Directory(
|
||||
ParsedDirectoryUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/foo/bar",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file:///foo/bar,
|
||||
install_path: "/foo/bar",
|
||||
editable: true,
|
||||
virtual: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/foo/bar",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file:///foo/bar,
|
||||
given: Some(
|
||||
"/foo/bar",
|
||||
),
|
||||
|
@ -2102,7 +2084,7 @@ mod test {
|
|||
no_binary: None,
|
||||
only_binary: None,
|
||||
}
|
||||
"###);
|
||||
"#);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
|
@ -2187,7 +2169,7 @@ mod test {
|
|||
insta::with_settings!({
|
||||
filters => path_filters(&path_filter(temp_dir.path())),
|
||||
}, {
|
||||
insta::assert_debug_snapshot!(requirements, @r###"
|
||||
insta::assert_debug_snapshot!(requirements, @r#"
|
||||
RequirementsTxt {
|
||||
requirements: [
|
||||
RequirementEntry {
|
||||
|
@ -2333,21 +2315,7 @@ mod test {
|
|||
editables: [],
|
||||
index_url: Some(
|
||||
VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"test.pypi.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/simple/",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: https://test.pypi.org/simple/,
|
||||
given: Some(
|
||||
"https://test.pypi.org/simple/",
|
||||
),
|
||||
|
@ -2359,7 +2327,7 @@ mod test {
|
|||
no_binary: All,
|
||||
only_binary: None,
|
||||
}
|
||||
"###);
|
||||
"#);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
|
@ -2402,7 +2370,7 @@ mod test {
|
|||
insta::with_settings!({
|
||||
filters => path_filters(&path_filter(temp_dir.path())),
|
||||
}, {
|
||||
insta::assert_debug_snapshot!(requirements, @r###"
|
||||
insta::assert_debug_snapshot!(requirements, @r#"
|
||||
RequirementsTxt {
|
||||
requirements: [
|
||||
RequirementEntry {
|
||||
|
@ -2411,33 +2379,13 @@ mod test {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Path(
|
||||
ParsedPathUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/importlib_metadata-8.3.0-py3-none-any.whl",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/importlib_metadata-8.3.0-py3-none-any.whl,
|
||||
install_path: "<REQUIREMENTS_DIR>/importlib_metadata-8.3.0-py3-none-any.whl",
|
||||
ext: Wheel,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/importlib_metadata-8.3.0-py3-none-any.whl",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/importlib_metadata-8.3.0-py3-none-any.whl,
|
||||
given: Some(
|
||||
"importlib_metadata-8.3.0-py3-none-any.whl",
|
||||
),
|
||||
|
@ -2460,33 +2408,13 @@ mod test {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Path(
|
||||
ParsedPathUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/importlib_metadata-8.2.0-py3-none-any.whl",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/importlib_metadata-8.2.0-py3-none-any.whl,
|
||||
install_path: "<REQUIREMENTS_DIR>/importlib_metadata-8.2.0-py3-none-any.whl",
|
||||
ext: Wheel,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/importlib_metadata-8.2.0-py3-none-any.whl",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/importlib_metadata-8.2.0-py3-none-any.whl,
|
||||
given: Some(
|
||||
"importlib_metadata-8.2.0-py3-none-any.whl",
|
||||
),
|
||||
|
@ -2509,33 +2437,13 @@ mod test {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Path(
|
||||
ParsedPathUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/importlib_metadata-8.2.0-py3-none-any.whl",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/importlib_metadata-8.2.0-py3-none-any.whl,
|
||||
install_path: "<REQUIREMENTS_DIR>/importlib_metadata-8.2.0-py3-none-any.whl",
|
||||
ext: Wheel,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/importlib_metadata-8.2.0-py3-none-any.whl",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/importlib_metadata-8.2.0-py3-none-any.whl,
|
||||
given: Some(
|
||||
"importlib_metadata-8.2.0-py3-none-any.whl",
|
||||
),
|
||||
|
@ -2562,33 +2470,13 @@ mod test {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Path(
|
||||
ParsedPathUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/importlib_metadata-8.2.0+local-py3-none-any.whl",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/importlib_metadata-8.2.0+local-py3-none-any.whl,
|
||||
install_path: "<REQUIREMENTS_DIR>/importlib_metadata-8.2.0+local-py3-none-any.whl",
|
||||
ext: Wheel,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/importlib_metadata-8.2.0+local-py3-none-any.whl",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/importlib_metadata-8.2.0+local-py3-none-any.whl,
|
||||
given: Some(
|
||||
"importlib_metadata-8.2.0+local-py3-none-any.whl",
|
||||
),
|
||||
|
@ -2611,33 +2499,13 @@ mod test {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Path(
|
||||
ParsedPathUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/importlib_metadata-8.2.0+local-py3-none-any.whl",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/importlib_metadata-8.2.0+local-py3-none-any.whl,
|
||||
install_path: "<REQUIREMENTS_DIR>/importlib_metadata-8.2.0+local-py3-none-any.whl",
|
||||
ext: Wheel,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/importlib_metadata-8.2.0+local-py3-none-any.whl",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/importlib_metadata-8.2.0+local-py3-none-any.whl,
|
||||
given: Some(
|
||||
"importlib_metadata-8.2.0+local-py3-none-any.whl",
|
||||
),
|
||||
|
@ -2660,33 +2528,13 @@ mod test {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Path(
|
||||
ParsedPathUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/importlib_metadata-8.2.0+local-py3-none-any.whl",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/importlib_metadata-8.2.0+local-py3-none-any.whl,
|
||||
install_path: "<REQUIREMENTS_DIR>/importlib_metadata-8.2.0+local-py3-none-any.whl",
|
||||
ext: Wheel,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/importlib_metadata-8.2.0+local-py3-none-any.whl",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/importlib_metadata-8.2.0+local-py3-none-any.whl,
|
||||
given: Some(
|
||||
"importlib_metadata-8.2.0+local-py3-none-any.whl",
|
||||
),
|
||||
|
@ -2717,7 +2565,7 @@ mod test {
|
|||
no_binary: None,
|
||||
only_binary: None,
|
||||
}
|
||||
"###);
|
||||
"#);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -39,21 +39,7 @@ RequirementsTxt {
|
|||
parsed_url: Git(
|
||||
ParsedGitUrl {
|
||||
url: GitUrl {
|
||||
repository: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"github.com",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/pandas-dev/pandas.git",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
repository: https://github.com/pandas-dev/pandas.git,
|
||||
reference: DefaultBranch,
|
||||
precise: None,
|
||||
},
|
||||
|
@ -61,21 +47,7 @@ RequirementsTxt {
|
|||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "git+https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"github.com",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/pandas-dev/pandas.git",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: git+https://github.com/pandas-dev/pandas.git,
|
||||
given: Some(
|
||||
"git+https://github.com/pandas-dev/pandas.git",
|
||||
),
|
||||
|
|
|
@ -10,34 +10,14 @@ RequirementsTxt {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Directory(
|
||||
ParsedDirectoryUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/scripts/packages/black_editable,
|
||||
install_path: "<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
editable: false,
|
||||
virtual: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/scripts/packages/black_editable,
|
||||
given: Some(
|
||||
"./scripts/packages/black_editable",
|
||||
),
|
||||
|
@ -60,34 +40,14 @@ RequirementsTxt {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Directory(
|
||||
ParsedDirectoryUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/scripts/packages/black_editable,
|
||||
install_path: "<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
editable: false,
|
||||
virtual: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/scripts/packages/black_editable,
|
||||
given: Some(
|
||||
"./scripts/packages/black_editable",
|
||||
),
|
||||
|
@ -114,34 +74,14 @@ RequirementsTxt {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Directory(
|
||||
ParsedDirectoryUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/scripts/packages/black_editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file:///scripts/packages/black_editable,
|
||||
install_path: "/scripts/packages/black_editable",
|
||||
editable: false,
|
||||
virtual: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/scripts/packages/black_editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file:///scripts/packages/black_editable,
|
||||
given: Some(
|
||||
"file:///scripts/packages/black_editable",
|
||||
),
|
||||
|
@ -164,34 +104,14 @@ RequirementsTxt {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Directory(
|
||||
ParsedDirectoryUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/scripts/packages/black%20editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/scripts/packages/black%20editable,
|
||||
install_path: "<REQUIREMENTS_DIR>/scripts/packages/black editable",
|
||||
editable: false,
|
||||
virtual: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/scripts/packages/black%20editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/scripts/packages/black%20editable,
|
||||
given: Some(
|
||||
"./scripts/packages/black editable",
|
||||
),
|
||||
|
@ -214,34 +134,14 @@ RequirementsTxt {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Directory(
|
||||
ParsedDirectoryUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/scripts/packages/black%20editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/scripts/packages/black%20editable,
|
||||
install_path: "<REQUIREMENTS_DIR>/scripts/packages/black editable",
|
||||
editable: false,
|
||||
virtual: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/scripts/packages/black%20editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/scripts/packages/black%20editable,
|
||||
given: Some(
|
||||
"./scripts/packages/black editable",
|
||||
),
|
||||
|
@ -264,34 +164,14 @@ RequirementsTxt {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Directory(
|
||||
ParsedDirectoryUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/scripts/packages/black%20editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/scripts/packages/black%20editable,
|
||||
install_path: "<REQUIREMENTS_DIR>/scripts/packages/black editable",
|
||||
editable: false,
|
||||
virtual: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/scripts/packages/black%20editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/scripts/packages/black%20editable,
|
||||
given: Some(
|
||||
"./scripts/packages/black editable",
|
||||
),
|
||||
|
|
|
@ -12,34 +12,14 @@ RequirementsTxt {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Directory(
|
||||
ParsedDirectoryUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/editable,
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
editable: true,
|
||||
virtual: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/editable,
|
||||
given: Some(
|
||||
"./editable",
|
||||
),
|
||||
|
@ -69,34 +49,14 @@ RequirementsTxt {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Directory(
|
||||
ParsedDirectoryUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/editable,
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
editable: true,
|
||||
virtual: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/editable,
|
||||
given: Some(
|
||||
"./editable",
|
||||
),
|
||||
|
@ -126,34 +86,14 @@ RequirementsTxt {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Directory(
|
||||
ParsedDirectoryUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/editable,
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
editable: true,
|
||||
virtual: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/editable,
|
||||
given: Some(
|
||||
"./editable",
|
||||
),
|
||||
|
@ -183,34 +123,14 @@ RequirementsTxt {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Directory(
|
||||
ParsedDirectoryUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/editable,
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
editable: true,
|
||||
virtual: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/editable,
|
||||
given: Some(
|
||||
"./editable",
|
||||
),
|
||||
|
@ -240,34 +160,14 @@ RequirementsTxt {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Directory(
|
||||
ParsedDirectoryUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/editable,
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
editable: true,
|
||||
virtual: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/editable,
|
||||
given: Some(
|
||||
"./editable",
|
||||
),
|
||||
|
@ -290,34 +190,14 @@ RequirementsTxt {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Directory(
|
||||
ParsedDirectoryUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/editable[d",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/editable[d,
|
||||
install_path: "<REQUIREMENTS_DIR>/editable[d",
|
||||
editable: true,
|
||||
virtual: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/editable[d",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/editable[d,
|
||||
given: Some(
|
||||
"./editable[d",
|
||||
),
|
||||
|
@ -340,34 +220,14 @@ RequirementsTxt {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Directory(
|
||||
ParsedDirectoryUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/editable,
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
editable: true,
|
||||
virtual: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/editable,
|
||||
given: Some(
|
||||
"./editable",
|
||||
),
|
||||
|
@ -390,34 +250,14 @@ RequirementsTxt {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Directory(
|
||||
ParsedDirectoryUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/editable,
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
editable: true,
|
||||
virtual: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file://<REQUIREMENTS_DIR>/editable,
|
||||
given: Some(
|
||||
"./editable",
|
||||
),
|
||||
|
|
|
@ -39,21 +39,7 @@ RequirementsTxt {
|
|||
parsed_url: Git(
|
||||
ParsedGitUrl {
|
||||
url: GitUrl {
|
||||
repository: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"github.com",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/pandas-dev/pandas.git",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
repository: https://github.com/pandas-dev/pandas.git,
|
||||
reference: DefaultBranch,
|
||||
precise: None,
|
||||
},
|
||||
|
@ -61,21 +47,7 @@ RequirementsTxt {
|
|||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "git+https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"github.com",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/pandas-dev/pandas.git",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: git+https://github.com/pandas-dev/pandas.git,
|
||||
given: Some(
|
||||
"git+https://github.com/pandas-dev/pandas.git",
|
||||
),
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
source: crates/requirements-txt/src/lib.rs
|
||||
source: crates/uv-requirements-txt/src/lib.rs
|
||||
expression: actual
|
||||
---
|
||||
RequirementsTxt {
|
||||
|
@ -10,34 +10,14 @@ RequirementsTxt {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Directory(
|
||||
ParsedDirectoryUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file:///<REQUIREMENTS_DIR>/scripts/packages/black_editable,
|
||||
install_path: "<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
editable: false,
|
||||
virtual: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file:///<REQUIREMENTS_DIR>/scripts/packages/black_editable,
|
||||
given: Some(
|
||||
"./scripts/packages/black_editable",
|
||||
),
|
||||
|
@ -60,34 +40,14 @@ RequirementsTxt {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Directory(
|
||||
ParsedDirectoryUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file:///<REQUIREMENTS_DIR>/scripts/packages/black_editable,
|
||||
install_path: "<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
editable: false,
|
||||
virtual: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file:///<REQUIREMENTS_DIR>/scripts/packages/black_editable,
|
||||
given: Some(
|
||||
"./scripts/packages/black_editable",
|
||||
),
|
||||
|
@ -114,34 +74,14 @@ RequirementsTxt {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Directory(
|
||||
ParsedDirectoryUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file:///<REQUIREMENTS_DIR>/scripts/packages/black_editable,
|
||||
install_path: "<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
editable: false,
|
||||
virtual: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file:///<REQUIREMENTS_DIR>/scripts/packages/black_editable,
|
||||
given: Some(
|
||||
"file:///scripts/packages/black_editable",
|
||||
),
|
||||
|
@ -164,34 +104,14 @@ RequirementsTxt {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Directory(
|
||||
ParsedDirectoryUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/<REQUIREMENTS_DIR>/scripts/packages/black%20editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file:///<REQUIREMENTS_DIR>/scripts/packages/black%20editable,
|
||||
install_path: "<REQUIREMENTS_DIR>/scripts/packages/black editable",
|
||||
editable: false,
|
||||
virtual: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/<REQUIREMENTS_DIR>/scripts/packages/black%20editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file:///<REQUIREMENTS_DIR>/scripts/packages/black%20editable,
|
||||
given: Some(
|
||||
"./scripts/packages/black editable",
|
||||
),
|
||||
|
@ -214,34 +134,14 @@ RequirementsTxt {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Directory(
|
||||
ParsedDirectoryUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/<REQUIREMENTS_DIR>/scripts/packages/black%20editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file:///<REQUIREMENTS_DIR>/scripts/packages/black%20editable,
|
||||
install_path: "<REQUIREMENTS_DIR>/scripts/packages/black editable",
|
||||
editable: false,
|
||||
virtual: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/<REQUIREMENTS_DIR>/scripts/packages/black%20editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file:///<REQUIREMENTS_DIR>/scripts/packages/black%20editable,
|
||||
given: Some(
|
||||
"./scripts/packages/black editable",
|
||||
),
|
||||
|
@ -264,34 +164,14 @@ RequirementsTxt {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Directory(
|
||||
ParsedDirectoryUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/<REQUIREMENTS_DIR>/scripts/packages/black%20editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file:///<REQUIREMENTS_DIR>/scripts/packages/black%20editable,
|
||||
install_path: "<REQUIREMENTS_DIR>/scripts/packages/black editable",
|
||||
editable: false,
|
||||
virtual: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/<REQUIREMENTS_DIR>/scripts/packages/black%20editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file:///<REQUIREMENTS_DIR>/scripts/packages/black%20editable,
|
||||
given: Some(
|
||||
"./scripts/packages/black editable",
|
||||
),
|
||||
|
|
|
@ -12,34 +12,14 @@ RequirementsTxt {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Directory(
|
||||
ParsedDirectoryUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/<REQUIREMENTS_DIR>/editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file:///<REQUIREMENTS_DIR>/editable,
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
editable: true,
|
||||
virtual: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/<REQUIREMENTS_DIR>/editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file:///<REQUIREMENTS_DIR>/editable,
|
||||
given: Some(
|
||||
"./editable",
|
||||
),
|
||||
|
@ -69,34 +49,14 @@ RequirementsTxt {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Directory(
|
||||
ParsedDirectoryUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/<REQUIREMENTS_DIR>/editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file:///<REQUIREMENTS_DIR>/editable,
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
editable: true,
|
||||
virtual: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/<REQUIREMENTS_DIR>/editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file:///<REQUIREMENTS_DIR>/editable,
|
||||
given: Some(
|
||||
"./editable",
|
||||
),
|
||||
|
@ -126,34 +86,14 @@ RequirementsTxt {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Directory(
|
||||
ParsedDirectoryUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/<REQUIREMENTS_DIR>/editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file:///<REQUIREMENTS_DIR>/editable,
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
editable: true,
|
||||
virtual: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/<REQUIREMENTS_DIR>/editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file:///<REQUIREMENTS_DIR>/editable,
|
||||
given: Some(
|
||||
"./editable",
|
||||
),
|
||||
|
@ -183,34 +123,14 @@ RequirementsTxt {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Directory(
|
||||
ParsedDirectoryUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/<REQUIREMENTS_DIR>/editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file:///<REQUIREMENTS_DIR>/editable,
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
editable: true,
|
||||
virtual: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/<REQUIREMENTS_DIR>/editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file:///<REQUIREMENTS_DIR>/editable,
|
||||
given: Some(
|
||||
"./editable",
|
||||
),
|
||||
|
@ -240,34 +160,14 @@ RequirementsTxt {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Directory(
|
||||
ParsedDirectoryUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/<REQUIREMENTS_DIR>/editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file:///<REQUIREMENTS_DIR>/editable,
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
editable: true,
|
||||
virtual: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/<REQUIREMENTS_DIR>/editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file:///<REQUIREMENTS_DIR>/editable,
|
||||
given: Some(
|
||||
"./editable",
|
||||
),
|
||||
|
@ -290,34 +190,14 @@ RequirementsTxt {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Directory(
|
||||
ParsedDirectoryUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/<REQUIREMENTS_DIR>/editable[d",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file:///<REQUIREMENTS_DIR>/editable[d,
|
||||
install_path: "<REQUIREMENTS_DIR>/editable[d",
|
||||
editable: true,
|
||||
virtual: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/<REQUIREMENTS_DIR>/editable[d",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file:///<REQUIREMENTS_DIR>/editable[d,
|
||||
given: Some(
|
||||
"./editable[d",
|
||||
),
|
||||
|
@ -340,34 +220,14 @@ RequirementsTxt {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Directory(
|
||||
ParsedDirectoryUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/<REQUIREMENTS_DIR>/editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file:///<REQUIREMENTS_DIR>/editable,
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
editable: true,
|
||||
virtual: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/<REQUIREMENTS_DIR>/editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file:///<REQUIREMENTS_DIR>/editable,
|
||||
given: Some(
|
||||
"./editable",
|
||||
),
|
||||
|
@ -390,34 +250,14 @@ RequirementsTxt {
|
|||
url: VerbatimParsedUrl {
|
||||
parsed_url: Directory(
|
||||
ParsedDirectoryUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/<REQUIREMENTS_DIR>/editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file:///<REQUIREMENTS_DIR>/editable,
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
editable: true,
|
||||
virtual: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/<REQUIREMENTS_DIR>/editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: file:///<REQUIREMENTS_DIR>/editable,
|
||||
given: Some(
|
||||
"./editable",
|
||||
),
|
||||
|
|
|
@ -28,6 +28,7 @@ uv-git = { workspace = true }
|
|||
uv-normalize = { workspace = true }
|
||||
uv-pep508 = { workspace = true }
|
||||
uv-pypi-types = { workspace = true }
|
||||
uv-redacted = { workspace = true }
|
||||
uv-requirements-txt = { workspace = true, features = ["http"] }
|
||||
uv-resolver = { workspace = true, features = ["clap"] }
|
||||
uv-types = { workspace = true }
|
||||
|
|
|
@ -16,6 +16,7 @@ use uv_distribution_types::{
|
|||
use uv_fs::Simplified;
|
||||
use uv_normalize::{ExtraName, PackageName};
|
||||
use uv_pep508::RequirementOrigin;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_resolver::{InMemoryIndex, MetadataResponse};
|
||||
use uv_types::{BuildContext, HashStrategy};
|
||||
|
||||
|
@ -180,7 +181,7 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> {
|
|||
return Ok(metadata);
|
||||
}
|
||||
|
||||
let Ok(url) = Url::from_directory_path(source_tree) else {
|
||||
let Ok(url) = Url::from_directory_path(source_tree).map(DisplaySafeUrl::from) else {
|
||||
return Err(anyhow::anyhow!("Failed to convert path to URL"));
|
||||
};
|
||||
let source = SourceUrl::Directory(DirectorySourceUrl {
|
||||
|
|
|
@ -33,6 +33,7 @@ uv-pep508 = { workspace = true }
|
|||
uv-platform-tags = { workspace = true }
|
||||
uv-pypi-types = { workspace = true }
|
||||
uv-python = { workspace = true }
|
||||
uv-redacted = { workspace = true }
|
||||
uv-requirements-txt = { workspace = true }
|
||||
uv-small-str = { workspace = true }
|
||||
uv-static = { workspace = true }
|
||||
|
|
|
@ -33,6 +33,7 @@ use uv_pep440::Version;
|
|||
use uv_pep508::{MarkerEnvironment, MarkerTree, VerbatimUrl};
|
||||
use uv_platform_tags::{TagCompatibility, TagPriority, Tags};
|
||||
use uv_pypi_types::{HashDigests, Hashes, ParsedGitUrl, VcsKind};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_small_str::SmallString;
|
||||
|
||||
use crate::lock::export::ExportableRequirements;
|
||||
|
@ -93,7 +94,7 @@ pub enum PylockTomlErrorKind {
|
|||
#[error("`packages.vcs` entry for `{0}` must have a `url` or `path`")]
|
||||
VcsMissingPathUrl(PackageName),
|
||||
#[error("URL must end in a valid wheel filename: `{0}`")]
|
||||
UrlMissingFilename(Url),
|
||||
UrlMissingFilename(DisplaySafeUrl),
|
||||
#[error("Path must end in a valid wheel filename: `{0}`")]
|
||||
PathMissingFilename(Box<Path>),
|
||||
#[error("Failed to convert path to URL")]
|
||||
|
@ -204,7 +205,7 @@ pub struct PylockTomlPackage {
|
|||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub version: Option<Version>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub index: Option<Url>,
|
||||
pub index: Option<DisplaySafeUrl>,
|
||||
#[serde(
|
||||
skip_serializing_if = "uv_pep508::marker::ser::is_empty",
|
||||
serialize_with = "uv_pep508::marker::ser::serialize",
|
||||
|
@ -247,7 +248,7 @@ struct PylockTomlDirectory {
|
|||
struct PylockTomlVcs {
|
||||
r#type: VcsKind,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
url: Option<Url>,
|
||||
url: Option<DisplaySafeUrl>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
path: Option<PortablePathBuf>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
@ -261,7 +262,7 @@ struct PylockTomlVcs {
|
|||
#[serde(rename_all = "kebab-case")]
|
||||
struct PylockTomlArchive {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
url: Option<Url>,
|
||||
url: Option<DisplaySafeUrl>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
path: Option<PortablePathBuf>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
@ -284,7 +285,7 @@ struct PylockTomlSdist {
|
|||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
name: Option<SmallString>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
url: Option<Url>,
|
||||
url: Option<DisplaySafeUrl>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
path: Option<PortablePathBuf>,
|
||||
#[serde(
|
||||
|
@ -305,7 +306,7 @@ struct PylockTomlWheel {
|
|||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
name: Option<WheelFilename>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
url: Option<Url>,
|
||||
url: Option<DisplaySafeUrl>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
path: Option<PortablePathBuf>,
|
||||
#[serde(
|
||||
|
@ -1324,7 +1325,7 @@ impl PylockTomlWheel {
|
|||
&self,
|
||||
install_path: &Path,
|
||||
name: &PackageName,
|
||||
index: Option<&Url>,
|
||||
index: Option<&DisplaySafeUrl>,
|
||||
) -> Result<RegistryBuiltWheel, PylockTomlErrorKind> {
|
||||
let filename = self.filename(name)?.into_owned();
|
||||
|
||||
|
@ -1332,7 +1333,8 @@ impl PylockTomlWheel {
|
|||
UrlString::from(url)
|
||||
} else if let Some(path) = self.path.as_ref() {
|
||||
let path = install_path.join(path);
|
||||
let url = Url::from_file_path(path).map_err(|()| PylockTomlErrorKind::PathToUrl)?;
|
||||
let url = DisplaySafeUrl::from_file_path(path)
|
||||
.map_err(|()| PylockTomlErrorKind::PathToUrl)?;
|
||||
UrlString::from(url)
|
||||
} else {
|
||||
return Err(PylockTomlErrorKind::WheelMissingPathUrl(name.clone()));
|
||||
|
@ -1408,8 +1410,10 @@ impl PylockTomlVcs {
|
|||
let mut url = if let Some(url) = self.url.as_ref() {
|
||||
url.clone()
|
||||
} else if let Some(path) = self.path.as_ref() {
|
||||
Url::from_directory_path(install_path.join(path))
|
||||
.map_err(|()| PylockTomlErrorKind::PathToUrl)?
|
||||
DisplaySafeUrl::from(
|
||||
Url::from_directory_path(install_path.join(path))
|
||||
.map_err(|()| PylockTomlErrorKind::PathToUrl)?,
|
||||
)
|
||||
} else {
|
||||
return Err(PylockTomlErrorKind::VcsMissingPathUrl(name.clone()));
|
||||
};
|
||||
|
@ -1427,7 +1431,7 @@ impl PylockTomlVcs {
|
|||
};
|
||||
|
||||
// Reconstruct the PEP 508-compatible URL from the `GitSource`.
|
||||
let url = Url::from(ParsedGitUrl {
|
||||
let url = DisplaySafeUrl::from(ParsedGitUrl {
|
||||
url: git_url.clone(),
|
||||
subdirectory: subdirectory.clone(),
|
||||
});
|
||||
|
@ -1469,7 +1473,7 @@ impl PylockTomlSdist {
|
|||
install_path: &Path,
|
||||
name: &PackageName,
|
||||
version: Option<&Version>,
|
||||
index: Option<&Url>,
|
||||
index: Option<&DisplaySafeUrl>,
|
||||
) -> Result<RegistrySourceDist, PylockTomlErrorKind> {
|
||||
let filename = self.filename(name)?.into_owned();
|
||||
let ext = SourceDistExtension::from_path(filename.as_ref())?;
|
||||
|
@ -1485,7 +1489,8 @@ impl PylockTomlSdist {
|
|||
UrlString::from(url)
|
||||
} else if let Some(path) = self.path.as_ref() {
|
||||
let path = install_path.join(path);
|
||||
let url = Url::from_file_path(path).map_err(|()| PylockTomlErrorKind::PathToUrl)?;
|
||||
let url = DisplaySafeUrl::from_file_path(path)
|
||||
.map_err(|()| PylockTomlErrorKind::PathToUrl)?;
|
||||
UrlString::from(url)
|
||||
} else {
|
||||
return Err(PylockTomlErrorKind::SdistMissingPathUrl(name.clone()));
|
||||
|
|
|
@ -13,6 +13,7 @@ use uv_fs::Simplified;
|
|||
use uv_git_types::GitReference;
|
||||
use uv_normalize::PackageName;
|
||||
use uv_pypi_types::{ParsedArchiveUrl, ParsedGitUrl};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
use crate::lock::export::{ExportableRequirement, ExportableRequirements};
|
||||
use crate::lock::{Package, PackageId, Source};
|
||||
|
@ -94,7 +95,7 @@ impl std::fmt::Display for RequirementsTxtExport<'_> {
|
|||
.expect("Internal Git URLs must have supported schemes");
|
||||
|
||||
// Reconstruct the PEP 508-compatible URL from the `GitSource`.
|
||||
let url = Url::from(ParsedGitUrl {
|
||||
let url = DisplaySafeUrl::from(ParsedGitUrl {
|
||||
url: git_url.clone(),
|
||||
subdirectory: git.subdirectory.clone(),
|
||||
});
|
||||
|
@ -102,7 +103,7 @@ impl std::fmt::Display for RequirementsTxtExport<'_> {
|
|||
write!(f, "{} @ {}", package.id.name, url)?;
|
||||
}
|
||||
Source::Direct(url, direct) => {
|
||||
let url = Url::from(ParsedArchiveUrl {
|
||||
let url = DisplaySafeUrl::from(ParsedArchiveUrl {
|
||||
url: url.to_url().map_err(|_| std::fmt::Error)?,
|
||||
subdirectory: direct.subdirectory.clone(),
|
||||
ext: DistExtension::Source(SourceDistExtension::TarGz),
|
||||
|
|
|
@ -30,7 +30,7 @@ use uv_distribution_types::{
|
|||
Dist, DistributionMetadata, FileLocation, GitSourceDist, IndexLocations, IndexMetadata,
|
||||
IndexUrl, Name, PathBuiltDist, PathSourceDist, RegistryBuiltDist, RegistryBuiltWheel,
|
||||
RegistrySourceDist, RemoteSource, Requirement, RequirementSource, ResolvedDist, StaticMetadata,
|
||||
ToUrlError, UrlString, redact_credentials,
|
||||
ToUrlError, UrlString,
|
||||
};
|
||||
use uv_fs::{PortablePath, PortablePathBuf, relative_to};
|
||||
use uv_git::{RepositoryReference, ResolvedRepositoryReference};
|
||||
|
@ -45,6 +45,7 @@ use uv_pypi_types::{
|
|||
ConflictPackage, Conflicts, HashAlgorithm, HashDigest, HashDigests, Hashes, ParsedArchiveUrl,
|
||||
ParsedGitUrl,
|
||||
};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_small_str::SmallString;
|
||||
use uv_types::{BuildContext, HashStrategy};
|
||||
use uv_workspace::WorkspaceMember;
|
||||
|
@ -1404,7 +1405,7 @@ impl Lock {
|
|||
.into_iter()
|
||||
.filter_map(|index| match index.url() {
|
||||
IndexUrl::Pypi(_) | IndexUrl::Url(_) => {
|
||||
Some(UrlString::from(index.url().redacted().as_ref()))
|
||||
Some(UrlString::from(index.url().without_credentials().as_ref()))
|
||||
}
|
||||
IndexUrl::Path(_) => None,
|
||||
})
|
||||
|
@ -2238,7 +2239,7 @@ impl Package {
|
|||
Source::Direct(url, direct) => {
|
||||
let filename: WheelFilename =
|
||||
self.wheels[best_wheel_index].filename.clone();
|
||||
let url = Url::from(ParsedArchiveUrl {
|
||||
let url = DisplaySafeUrl::from(ParsedArchiveUrl {
|
||||
url: url.to_url().map_err(LockErrorKind::InvalidUrl)?,
|
||||
subdirectory: direct.subdirectory.clone(),
|
||||
ext: DistExtension::Wheel,
|
||||
|
@ -2400,7 +2401,7 @@ impl Package {
|
|||
GitUrl::from_commit(url, GitReference::from(git.kind.clone()), git.precise)?;
|
||||
|
||||
// Reconstruct the PEP 508-compatible URL from the `GitSource`.
|
||||
let url = Url::from(ParsedGitUrl {
|
||||
let url = DisplaySafeUrl::from(ParsedGitUrl {
|
||||
url: git_url.clone(),
|
||||
subdirectory: git.subdirectory.clone(),
|
||||
});
|
||||
|
@ -2419,7 +2420,7 @@ impl Package {
|
|||
return Ok(None);
|
||||
};
|
||||
let location = url.to_url().map_err(LockErrorKind::InvalidUrl)?;
|
||||
let url = Url::from(ParsedArchiveUrl {
|
||||
let url = DisplaySafeUrl::from(ParsedArchiveUrl {
|
||||
url: location.clone(),
|
||||
subdirectory: direct.subdirectory.clone(),
|
||||
ext: DistExtension::Source(ext),
|
||||
|
@ -2498,8 +2499,9 @@ impl Package {
|
|||
name: name.clone(),
|
||||
version: version.clone(),
|
||||
})?;
|
||||
let file_url = Url::from_file_path(workspace_root.join(path).join(file_path))
|
||||
.map_err(|()| LockErrorKind::PathToUrl)?;
|
||||
let file_url =
|
||||
DisplaySafeUrl::from_file_path(workspace_root.join(path).join(file_path))
|
||||
.map_err(|()| LockErrorKind::PathToUrl)?;
|
||||
let filename = sdist
|
||||
.filename()
|
||||
.ok_or_else(|| LockErrorKind::MissingFilename {
|
||||
|
@ -3192,7 +3194,7 @@ impl Source {
|
|||
match index_url {
|
||||
IndexUrl::Pypi(_) | IndexUrl::Url(_) => {
|
||||
// Remove any sensitive credentials from the index URL.
|
||||
let redacted = index_url.redacted();
|
||||
let redacted = index_url.without_credentials();
|
||||
let source = RegistrySource::Url(UrlString::from(redacted.as_ref()));
|
||||
Ok(Source::Registry(source))
|
||||
}
|
||||
|
@ -3405,7 +3407,7 @@ impl TryFrom<SourceWire> for Source {
|
|||
match wire {
|
||||
Registry { registry } => Ok(Source::Registry(registry.into())),
|
||||
Git { git } => {
|
||||
let url = Url::parse(&git)
|
||||
let url = DisplaySafeUrl::parse(&git)
|
||||
.map_err(|err| SourceParseError::InvalidUrl {
|
||||
given: git.to_string(),
|
||||
err,
|
||||
|
@ -3913,12 +3915,12 @@ impl From<GitSourceKind> for GitReference {
|
|||
}
|
||||
}
|
||||
|
||||
/// Construct the lockfile-compatible [`URL`] for a [`GitSourceDist`].
|
||||
fn locked_git_url(git_dist: &GitSourceDist) -> Url {
|
||||
/// Construct the lockfile-compatible [`DisplaySafeUrl`] for a [`GitSourceDist`].
|
||||
fn locked_git_url(git_dist: &GitSourceDist) -> DisplaySafeUrl {
|
||||
let mut url = git_dist.git.repository().clone();
|
||||
|
||||
// Redact the credentials.
|
||||
redact_credentials(&mut url);
|
||||
// Remove the credentials.
|
||||
url.remove_credentials();
|
||||
|
||||
// Clear out any existing state.
|
||||
url.set_fragment(None);
|
||||
|
@ -4183,8 +4185,9 @@ impl Wheel {
|
|||
.into());
|
||||
}
|
||||
};
|
||||
let file_url = Url::from_file_path(root.join(index_path).join(file_path))
|
||||
.map_err(|()| LockErrorKind::PathToUrl)?;
|
||||
let file_url =
|
||||
DisplaySafeUrl::from_file_path(root.join(index_path).join(file_path))
|
||||
.map_err(|()| LockErrorKind::PathToUrl)?;
|
||||
let file = Box::new(uv_distribution_types::File {
|
||||
dist_info_metadata: false,
|
||||
filename: SmallString::from(filename.to_string()),
|
||||
|
@ -4571,8 +4574,8 @@ fn normalize_file_location(location: &FileLocation) -> Result<UrlString, ToUrlEr
|
|||
}
|
||||
}
|
||||
|
||||
/// Convert a [`Url`] into a normalized [`UrlString`] by removing the fragment.
|
||||
fn normalize_url(mut url: Url) -> UrlString {
|
||||
/// Convert a [`DisplaySafeUrl`] into a normalized [`UrlString`] by removing the fragment.
|
||||
fn normalize_url(mut url: DisplaySafeUrl) -> UrlString {
|
||||
url.set_fragment(None);
|
||||
UrlString::from(url)
|
||||
}
|
||||
|
@ -4606,8 +4609,8 @@ fn normalize_requirement(
|
|||
let git = {
|
||||
let mut repository = git.repository().clone();
|
||||
|
||||
// Redact the credentials.
|
||||
redact_credentials(&mut repository);
|
||||
// Remove the credentials.
|
||||
repository.remove_credentials();
|
||||
|
||||
// Remove the fragment and query from the URL; they're already present in the source.
|
||||
repository.set_fragment(None);
|
||||
|
@ -4617,7 +4620,7 @@ fn normalize_requirement(
|
|||
};
|
||||
|
||||
// Reconstruct the PEP 508 URL from the underlying data.
|
||||
let url = Url::from(ParsedGitUrl {
|
||||
let url = DisplaySafeUrl::from(ParsedGitUrl {
|
||||
url: git.clone(),
|
||||
subdirectory: subdirectory.clone(),
|
||||
});
|
||||
|
@ -4692,7 +4695,7 @@ fn normalize_requirement(
|
|||
let index = index
|
||||
.map(|index| index.url.into_url())
|
||||
.map(|mut index| {
|
||||
redact_credentials(&mut index);
|
||||
index.remove_credentials();
|
||||
index
|
||||
})
|
||||
.map(|index| IndexMetadata::from(IndexUrl::from(VerbatimUrl::from_url(index))));
|
||||
|
@ -4715,14 +4718,14 @@ fn normalize_requirement(
|
|||
ext,
|
||||
url: _,
|
||||
} => {
|
||||
// Redact the credentials.
|
||||
redact_credentials(&mut location);
|
||||
// Remove the credentials.
|
||||
location.remove_credentials();
|
||||
|
||||
// Remove the fragment from the URL; it's already present in the source.
|
||||
location.set_fragment(None);
|
||||
|
||||
// Reconstruct the PEP 508 URL from the underlying data.
|
||||
let url = Url::from(ParsedArchiveUrl {
|
||||
let url = DisplaySafeUrl::from(ParsedArchiveUrl {
|
||||
url: location.clone(),
|
||||
subdirectory: subdirectory.clone(),
|
||||
ext,
|
||||
|
|
|
@ -1518,7 +1518,7 @@ impl std::fmt::Display for PubGrubHint {
|
|||
"hint".bold().cyan(),
|
||||
":".bold(),
|
||||
name.cyan(),
|
||||
found_index.redacted().cyan(),
|
||||
found_index.without_credentials().cyan(),
|
||||
PackageRange::compatibility(&PubGrubPackage::base(name), range, None).cyan(),
|
||||
next_index.cyan(),
|
||||
"--index-strategy unsafe-best-match".green(),
|
||||
|
@ -1530,7 +1530,7 @@ impl std::fmt::Display for PubGrubHint {
|
|||
"{}{} An index URL ({}) could not be queried due to a lack of valid authentication credentials ({}).",
|
||||
"hint".bold().cyan(),
|
||||
":".bold(),
|
||||
index.redacted().cyan(),
|
||||
index.without_credentials().cyan(),
|
||||
"401 Unauthorized".red(),
|
||||
)
|
||||
}
|
||||
|
@ -1540,7 +1540,7 @@ impl std::fmt::Display for PubGrubHint {
|
|||
"{}{} An index URL ({}) could not be queried due to a lack of valid authentication credentials ({}).",
|
||||
"hint".bold().cyan(),
|
||||
":".bold(),
|
||||
index.redacted().cyan(),
|
||||
index.without_credentials().cyan(),
|
||||
"403 Forbidden".red(),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
use url::Url;
|
||||
|
||||
use uv_git::GitResolver;
|
||||
use uv_pep508::VerbatimUrl;
|
||||
use uv_pypi_types::{ParsedGitUrl, ParsedUrl, VerbatimParsedUrl};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
/// Map a URL to a precise URL, if possible.
|
||||
pub(crate) fn url_to_precise(url: VerbatimParsedUrl, git: &GitResolver) -> VerbatimParsedUrl {
|
||||
|
@ -26,7 +25,7 @@ pub(crate) fn url_to_precise(url: VerbatimParsedUrl, git: &GitResolver) -> Verba
|
|||
url: new_git_url,
|
||||
subdirectory: subdirectory.clone(),
|
||||
};
|
||||
let new_url = Url::from(new_parsed_url.clone());
|
||||
let new_url = DisplaySafeUrl::from(new_parsed_url.clone());
|
||||
let new_verbatim_url = apply_redirect(&url.verbatim, new_url);
|
||||
VerbatimParsedUrl {
|
||||
parsed_url: ParsedUrl::Git(new_parsed_url),
|
||||
|
@ -36,7 +35,7 @@ pub(crate) fn url_to_precise(url: VerbatimParsedUrl, git: &GitResolver) -> Verba
|
|||
|
||||
/// Given a [`VerbatimUrl`] and a redirect, apply the redirect to the URL while preserving as much
|
||||
/// of the verbatim representation as possible.
|
||||
fn apply_redirect(url: &VerbatimUrl, redirect: Url) -> VerbatimUrl {
|
||||
fn apply_redirect(url: &VerbatimUrl, redirect: DisplaySafeUrl) -> VerbatimUrl {
|
||||
let redirect = VerbatimUrl::from_url(redirect);
|
||||
|
||||
// The redirect should be the "same" URL, but with a specific commit hash added after the `@`.
|
||||
|
@ -85,9 +84,8 @@ fn apply_redirect(url: &VerbatimUrl, redirect: Url) -> VerbatimUrl {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use url::Url;
|
||||
|
||||
use uv_pep508::VerbatimUrl;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
use crate::redirect::apply_redirect;
|
||||
|
||||
|
@ -97,8 +95,9 @@ mod tests {
|
|||
// to the given representation.
|
||||
let verbatim = VerbatimUrl::parse_url("https://github.com/flask.git")?
|
||||
.with_given("git+https://github.com/flask.git");
|
||||
let redirect =
|
||||
Url::parse("https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe")?;
|
||||
let redirect = DisplaySafeUrl::parse(
|
||||
"https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe",
|
||||
)?;
|
||||
|
||||
let expected = VerbatimUrl::parse_url(
|
||||
"https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe",
|
||||
|
@ -111,8 +110,9 @@ mod tests {
|
|||
// representation.
|
||||
let verbatim = VerbatimUrl::parse_url("https://github.com/flask.git@main")?
|
||||
.with_given("git+https://${DOMAIN}.com/flask.git@main");
|
||||
let redirect =
|
||||
Url::parse("https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe")?;
|
||||
let redirect = DisplaySafeUrl::parse(
|
||||
"https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe",
|
||||
)?;
|
||||
|
||||
let expected = VerbatimUrl::parse_url(
|
||||
"https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe",
|
||||
|
@ -123,8 +123,9 @@ mod tests {
|
|||
// If there's a conflict after the `@`, discard the original representation.
|
||||
let verbatim = VerbatimUrl::parse_url("https://github.com/flask.git@main")?
|
||||
.with_given("git+https://github.com/flask.git@${TAG}");
|
||||
let redirect =
|
||||
Url::parse("https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe")?;
|
||||
let redirect = DisplaySafeUrl::parse(
|
||||
"https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe",
|
||||
)?;
|
||||
|
||||
let expected = VerbatimUrl::parse_url(
|
||||
"https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe",
|
||||
|
@ -134,7 +135,7 @@ mod tests {
|
|||
// We should preserve subdirectory fragments.
|
||||
let verbatim = VerbatimUrl::parse_url("https://github.com/flask.git#subdirectory=src")?
|
||||
.with_given("git+https://github.com/flask.git#subdirectory=src");
|
||||
let redirect = Url::parse(
|
||||
let redirect = DisplaySafeUrl::parse(
|
||||
"https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe#subdirectory=src",
|
||||
)?;
|
||||
|
||||
|
|
|
@ -290,7 +290,7 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> {
|
|||
// `# from https://pypi.org/simple`).
|
||||
if self.include_index_annotation {
|
||||
if let Some(index) = node.dist.index() {
|
||||
let url = index.redacted();
|
||||
let url = index.without_credentials();
|
||||
writeln!(f, "{}", format!(" # from {url}").green())?;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use url::Url;
|
||||
|
||||
use uv_distribution_types::{BuildableSource, VersionOrUrlRef};
|
||||
use uv_normalize::PackageName;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
pub type BuildId = usize;
|
||||
|
||||
|
@ -31,10 +30,10 @@ pub trait Reporter: Send + Sync {
|
|||
fn on_download_complete(&self, name: &PackageName, id: usize);
|
||||
|
||||
/// Callback to invoke when a repository checkout begins.
|
||||
fn on_checkout_start(&self, url: &Url, rev: &str) -> usize;
|
||||
fn on_checkout_start(&self, url: &DisplaySafeUrl, rev: &str) -> usize;
|
||||
|
||||
/// Callback to invoke when a repository checkout completes.
|
||||
fn on_checkout_complete(&self, url: &Url, rev: &str, id: usize);
|
||||
fn on_checkout_complete(&self, url: &DisplaySafeUrl, rev: &str, id: usize);
|
||||
}
|
||||
|
||||
impl dyn Reporter {
|
||||
|
@ -62,11 +61,11 @@ impl uv_distribution::Reporter for Facade {
|
|||
self.reporter.on_build_complete(source, id);
|
||||
}
|
||||
|
||||
fn on_checkout_start(&self, url: &Url, rev: &str) -> usize {
|
||||
fn on_checkout_start(&self, url: &DisplaySafeUrl, rev: &str) -> usize {
|
||||
self.reporter.on_checkout_start(url, rev)
|
||||
}
|
||||
|
||||
fn on_checkout_complete(&self, url: &Url, rev: &str, id: usize) {
|
||||
fn on_checkout_complete(&self, url: &DisplaySafeUrl, rev: &str, id: usize) {
|
||||
self.reporter.on_checkout_complete(url, rev, id);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use pubgrub::Ranges;
|
||||
use url::Url;
|
||||
|
||||
use uv_normalize::PackageName;
|
||||
use uv_pep440::Version;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_torch::TorchBackend;
|
||||
|
||||
use crate::pubgrub::{PubGrubDependency, PubGrubPackage, PubGrubPackageInner};
|
||||
|
@ -21,7 +21,7 @@ impl SystemDependency {
|
|||
/// Extract a [`SystemDependency`] from an index URL.
|
||||
///
|
||||
/// For example, given `https://download.pytorch.org/whl/cu124`, returns CUDA 12.4.
|
||||
pub(super) fn from_index(index: &Url) -> Option<Self> {
|
||||
pub(super) fn from_index(index: &DisplaySafeUrl) -> Option<Self> {
|
||||
let backend = TorchBackend::from_index(index)?;
|
||||
let cuda_version = backend.cuda_version()?;
|
||||
Some(Self {
|
||||
|
@ -51,22 +51,21 @@ impl From<SystemDependency> for PubGrubDependency {
|
|||
mod tests {
|
||||
use std::str::FromStr;
|
||||
|
||||
use url::Url;
|
||||
|
||||
use uv_normalize::PackageName;
|
||||
use uv_pep440::Version;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
use crate::resolver::system::SystemDependency;
|
||||
|
||||
#[test]
|
||||
fn pypi() {
|
||||
let url = Url::parse("https://pypi.org/simple").unwrap();
|
||||
let url = DisplaySafeUrl::parse("https://pypi.org/simple").unwrap();
|
||||
assert_eq!(SystemDependency::from_index(&url), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pytorch_cuda_12_4() {
|
||||
let url = Url::parse("https://download.pytorch.org/whl/cu124").unwrap();
|
||||
let url = DisplaySafeUrl::parse("https://download.pytorch.org/whl/cu124").unwrap();
|
||||
assert_eq!(
|
||||
SystemDependency::from_index(&url),
|
||||
Some(SystemDependency {
|
||||
|
@ -78,7 +77,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn pytorch_cpu() {
|
||||
let url = Url::parse("https://download.pytorch.org/whl/cpu").unwrap();
|
||||
let url = DisplaySafeUrl::parse("https://download.pytorch.org/whl/cpu").unwrap();
|
||||
assert_eq!(SystemDependency::from_index(&url), None);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ workspace = true
|
|||
uv-pep440 = { workspace = true }
|
||||
uv-pep508 = { workspace = true }
|
||||
uv-pypi-types = { workspace = true }
|
||||
uv-redacted = { workspace = true }
|
||||
uv-settings = { workspace = true }
|
||||
uv-workspace = { workspace = true }
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ use url::Url;
|
|||
use uv_pep440::VersionSpecifiers;
|
||||
use uv_pep508::PackageName;
|
||||
use uv_pypi_types::VerbatimParsedUrl;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_settings::{GlobalOptions, ResolverInstallerOptions};
|
||||
use uv_workspace::pyproject::Sources;
|
||||
|
||||
|
@ -25,7 +26,7 @@ pub enum Pep723Item {
|
|||
/// A PEP 723 script provided via `stdin`.
|
||||
Stdin(Pep723Metadata),
|
||||
/// A PEP 723 script provided via a remote URL.
|
||||
Remote(Pep723Metadata, Url),
|
||||
Remote(Pep723Metadata, DisplaySafeUrl),
|
||||
}
|
||||
|
||||
impl Pep723Item {
|
||||
|
|
|
@ -28,6 +28,7 @@ uv-options-metadata = { workspace = true }
|
|||
uv-pep508 = { workspace = true }
|
||||
uv-pypi-types = { workspace = true }
|
||||
uv-python = { workspace = true, features = ["schemars", "clap"] }
|
||||
uv-redacted = { workspace = true }
|
||||
uv-resolver = { workspace = true, features = ["schemars", "clap"] }
|
||||
uv-static = { workspace = true }
|
||||
uv-torch = { workspace = true, features = ["schemars", "clap"] }
|
||||
|
|
|
@ -11,6 +11,7 @@ use uv_distribution_types::{Index, IndexUrl, PipExtraIndex, PipFindLinks, PipInd
|
|||
use uv_install_wheel::LinkMode;
|
||||
use uv_pypi_types::{SchemaConflicts, SupportedEnvironments};
|
||||
use uv_python::{PythonDownloads, PythonPreference, PythonVersion};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_resolver::{AnnotationStyle, ExcludeNewer, ForkStrategy, PrereleaseMode, ResolutionMode};
|
||||
use uv_torch::TorchMode;
|
||||
|
||||
|
@ -82,6 +83,7 @@ impl_combine_or!(IndexStrategy);
|
|||
impl_combine_or!(IndexUrl);
|
||||
impl_combine_or!(KeyringProviderType);
|
||||
impl_combine_or!(LinkMode);
|
||||
impl_combine_or!(DisplaySafeUrl);
|
||||
impl_combine_or!(NonZeroUsize);
|
||||
impl_combine_or!(PathBuf);
|
||||
impl_combine_or!(PipExtraIndex);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use std::{fmt::Debug, num::NonZeroUsize, path::Path, path::PathBuf};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
use uv_cache_info::CacheKey;
|
||||
use uv_configuration::{
|
||||
|
@ -17,6 +16,7 @@ use uv_normalize::{ExtraName, PackageName, PipGroupName};
|
|||
use uv_pep508::Requirement;
|
||||
use uv_pypi_types::{SupportedEnvironments, VerbatimParsedUrl};
|
||||
use uv_python::{PythonDownloads, PythonPreference, PythonVersion};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_resolver::{AnnotationStyle, ExcludeNewer, ForkStrategy, PrereleaseMode, ResolutionMode};
|
||||
use uv_static::EnvVars;
|
||||
use uv_torch::TorchMode;
|
||||
|
@ -1837,7 +1837,7 @@ pub struct OptionsWire {
|
|||
|
||||
// #[serde(flatten)]
|
||||
// publish: PublishOptions
|
||||
publish_url: Option<Url>,
|
||||
publish_url: Option<DisplaySafeUrl>,
|
||||
trusted_publishing: Option<TrustedPublishing>,
|
||||
check_url: Option<IndexUrl>,
|
||||
|
||||
|
@ -2019,7 +2019,7 @@ pub struct PublishOptions {
|
|||
publish-url = "https://test.pypi.org/legacy/"
|
||||
"#
|
||||
)]
|
||||
pub publish_url: Option<Url>,
|
||||
pub publish_url: Option<DisplaySafeUrl>,
|
||||
|
||||
/// Configure trusted publishing via GitHub Actions.
|
||||
///
|
||||
|
|
|
@ -27,12 +27,12 @@ uv-pep440 = { workspace = true }
|
|||
uv-pep508 = { workspace = true }
|
||||
uv-pypi-types = { workspace = true }
|
||||
uv-python = { workspace = true }
|
||||
uv-redacted = { workspace = true }
|
||||
uv-workspace = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
url = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
|
|
@ -2,7 +2,6 @@ use std::str::FromStr;
|
|||
use std::sync::Arc;
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
use url::Url;
|
||||
|
||||
use uv_configuration::HashCheckingMode;
|
||||
use uv_distribution_types::{
|
||||
|
@ -12,6 +11,7 @@ use uv_distribution_types::{
|
|||
use uv_normalize::PackageName;
|
||||
use uv_pep440::Version;
|
||||
use uv_pypi_types::{HashDigest, HashDigests, HashError, ResolverMarkerEnvironment};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub enum HashStrategy {
|
||||
|
@ -76,7 +76,7 @@ impl HashStrategy {
|
|||
}
|
||||
|
||||
/// Return the [`HashPolicy`] for the given direct URL package.
|
||||
pub fn get_url(&self, url: &Url) -> HashPolicy {
|
||||
pub fn get_url(&self, url: &DisplaySafeUrl) -> HashPolicy {
|
||||
match self {
|
||||
Self::None => HashPolicy::None,
|
||||
Self::Generate(mode) => HashPolicy::Generate(*mode),
|
||||
|
@ -109,7 +109,7 @@ impl HashStrategy {
|
|||
}
|
||||
|
||||
/// Returns `true` if the given direct URL package is allowed.
|
||||
pub fn allows_url(&self, url: &Url) -> bool {
|
||||
pub fn allows_url(&self, url: &DisplaySafeUrl) -> bool {
|
||||
match self {
|
||||
Self::None => true,
|
||||
Self::Generate(_) => true,
|
||||
|
|
|
@ -27,6 +27,7 @@ uv-options-metadata = { workspace = true }
|
|||
uv-pep440 = { workspace = true }
|
||||
uv-pep508 = { workspace = true }
|
||||
uv-pypi-types = { workspace = true }
|
||||
uv-redacted = { workspace = true }
|
||||
uv-static = { workspace = true }
|
||||
uv-warnings = { workspace = true }
|
||||
|
||||
|
@ -42,7 +43,6 @@ tokio = { workspace = true }
|
|||
toml = { workspace = true }
|
||||
toml_edit = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
url = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
|
@ -52,7 +52,7 @@ regex = { workspace = true }
|
|||
tempfile = { workspace = true }
|
||||
|
||||
[features]
|
||||
schemars = ["dep:schemars", "uv-pypi-types/schemars"]
|
||||
schemars = ["dep:schemars", "uv-pypi-types/schemars", "uv-redacted/schemars"]
|
||||
|
||||
[package.metadata.cargo-shear]
|
||||
ignored = ["uv-options-metadata"]
|
||||
|
|
|
@ -17,7 +17,6 @@ use owo_colors::OwoColorize;
|
|||
use rustc_hash::{FxBuildHasher, FxHashSet};
|
||||
use serde::{Deserialize, Deserializer, Serialize, de::IntoDeserializer, de::SeqAccess};
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
use uv_build_backend::BuildBackendSettings;
|
||||
use uv_distribution_types::{Index, IndexName, RequirementSource};
|
||||
use uv_fs::{PortablePathBuf, relative_to};
|
||||
|
@ -29,6 +28,7 @@ use uv_pep508::MarkerTree;
|
|||
use uv_pypi_types::{
|
||||
Conflicts, DependencyGroups, SchemaConflicts, SupportedEnvironments, VerbatimParsedUrl,
|
||||
};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum PyprojectTomlError {
|
||||
|
@ -891,7 +891,7 @@ pub enum Source {
|
|||
/// ```
|
||||
Git {
|
||||
/// The repository URL (without the `git+` prefix).
|
||||
git: Url,
|
||||
git: DisplaySafeUrl,
|
||||
/// The path to the directory with the `pyproject.toml`, if it's not in the archive root.
|
||||
subdirectory: Option<PortablePathBuf>,
|
||||
// Only one of the three may be used; we'll validate this later and emit a custom error.
|
||||
|
@ -915,7 +915,7 @@ pub enum Source {
|
|||
/// flask = { url = "https://files.pythonhosted.org/packages/61/80/ffe1da13ad9300f87c93af113edd0638c75138c42a0994becfacac078c06/flask-3.0.3-py3-none-any.whl" }
|
||||
/// ```
|
||||
Url {
|
||||
url: Url,
|
||||
url: DisplaySafeUrl,
|
||||
/// For source distributions, the path to the directory with the `pyproject.toml`, if it's
|
||||
/// not in the archive root.
|
||||
subdirectory: Option<PortablePathBuf>,
|
||||
|
@ -989,12 +989,12 @@ impl<'de> Deserialize<'de> for Source {
|
|||
#[derive(Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
||||
struct CatchAll {
|
||||
git: Option<Url>,
|
||||
git: Option<DisplaySafeUrl>,
|
||||
subdirectory: Option<PortablePathBuf>,
|
||||
rev: Option<String>,
|
||||
tag: Option<String>,
|
||||
branch: Option<String>,
|
||||
url: Option<Url>,
|
||||
url: Option<DisplaySafeUrl>,
|
||||
path: Option<PortablePathBuf>,
|
||||
editable: Option<bool>,
|
||||
package: Option<bool>,
|
||||
|
@ -1083,7 +1083,7 @@ impl<'de> Deserialize<'de> for Source {
|
|||
|
||||
// If the user prefixed the URL with `git+`, strip it.
|
||||
let git = if let Some(git) = git.as_str().strip_prefix("git+") {
|
||||
Url::parse(git).map_err(serde::de::Error::custom)?
|
||||
DisplaySafeUrl::parse(git).map_err(serde::de::Error::custom)?
|
||||
} else {
|
||||
git
|
||||
};
|
||||
|
|
|
@ -7,7 +7,6 @@ use thiserror::Error;
|
|||
use toml_edit::{
|
||||
Array, ArrayOfTables, DocumentMut, Formatted, Item, RawString, Table, TomlError, Value,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
use uv_cache_key::CanonicalUrl;
|
||||
use uv_distribution_types::Index;
|
||||
|
@ -15,6 +14,7 @@ use uv_fs::PortablePath;
|
|||
use uv_normalize::GroupName;
|
||||
use uv_pep440::{Version, VersionParseError, VersionSpecifier, VersionSpecifiers};
|
||||
use uv_pep508::{ExtraName, MarkerTree, PackageName, Requirement, VersionOrUrl};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
use crate::pyproject::{DependencyType, Source};
|
||||
|
||||
|
@ -171,6 +171,7 @@ impl PyProjectTomlMut {
|
|||
&mut self,
|
||||
req: &Requirement,
|
||||
source: Option<&Source>,
|
||||
raw: bool,
|
||||
) -> Result<ArrayEdit, Error> {
|
||||
// Get or create `project.dependencies`.
|
||||
let dependencies = self
|
||||
|
@ -180,7 +181,7 @@ impl PyProjectTomlMut {
|
|||
.as_array_mut()
|
||||
.ok_or(Error::MalformedDependencies)?;
|
||||
|
||||
let edit = add_dependency(req, dependencies, source.is_some())?;
|
||||
let edit = add_dependency(req, dependencies, source.is_some(), raw)?;
|
||||
|
||||
if let Some(source) = source {
|
||||
self.add_source(&req.name, source)?;
|
||||
|
@ -196,6 +197,7 @@ impl PyProjectTomlMut {
|
|||
&mut self,
|
||||
req: &Requirement,
|
||||
source: Option<&Source>,
|
||||
raw: bool,
|
||||
) -> Result<ArrayEdit, Error> {
|
||||
// Get or create `tool.uv.dev-dependencies`.
|
||||
let dev_dependencies = self
|
||||
|
@ -213,7 +215,7 @@ impl PyProjectTomlMut {
|
|||
.as_array_mut()
|
||||
.ok_or(Error::MalformedDependencies)?;
|
||||
|
||||
let edit = add_dependency(req, dev_dependencies, source.is_some())?;
|
||||
let edit = add_dependency(req, dev_dependencies, source.is_some(), raw)?;
|
||||
|
||||
if let Some(source) = source {
|
||||
self.add_source(&req.name, source)?;
|
||||
|
@ -267,7 +269,7 @@ impl PyProjectTomlMut {
|
|||
if table
|
||||
.get("url")
|
||||
.and_then(|item| item.as_str())
|
||||
.and_then(|url| Url::parse(url).ok())
|
||||
.and_then(|url| DisplaySafeUrl::parse(url).ok())
|
||||
.is_some_and(|url| {
|
||||
CanonicalUrl::new(&url) == CanonicalUrl::new(index.url.url())
|
||||
})
|
||||
|
@ -304,10 +306,10 @@ impl PyProjectTomlMut {
|
|||
if table
|
||||
.get("url")
|
||||
.and_then(|item| item.as_str())
|
||||
.and_then(|url| Url::parse(url).ok())
|
||||
.and_then(|url| DisplaySafeUrl::parse(url).ok())
|
||||
.is_none_or(|url| CanonicalUrl::new(&url) != CanonicalUrl::new(index.url.url()))
|
||||
{
|
||||
let mut formatted = Formatted::new(index.url.redacted().to_string());
|
||||
let mut formatted = Formatted::new(index.url.without_credentials().to_string());
|
||||
if let Some(value) = table.get("url").and_then(Item::as_value) {
|
||||
if let Some(prefix) = value.decor().prefix() {
|
||||
formatted.decor_mut().set_prefix(prefix.clone());
|
||||
|
@ -365,7 +367,7 @@ impl PyProjectTomlMut {
|
|||
if table
|
||||
.get("url")
|
||||
.and_then(|item| item.as_str())
|
||||
.and_then(|url| Url::parse(url).ok())
|
||||
.and_then(|url| DisplaySafeUrl::parse(url).ok())
|
||||
.is_some_and(|url| CanonicalUrl::new(&url) == CanonicalUrl::new(index.url.url()))
|
||||
{
|
||||
return false;
|
||||
|
@ -400,6 +402,7 @@ impl PyProjectTomlMut {
|
|||
group: &ExtraName,
|
||||
req: &Requirement,
|
||||
source: Option<&Source>,
|
||||
raw: bool,
|
||||
) -> Result<ArrayEdit, Error> {
|
||||
// Get or create `project.optional-dependencies`.
|
||||
let optional_dependencies = self
|
||||
|
@ -428,7 +431,7 @@ impl PyProjectTomlMut {
|
|||
.as_array_mut()
|
||||
.ok_or(Error::MalformedDependencies)?;
|
||||
|
||||
let added = add_dependency(req, group, source.is_some())?;
|
||||
let added = add_dependency(req, group, source.is_some(), raw)?;
|
||||
|
||||
// If `project.optional-dependencies` is an inline table, reformat it.
|
||||
//
|
||||
|
@ -457,6 +460,7 @@ impl PyProjectTomlMut {
|
|||
group: &GroupName,
|
||||
req: &Requirement,
|
||||
source: Option<&Source>,
|
||||
raw: bool,
|
||||
) -> Result<ArrayEdit, Error> {
|
||||
// Get or create `dependency-groups`.
|
||||
let dependency_groups = self
|
||||
|
@ -492,7 +496,7 @@ impl PyProjectTomlMut {
|
|||
.as_array_mut()
|
||||
.ok_or(Error::MalformedDependencies)?;
|
||||
|
||||
let added = add_dependency(req, group, source.is_some())?;
|
||||
let added = add_dependency(req, group, source.is_some(), raw)?;
|
||||
|
||||
// To avoid churn in pyproject.toml, we only sort new group keys if the
|
||||
// existing keys were sorted.
|
||||
|
@ -999,6 +1003,7 @@ pub fn add_dependency(
|
|||
req: &Requirement,
|
||||
deps: &mut Array,
|
||||
has_source: bool,
|
||||
raw: bool,
|
||||
) -> Result<ArrayEdit, Error> {
|
||||
let mut to_replace = find_dependencies(&req.name, Some(&req.marker), deps);
|
||||
|
||||
|
@ -1057,7 +1062,11 @@ pub fn add_dependency(
|
|||
Sort::Unsorted
|
||||
};
|
||||
|
||||
let req_string = req.to_string();
|
||||
let req_string = if raw {
|
||||
req.displayable_with_credentials().to_string()
|
||||
} else {
|
||||
req.to_string()
|
||||
};
|
||||
let index = match sort {
|
||||
Sort::CaseInsensitive => deps.iter().position(|dep| {
|
||||
dep.as_str().is_some_and(|dep| {
|
||||
|
|
|
@ -24,7 +24,7 @@ use uv_dispatch::BuildDispatch;
|
|||
use uv_distribution::DistributionDatabase;
|
||||
use uv_distribution_types::{
|
||||
Index, IndexName, IndexUrls, NameRequirementSpecification, Requirement, RequirementSource,
|
||||
UnresolvedRequirement, VersionId, redact_credentials,
|
||||
UnresolvedRequirement, VersionId,
|
||||
};
|
||||
use uv_fs::Simplified;
|
||||
use uv_git::GIT_STORE;
|
||||
|
@ -33,6 +33,7 @@ use uv_normalize::{DEV_DEPENDENCIES, DefaultExtras, PackageName};
|
|||
use uv_pep508::{ExtraName, MarkerTree, UnnamedRequirement, VersionOrUrl};
|
||||
use uv_pypi_types::{ParsedUrl, VerbatimParsedUrl};
|
||||
use uv_python::{Interpreter, PythonDownloads, PythonEnvironment, PythonPreference, PythonRequest};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_requirements::{NamedRequirementsResolver, RequirementsSource, RequirementsSpecification};
|
||||
use uv_resolver::FlatIndex;
|
||||
use uv_scripts::{Pep723ItemRef, Pep723Metadata, Pep723Script};
|
||||
|
@ -627,7 +628,7 @@ fn edits(
|
|||
}
|
||||
};
|
||||
|
||||
// Redact any credentials. By default, we avoid writing sensitive credentials to files that
|
||||
// Remove any credentials. By default, we avoid writing sensitive credentials to files that
|
||||
// will be checked into version control (e.g., `pyproject.toml` and `uv.lock`). Instead,
|
||||
// we store the credentials in a global store, and reuse them during resolution. The
|
||||
// expectation is that subsequent resolutions steps will succeed by reading from (e.g.) the
|
||||
|
@ -649,7 +650,7 @@ fn edits(
|
|||
GIT_STORE.insert(RepositoryUrl::new(&git), credentials);
|
||||
|
||||
// Redact the credentials.
|
||||
redact_credentials(&mut git);
|
||||
git.remove_credentials();
|
||||
}
|
||||
Some(Source::Git {
|
||||
git,
|
||||
|
@ -705,13 +706,15 @@ fn edits(
|
|||
|
||||
// Update the `pyproject.toml`.
|
||||
let edit = match &dependency_type {
|
||||
DependencyType::Production => toml.add_dependency(&requirement, source.as_ref())?,
|
||||
DependencyType::Dev => toml.add_dev_dependency(&requirement, source.as_ref())?,
|
||||
DependencyType::Production => {
|
||||
toml.add_dependency(&requirement, source.as_ref(), raw)?
|
||||
}
|
||||
DependencyType::Dev => toml.add_dev_dependency(&requirement, source.as_ref(), raw)?,
|
||||
DependencyType::Optional(extra) => {
|
||||
toml.add_optional_dependency(extra, &requirement, source.as_ref())?
|
||||
toml.add_optional_dependency(extra, &requirement, source.as_ref(), raw)?
|
||||
}
|
||||
DependencyType::Group(group) => {
|
||||
toml.add_dependency_group_requirement(group, &requirement, source.as_ref())?
|
||||
toml.add_dependency_group_requirement(group, &requirement, source.as_ref(), raw)?
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -863,6 +866,7 @@ async fn lock_and_sync(
|
|||
// Invalidate the project metadata.
|
||||
if let AddTarget::Project(VirtualProject::Project(ref project), _) = target {
|
||||
let url = Url::from_file_path(project.project_root())
|
||||
.map(DisplaySafeUrl::from)
|
||||
.expect("project root is a valid URL");
|
||||
let version_id = VersionId::from_url(&url);
|
||||
let existing = lock_state.index().distributions().remove(&version_id);
|
||||
|
|
|
@ -30,6 +30,7 @@ use uv_python::{
|
|||
PythonInstallation, PythonPreference, PythonRequest, PythonVersionFile,
|
||||
VersionFileDiscoveryOptions,
|
||||
};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_requirements::{RequirementsSource, RequirementsSpecification};
|
||||
use uv_resolver::{Installable, Lock, Preference};
|
||||
use uv_scripts::Pep723Item;
|
||||
|
@ -1198,7 +1199,7 @@ pub(crate) enum RunCommand {
|
|||
/// Execute a `pythonw` script provided via `stdin`.
|
||||
PythonGuiStdin(Vec<u8>, Vec<OsString>),
|
||||
/// Execute a Python script provided via a remote URL.
|
||||
PythonRemote(Url, tempfile::NamedTempFile, Vec<OsString>),
|
||||
PythonRemote(DisplaySafeUrl, tempfile::NamedTempFile, Vec<OsString>),
|
||||
/// Execute an external command.
|
||||
External(OsString, Vec<OsString>),
|
||||
/// Execute an empty command (in practice, `python` with no arguments).
|
||||
|
@ -1464,7 +1465,7 @@ impl RunCommand {
|
|||
// We don't do this check on Windows since the file path would
|
||||
// be invalid anyway, and thus couldn't refer to a local file.
|
||||
if !cfg!(unix) || matches!(target_path.try_exists(), Ok(false)) {
|
||||
let url = Url::parse(&target.to_string_lossy())?;
|
||||
let url = DisplaySafeUrl::parse(&target.to_string_lossy())?;
|
||||
|
||||
let file_stem = url
|
||||
.path_segments()
|
||||
|
@ -1481,7 +1482,11 @@ impl RunCommand {
|
|||
.native_tls(network_settings.native_tls)
|
||||
.allow_insecure_host(network_settings.allow_insecure_host.clone())
|
||||
.build();
|
||||
let response = client.for_host(&url).get(url.clone()).send().await?;
|
||||
let response = client
|
||||
.for_host(&url)
|
||||
.get(Url::from(url.clone()))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
// Stream the response to the file.
|
||||
let mut writer = file.as_file();
|
||||
|
|
|
@ -8,7 +8,6 @@ use console::Term;
|
|||
use owo_colors::OwoColorize;
|
||||
use tokio::sync::Semaphore;
|
||||
use tracing::{debug, info};
|
||||
use url::Url;
|
||||
use uv_auth::Credentials;
|
||||
use uv_cache::Cache;
|
||||
use uv_client::{AuthIntegration, BaseClient, BaseClientBuilder, RegistryClientBuilder};
|
||||
|
@ -17,6 +16,7 @@ use uv_distribution_types::{Index, IndexCapabilities, IndexLocations, IndexUrl};
|
|||
use uv_publish::{
|
||||
CheckUrlClient, TrustedPublishResult, check_trusted_publishing, files_for_publishing, upload,
|
||||
};
|
||||
use uv_redacted::{DisplaySafeUrl, DisplaySafeUrlRef};
|
||||
use uv_warnings::warn_user_once;
|
||||
|
||||
use crate::commands::reporters::PublishReporter;
|
||||
|
@ -26,7 +26,7 @@ use crate::settings::NetworkSettings;
|
|||
|
||||
pub(crate) async fn publish(
|
||||
paths: Vec<String>,
|
||||
publish_url: Url,
|
||||
publish_url: DisplaySafeUrl,
|
||||
trusted_publishing: TrustedPublishing,
|
||||
keyring_provider: KeyringProviderType,
|
||||
network_settings: &NetworkSettings,
|
||||
|
@ -196,7 +196,7 @@ enum Prompt {
|
|||
///
|
||||
/// Returns the publish URL, the username and the password.
|
||||
async fn gather_credentials(
|
||||
mut publish_url: Url,
|
||||
mut publish_url: DisplaySafeUrl,
|
||||
mut username: Option<String>,
|
||||
mut password: Option<String>,
|
||||
trusted_publishing: TrustedPublishing,
|
||||
|
@ -205,7 +205,7 @@ async fn gather_credentials(
|
|||
check_url: Option<&IndexUrl>,
|
||||
prompt: Prompt,
|
||||
printer: Printer,
|
||||
) -> Result<(Url, Credentials)> {
|
||||
) -> Result<(DisplaySafeUrl, Credentials)> {
|
||||
// Support reading username and password from the URL, for symmetry with the index API.
|
||||
if let Some(url_password) = publish_url.password() {
|
||||
if password.is_some_and(|password| password != url_password) {
|
||||
|
@ -296,7 +296,7 @@ async fn gather_credentials(
|
|||
if let Some(username) = &username {
|
||||
debug!("Fetching password from keyring");
|
||||
if let Some(keyring_password) = keyring_provider
|
||||
.fetch(&publish_url, Some(username))
|
||||
.fetch(&DisplaySafeUrlRef::from(&publish_url), Some(username))
|
||||
.await
|
||||
.as_ref()
|
||||
.and_then(|credentials| credentials.password())
|
||||
|
@ -342,13 +342,14 @@ mod tests {
|
|||
use std::str::FromStr;
|
||||
|
||||
use insta::assert_snapshot;
|
||||
use url::Url;
|
||||
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
async fn get_credentials(
|
||||
url: Url,
|
||||
url: DisplaySafeUrl,
|
||||
username: Option<String>,
|
||||
password: Option<String>,
|
||||
) -> Result<(Url, Credentials)> {
|
||||
) -> Result<(DisplaySafeUrl, Credentials)> {
|
||||
let client = BaseClientBuilder::new().build();
|
||||
gather_credentials(
|
||||
url,
|
||||
|
@ -366,10 +367,10 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn username_password_sources() {
|
||||
let example_url = Url::from_str("https://example.com").unwrap();
|
||||
let example_url_username = Url::from_str("https://ferris@example.com").unwrap();
|
||||
let example_url = DisplaySafeUrl::from_str("https://example.com").unwrap();
|
||||
let example_url_username = DisplaySafeUrl::from_str("https://ferris@example.com").unwrap();
|
||||
let example_url_username_password =
|
||||
Url::from_str("https://ferris:f3rr1s@example.com").unwrap();
|
||||
DisplaySafeUrl::from_str("https://ferris:f3rr1s@example.com").unwrap();
|
||||
|
||||
let (publish_url, credentials) = get_credentials(example_url.clone(), None, None)
|
||||
.await
|
||||
|
|
|
@ -8,8 +8,6 @@ use std::time::Duration;
|
|||
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
|
||||
use owo_colors::OwoColorize;
|
||||
use rustc_hash::FxHashMap;
|
||||
use url::Url;
|
||||
use uv_redacted::redacted_url;
|
||||
|
||||
use crate::commands::human_readable_bytes;
|
||||
use crate::printer::Printer;
|
||||
|
@ -20,6 +18,7 @@ use uv_distribution_types::{
|
|||
use uv_normalize::PackageName;
|
||||
use uv_pep440::Version;
|
||||
use uv_python::PythonInstallationKey;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_static::EnvVars;
|
||||
|
||||
/// Since downloads, fetches and builds run in parallel, their message output order is
|
||||
|
@ -359,8 +358,7 @@ impl ProgressReporter {
|
|||
self.on_request_start(Direction::Upload, name, size)
|
||||
}
|
||||
|
||||
fn on_checkout_start(&self, url: &Url, rev: &str) -> usize {
|
||||
let url = redacted_url(url);
|
||||
fn on_checkout_start(&self, url: &DisplaySafeUrl, rev: &str) -> usize {
|
||||
let ProgressMode::Multi {
|
||||
multi_progress,
|
||||
state,
|
||||
|
@ -390,8 +388,7 @@ impl ProgressReporter {
|
|||
id
|
||||
}
|
||||
|
||||
fn on_checkout_complete(&self, url: &Url, rev: &str, id: usize) {
|
||||
let url = redacted_url(url);
|
||||
fn on_checkout_complete(&self, url: &DisplaySafeUrl, rev: &str, id: usize) {
|
||||
let ProgressMode::Multi {
|
||||
state,
|
||||
multi_progress,
|
||||
|
@ -481,11 +478,11 @@ impl uv_installer::PrepareReporter for PrepareReporter {
|
|||
self.reporter.on_download_complete(id);
|
||||
}
|
||||
|
||||
fn on_checkout_start(&self, url: &Url, rev: &str) -> usize {
|
||||
fn on_checkout_start(&self, url: &DisplaySafeUrl, rev: &str) -> usize {
|
||||
self.reporter.on_checkout_start(url, rev)
|
||||
}
|
||||
|
||||
fn on_checkout_complete(&self, url: &Url, rev: &str, id: usize) {
|
||||
fn on_checkout_complete(&self, url: &DisplaySafeUrl, rev: &str, id: usize) {
|
||||
self.reporter.on_checkout_complete(url, rev, id);
|
||||
}
|
||||
}
|
||||
|
@ -545,11 +542,11 @@ impl uv_resolver::ResolverReporter for ResolverReporter {
|
|||
self.reporter.on_build_complete(source, id);
|
||||
}
|
||||
|
||||
fn on_checkout_start(&self, url: &Url, rev: &str) -> usize {
|
||||
fn on_checkout_start(&self, url: &DisplaySafeUrl, rev: &str) -> usize {
|
||||
self.reporter.on_checkout_start(url, rev)
|
||||
}
|
||||
|
||||
fn on_checkout_complete(&self, url: &Url, rev: &str, id: usize) {
|
||||
fn on_checkout_complete(&self, url: &DisplaySafeUrl, rev: &str, id: usize) {
|
||||
self.reporter.on_checkout_complete(url, rev, id);
|
||||
}
|
||||
|
||||
|
@ -587,11 +584,11 @@ impl uv_distribution::Reporter for ResolverReporter {
|
|||
self.reporter.on_download_complete(id);
|
||||
}
|
||||
|
||||
fn on_checkout_start(&self, url: &Url, rev: &str) -> usize {
|
||||
fn on_checkout_start(&self, url: &DisplaySafeUrl, rev: &str) -> usize {
|
||||
self.reporter.on_checkout_start(url, rev)
|
||||
}
|
||||
|
||||
fn on_checkout_complete(&self, url: &Url, rev: &str, id: usize) {
|
||||
fn on_checkout_complete(&self, url: &DisplaySafeUrl, rev: &str, id: usize) {
|
||||
self.reporter.on_checkout_complete(url, rev, id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,6 @@ use std::path::PathBuf;
|
|||
use std::process;
|
||||
use std::str::FromStr;
|
||||
|
||||
use url::Url;
|
||||
|
||||
use uv_cache::{CacheArgs, Refresh};
|
||||
use uv_cli::comma::CommaSeparatedRequirements;
|
||||
use uv_cli::{
|
||||
|
@ -35,6 +33,7 @@ use uv_normalize::{PackageName, PipGroupName};
|
|||
use uv_pep508::{ExtraName, MarkerTree, RequirementOrigin};
|
||||
use uv_pypi_types::SupportedEnvironments;
|
||||
use uv_python::{Prefix, PythonDownloads, PythonPreference, PythonVersion, Target};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_resolver::{
|
||||
AnnotationStyle, DependencyMode, ExcludeNewer, ForkStrategy, PrereleaseMode, ResolutionMode,
|
||||
};
|
||||
|
@ -3162,7 +3161,7 @@ pub(crate) struct PublishSettings {
|
|||
pub(crate) index: Option<String>,
|
||||
|
||||
// Both CLI and configuration.
|
||||
pub(crate) publish_url: Url,
|
||||
pub(crate) publish_url: DisplaySafeUrl,
|
||||
pub(crate) trusted_publishing: TrustedPublishing,
|
||||
pub(crate) keyring_provider: KeyringProviderType,
|
||||
pub(crate) check_url: Option<IndexUrl>,
|
||||
|
@ -3207,7 +3206,7 @@ impl PublishSettings {
|
|||
publish_url: args
|
||||
.publish_url
|
||||
.combine(publish_url)
|
||||
.unwrap_or_else(|| Url::parse(PYPI_PUBLISH_URL).unwrap()),
|
||||
.unwrap_or_else(|| DisplaySafeUrl::parse(PYPI_PUBLISH_URL).unwrap()),
|
||||
trusted_publishing: trusted_publishing
|
||||
.combine(args.trusted_publishing)
|
||||
.unwrap_or_default(),
|
||||
|
|
|
@ -1622,9 +1622,13 @@ pub async fn download_to_disk(url: &str, path: &Path) {
|
|||
let client = uv_client::BaseClientBuilder::new()
|
||||
.allow_insecure_host(trusted_hosts)
|
||||
.build();
|
||||
let url: reqwest::Url = url.parse().unwrap();
|
||||
let url = url.parse().unwrap();
|
||||
let client = client.for_host(&url);
|
||||
let response = client.request(http::Method::GET, url).send().await.unwrap();
|
||||
let response = client
|
||||
.request(http::Method::GET, reqwest::Url::from(url))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut file = tokio::fs::File::create(path).await.unwrap();
|
||||
let mut stream = response.bytes_stream();
|
||||
|
|
|
@ -405,6 +405,8 @@ fn add_git_private_source() -> Result<()> {
|
|||
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#"
|
||||
|
@ -415,7 +417,7 @@ fn add_git_private_raw() -> Result<()> {
|
|||
dependencies = []
|
||||
"#})?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.add().arg(format!("uv-private-pypackage @ git+https://{token}@github.com/astral-test/uv-private-pypackage")).arg("--raw-sources"), @r"
|
||||
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 -----
|
||||
|
@ -429,16 +431,11 @@ fn add_git_private_raw() -> Result<()> {
|
|||
|
||||
let pyproject_toml = context.read("pyproject.toml");
|
||||
|
||||
let filters: Vec<_> = [(token.as_str(), "***")]
|
||||
.into_iter()
|
||||
.chain(context.filters())
|
||||
.collect();
|
||||
|
||||
insta::with_settings!({
|
||||
filters => filters
|
||||
filters => filters.clone()
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
pyproject_toml, @r###"
|
||||
pyproject_toml, @r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
|
@ -446,14 +443,14 @@ fn add_git_private_raw() -> Result<()> {
|
|||
dependencies = [
|
||||
"uv-private-pypackage @ git+https://***@github.com/astral-test/uv-private-pypackage",
|
||||
]
|
||||
"###
|
||||
"#
|
||||
);
|
||||
});
|
||||
|
||||
let lock = context.read("uv.lock");
|
||||
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
filters => filters.clone(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
lock, @r#"
|
||||
|
@ -484,7 +481,7 @@ fn add_git_private_raw() -> Result<()> {
|
|||
});
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r"
|
||||
uv_snapshot!(filters, context.sync().arg("--frozen"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
|
|
@ -7983,11 +7983,6 @@ fn lock_redact_git_pep508() -> Result<()> {
|
|||
let context = TestContext::new("3.12").with_filtered_link_mode_warning();
|
||||
let token = decode_token(common::READ_ONLY_GITHUB_TOKEN);
|
||||
|
||||
let filters: Vec<_> = [(token.as_str(), "***")]
|
||||
.into_iter()
|
||||
.chain(context.filters())
|
||||
.collect();
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(&formatdoc! {
|
||||
r#"
|
||||
|
@ -8000,7 +7995,7 @@ fn lock_redact_git_pep508() -> Result<()> {
|
|||
token = token,
|
||||
})?;
|
||||
|
||||
uv_snapshot!(&filters, context.lock(), @r###"
|
||||
uv_snapshot!(&context.filters(), context.lock(), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -8012,7 +8007,7 @@ fn lock_redact_git_pep508() -> Result<()> {
|
|||
let lock = context.read("uv.lock");
|
||||
|
||||
insta::with_settings!({
|
||||
filters => filters.clone(),
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
lock, @r#"
|
||||
|
@ -8043,7 +8038,7 @@ fn lock_redact_git_pep508() -> Result<()> {
|
|||
});
|
||||
|
||||
// Re-run with `--locked`.
|
||||
uv_snapshot!(&filters, context.lock().arg("--locked"), @r###"
|
||||
uv_snapshot!(&context.filters(), context.lock().arg("--locked"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -8053,7 +8048,7 @@ fn lock_redact_git_pep508() -> Result<()> {
|
|||
"###);
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(&filters, context.sync().arg("--frozen").arg("--reinstall").arg("--no-cache"), @r###"
|
||||
uv_snapshot!(&context.filters(), context.sync().arg("--frozen").arg("--reinstall").arg("--no-cache"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -8073,11 +8068,6 @@ fn lock_redact_git_sources() -> Result<()> {
|
|||
let context = TestContext::new("3.12").with_filtered_link_mode_warning();
|
||||
let token = decode_token(common::READ_ONLY_GITHUB_TOKEN);
|
||||
|
||||
let filters: Vec<_> = [(token.as_str(), "***")]
|
||||
.into_iter()
|
||||
.chain(context.filters())
|
||||
.collect();
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(&formatdoc! {
|
||||
r#"
|
||||
|
@ -8093,7 +8083,7 @@ fn lock_redact_git_sources() -> Result<()> {
|
|||
token = token,
|
||||
})?;
|
||||
|
||||
uv_snapshot!(&filters, context.lock(), @r###"
|
||||
uv_snapshot!(&context.filters(), context.lock(), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -8105,7 +8095,7 @@ fn lock_redact_git_sources() -> Result<()> {
|
|||
let lock = context.read("uv.lock");
|
||||
|
||||
insta::with_settings!({
|
||||
filters => filters.clone(),
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
lock, @r#"
|
||||
|
@ -8136,7 +8126,7 @@ fn lock_redact_git_sources() -> Result<()> {
|
|||
});
|
||||
|
||||
// Re-run with `--locked`.
|
||||
uv_snapshot!(&filters, context.lock().arg("--locked"), @r###"
|
||||
uv_snapshot!(&context.filters(), context.lock().arg("--locked"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -8146,7 +8136,7 @@ fn lock_redact_git_sources() -> Result<()> {
|
|||
"###);
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(&filters, context.sync().arg("--frozen").arg("--reinstall").arg("--no-cache"), @r###"
|
||||
uv_snapshot!(&context.filters(), context.sync().arg("--frozen").arg("--reinstall").arg("--no-cache"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -8166,11 +8156,6 @@ fn lock_redact_git_pep508_non_project() -> Result<()> {
|
|||
let context = TestContext::new("3.12").with_filtered_link_mode_warning();
|
||||
let token = decode_token(common::READ_ONLY_GITHUB_TOKEN);
|
||||
|
||||
let filters: Vec<_> = [(token.as_str(), "***")]
|
||||
.into_iter()
|
||||
.chain(context.filters())
|
||||
.collect();
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(&formatdoc! {
|
||||
r#"
|
||||
|
@ -8183,7 +8168,7 @@ fn lock_redact_git_pep508_non_project() -> Result<()> {
|
|||
token = token,
|
||||
})?;
|
||||
|
||||
uv_snapshot!(&filters, context.lock(), @r###"
|
||||
uv_snapshot!(&context.filters(), context.lock(), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -8196,7 +8181,7 @@ fn lock_redact_git_pep508_non_project() -> Result<()> {
|
|||
let lock = context.read("uv.lock");
|
||||
|
||||
insta::with_settings!({
|
||||
filters => filters.clone(),
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
lock, @r#"
|
||||
|
@ -8221,7 +8206,7 @@ fn lock_redact_git_pep508_non_project() -> Result<()> {
|
|||
});
|
||||
|
||||
// Re-run with `--locked`.
|
||||
uv_snapshot!(&filters, context.lock().arg("--locked"), @r###"
|
||||
uv_snapshot!(&context.filters(), context.lock().arg("--locked"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -8232,7 +8217,7 @@ fn lock_redact_git_pep508_non_project() -> Result<()> {
|
|||
"###);
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(&filters, context.sync().arg("--frozen").arg("--reinstall").arg("--no-cache"), @r###"
|
||||
uv_snapshot!(&context.filters(), context.sync().arg("--frozen").arg("--reinstall").arg("--no-cache"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -8249,12 +8234,6 @@ fn lock_redact_git_pep508_non_project() -> Result<()> {
|
|||
#[test]
|
||||
fn lock_redact_index_sources() -> Result<()> {
|
||||
let context = TestContext::new("3.12").with_filtered_link_mode_warning();
|
||||
let token = decode_token(common::READ_ONLY_GITHUB_TOKEN);
|
||||
|
||||
let filters: Vec<_> = [(token.as_str(), "***")]
|
||||
.into_iter()
|
||||
.chain(context.filters())
|
||||
.collect();
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
|
@ -8274,7 +8253,7 @@ fn lock_redact_index_sources() -> Result<()> {
|
|||
"#,
|
||||
)?;
|
||||
|
||||
uv_snapshot!(&filters, context.lock(), @r###"
|
||||
uv_snapshot!(&context.filters(), context.lock(), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -8286,7 +8265,7 @@ fn lock_redact_index_sources() -> Result<()> {
|
|||
let lock = context.read("uv.lock");
|
||||
|
||||
insta::with_settings!({
|
||||
filters => filters.clone(),
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
lock, @r#"
|
||||
|
@ -8321,7 +8300,7 @@ fn lock_redact_index_sources() -> Result<()> {
|
|||
});
|
||||
|
||||
// Re-run with `--locked`.
|
||||
uv_snapshot!(&filters, context.lock().arg("--locked"), @r###"
|
||||
uv_snapshot!(&context.filters(), context.lock().arg("--locked"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -8331,7 +8310,7 @@ fn lock_redact_index_sources() -> Result<()> {
|
|||
"###);
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(&filters, context.sync().arg("--frozen").arg("--reinstall").arg("--no-cache"), @r###"
|
||||
uv_snapshot!(&context.filters(), context.sync().arg("--frozen").arg("--reinstall").arg("--no-cache"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -8348,12 +8327,6 @@ fn lock_redact_index_sources() -> Result<()> {
|
|||
#[test]
|
||||
fn lock_redact_url_sources() -> Result<()> {
|
||||
let context = TestContext::new("3.12").with_filtered_link_mode_warning();
|
||||
let token = decode_token(common::READ_ONLY_GITHUB_TOKEN);
|
||||
|
||||
let filters: Vec<_> = [(token.as_str(), "***")]
|
||||
.into_iter()
|
||||
.chain(context.filters())
|
||||
.collect();
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(r#"
|
||||
|
@ -8367,7 +8340,7 @@ fn lock_redact_url_sources() -> Result<()> {
|
|||
iniconfig = { url = "https://public:heron@pypi-proxy.fly.dev/basic-auth/files/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl" }
|
||||
"#)?;
|
||||
|
||||
uv_snapshot!(&filters, context.lock(), @r###"
|
||||
uv_snapshot!(&context.filters(), context.lock(), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -8379,7 +8352,7 @@ fn lock_redact_url_sources() -> Result<()> {
|
|||
let lock = context.read("uv.lock");
|
||||
|
||||
insta::with_settings!({
|
||||
filters => filters.clone(),
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
lock, @r#"
|
||||
|
@ -8413,7 +8386,7 @@ fn lock_redact_url_sources() -> Result<()> {
|
|||
});
|
||||
|
||||
// Re-run with `--locked`.
|
||||
uv_snapshot!(&filters, context.lock().arg("--locked"), @r###"
|
||||
uv_snapshot!(&context.filters(), context.lock().arg("--locked"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -8423,7 +8396,7 @@ fn lock_redact_url_sources() -> Result<()> {
|
|||
"###);
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(&filters, context.sync().arg("--frozen").arg("--reinstall").arg("--no-cache"), @r###"
|
||||
uv_snapshot!(&context.filters(), context.sync().arg("--frozen").arg("--reinstall").arg("--no-cache"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -8431,8 +8404,8 @@ fn lock_redact_url_sources() -> Result<()> {
|
|||
----- stderr -----
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ iniconfig==2.0.0 (from https://public:heron@pypi-proxy.fly.dev/basic-auth/files/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl)
|
||||
"###);
|
||||
+ iniconfig==2.0.0 (from https://public:****@pypi-proxy.fly.dev/basic-auth/files/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl)
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -15837,7 +15810,7 @@ fn lock_explicit_default_index() -> Result<()> {
|
|||
DEBUG No workspace root found, using project root
|
||||
DEBUG Ignoring existing lockfile due to mismatched requirements for: `project==0.1.0`
|
||||
Requested: {Requirement { name: PackageName("anyio"), extras: [], groups: [], marker: true, source: Registry { specifier: VersionSpecifiers([]), index: None, conflict: None }, origin: None }}
|
||||
Existing: {Requirement { name: PackageName("iniconfig"), extras: [], groups: [], marker: true, source: Registry { specifier: VersionSpecifiers([VersionSpecifier { operator: Equal, version: "2.0.0" }]), index: Some(IndexMetadata { url: Url(VerbatimUrl { url: Url { scheme: "https", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("test.pypi.org")), port: None, path: "/simple", query: None, fragment: None }, given: None }), format: Simple }), conflict: None }, origin: None }}
|
||||
Existing: {Requirement { name: PackageName("iniconfig"), extras: [], groups: [], marker: true, source: Registry { specifier: VersionSpecifiers([VersionSpecifier { operator: Equal, version: "2.0.0" }]), index: Some(IndexMetadata { url: Url(VerbatimUrl { url: https://test.pypi.org/simple, given: None }), format: Simple }), conflict: None }, origin: None }}
|
||||
DEBUG Solving with installed Python version: 3.12.[X]
|
||||
DEBUG Solving with target Python version: >=3.12
|
||||
DEBUG Adding direct dependency: project*
|
||||
|
|
|
@ -2119,18 +2119,12 @@ fn install_git_private_https_pat() {
|
|||
|
||||
let context = TestContext::new("3.8");
|
||||
let token = decode_token(common::READ_ONLY_GITHUB_TOKEN);
|
||||
|
||||
let filters: Vec<_> = [(token.as_str(), "***")]
|
||||
.into_iter()
|
||||
.chain(context.filters())
|
||||
.collect();
|
||||
|
||||
let package = format!(
|
||||
"uv-private-pypackage@ git+https://{token}@github.com/astral-test/uv-private-pypackage"
|
||||
);
|
||||
|
||||
uv_snapshot!(filters, context.pip_install().arg(package)
|
||||
, @r###"
|
||||
uv_snapshot!(context.filters(), context.pip_install().arg(package)
|
||||
, @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -2139,8 +2133,8 @@ fn install_git_private_https_pat() {
|
|||
Resolved 1 package 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)
|
||||
"###);
|
||||
+ uv-private-pypackage==0.1.0 (from git+https://****@github.com/astral-test/uv-private-pypackage@d780faf0ac91257d4d5a4f0c5a0e4509608c0071)
|
||||
");
|
||||
|
||||
context.assert_installed("uv_private_pypackage", "0.1.0");
|
||||
}
|
||||
|
@ -2153,16 +2147,11 @@ fn install_git_private_https_pat_mixed_with_public() {
|
|||
let context = TestContext::new("3.8");
|
||||
let token = decode_token(common::READ_ONLY_GITHUB_TOKEN);
|
||||
|
||||
let filters: Vec<_> = [(token.as_str(), "***")]
|
||||
.into_iter()
|
||||
.chain(context.filters())
|
||||
.collect();
|
||||
|
||||
let package = format!(
|
||||
"uv-private-pypackage @ git+https://{token}@github.com/astral-test/uv-private-pypackage"
|
||||
);
|
||||
|
||||
uv_snapshot!(filters, context.pip_install().arg(package).arg("uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage"),
|
||||
uv_snapshot!(context.filters(), context.pip_install().arg(package).arg("uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage"),
|
||||
@r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
|
@ -2172,7 +2161,7 @@ fn install_git_private_https_pat_mixed_with_public() {
|
|||
Resolved 2 packages in [TIME]
|
||||
Prepared 2 packages in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ uv-private-pypackage==0.1.0 (from git+https://***@github.com/astral-test/uv-private-pypackage@d780faf0ac91257d4d5a4f0c5a0e4509608c0071)
|
||||
+ uv-private-pypackage==0.1.0 (from git+https://****@github.com/astral-test/uv-private-pypackage@d780faf0ac91257d4d5a4f0c5a0e4509608c0071)
|
||||
+ uv-public-pypackage==0.1.0 (from git+https://github.com/astral-test/uv-public-pypackage@b270df1a2fb5d012294e9aaf05e7e0bab1e6a389)
|
||||
"###);
|
||||
|
||||
|
@ -2187,11 +2176,6 @@ fn install_git_private_https_multiple_pat() {
|
|||
let token_1 = decode_token(common::READ_ONLY_GITHUB_TOKEN);
|
||||
let token_2 = decode_token(common::READ_ONLY_GITHUB_TOKEN_2);
|
||||
|
||||
let filters: Vec<_> = [(token_1.as_str(), "***_1"), (token_2.as_str(), "***_2")]
|
||||
.into_iter()
|
||||
.chain(context.filters())
|
||||
.collect();
|
||||
|
||||
let package_1 = format!(
|
||||
"uv-private-pypackage @ git+https://{token_1}@github.com/astral-test/uv-private-pypackage"
|
||||
);
|
||||
|
@ -2199,7 +2183,7 @@ fn install_git_private_https_multiple_pat() {
|
|||
"uv-private-pypackage-2 @ git+https://{token_2}@github.com/astral-test/uv-private-pypackage-2"
|
||||
);
|
||||
|
||||
uv_snapshot!(filters, context.pip_install().arg(package_1).arg(package_2)
|
||||
uv_snapshot!(context.filters(), context.pip_install().arg(package_1).arg(package_2)
|
||||
, @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
|
@ -2209,8 +2193,8 @@ fn install_git_private_https_multiple_pat() {
|
|||
Resolved 2 packages in [TIME]
|
||||
Prepared 2 packages in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ uv-private-pypackage==0.1.0 (from git+https://***_1@github.com/astral-test/uv-private-pypackage@d780faf0ac91257d4d5a4f0c5a0e4509608c0071)
|
||||
+ uv-private-pypackage-2==0.1.0 (from git+https://***_2@github.com/astral-test/uv-private-pypackage-2@45c0bec7365710f09b1f4dbca61c86dde9537e4e)
|
||||
+ uv-private-pypackage==0.1.0 (from git+https://****@github.com/astral-test/uv-private-pypackage@d780faf0ac91257d4d5a4f0c5a0e4509608c0071)
|
||||
+ uv-private-pypackage-2==0.1.0 (from git+https://****@github.com/astral-test/uv-private-pypackage-2@45c0bec7365710f09b1f4dbca61c86dde9537e4e)
|
||||
"###);
|
||||
|
||||
context.assert_installed("uv_private_pypackage", "0.1.0");
|
||||
|
@ -2223,11 +2207,7 @@ fn install_git_private_https_pat_at_ref() {
|
|||
let context = TestContext::new("3.8");
|
||||
let token = decode_token(common::READ_ONLY_GITHUB_TOKEN);
|
||||
|
||||
let mut filters: Vec<_> = [(token.as_str(), "***")]
|
||||
.into_iter()
|
||||
.chain(context.filters())
|
||||
.collect();
|
||||
|
||||
let mut filters = context.filters();
|
||||
filters.push((r"git\+https://", ""));
|
||||
|
||||
// A user is _required_ on Windows
|
||||
|
@ -2241,8 +2221,8 @@ fn install_git_private_https_pat_at_ref() {
|
|||
let package = format!(
|
||||
"uv-private-pypackage @ git+https://{user}{token}@github.com/astral-test/uv-private-pypackage@6c09ce9ae81f50670a60abd7d95f30dd416d00ac"
|
||||
);
|
||||
uv_snapshot!(filters, context.pip_install()
|
||||
.arg(package), @r###"
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg(package), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -2251,8 +2231,8 @@ fn install_git_private_https_pat_at_ref() {
|
|||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ uv-private-pypackage==0.1.0 (from ***@github.com/astral-test/uv-private-pypackage@6c09ce9ae81f50670a60abd7d95f30dd416d00ac)
|
||||
"###);
|
||||
+ uv-private-pypackage==0.1.0 (from git+https://****@github.com/astral-test/uv-private-pypackage@6c09ce9ae81f50670a60abd7d95f30dd416d00ac)
|
||||
");
|
||||
|
||||
context.assert_installed("uv_private_pypackage", "0.1.0");
|
||||
}
|
||||
|
@ -2270,12 +2250,7 @@ fn install_git_private_https_pat_and_username() {
|
|||
let token = decode_token(common::READ_ONLY_GITHUB_TOKEN);
|
||||
let user = "astral-test-bot";
|
||||
|
||||
let filters: Vec<_> = [(token.as_str(), "***")]
|
||||
.into_iter()
|
||||
.chain(context.filters())
|
||||
.collect();
|
||||
|
||||
uv_snapshot!(filters, context.pip_install().arg(format!("uv-private-pypackage @ git+https://{user}:{token}@github.com/astral-test/uv-private-pypackage"))
|
||||
uv_snapshot!(context.filters(), context.pip_install().arg(format!("uv-private-pypackage @ git+https://{user}:{token}@github.com/astral-test/uv-private-pypackage"))
|
||||
, @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
|
@ -2285,7 +2260,7 @@ fn install_git_private_https_pat_and_username() {
|
|||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ uv-private-pypackage==0.1.0 (from git+https://astral-test-bot:***@github.com/astral-test/uv-private-pypackage@6c09ce9ae81f50670a60abd7d95f30dd416d00ac)
|
||||
+ uv-private-pypackage==0.1.0 (from git+https://astral-test-bot:****@github.com/astral-test/uv-private-pypackage@6c09ce9ae81f50670a60abd7d95f30dd416d00ac)
|
||||
"###);
|
||||
|
||||
context.assert_installed("uv_private_pypackage", "0.1.0");
|
||||
|
@ -2301,7 +2276,10 @@ fn install_git_private_https_pat_not_authorized() {
|
|||
let token = "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";
|
||||
|
||||
let mut filters = context.filters();
|
||||
filters.insert(0, (token, "***"));
|
||||
// TODO(john): We need this filter because we are displaying the token when
|
||||
// an underlying process error message is being displayed. We should actually
|
||||
// mask it.
|
||||
filters.push((token, "***"));
|
||||
filters.push(("`.*/git fetch (.*)`", "`git fetch $1`"));
|
||||
|
||||
// We provide a username otherwise (since the token is invalid), the git cli will prompt for a password
|
||||
|
@ -2314,7 +2292,7 @@ fn install_git_private_https_pat_not_authorized() {
|
|||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
× Failed to download and build `uv-private-pypackage @ git+https://git:***@github.com/astral-test/uv-private-pypackage`
|
||||
× Failed to download and build `uv-private-pypackage @ git+https://git:****@github.com/astral-test/uv-private-pypackage`
|
||||
├─▶ Git operation failed
|
||||
├─▶ failed to clone into: [CACHE_DIR]/git-v0/db/8401f5508e3e612d
|
||||
╰─▶ process didn't exit successfully: `git fetch --force --update-head-ok 'https://git:***@github.com/astral-test/uv-private-pypackage' '+HEAD:refs/remotes/origin/HEAD'` (exit status: 128)
|
||||
|
@ -2334,17 +2312,12 @@ fn install_github_artifact_private_https_pat_mixed_with_public() {
|
|||
let context = TestContext::new("3.8");
|
||||
let token = decode_token(common::READ_ONLY_GITHUB_TOKEN);
|
||||
|
||||
let filters: Vec<_> = [(token.as_str(), "***")]
|
||||
.into_iter()
|
||||
.chain(context.filters())
|
||||
.collect();
|
||||
|
||||
let private_package = format!(
|
||||
"uv-private-pypackage @ https://{token}@raw.githubusercontent.com/astral-test/uv-private-pypackage/main/dist/uv_private_pypackage-0.1.0-py3-none-any.whl"
|
||||
);
|
||||
let public_package = "uv-public-pypackage @ https://raw.githubusercontent.com/astral-test/uv-public-pypackage/main/dist/uv_public_pypackage-0.1.0-py3-none-any.whl";
|
||||
|
||||
uv_snapshot!(filters, context.pip_install().arg(private_package).arg(public_package),
|
||||
uv_snapshot!(context.filters(), context.pip_install().arg(private_package).arg(public_package),
|
||||
@r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
|
@ -2354,7 +2327,7 @@ fn install_github_artifact_private_https_pat_mixed_with_public() {
|
|||
Resolved 2 packages in [TIME]
|
||||
Prepared 2 packages in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ uv-private-pypackage==0.1.0 (from https://***@raw.githubusercontent.com/astral-test/uv-private-pypackage/main/dist/uv_private_pypackage-0.1.0-py3-none-any.whl)
|
||||
+ uv-private-pypackage==0.1.0 (from https://****@raw.githubusercontent.com/astral-test/uv-private-pypackage/main/dist/uv_private_pypackage-0.1.0-py3-none-any.whl)
|
||||
+ uv-public-pypackage==0.1.0 (from https://raw.githubusercontent.com/astral-test/uv-public-pypackage/main/dist/uv_public_pypackage-0.1.0-py3-none-any.whl)
|
||||
"###);
|
||||
|
||||
|
@ -2370,11 +2343,6 @@ fn install_github_artifact_private_https_multiple_pat() {
|
|||
let token_1 = decode_token(common::READ_ONLY_GITHUB_TOKEN);
|
||||
let token_2 = decode_token(common::READ_ONLY_GITHUB_TOKEN_2);
|
||||
|
||||
let filters: Vec<_> = [(token_1.as_str(), "***_1"), (token_2.as_str(), "***_2")]
|
||||
.into_iter()
|
||||
.chain(context.filters())
|
||||
.collect();
|
||||
|
||||
let package_1 = format!(
|
||||
"uv-private-pypackage @ https://astral-test-bot:{token_1}@raw.githubusercontent.com/astral-test/uv-private-pypackage/main/dist/uv_private_pypackage-0.1.0-py3-none-any.whl"
|
||||
);
|
||||
|
@ -2382,7 +2350,7 @@ fn install_github_artifact_private_https_multiple_pat() {
|
|||
"uv-private-pypackage-2 @ https://astral-test-bot:{token_2}@raw.githubusercontent.com/astral-test/uv-private-pypackage-2/main/dist/uv_private_pypackage_2-0.1.0-py3-none-any.whl"
|
||||
);
|
||||
|
||||
uv_snapshot!(filters, context.pip_install().arg(package_1).arg(package_2)
|
||||
uv_snapshot!(context.filters(), context.pip_install().arg(package_1).arg(package_2)
|
||||
, @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
|
@ -2392,8 +2360,8 @@ fn install_github_artifact_private_https_multiple_pat() {
|
|||
Resolved 2 packages in [TIME]
|
||||
Prepared 2 packages in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ uv-private-pypackage==0.1.0 (from https://astral-test-bot:***_1@raw.githubusercontent.com/astral-test/uv-private-pypackage/main/dist/uv_private_pypackage-0.1.0-py3-none-any.whl)
|
||||
+ uv-private-pypackage-2==0.1.0 (from https://astral-test-bot:***_2@raw.githubusercontent.com/astral-test/uv-private-pypackage-2/main/dist/uv_private_pypackage_2-0.1.0-py3-none-any.whl)
|
||||
+ uv-private-pypackage==0.1.0 (from https://astral-test-bot:****@raw.githubusercontent.com/astral-test/uv-private-pypackage/main/dist/uv_private_pypackage-0.1.0-py3-none-any.whl)
|
||||
+ uv-private-pypackage-2==0.1.0 (from https://astral-test-bot:****@raw.githubusercontent.com/astral-test/uv-private-pypackage-2/main/dist/uv_private_pypackage_2-0.1.0-py3-none-any.whl)
|
||||
"###);
|
||||
|
||||
context.assert_installed("uv_private_pypackage", "0.1.0");
|
||||
|
|
|
@ -112,21 +112,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
|
|||
name: None,
|
||||
url: Pypi(
|
||||
VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"pypi.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/simple",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: https://pypi.org/simple,
|
||||
given: Some(
|
||||
"https://pypi.org/simple",
|
||||
),
|
||||
|
@ -293,21 +279,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
|
|||
name: None,
|
||||
url: Pypi(
|
||||
VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"pypi.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/simple",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: https://pypi.org/simple,
|
||||
given: Some(
|
||||
"https://pypi.org/simple",
|
||||
),
|
||||
|
@ -475,21 +447,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
|
|||
name: None,
|
||||
url: Pypi(
|
||||
VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"pypi.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/simple",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: https://pypi.org/simple,
|
||||
given: Some(
|
||||
"https://pypi.org/simple",
|
||||
),
|
||||
|
@ -689,21 +647,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
|
|||
name: None,
|
||||
url: Pypi(
|
||||
VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"pypi.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/simple",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: https://pypi.org/simple,
|
||||
given: Some(
|
||||
"https://pypi.org/simple",
|
||||
),
|
||||
|
@ -1032,21 +976,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
|
|||
name: None,
|
||||
url: Pypi(
|
||||
VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"pypi.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/simple",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: https://pypi.org/simple,
|
||||
given: Some(
|
||||
"https://pypi.org/simple",
|
||||
),
|
||||
|
@ -1240,21 +1170,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
|
|||
name: None,
|
||||
url: Pypi(
|
||||
VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"pypi.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/simple",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: https://pypi.org/simple,
|
||||
given: Some(
|
||||
"https://pypi.org/simple",
|
||||
),
|
||||
|
@ -1272,21 +1188,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
|
|||
name: None,
|
||||
url: Url(
|
||||
VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"test.pypi.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/simple",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: https://test.pypi.org/simple,
|
||||
given: Some(
|
||||
"https://test.pypi.org/simple",
|
||||
),
|
||||
|
@ -1455,21 +1357,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
|
|||
name: None,
|
||||
url: Url(
|
||||
VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"test.pypi.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/simple",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: https://test.pypi.org/simple,
|
||||
given: Some(
|
||||
"https://test.pypi.org/simple",
|
||||
),
|
||||
|
@ -1489,21 +1377,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
|
|||
name: None,
|
||||
url: Pypi(
|
||||
VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"pypi.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/simple",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: https://pypi.org/simple,
|
||||
given: Some(
|
||||
"https://pypi.org/simple",
|
||||
),
|
||||
|
@ -1521,21 +1395,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
|
|||
name: None,
|
||||
url: Url(
|
||||
VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"test.pypi.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/simple",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: https://test.pypi.org/simple,
|
||||
given: Some(
|
||||
"https://test.pypi.org/simple",
|
||||
),
|
||||
|
@ -1728,21 +1588,7 @@ fn resolve_find_links() -> anyhow::Result<()> {
|
|||
name: None,
|
||||
url: Url(
|
||||
VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"download.pytorch.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/whl/torch_stable.html",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: https://download.pytorch.org/whl/torch_stable.html,
|
||||
given: Some(
|
||||
"https://download.pytorch.org/whl/torch_stable.html",
|
||||
),
|
||||
|
@ -2097,21 +1943,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
|
|||
name: None,
|
||||
url: Url(
|
||||
VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"download.pytorch.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/whl",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: https://download.pytorch.org/whl,
|
||||
given: Some(
|
||||
"https://download.pytorch.org/whl",
|
||||
),
|
||||
|
@ -2129,21 +1961,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
|
|||
name: None,
|
||||
url: Url(
|
||||
VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"test.pypi.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/simple",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: https://test.pypi.org/simple,
|
||||
given: Some(
|
||||
"https://test.pypi.org/simple",
|
||||
),
|
||||
|
@ -2310,21 +2128,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
|
|||
name: None,
|
||||
url: Url(
|
||||
VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"download.pytorch.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/whl",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: https://download.pytorch.org/whl,
|
||||
given: Some(
|
||||
"https://download.pytorch.org/whl",
|
||||
),
|
||||
|
@ -2342,21 +2146,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
|
|||
name: None,
|
||||
url: Url(
|
||||
VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"test.pypi.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/simple",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: https://test.pypi.org/simple,
|
||||
given: Some(
|
||||
"https://test.pypi.org/simple",
|
||||
),
|
||||
|
@ -3537,21 +3327,7 @@ fn resolve_both() -> anyhow::Result<()> {
|
|||
name: None,
|
||||
url: Pypi(
|
||||
VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"pypi.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/simple",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: https://pypi.org/simple,
|
||||
given: Some(
|
||||
"https://pypi.org/simple",
|
||||
),
|
||||
|
@ -3843,21 +3619,7 @@ fn resolve_config_file() -> anyhow::Result<()> {
|
|||
name: None,
|
||||
url: Pypi(
|
||||
VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"pypi.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/simple",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: https://pypi.org/simple,
|
||||
given: Some(
|
||||
"https://pypi.org/simple",
|
||||
),
|
||||
|
@ -4629,21 +4391,7 @@ fn index_priority() -> anyhow::Result<()> {
|
|||
name: None,
|
||||
url: Url(
|
||||
VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"cli.pypi.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/simple",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: https://cli.pypi.org/simple,
|
||||
given: Some(
|
||||
"https://cli.pypi.org/simple",
|
||||
),
|
||||
|
@ -4663,21 +4411,7 @@ fn index_priority() -> anyhow::Result<()> {
|
|||
name: None,
|
||||
url: Url(
|
||||
VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"file.pypi.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/simple",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: https://file.pypi.org/simple,
|
||||
given: Some(
|
||||
"https://file.pypi.org/simple",
|
||||
),
|
||||
|
@ -4844,21 +4578,7 @@ fn index_priority() -> anyhow::Result<()> {
|
|||
name: None,
|
||||
url: Url(
|
||||
VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"cli.pypi.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/simple",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: https://cli.pypi.org/simple,
|
||||
given: Some(
|
||||
"https://cli.pypi.org/simple",
|
||||
),
|
||||
|
@ -4878,21 +4598,7 @@ fn index_priority() -> anyhow::Result<()> {
|
|||
name: None,
|
||||
url: Url(
|
||||
VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"file.pypi.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/simple",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: https://file.pypi.org/simple,
|
||||
given: Some(
|
||||
"https://file.pypi.org/simple",
|
||||
),
|
||||
|
@ -5065,21 +4771,7 @@ fn index_priority() -> anyhow::Result<()> {
|
|||
name: None,
|
||||
url: Url(
|
||||
VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"cli.pypi.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/simple",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: https://cli.pypi.org/simple,
|
||||
given: Some(
|
||||
"https://cli.pypi.org/simple",
|
||||
),
|
||||
|
@ -5099,21 +4791,7 @@ fn index_priority() -> anyhow::Result<()> {
|
|||
name: None,
|
||||
url: Url(
|
||||
VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"file.pypi.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/simple",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: https://file.pypi.org/simple,
|
||||
given: Some(
|
||||
"https://file.pypi.org/simple",
|
||||
),
|
||||
|
@ -5281,21 +4959,7 @@ fn index_priority() -> anyhow::Result<()> {
|
|||
name: None,
|
||||
url: Url(
|
||||
VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"cli.pypi.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/simple",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: https://cli.pypi.org/simple,
|
||||
given: Some(
|
||||
"https://cli.pypi.org/simple",
|
||||
),
|
||||
|
@ -5315,21 +4979,7 @@ fn index_priority() -> anyhow::Result<()> {
|
|||
name: None,
|
||||
url: Url(
|
||||
VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"file.pypi.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/simple",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: https://file.pypi.org/simple,
|
||||
given: Some(
|
||||
"https://file.pypi.org/simple",
|
||||
),
|
||||
|
@ -5504,21 +5154,7 @@ fn index_priority() -> anyhow::Result<()> {
|
|||
name: None,
|
||||
url: Url(
|
||||
VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"cli.pypi.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/simple",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: https://cli.pypi.org/simple,
|
||||
given: Some(
|
||||
"https://cli.pypi.org/simple",
|
||||
),
|
||||
|
@ -5538,21 +5174,7 @@ fn index_priority() -> anyhow::Result<()> {
|
|||
name: None,
|
||||
url: Url(
|
||||
VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"file.pypi.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/simple",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: https://file.pypi.org/simple,
|
||||
given: Some(
|
||||
"https://file.pypi.org/simple",
|
||||
),
|
||||
|
@ -5720,21 +5342,7 @@ fn index_priority() -> anyhow::Result<()> {
|
|||
name: None,
|
||||
url: Url(
|
||||
VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"cli.pypi.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/simple",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: https://cli.pypi.org/simple,
|
||||
given: Some(
|
||||
"https://cli.pypi.org/simple",
|
||||
),
|
||||
|
@ -5754,21 +5362,7 @@ fn index_priority() -> anyhow::Result<()> {
|
|||
name: None,
|
||||
url: Url(
|
||||
VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"file.pypi.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/simple",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
url: https://file.pypi.org/simple,
|
||||
given: Some(
|
||||
"https://file.pypi.org/simple",
|
||||
),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue