mirror of
https://github.com/astral-sh/uv
synced 2026-01-21 05:20:09 -05:00
Allow setting proxy variables via global / user configuration (#16918)
<!-- Thank you for contributing to uv! To help us out with reviewing, please consider the following: - Does this pull request include a summary of the change? (See below.) - Does this pull request include a descriptive title? - Does this pull request include references to any relevant issues? --> ## Summary <!-- What's the purpose of the change? What does it do, and why? --> This allows users to set the HTTP, HTTPS, and no proxy variables via the configuration files like ~pyproject.toml~ and uv.toml. Users can set like so: `uv.toml` ```toml https-proxy = "http://my_cool_proxy:10500" http-proxy = "http://my_cool_proxy:10500" no-proxy = [ "dontproxyme.com", "localhost", ] ``` Resolves https://github.com/astral-sh/uv/issues/9472 ## Test Plan <!-- How was it tested? --> It also adds a new integration test for the proxy support in `uv-client`. This was tested on some of our developer machines with our proxy setup using `~/.config/uv/uv.toml` with values like: ```toml https-proxy = "http://my_cool_proxy:10500" http-proxy = "http://my_cool_proxy:10500" no-proxy = [ "dontproxyme.com", "localhost", ] ``` --------- Signed-off-by: Eli Uriegas <eliuriegas@meta.com> Co-authored-by: Zanie Blue <contact@zanie.dev>
This commit is contained in:
3
Cargo.lock
generated
3
Cargo.lock
generated
@@ -5845,12 +5845,15 @@ dependencies = [
|
||||
"clap",
|
||||
"either",
|
||||
"fs-err",
|
||||
"insta",
|
||||
"rayon",
|
||||
"reqwest",
|
||||
"rustc-hash",
|
||||
"same-file",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde-untagged",
|
||||
"serde_json",
|
||||
"thiserror 2.0.17",
|
||||
"tracing",
|
||||
"url",
|
||||
|
||||
@@ -16,7 +16,7 @@ use http::{
|
||||
},
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use reqwest::{Client, ClientBuilder, IntoUrl, Proxy, Request, Response, multipart};
|
||||
use reqwest::{Client, ClientBuilder, IntoUrl, NoProxy, Proxy, Request, Response, multipart};
|
||||
use reqwest_middleware::{ClientWithMiddleware, Middleware};
|
||||
use reqwest_retry::policies::ExponentialBackoff;
|
||||
use reqwest_retry::{
|
||||
@@ -29,7 +29,8 @@ use url::ParseError;
|
||||
use url::Url;
|
||||
|
||||
use uv_auth::{AuthMiddleware, Credentials, CredentialsCache, Indexes, PyxTokenStore};
|
||||
use uv_configuration::{KeyringProviderType, TrustedHost};
|
||||
use uv_configuration::ProxyUrlKind;
|
||||
use uv_configuration::{KeyringProviderType, ProxyUrl, TrustedHost};
|
||||
use uv_fs::Simplified;
|
||||
use uv_pep508::MarkerEnvironment;
|
||||
use uv_platform_tags::Platform;
|
||||
@@ -84,6 +85,9 @@ pub struct BaseClientBuilder<'a> {
|
||||
timeout: Duration,
|
||||
extra_middleware: Option<ExtraMiddleware>,
|
||||
proxies: Vec<Proxy>,
|
||||
http_proxy: Option<ProxyUrl>,
|
||||
https_proxy: Option<ProxyUrl>,
|
||||
no_proxy: Option<Vec<String>>,
|
||||
redirect_policy: RedirectPolicy,
|
||||
/// Whether credentials should be propagated during cross-origin redirects.
|
||||
///
|
||||
@@ -148,6 +152,9 @@ impl Default for BaseClientBuilder<'_> {
|
||||
timeout: Duration::from_secs(30),
|
||||
extra_middleware: None,
|
||||
proxies: vec![],
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
redirect_policy: RedirectPolicy::default(),
|
||||
cross_origin_credential_policy: CrossOriginCredentialsPolicy::Secure,
|
||||
custom_client: None,
|
||||
@@ -265,6 +272,24 @@ impl<'a> BaseClientBuilder<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn http_proxy(mut self, http_proxy: Option<ProxyUrl>) -> Self {
|
||||
self.http_proxy = http_proxy;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn https_proxy(mut self, https_proxy: Option<ProxyUrl>) -> Self {
|
||||
self.https_proxy = https_proxy;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn no_proxy(mut self, no_proxy: Option<Vec<String>>) -> Self {
|
||||
self.no_proxy = no_proxy;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn redirect(mut self, policy: RedirectPolicy) -> Self {
|
||||
self.redirect_policy = policy;
|
||||
@@ -526,6 +551,24 @@ impl<'a> BaseClientBuilder<'a> {
|
||||
for p in &self.proxies {
|
||||
client_builder = client_builder.proxy(p.clone());
|
||||
}
|
||||
|
||||
let no_proxy = self
|
||||
.no_proxy
|
||||
.as_ref()
|
||||
.and_then(|no_proxy| NoProxy::from_string(&no_proxy.join(",")));
|
||||
|
||||
if let Some(http_proxy) = &self.http_proxy {
|
||||
let proxy = http_proxy
|
||||
.as_proxy(ProxyUrlKind::Http)
|
||||
.no_proxy(no_proxy.clone());
|
||||
client_builder = client_builder.proxy(proxy);
|
||||
}
|
||||
|
||||
if let Some(https_proxy) = &self.https_proxy {
|
||||
let proxy = https_proxy.as_proxy(ProxyUrlKind::Https).no_proxy(no_proxy);
|
||||
client_builder = client_builder.proxy(proxy);
|
||||
}
|
||||
|
||||
let client_builder = client_builder;
|
||||
|
||||
client_builder
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
mod http_util;
|
||||
mod proxy;
|
||||
mod remote_metadata;
|
||||
mod ssl_certs;
|
||||
mod user_agent_version;
|
||||
|
||||
100
crates/uv-client/tests/it/proxy.rs
Normal file
100
crates/uv-client/tests/it/proxy.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
//! An integration test for proxy support in `uv-client`.
|
||||
|
||||
use anyhow::Result;
|
||||
use wiremock::matchers::{any, method};
|
||||
use wiremock::{Mock, MockServer, ResponseTemplate};
|
||||
|
||||
use uv_client::BaseClientBuilder;
|
||||
use uv_configuration::ProxyUrl;
|
||||
|
||||
#[tokio::test]
|
||||
async fn http_proxy() -> Result<()> {
|
||||
// Start a mock server to act as the target.
|
||||
let target_server = MockServer::start().await;
|
||||
Mock::given(method("GET"))
|
||||
.respond_with(ResponseTemplate::new(200))
|
||||
.mount(&target_server)
|
||||
.await;
|
||||
|
||||
// Start a mock server to act as the proxy.
|
||||
let proxy_server = MockServer::start().await;
|
||||
Mock::given(any())
|
||||
.respond_with(ResponseTemplate::new(200))
|
||||
.mount(&proxy_server)
|
||||
.await;
|
||||
|
||||
// Create a client with the proxy.
|
||||
let client = BaseClientBuilder::new(
|
||||
uv_client::Connectivity::Online,
|
||||
false,
|
||||
vec![],
|
||||
uv_preview::Preview::default(),
|
||||
std::time::Duration::from_secs(30),
|
||||
3,
|
||||
)
|
||||
.http_proxy(Some(proxy_server.uri().parse::<ProxyUrl>()?))
|
||||
.build();
|
||||
|
||||
// Make a request to the target.
|
||||
let response = client
|
||||
.for_host(&target_server.uri().parse()?)
|
||||
.get(target_server.uri())
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
assert_eq!(response.status(), 200);
|
||||
|
||||
// Assert that the proxy was called.
|
||||
let received_requests = proxy_server.received_requests().await.unwrap();
|
||||
assert_eq!(received_requests.len(), 1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn no_proxy() -> Result<()> {
|
||||
// Start a mock server to act as the target.
|
||||
let target_server = MockServer::start().await;
|
||||
Mock::given(method("GET"))
|
||||
.respond_with(ResponseTemplate::new(200))
|
||||
.mount(&target_server)
|
||||
.await;
|
||||
|
||||
// Start a mock server to act as the proxy.
|
||||
let proxy_server = MockServer::start().await;
|
||||
Mock::given(any())
|
||||
.respond_with(ResponseTemplate::new(200))
|
||||
.mount(&proxy_server)
|
||||
.await;
|
||||
|
||||
// The host of the target server should be excluded from proxying.
|
||||
let target_host = target_server.address().ip().to_string();
|
||||
|
||||
// Create a client with the proxy.
|
||||
let client = BaseClientBuilder::new(
|
||||
uv_client::Connectivity::Online,
|
||||
false,
|
||||
vec![],
|
||||
uv_preview::Preview::default(),
|
||||
std::time::Duration::from_secs(30),
|
||||
3,
|
||||
)
|
||||
.http_proxy(Some(proxy_server.uri().parse::<ProxyUrl>()?))
|
||||
.no_proxy(Some(vec![target_host]))
|
||||
.build();
|
||||
|
||||
// Make a request to the target.
|
||||
let response = client
|
||||
.for_host(&target_server.uri().parse()?)
|
||||
.get(target_server.uri())
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
assert_eq!(response.status(), 200);
|
||||
|
||||
// Assert that the proxy was NOT called.
|
||||
let received_requests = proxy_server.received_requests().await.unwrap();
|
||||
assert_eq!(received_requests.len(), 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -30,6 +30,7 @@ clap = { workspace = true, features = ["derive"], optional = true }
|
||||
either = { workspace = true }
|
||||
fs-err = { workspace = true }
|
||||
rayon = { workspace = true }
|
||||
reqwest = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
same-file = { workspace = true }
|
||||
schemars = { workspace = true, optional = true }
|
||||
@@ -41,6 +42,8 @@ url = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
insta = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -15,6 +15,7 @@ pub use name_specifiers::*;
|
||||
pub use overrides::*;
|
||||
pub use package_options::*;
|
||||
pub use project_build_backend::*;
|
||||
pub use proxy_url::*;
|
||||
pub use required_version::*;
|
||||
pub use sources::*;
|
||||
pub use target_triple::*;
|
||||
@@ -40,6 +41,7 @@ mod name_specifiers;
|
||||
mod overrides;
|
||||
mod package_options;
|
||||
mod project_build_backend;
|
||||
mod proxy_url;
|
||||
mod required_version;
|
||||
mod sources;
|
||||
mod target_triple;
|
||||
|
||||
237
crates/uv-configuration/src/proxy_url.rs
Normal file
237
crates/uv-configuration/src/proxy_url.rs
Normal file
@@ -0,0 +1,237 @@
|
||||
#[cfg(feature = "schemars")]
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
|
||||
use reqwest::Proxy;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use url::Url;
|
||||
|
||||
/// A validated proxy URL.
|
||||
///
|
||||
/// This type validates that the [`Url`] is valid for a [`reqwest::Proxy`] on construction.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct ProxyUrl(Url);
|
||||
|
||||
/// Mapping to [`reqwest::proxy::Intercept`] kinds which are not public API.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ProxyUrlKind {
|
||||
Http,
|
||||
Https,
|
||||
}
|
||||
|
||||
impl ProxyUrl {
|
||||
/// Returns a reference to the underlying [`Url`].
|
||||
fn as_url(&self) -> &Url {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Constructs a [`reqwest::Proxy`] from this [`ProxyUrl`] for the given [`ProxyUrlKind`].
|
||||
pub fn as_proxy(&self, kind: ProxyUrlKind) -> Proxy {
|
||||
// SAFETY: Constructing a [`Proxy`] from a [`Url`] is infallible.
|
||||
match kind {
|
||||
ProxyUrlKind::Http => Proxy::http(self.0.as_str())
|
||||
.expect("Constructing a proxy from a url should never fail"),
|
||||
ProxyUrlKind::Https => Proxy::https(self.0.as_str())
|
||||
.expect("Constructing a proxy from a url should never fail"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ProxyUrlError {
|
||||
#[error("invalid proxy URL: {0}")]
|
||||
InvalidUrl(#[from] url::ParseError),
|
||||
#[error(
|
||||
"invalid proxy URL scheme `{scheme}` in `{url}`: expected http, https, socks5, or socks5h"
|
||||
)]
|
||||
InvalidScheme { scheme: String, url: Url },
|
||||
}
|
||||
|
||||
/// Returns true if the input likely has no scheme (no "://" present).
|
||||
fn lacks_scheme(s: &str) -> bool {
|
||||
!s.contains("://")
|
||||
}
|
||||
|
||||
impl FromStr for ProxyUrl {
|
||||
type Err = ProxyUrlError;
|
||||
|
||||
/// Parses a proxy URL from a string, assuming `http://` if no scheme is present.
|
||||
///
|
||||
/// This matches reqwest's and curl's behavior.
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
fn try_with_http_scheme(s: &str) -> Result<ProxyUrl, ProxyUrlError> {
|
||||
let with_scheme = format!("http://{s}");
|
||||
let url = Url::parse(&with_scheme)?;
|
||||
ProxyUrl::try_from(url)
|
||||
}
|
||||
|
||||
match Url::parse(s) {
|
||||
Ok(url) => match Self::try_from(url) {
|
||||
Ok(proxy) => Ok(proxy),
|
||||
Err(ProxyUrlError::InvalidScheme { .. }) if lacks_scheme(s) => {
|
||||
try_with_http_scheme(s)
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
Err(url::ParseError::RelativeUrlWithoutBase) => try_with_http_scheme(s),
|
||||
Err(err) => Err(ProxyUrlError::InvalidUrl(err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Url> for ProxyUrl {
|
||||
type Error = ProxyUrlError;
|
||||
|
||||
fn try_from(url: Url) -> Result<Self, Self::Error> {
|
||||
match url.scheme() {
|
||||
"http" | "https" | "socks5" | "socks5h" => Ok(Self(url)),
|
||||
scheme => Err(ProxyUrlError::InvalidScheme {
|
||||
scheme: scheme.to_string(),
|
||||
url,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ProxyUrl {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ProxyUrl {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
Self::from_str(&s).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for ProxyUrl {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::ser::Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.as_url().as_str())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "schemars")]
|
||||
impl schemars::JsonSchema for ProxyUrl {
|
||||
fn schema_name() -> Cow<'static, str> {
|
||||
Cow::Borrowed("ProxyUrl")
|
||||
}
|
||||
|
||||
fn json_schema(_generator: &mut schemars::generate::SchemaGenerator) -> schemars::Schema {
|
||||
schemars::json_schema!({
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"description": "A proxy URL (e.g., `http://proxy.example.com:8080`)."
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_valid_proxy_urls() {
|
||||
// HTTP proxy
|
||||
let url = "http://proxy.example.com:8080".parse::<ProxyUrl>().unwrap();
|
||||
assert_eq!(url.to_string(), "http://proxy.example.com:8080/");
|
||||
|
||||
// HTTPS proxy
|
||||
let url = "https://proxy.example.com:8080"
|
||||
.parse::<ProxyUrl>()
|
||||
.unwrap();
|
||||
assert_eq!(url.to_string(), "https://proxy.example.com:8080/");
|
||||
|
||||
// SOCKS5 proxy (no trailing slash for socks URLs)
|
||||
let url = "socks5://proxy.example.com:1080"
|
||||
.parse::<ProxyUrl>()
|
||||
.unwrap();
|
||||
assert_eq!(url.to_string(), "socks5://proxy.example.com:1080");
|
||||
|
||||
// SOCKS5H proxy
|
||||
let url = "socks5h://proxy.example.com:1080"
|
||||
.parse::<ProxyUrl>()
|
||||
.unwrap();
|
||||
assert_eq!(url.to_string(), "socks5h://proxy.example.com:1080");
|
||||
|
||||
// Proxy with auth
|
||||
let url = "http://user:pass@proxy.example.com:8080"
|
||||
.parse::<ProxyUrl>()
|
||||
.unwrap();
|
||||
assert_eq!(url.to_string(), "http://user:pass@proxy.example.com:8080/");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_proxy_url_without_scheme() {
|
||||
// URL without a scheme (no "://") should default to http://
|
||||
// This matches curl and reqwest behavior
|
||||
let url = "proxy.example.com:8080".parse::<ProxyUrl>().unwrap();
|
||||
assert_eq!(url.to_string(), "http://proxy.example.com:8080/");
|
||||
|
||||
// With auth but no scheme
|
||||
let url = "user:pass@proxy.example.com:8080"
|
||||
.parse::<ProxyUrl>()
|
||||
.unwrap();
|
||||
assert_eq!(url.to_string(), "http://user:pass@proxy.example.com:8080/");
|
||||
|
||||
// Just hostname
|
||||
let url = "proxy.example.com".parse::<ProxyUrl>().unwrap();
|
||||
assert_eq!(url.to_string(), "http://proxy.example.com/");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_invalid_proxy_urls() {
|
||||
let result = "ftp://proxy.example.com:8080".parse::<ProxyUrl>();
|
||||
assert!(matches!(result, Err(ProxyUrlError::InvalidScheme { .. })));
|
||||
insta::assert_snapshot!(
|
||||
result.unwrap_err().to_string(),
|
||||
@"invalid proxy URL scheme `ftp` in `ftp://proxy.example.com:8080/`: expected http, https, socks5, or socks5h"
|
||||
);
|
||||
|
||||
// Invalid URL (spaces are not allowed)
|
||||
let result = "not a url".parse::<ProxyUrl>();
|
||||
assert!(matches!(result, Err(ProxyUrlError::InvalidUrl(_))));
|
||||
insta::assert_snapshot!(
|
||||
result.unwrap_err().to_string(),
|
||||
@"invalid proxy URL: invalid international domain name"
|
||||
);
|
||||
|
||||
// Empty string
|
||||
let result = "".parse::<ProxyUrl>();
|
||||
assert!(matches!(result, Err(ProxyUrlError::InvalidUrl(_))));
|
||||
insta::assert_snapshot!(
|
||||
result.unwrap_err().to_string(),
|
||||
@"invalid proxy URL: empty host"
|
||||
);
|
||||
|
||||
let result = "file:///path/to/file".parse::<ProxyUrl>();
|
||||
assert!(matches!(result, Err(ProxyUrlError::InvalidScheme { .. })));
|
||||
insta::assert_snapshot!(
|
||||
result.unwrap_err().to_string(),
|
||||
@"invalid proxy URL scheme `file` in `file:///path/to/file`: expected http, https, socks5, or socks5h"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_invalid_proxy_url() {
|
||||
let result: Result<ProxyUrl, _> = serde_json::from_str(r#""ftp://proxy.example.com:8080""#);
|
||||
insta::assert_snapshot!(
|
||||
result.unwrap_err().to_string(),
|
||||
@r#"invalid proxy URL scheme `ftp` in `ftp://proxy.example.com:8080/`: expected http, https, socks5, or socks5h"#
|
||||
);
|
||||
|
||||
let result: Result<ProxyUrl, _> = serde_json::from_str(r#""not a url""#);
|
||||
insta::assert_snapshot!(
|
||||
result.unwrap_err().to_string(),
|
||||
@"invalid proxy URL: invalid international domain name"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -283,26 +283,40 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parents: &[S
|
||||
option_type: OptionType::Configuration,
|
||||
..
|
||||
} => {
|
||||
output.push_str(&format_tab(
|
||||
"pyproject.toml",
|
||||
&format_header(
|
||||
field.scope,
|
||||
// For uv_toml_only options, only show the uv.toml tab
|
||||
if field.uv_toml_only {
|
||||
output.push_str(&format_code(
|
||||
"uv.toml",
|
||||
&format_header(
|
||||
field.scope,
|
||||
field.example,
|
||||
parents,
|
||||
ConfigurationFile::UvToml,
|
||||
),
|
||||
field.example,
|
||||
parents,
|
||||
ConfigurationFile::PyprojectToml,
|
||||
),
|
||||
field.example,
|
||||
));
|
||||
output.push_str(&format_tab(
|
||||
"uv.toml",
|
||||
&format_header(
|
||||
field.scope,
|
||||
));
|
||||
} else {
|
||||
output.push_str(&format_tab(
|
||||
"pyproject.toml",
|
||||
&format_header(
|
||||
field.scope,
|
||||
field.example,
|
||||
parents,
|
||||
ConfigurationFile::PyprojectToml,
|
||||
),
|
||||
field.example,
|
||||
parents,
|
||||
ConfigurationFile::UvToml,
|
||||
),
|
||||
field.example,
|
||||
));
|
||||
));
|
||||
output.push_str(&format_tab(
|
||||
"uv.toml",
|
||||
&format_header(
|
||||
field.scope,
|
||||
field.example,
|
||||
parents,
|
||||
ConfigurationFile::UvToml,
|
||||
),
|
||||
field.example,
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -195,6 +195,7 @@ fn handle_option(field: &Field, attr: &Attribute) -> syn::Result<TokenStream> {
|
||||
example,
|
||||
scope,
|
||||
possible_values,
|
||||
uv_toml_only,
|
||||
} = parse_field_attributes(attr)?;
|
||||
let kebab_name = LitStr::new(&ident.to_string().replace('_', "-"), ident.span());
|
||||
|
||||
@@ -254,6 +255,7 @@ fn handle_option(field: &Field, attr: &Attribute) -> syn::Result<TokenStream> {
|
||||
scope: #scope,
|
||||
deprecated: #deprecated,
|
||||
possible_values: #possible_values,
|
||||
uv_toml_only: #uv_toml_only,
|
||||
})
|
||||
}
|
||||
))
|
||||
@@ -266,6 +268,7 @@ struct FieldAttributes {
|
||||
example: String,
|
||||
scope: Option<String>,
|
||||
possible_values: Option<bool>,
|
||||
uv_toml_only: bool,
|
||||
}
|
||||
|
||||
fn parse_field_attributes(attribute: &Attribute) -> syn::Result<FieldAttributes> {
|
||||
@@ -274,6 +277,7 @@ fn parse_field_attributes(attribute: &Attribute) -> syn::Result<FieldAttributes>
|
||||
let mut example = None;
|
||||
let mut scope = None;
|
||||
let mut possible_values = None;
|
||||
let mut uv_toml_only = None;
|
||||
|
||||
attribute.parse_nested_meta(|meta| {
|
||||
if meta.path.is_ident("default") {
|
||||
@@ -287,6 +291,8 @@ fn parse_field_attributes(attribute: &Attribute) -> syn::Result<FieldAttributes>
|
||||
example = Some(dedent(&example_text).trim_matches('\n').to_string());
|
||||
} else if meta.path.is_ident("possible_values") {
|
||||
possible_values = get_bool_literal(&meta, "possible_values", "option")?;
|
||||
} else if meta.path.is_ident("uv_toml_only") {
|
||||
uv_toml_only = get_bool_literal(&meta, "uv_toml_only", "option")?;
|
||||
} else {
|
||||
return Err(syn::Error::new(
|
||||
meta.path.span(),
|
||||
@@ -327,6 +333,7 @@ fn parse_field_attributes(attribute: &Attribute) -> syn::Result<FieldAttributes>
|
||||
example,
|
||||
scope,
|
||||
possible_values,
|
||||
uv_toml_only: uv_toml_only.unwrap_or(false),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -258,6 +258,8 @@ pub struct OptionField {
|
||||
pub example: &'static str,
|
||||
pub deprecated: Option<Deprecated>,
|
||||
pub possible_values: Option<Vec<PossibleValue>>,
|
||||
/// If true, this option is only available in `uv.toml`, not `pyproject.toml`.
|
||||
pub uv_toml_only: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
|
||||
@@ -343,6 +345,7 @@ mod tests {
|
||||
scope: None,
|
||||
deprecated: None,
|
||||
possible_values: None,
|
||||
uv_toml_only: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -368,6 +371,7 @@ mod tests {
|
||||
scope: None,
|
||||
deprecated: None,
|
||||
possible_values: None,
|
||||
uv_toml_only: false,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -389,6 +393,7 @@ mod tests {
|
||||
scope: None,
|
||||
deprecated: None,
|
||||
possible_values: None,
|
||||
uv_toml_only: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -411,6 +416,7 @@ mod tests {
|
||||
scope: None,
|
||||
deprecated: None,
|
||||
possible_values: None,
|
||||
uv_toml_only: false,
|
||||
};
|
||||
|
||||
impl OptionsMetadata for WithOptions {
|
||||
@@ -436,6 +442,7 @@ mod tests {
|
||||
scope: None,
|
||||
deprecated: None,
|
||||
possible_values: None,
|
||||
uv_toml_only: false,
|
||||
};
|
||||
|
||||
struct Root;
|
||||
@@ -452,6 +459,7 @@ mod tests {
|
||||
scope: None,
|
||||
deprecated: None,
|
||||
possible_values: None,
|
||||
uv_toml_only: false,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ use std::{collections::BTreeMap, num::NonZeroUsize};
|
||||
use url::Url;
|
||||
|
||||
use uv_configuration::{
|
||||
BuildIsolation, ExportFormat, IndexStrategy, KeyringProviderType, Reinstall, RequiredVersion,
|
||||
TargetTriple, TrustedPublishing, Upgrade,
|
||||
BuildIsolation, ExportFormat, IndexStrategy, KeyringProviderType, ProxyUrl, Reinstall,
|
||||
RequiredVersion, TargetTriple, TrustedPublishing, Upgrade,
|
||||
};
|
||||
use uv_distribution_types::{
|
||||
ConfigSettings, ExtraBuildVariables, Index, IndexUrl, PackageConfigSettings, PipExtraIndex,
|
||||
@@ -100,6 +100,7 @@ impl_combine_or!(PipExtraIndex);
|
||||
impl_combine_or!(PipFindLinks);
|
||||
impl_combine_or!(PipIndex);
|
||||
impl_combine_or!(PrereleaseMode);
|
||||
impl_combine_or!(ProxyUrl);
|
||||
impl_combine_or!(PythonDownloads);
|
||||
impl_combine_or!(PythonPreference);
|
||||
impl_combine_or!(PythonVersion);
|
||||
|
||||
@@ -303,6 +303,9 @@ fn warn_uv_toml_masked_fields(options: &Options) {
|
||||
concurrent_builds,
|
||||
concurrent_installs,
|
||||
allow_insecure_host,
|
||||
http_proxy,
|
||||
https_proxy,
|
||||
no_proxy,
|
||||
},
|
||||
top_level:
|
||||
ResolverInstallerSchema {
|
||||
@@ -408,6 +411,15 @@ fn warn_uv_toml_masked_fields(options: &Options) {
|
||||
if allow_insecure_host.is_some() {
|
||||
masked_fields.push("allow-insecure-host");
|
||||
}
|
||||
if http_proxy.is_some() {
|
||||
masked_fields.push("http-proxy");
|
||||
}
|
||||
if https_proxy.is_some() {
|
||||
masked_fields.push("https-proxy");
|
||||
}
|
||||
if no_proxy.is_some() {
|
||||
masked_fields.push("no-proxy");
|
||||
}
|
||||
if index.is_some() {
|
||||
masked_fields.push("index");
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use uv_cache_info::CacheKey;
|
||||
use uv_configuration::{
|
||||
BuildIsolation, IndexStrategy, KeyringProviderType, PackageNameSpecifier, Reinstall,
|
||||
BuildIsolation, IndexStrategy, KeyringProviderType, PackageNameSpecifier, ProxyUrl, Reinstall,
|
||||
RequiredVersion, TargetTriple, TrustedHost, TrustedPublishing, Upgrade,
|
||||
};
|
||||
use uv_distribution_types::{
|
||||
@@ -311,6 +311,36 @@ pub struct GlobalOptions {
|
||||
"#
|
||||
)]
|
||||
pub concurrent_installs: Option<NonZeroUsize>,
|
||||
/// The URL of the HTTP proxy to use.
|
||||
#[option(
|
||||
default = "None",
|
||||
value_type = "str",
|
||||
uv_toml_only = true,
|
||||
example = r#"
|
||||
http-proxy = "http://proxy.example.com"
|
||||
"#
|
||||
)]
|
||||
pub http_proxy: Option<ProxyUrl>,
|
||||
/// The URL of the HTTPS proxy to use.
|
||||
#[option(
|
||||
default = "None",
|
||||
value_type = "str",
|
||||
uv_toml_only = true,
|
||||
example = r#"
|
||||
https-proxy = "https://proxy.example.com"
|
||||
"#
|
||||
)]
|
||||
pub https_proxy: Option<ProxyUrl>,
|
||||
/// A list of hosts to exclude from proxying.
|
||||
#[option(
|
||||
default = "None",
|
||||
value_type = "list[str]",
|
||||
uv_toml_only = true,
|
||||
example = r#"
|
||||
no-proxy = ["localhost", "127.0.0.1"]
|
||||
"#
|
||||
)]
|
||||
pub no_proxy: Option<Vec<String>>,
|
||||
/// Allow insecure connections to host.
|
||||
///
|
||||
/// Expects to receive either a hostname (e.g., `localhost`), a host-port pair (e.g.,
|
||||
@@ -2105,6 +2135,9 @@ pub struct OptionsWire {
|
||||
find_links: Option<Vec<PipFindLinks>>,
|
||||
index_strategy: Option<IndexStrategy>,
|
||||
keyring_provider: Option<KeyringProviderType>,
|
||||
http_proxy: Option<ProxyUrl>,
|
||||
https_proxy: Option<ProxyUrl>,
|
||||
no_proxy: Option<Vec<String>>,
|
||||
allow_insecure_host: Option<Vec<TrustedHost>>,
|
||||
resolution: Option<ResolutionMode>,
|
||||
prerelease: Option<PrereleaseMode>,
|
||||
@@ -2200,6 +2233,9 @@ impl From<OptionsWire> for Options {
|
||||
find_links,
|
||||
index_strategy,
|
||||
keyring_provider,
|
||||
http_proxy,
|
||||
https_proxy,
|
||||
no_proxy,
|
||||
allow_insecure_host,
|
||||
resolution,
|
||||
prerelease,
|
||||
@@ -2262,6 +2298,9 @@ impl From<OptionsWire> for Options {
|
||||
concurrent_downloads,
|
||||
concurrent_builds,
|
||||
concurrent_installs,
|
||||
http_proxy,
|
||||
https_proxy,
|
||||
no_proxy,
|
||||
// Used twice for backwards compatibility
|
||||
allow_insecure_host: allow_insecure_host.clone(),
|
||||
},
|
||||
|
||||
@@ -199,7 +199,10 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
||||
settings.preview,
|
||||
settings.network_settings.timeout,
|
||||
settings.network_settings.retries,
|
||||
);
|
||||
)
|
||||
.http_proxy(settings.network_settings.http_proxy)
|
||||
.https_proxy(settings.network_settings.https_proxy)
|
||||
.no_proxy(settings.network_settings.no_proxy);
|
||||
Some(
|
||||
RunCommand::from_args(command, client_builder, *module, *script, *gui_script)
|
||||
.await?,
|
||||
@@ -472,7 +475,10 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
||||
globals.preview,
|
||||
globals.network_settings.timeout,
|
||||
globals.network_settings.retries,
|
||||
);
|
||||
)
|
||||
.http_proxy(globals.network_settings.http_proxy.clone())
|
||||
.https_proxy(globals.network_settings.https_proxy.clone())
|
||||
.no_proxy(globals.network_settings.no_proxy.clone());
|
||||
|
||||
match *cli.command {
|
||||
Commands::Auth(AuthNamespace {
|
||||
|
||||
@@ -28,8 +28,8 @@ use uv_configuration::{
|
||||
BuildIsolation, BuildOptions, Concurrency, DependencyGroups, DryRun, EditableMode, EnvFile,
|
||||
ExportFormat, ExtrasSpecification, GitLfsSetting, HashCheckingMode, IndexStrategy,
|
||||
InstallOptions, KeyringProviderType, NoBinary, NoBuild, PipCompileFormat, ProjectBuildBackend,
|
||||
Reinstall, RequiredVersion, SourceStrategy, TargetTriple, TrustedHost, TrustedPublishing,
|
||||
Upgrade, VersionControlSystem,
|
||||
ProxyUrl, Reinstall, RequiredVersion, SourceStrategy, TargetTriple, TrustedHost,
|
||||
TrustedPublishing, Upgrade, VersionControlSystem,
|
||||
};
|
||||
use uv_distribution_types::{
|
||||
ConfigSettings, DependencyMetadata, ExtraBuildVariables, Index, IndexLocations, IndexUrl,
|
||||
@@ -184,6 +184,9 @@ fn resolve_python_preference(
|
||||
pub(crate) struct NetworkSettings {
|
||||
pub(crate) connectivity: Connectivity,
|
||||
pub(crate) native_tls: bool,
|
||||
pub(crate) http_proxy: Option<ProxyUrl>,
|
||||
pub(crate) https_proxy: Option<ProxyUrl>,
|
||||
pub(crate) no_proxy: Option<Vec<String>>,
|
||||
pub(crate) allow_insecure_host: Vec<TrustedHost>,
|
||||
pub(crate) timeout: Duration,
|
||||
pub(crate) retries: u32,
|
||||
@@ -223,9 +226,16 @@ impl NetworkSettings {
|
||||
.flatten(),
|
||||
)
|
||||
.collect();
|
||||
let http_proxy = workspace.and_then(|workspace| workspace.globals.http_proxy.clone());
|
||||
let https_proxy = workspace.and_then(|workspace| workspace.globals.https_proxy.clone());
|
||||
let no_proxy = workspace.and_then(|workspace| workspace.globals.no_proxy.clone());
|
||||
|
||||
Self {
|
||||
connectivity,
|
||||
native_tls,
|
||||
http_proxy,
|
||||
https_proxy,
|
||||
no_proxy,
|
||||
allow_insecure_host,
|
||||
timeout: environment.http_timeout,
|
||||
retries: environment.http_retries,
|
||||
|
||||
@@ -4,15 +4,121 @@ use assert_fs::fixture::{ChildPath, FileWriteStr, PathChild};
|
||||
use http::StatusCode;
|
||||
use serde_json::json;
|
||||
use uv_static::EnvVars;
|
||||
use wiremock::matchers::method;
|
||||
use wiremock::matchers::{any, method};
|
||||
use wiremock::{Mock, MockServer, Request, ResponseTemplate};
|
||||
|
||||
use crate::common::{TestContext, uv_snapshot};
|
||||
|
||||
/// Creates a CONNECT tunnel proxy that forwards connections to the target.
|
||||
///
|
||||
/// Returns the proxy address. The proxy runs in a background thread.
|
||||
fn start_connect_tunnel_proxy() -> std::net::SocketAddr {
|
||||
use std::io::{Read, Write};
|
||||
use std::net::{TcpListener, TcpStream};
|
||||
|
||||
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
|
||||
// Spawn a real OS thread for the proxy server
|
||||
std::thread::spawn(move || {
|
||||
for stream in listener.incoming() {
|
||||
let Ok(mut client) = stream else { break };
|
||||
|
||||
// Handle each connection in its own thread
|
||||
std::thread::spawn(move || {
|
||||
// Read the CONNECT request
|
||||
let mut buf = vec![0u8; 4096];
|
||||
let mut total_read = 0;
|
||||
loop {
|
||||
let n = match client.read(&mut buf[total_read..]) {
|
||||
Ok(0) | Err(_) => return,
|
||||
Ok(n) => n,
|
||||
};
|
||||
total_read += n;
|
||||
if buf[..total_read].windows(4).any(|w| w == b"\r\n\r\n") {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let request = String::from_utf8_lossy(&buf[..total_read]);
|
||||
|
||||
// Parse "CONNECT host:port HTTP/1.1\r\n"
|
||||
let Some(target_addr) = request
|
||||
.lines()
|
||||
.next()
|
||||
.and_then(|line| line.strip_prefix("CONNECT "))
|
||||
.and_then(|s| s.split_whitespace().next())
|
||||
.map(ToString::to_string)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Connect to the target
|
||||
let Ok(mut target) = TcpStream::connect(&target_addr) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Send 200 Connection Established
|
||||
if client
|
||||
.write_all(b"HTTP/1.1 200 Connection Established\r\n\r\n")
|
||||
.is_err()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Bidirectionally forward data using two threads
|
||||
let mut client_read = client.try_clone().unwrap();
|
||||
let mut target_write = target.try_clone().unwrap();
|
||||
|
||||
let c2t =
|
||||
std::thread::spawn(move || std::io::copy(&mut client_read, &mut target_write));
|
||||
|
||||
let _ = std::io::copy(&mut target, &mut client);
|
||||
let _ = c2t.join();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
addr
|
||||
}
|
||||
|
||||
/// Creates a mock that serves a Simple API index page for iniconfig.
|
||||
async fn mock_simple_api(server: &MockServer) {
|
||||
// Simple API response for iniconfig pointing to the real PyPI wheel.
|
||||
// Uses upload-time before EXCLUDE_NEWER (2024-03-25) so the package is available.
|
||||
let body = json!({
|
||||
"name": "iniconfig",
|
||||
"files": [{
|
||||
"filename": "iniconfig-2.0.0-py3-none-any.whl",
|
||||
"url": "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl",
|
||||
"hashes": {
|
||||
"sha256": "2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"
|
||||
},
|
||||
"requires-python": ">=3.8",
|
||||
"upload-time": "2024-01-01T00:00:00Z"
|
||||
}]
|
||||
});
|
||||
|
||||
// Serve the simple index for iniconfig - use any() matcher since HTTP proxy
|
||||
// requests may have the full URL in the path
|
||||
Mock::given(any())
|
||||
.respond_with(
|
||||
ResponseTemplate::new(200)
|
||||
.set_body_raw(body.to_string(), "application/vnd.pypi.simple.v1+json"),
|
||||
)
|
||||
.mount(server)
|
||||
.await;
|
||||
}
|
||||
|
||||
fn connection_reset(_request: &wiremock::Request) -> io::Error {
|
||||
io::Error::new(io::ErrorKind::ConnectionReset, "Connection reset by peer")
|
||||
}
|
||||
|
||||
/// Returns true if the mock server has received any requests.
|
||||
async fn has_received_requests(server: &MockServer) -> bool {
|
||||
!server.received_requests().await.unwrap().is_empty()
|
||||
}
|
||||
|
||||
/// Answers with a retryable HTTP status 500.
|
||||
async fn http_error_server() -> (MockServer, String) {
|
||||
let server = MockServer::start().await;
|
||||
@@ -538,3 +644,290 @@ async fn rfc9457_problem_details_license_violation() {
|
||||
╰─▶ HTTP status client error (403 Forbidden) for url ([SERVER]/packages/tqdm-4.67.1-py3-none-any.whl)
|
||||
");
|
||||
}
|
||||
|
||||
/// Test that invalid proxy URL in uv.toml produces a helpful error message.
|
||||
#[tokio::test]
|
||||
async fn proxy_invalid_url_in_uv_toml() {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let uv_toml = context.temp_dir.child("uv.toml");
|
||||
uv_toml
|
||||
.write_str(indoc::indoc! {r#"
|
||||
http-proxy = "ftp://proxy.example.com:8080"
|
||||
"#})
|
||||
.unwrap();
|
||||
|
||||
uv_snapshot!(context.filters(), context
|
||||
.pip_install()
|
||||
.arg("iniconfig")
|
||||
.env_remove(EnvVars::HTTP_PROXY)
|
||||
.env_remove(EnvVars::HTTPS_PROXY), @r#"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: Failed to parse: `uv.toml`
|
||||
Caused by: TOML parse error at line 1, column 14
|
||||
|
|
||||
1 | http-proxy = "ftp://proxy.example.com:8080"
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
invalid proxy URL scheme `ftp` in `ftp://proxy.example.com:8080/`: expected http, https, socks5, or socks5h
|
||||
"#);
|
||||
}
|
||||
|
||||
/// Test that invalid proxy URL (not a URL) in uv.toml produces a helpful error message.
|
||||
#[tokio::test]
|
||||
async fn proxy_invalid_url_not_a_url_in_uv_toml() {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let uv_toml = context.temp_dir.child("uv.toml");
|
||||
uv_toml
|
||||
.write_str(indoc::indoc! {r#"
|
||||
http-proxy = "not a valid url"
|
||||
"#})
|
||||
.unwrap();
|
||||
|
||||
uv_snapshot!(context.filters(), context
|
||||
.pip_install()
|
||||
.arg("iniconfig")
|
||||
.env_remove(EnvVars::HTTP_PROXY)
|
||||
.env_remove(EnvVars::HTTPS_PROXY), @r#"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: Failed to parse: `uv.toml`
|
||||
Caused by: TOML parse error at line 1, column 14
|
||||
|
|
||||
1 | http-proxy = "not a valid url"
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
invalid proxy URL: invalid international domain name
|
||||
"#);
|
||||
}
|
||||
|
||||
/// Test that valid proxy URL in uv.toml routes requests through the proxy.
|
||||
#[tokio::test]
|
||||
async fn proxy_valid_url_in_uv_toml() {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let target_server = MockServer::start().await;
|
||||
Mock::given(any())
|
||||
.respond_with(ResponseTemplate::new(200))
|
||||
.mount(&target_server)
|
||||
.await;
|
||||
|
||||
let proxy_server = MockServer::start().await;
|
||||
mock_simple_api(&proxy_server).await;
|
||||
|
||||
let target_uri = target_server.uri();
|
||||
let proxy_uri = proxy_server.uri();
|
||||
|
||||
let context = context
|
||||
.with_filter((target_uri.clone(), "[TARGET]"))
|
||||
.with_filter((proxy_uri.clone(), "[PROXY]"));
|
||||
|
||||
let uv_toml = context.temp_dir.child("uv.toml");
|
||||
uv_toml
|
||||
.write_str(&format!(r#"http-proxy = "{proxy_uri}""#))
|
||||
.unwrap();
|
||||
|
||||
uv_snapshot!(context.filters(), context
|
||||
.pip_install()
|
||||
.arg("iniconfig")
|
||||
.arg("--index-url")
|
||||
.arg(&target_uri)
|
||||
.arg("--config-file")
|
||||
.arg(uv_toml.path())
|
||||
.env_remove(EnvVars::HTTP_PROXY)
|
||||
.env_remove(EnvVars::HTTPS_PROXY)
|
||||
.env_remove(EnvVars::ALL_PROXY)
|
||||
.env_remove(EnvVars::NO_PROXY), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ iniconfig==2.0.0
|
||||
");
|
||||
|
||||
assert!(
|
||||
has_received_requests(&proxy_server).await,
|
||||
"Proxy should have received the request"
|
||||
);
|
||||
assert!(
|
||||
!has_received_requests(&target_server).await,
|
||||
"Target should NOT have been called directly when proxy is configured"
|
||||
);
|
||||
}
|
||||
|
||||
/// Test that https-proxy in uv.toml routes HTTPS requests through a CONNECT tunnel proxy.
|
||||
#[test]
|
||||
fn proxy_https_proxy_in_uv_toml() {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let proxy_addr = start_connect_tunnel_proxy();
|
||||
let proxy_uri = format!("http://{proxy_addr}");
|
||||
|
||||
let context = context.with_filter((proxy_uri.clone(), "[PROXY]"));
|
||||
|
||||
let uv_toml = context.temp_dir.child("uv.toml");
|
||||
uv_toml
|
||||
.write_str(&format!(r#"https-proxy = "{proxy_uri}""#))
|
||||
.unwrap();
|
||||
|
||||
uv_snapshot!(context.filters(), context
|
||||
.pip_install()
|
||||
.arg("--config-file")
|
||||
.arg(uv_toml.path())
|
||||
.arg("iniconfig")
|
||||
.env_remove(EnvVars::HTTP_PROXY)
|
||||
.env_remove(EnvVars::HTTPS_PROXY)
|
||||
.env_remove(EnvVars::ALL_PROXY)
|
||||
.env_remove(EnvVars::NO_PROXY), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ iniconfig==2.0.0
|
||||
");
|
||||
}
|
||||
|
||||
/// Test that no-proxy in uv.toml bypasses the proxy for specified hosts.
|
||||
#[tokio::test]
|
||||
async fn proxy_no_proxy_in_uv_toml() {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let target_server = MockServer::start().await;
|
||||
mock_simple_api(&target_server).await;
|
||||
|
||||
let proxy_server = MockServer::start().await;
|
||||
Mock::given(any())
|
||||
.respond_with(ResponseTemplate::new(200))
|
||||
.mount(&proxy_server)
|
||||
.await;
|
||||
|
||||
let target_uri = target_server.uri();
|
||||
let proxy_uri = proxy_server.uri();
|
||||
|
||||
// Note: reqwest's NoProxy matches on host only, not host:port
|
||||
let target_url = url::Url::parse(&target_uri).unwrap();
|
||||
let target_host = target_url.host_str().unwrap();
|
||||
|
||||
let context = context
|
||||
.with_filter((target_uri.clone(), "[TARGET]"))
|
||||
.with_filter((proxy_uri.clone(), "[PROXY]"));
|
||||
|
||||
let uv_toml = context.temp_dir.child("uv.toml");
|
||||
uv_toml
|
||||
.write_str(&format!(
|
||||
r#"
|
||||
http-proxy = "{proxy_uri}"
|
||||
no-proxy = ["{target_host}"]
|
||||
"#
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
uv_snapshot!(context.filters(), context
|
||||
.pip_install()
|
||||
.arg("iniconfig")
|
||||
.arg("--index-url")
|
||||
.arg(&target_uri)
|
||||
.arg("--config-file")
|
||||
.arg(uv_toml.path())
|
||||
.env_remove(EnvVars::HTTP_PROXY)
|
||||
.env_remove(EnvVars::HTTPS_PROXY)
|
||||
.env_remove(EnvVars::ALL_PROXY)
|
||||
.env_remove(EnvVars::NO_PROXY), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ iniconfig==2.0.0
|
||||
");
|
||||
|
||||
assert!(
|
||||
has_received_requests(&target_server).await,
|
||||
"Target should have received the request directly when in no-proxy list"
|
||||
);
|
||||
assert!(
|
||||
!has_received_requests(&proxy_server).await,
|
||||
"Proxy should NOT have received requests when target is in no-proxy list"
|
||||
);
|
||||
}
|
||||
|
||||
/// Test that proxy URLs without a scheme in uv.toml default to http://.
|
||||
#[tokio::test]
|
||||
async fn proxy_schemeless_url_in_uv_toml() {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let target_server = MockServer::start().await;
|
||||
Mock::given(any())
|
||||
.respond_with(ResponseTemplate::new(200))
|
||||
.mount(&target_server)
|
||||
.await;
|
||||
|
||||
let proxy_server = MockServer::start().await;
|
||||
mock_simple_api(&proxy_server).await;
|
||||
|
||||
let target_uri = target_server.uri();
|
||||
let proxy_uri = proxy_server.uri();
|
||||
|
||||
// Strip scheme to test schemeless URL handling
|
||||
let proxy_host = proxy_uri
|
||||
.strip_prefix("http://")
|
||||
.unwrap_or(proxy_uri.as_str());
|
||||
|
||||
let context = context
|
||||
.with_filter((target_uri.clone(), "[TARGET]"))
|
||||
.with_filter((proxy_uri.clone(), "[PROXY]"))
|
||||
.with_filter((proxy_host, "[PROXY_HOST]"));
|
||||
|
||||
let uv_toml = context.temp_dir.child("uv.toml");
|
||||
uv_toml
|
||||
.write_str(&format!(r#"http-proxy = "{proxy_host}""#))
|
||||
.unwrap();
|
||||
|
||||
uv_snapshot!(context.filters(), context
|
||||
.pip_install()
|
||||
.arg("iniconfig")
|
||||
.arg("--index-url")
|
||||
.arg(&target_uri)
|
||||
.arg("--config-file")
|
||||
.arg(uv_toml.path())
|
||||
.env_remove(EnvVars::HTTP_PROXY)
|
||||
.env_remove(EnvVars::HTTPS_PROXY)
|
||||
.env_remove(EnvVars::ALL_PROXY)
|
||||
.env_remove(EnvVars::NO_PROXY), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ iniconfig==2.0.0
|
||||
");
|
||||
|
||||
assert!(
|
||||
has_received_requests(&proxy_server).await,
|
||||
"Proxy should have received the request even with schemeless URL"
|
||||
);
|
||||
assert!(
|
||||
!has_received_requests(&target_server).await,
|
||||
"Target should NOT have been called directly when proxy is configured"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -65,6 +65,9 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -268,6 +271,9 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -472,6 +478,9 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -708,6 +717,9 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -913,6 +925,9 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -1094,6 +1109,9 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -1324,6 +1342,9 @@ fn resolve_index_url() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -1562,6 +1583,9 @@ fn resolve_index_url() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -1858,6 +1882,9 @@ fn resolve_find_links() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -2085,6 +2112,9 @@ fn resolve_top_level() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -2271,6 +2301,9 @@ fn resolve_top_level() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -2507,6 +2540,9 @@ fn resolve_top_level() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -2766,6 +2802,9 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -2942,6 +2981,9 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -3118,6 +3160,9 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -3296,6 +3341,9 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -3493,6 +3541,9 @@ fn resolve_tool() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -3684,6 +3735,9 @@ fn resolve_poetry_toml() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -3894,6 +3948,9 @@ fn resolve_both() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -4143,6 +4200,9 @@ fn resolve_both_special_fields() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -4471,6 +4531,9 @@ fn resolve_config_file() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -4686,7 +4749,7 @@ fn resolve_config_file() -> anyhow::Result<()> {
|
||||
|
|
||||
1 | [project]
|
||||
| ^^^^^^^
|
||||
unknown field `project`, expected one of `required-version`, `native-tls`, `offline`, `no-cache`, `cache-dir`, `preview`, `python-preference`, `python-downloads`, `concurrent-downloads`, `concurrent-builds`, `concurrent-installs`, `index`, `index-url`, `extra-index-url`, `no-index`, `find-links`, `index-strategy`, `keyring-provider`, `allow-insecure-host`, `resolution`, `prerelease`, `fork-strategy`, `dependency-metadata`, `config-settings`, `config-settings-package`, `no-build-isolation`, `no-build-isolation-package`, `extra-build-dependencies`, `extra-build-variables`, `exclude-newer`, `exclude-newer-package`, `link-mode`, `compile-bytecode`, `no-sources`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `torch-backend`, `python-install-mirror`, `pypy-install-mirror`, `python-downloads-json-url`, `publish-url`, `trusted-publishing`, `check-url`, `add-bounds`, `pip`, `cache-keys`, `override-dependencies`, `exclude-dependencies`, `constraint-dependencies`, `build-constraint-dependencies`, `environments`, `required-environments`, `conflicts`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dependency-groups`, `dev-dependencies`, `build-backend`
|
||||
unknown field `project`, expected one of `required-version`, `native-tls`, `offline`, `no-cache`, `cache-dir`, `preview`, `python-preference`, `python-downloads`, `concurrent-downloads`, `concurrent-builds`, `concurrent-installs`, `index`, `index-url`, `extra-index-url`, `no-index`, `find-links`, `index-strategy`, `keyring-provider`, `http-proxy`, `https-proxy`, `no-proxy`, `allow-insecure-host`, `resolution`, `prerelease`, `fork-strategy`, `dependency-metadata`, `config-settings`, `config-settings-package`, `no-build-isolation`, `no-build-isolation-package`, `extra-build-dependencies`, `extra-build-variables`, `exclude-newer`, `exclude-newer-package`, `link-mode`, `compile-bytecode`, `no-sources`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `torch-backend`, `python-install-mirror`, `pypy-install-mirror`, `python-downloads-json-url`, `publish-url`, `trusted-publishing`, `check-url`, `add-bounds`, `pip`, `cache-keys`, `override-dependencies`, `exclude-dependencies`, `constraint-dependencies`, `build-constraint-dependencies`, `environments`, `required-environments`, `conflicts`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dependency-groups`, `dev-dependencies`, `build-backend`
|
||||
"
|
||||
);
|
||||
|
||||
@@ -4774,6 +4837,9 @@ fn resolve_skip_empty() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -4953,6 +5019,9 @@ fn resolve_skip_empty() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -5140,6 +5209,9 @@ fn allow_insecure_host() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [
|
||||
Host {
|
||||
scheme: None,
|
||||
@@ -5341,6 +5413,9 @@ fn index_priority() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -5579,6 +5654,9 @@ fn index_priority() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -5823,6 +5901,9 @@ fn index_priority() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -6062,6 +6143,9 @@ fn index_priority() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -6308,6 +6392,9 @@ fn index_priority() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -6547,6 +6634,9 @@ fn index_priority() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -6799,6 +6889,9 @@ fn verify_hashes() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -6968,6 +7061,9 @@ fn verify_hashes() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -7135,6 +7231,9 @@ fn verify_hashes() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -7304,6 +7403,9 @@ fn verify_hashes() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -7471,6 +7573,9 @@ fn verify_hashes() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -7639,6 +7744,9 @@ fn verify_hashes() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -7822,6 +7930,9 @@ fn preview_features() {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -7937,6 +8048,9 @@ fn preview_features() {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -8052,6 +8166,9 @@ fn preview_features() {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -8167,6 +8284,9 @@ fn preview_features() {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -8282,6 +8402,9 @@ fn preview_features() {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -8399,6 +8522,9 @@ fn preview_features() {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -8535,6 +8661,9 @@ fn upgrade_pip_cli_config_interaction() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -8712,6 +8841,9 @@ fn upgrade_pip_cli_config_interaction() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -8912,6 +9044,9 @@ fn upgrade_pip_cli_config_interaction() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -9087,6 +9222,9 @@ fn upgrade_pip_cli_config_interaction() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -9256,6 +9394,9 @@ fn upgrade_pip_cli_config_interaction() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -9426,6 +9567,9 @@ fn upgrade_pip_cli_config_interaction() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -9661,6 +9805,9 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -9781,6 +9928,9 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -9924,6 +10074,9 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -10042,6 +10195,9 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -10150,6 +10306,9 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -10259,6 +10418,9 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -10432,6 +10594,9 @@ fn build_isolation_override() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
@@ -10604,6 +10769,9 @@ fn build_isolation_override() -> anyhow::Result<()> {
|
||||
network_settings: NetworkSettings {
|
||||
connectivity: Online,
|
||||
native_tls: false,
|
||||
http_proxy: None,
|
||||
https_proxy: None,
|
||||
no_proxy: None,
|
||||
allow_insecure_host: [],
|
||||
timeout: [TIME],
|
||||
retries: 3,
|
||||
|
||||
34
uv.schema.json
generated
34
uv.schema.json
generated
@@ -243,6 +243,28 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"http-proxy": {
|
||||
"description": "The URL of the HTTP proxy to use.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ProxyUrl"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"https-proxy": {
|
||||
"description": "The URL of the HTTPS proxy to use.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ProxyUrl"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"index": {
|
||||
"description": "The indexes to use when resolving dependencies.\n\nAccepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/)\n(the simple repository API), or a local directory laid out in the same format.\n\nIndexes are considered in the order in which they're defined, such that the first-defined\nindex has the highest priority. Further, the indexes provided by this setting are given\nhigher priority than any indexes specified via [`index_url`](#index-url) or\n[`extra_index_url`](#extra-index-url). uv will only consider the first index that contains\na given package, unless an alternative [index strategy](#index-strategy) is specified.\n\nIf an index is marked as `explicit = true`, it will be used exclusively for the\ndependencies that select it explicitly via `[tool.uv.sources]`, as in:\n\n```toml\n[[tool.uv.index]]\nname = \"pytorch\"\nurl = \"https://download.pytorch.org/whl/cu121\"\nexplicit = true\n\n[tool.uv.sources]\ntorch = { index = \"pytorch\" }\n```\n\nIf an index is marked as `default = true`, it will be moved to the end of the prioritized list, such that it is\ngiven the lowest priority when resolving packages. Additionally, marking an index as default will disable the\nPyPI default index.",
|
||||
"type": ["array", "null"],
|
||||
@@ -344,6 +366,13 @@
|
||||
"description": "Ignore all registry indexes (e.g., PyPI), instead relying on direct URL dependencies and\nthose provided via `--find-links`.",
|
||||
"type": ["boolean", "null"]
|
||||
},
|
||||
"no-proxy": {
|
||||
"description": "A list of hosts to exclude from proxying.",
|
||||
"type": ["array", "null"],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"no-sources": {
|
||||
"description": "Ignore the `tool.uv.sources` table when resolving dependencies. Used to lock against the\nstandards-compliant, publishable package metadata, as opposed to using any local or Git\nsources.",
|
||||
"type": ["boolean", "null"]
|
||||
@@ -1521,6 +1550,11 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ProxyUrl": {
|
||||
"description": "A proxy URL (e.g., `http://proxy.example.com:8080`).",
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
},
|
||||
"PythonDownloads": {
|
||||
"oneOf": [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user