mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-22 16:22:09 +00:00
Expand environment variables prior to detecting scheme (#2394)
## Summary This PR ensures that we expand environment variables _before_ sniffing for the URL scheme (e.g., `file://` vs. `https://` vs. something else). Closes https://github.com/astral-sh/uv/issues/2375.
This commit is contained in:
parent
3bf20f95e4
commit
7220894ffb
7 changed files with 286 additions and 111 deletions
|
@ -9,7 +9,7 @@ use once_cell::sync::Lazy;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use pep508_rs::{split_scheme, Scheme, VerbatimUrl};
|
use pep508_rs::{expand_env_vars, split_scheme, Scheme, VerbatimUrl};
|
||||||
use uv_fs::normalize_url_path;
|
use uv_fs::normalize_url_path;
|
||||||
|
|
||||||
use crate::Verbatim;
|
use crate::Verbatim;
|
||||||
|
@ -108,7 +108,11 @@ impl FromStr for FlatIndexLocation {
|
||||||
/// - `../ferris/`
|
/// - `../ferris/`
|
||||||
/// - `https://download.pytorch.org/whl/torch_stable.html`
|
/// - `https://download.pytorch.org/whl/torch_stable.html`
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
if let Some((scheme, path)) = split_scheme(s) {
|
// Expand environment variables.
|
||||||
|
let expanded = expand_env_vars(s);
|
||||||
|
|
||||||
|
// Parse the expanded path.
|
||||||
|
if let Some((scheme, path)) = split_scheme(&expanded) {
|
||||||
match Scheme::parse(scheme) {
|
match Scheme::parse(scheme) {
|
||||||
// Ex) `file:///home/ferris/project/scripts/...` or `file:../ferris/`
|
// Ex) `file:///home/ferris/project/scripts/...` or `file:../ferris/`
|
||||||
Some(Scheme::File) => {
|
Some(Scheme::File) => {
|
||||||
|
@ -123,19 +127,19 @@ impl FromStr for FlatIndexLocation {
|
||||||
|
|
||||||
// Ex) `https://download.pytorch.org/whl/torch_stable.html`
|
// Ex) `https://download.pytorch.org/whl/torch_stable.html`
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
let url = Url::parse(s)?;
|
let url = Url::parse(expanded.as_ref())?;
|
||||||
Ok(Self::Url(url))
|
Ok(Self::Url(url))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ex) `C:\Users\ferris\wheel-0.42.0.tar.gz`
|
// Ex) `C:\Users\ferris\wheel-0.42.0.tar.gz`
|
||||||
None => {
|
None => {
|
||||||
let path = PathBuf::from(s);
|
let path = PathBuf::from(expanded.as_ref());
|
||||||
Ok(Self::Path(path))
|
Ok(Self::Path(path))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Ex) `../ferris/`
|
// Ex) `../ferris/`
|
||||||
let path = PathBuf::from(s);
|
let path = PathBuf::from(expanded.as_ref());
|
||||||
Ok(Self::Path(path))
|
Ok(Self::Path(path))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,8 +44,9 @@ pub use marker::{
|
||||||
use pep440_rs::{Version, VersionSpecifier, VersionSpecifiers};
|
use pep440_rs::{Version, VersionSpecifier, VersionSpecifiers};
|
||||||
use uv_fs::normalize_url_path;
|
use uv_fs::normalize_url_path;
|
||||||
// Parity with the crates.io version of pep508_rs
|
// Parity with the crates.io version of pep508_rs
|
||||||
|
use crate::verbatim_url::VerbatimUrlError;
|
||||||
pub use uv_normalize::{ExtraName, InvalidNameError, PackageName};
|
pub use uv_normalize::{ExtraName, InvalidNameError, PackageName};
|
||||||
pub use verbatim_url::{expand_path_vars, split_scheme, Scheme, VerbatimUrl};
|
pub use verbatim_url::{expand_env_vars, split_scheme, Scheme, VerbatimUrl};
|
||||||
|
|
||||||
mod marker;
|
mod marker;
|
||||||
mod verbatim_url;
|
mod verbatim_url;
|
||||||
|
@ -803,7 +804,10 @@ fn preprocess_url(
|
||||||
start: usize,
|
start: usize,
|
||||||
len: usize,
|
len: usize,
|
||||||
) -> Result<VerbatimUrl, Pep508Error> {
|
) -> Result<VerbatimUrl, Pep508Error> {
|
||||||
if let Some((scheme, path)) = split_scheme(url) {
|
// Expand environment variables in the URL.
|
||||||
|
let expanded = expand_env_vars(url);
|
||||||
|
|
||||||
|
if let Some((scheme, path)) = split_scheme(&expanded) {
|
||||||
match Scheme::parse(scheme) {
|
match Scheme::parse(scheme) {
|
||||||
// Ex) `file:///home/ferris/project/scripts/...` or `file:../editable/`.
|
// Ex) `file:///home/ferris/project/scripts/...` or `file:../editable/`.
|
||||||
Some(Scheme::File) => {
|
Some(Scheme::File) => {
|
||||||
|
@ -814,12 +818,11 @@ fn preprocess_url(
|
||||||
|
|
||||||
#[cfg(feature = "non-pep508-extensions")]
|
#[cfg(feature = "non-pep508-extensions")]
|
||||||
if let Some(working_dir) = working_dir {
|
if let Some(working_dir) = working_dir {
|
||||||
return Ok(
|
return Ok(VerbatimUrl::parse_path(path.as_ref(), working_dir)
|
||||||
VerbatimUrl::parse_path(path, working_dir).with_given(url.to_string())
|
.with_given(url.to_string()));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(VerbatimUrl::parse_absolute_path(path)
|
Ok(VerbatimUrl::parse_absolute_path(path.as_ref())
|
||||||
.map_err(|err| Pep508Error {
|
.map_err(|err| Pep508Error {
|
||||||
message: Pep508ErrorSource::UrlError(err),
|
message: Pep508ErrorSource::UrlError(err),
|
||||||
start,
|
start,
|
||||||
|
@ -831,24 +834,25 @@ fn preprocess_url(
|
||||||
// Ex) `https://download.pytorch.org/whl/torch_stable.html`
|
// Ex) `https://download.pytorch.org/whl/torch_stable.html`
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
// Ex) `https://download.pytorch.org/whl/torch_stable.html`
|
// Ex) `https://download.pytorch.org/whl/torch_stable.html`
|
||||||
Ok(VerbatimUrl::from_str(url).map_err(|err| Pep508Error {
|
Ok(VerbatimUrl::parse_url(expanded.as_ref())
|
||||||
message: Pep508ErrorSource::UrlError(err),
|
.map_err(|err| Pep508Error {
|
||||||
|
message: Pep508ErrorSource::UrlError(VerbatimUrlError::Url(err)),
|
||||||
start,
|
start,
|
||||||
len,
|
len,
|
||||||
input: cursor.to_string(),
|
input: cursor.to_string(),
|
||||||
})?)
|
})?
|
||||||
|
.with_given(url.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ex) `C:\Users\ferris\wheel-0.42.0.tar.gz`
|
// Ex) `C:\Users\ferris\wheel-0.42.0.tar.gz`
|
||||||
_ => {
|
_ => {
|
||||||
#[cfg(feature = "non-pep508-extensions")]
|
#[cfg(feature = "non-pep508-extensions")]
|
||||||
if let Some(working_dir) = working_dir {
|
if let Some(working_dir) = working_dir {
|
||||||
return Ok(
|
return Ok(VerbatimUrl::parse_path(expanded.as_ref(), working_dir)
|
||||||
VerbatimUrl::parse_path(url, working_dir).with_given(url.to_string())
|
.with_given(url.to_string()));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(VerbatimUrl::parse_absolute_path(url)
|
Ok(VerbatimUrl::parse_absolute_path(expanded.as_ref())
|
||||||
.map_err(|err| Pep508Error {
|
.map_err(|err| Pep508Error {
|
||||||
message: Pep508ErrorSource::UrlError(err),
|
message: Pep508ErrorSource::UrlError(err),
|
||||||
start,
|
start,
|
||||||
|
@ -862,10 +866,12 @@ fn preprocess_url(
|
||||||
// Ex) `../editable/`
|
// Ex) `../editable/`
|
||||||
#[cfg(feature = "non-pep508-extensions")]
|
#[cfg(feature = "non-pep508-extensions")]
|
||||||
if let Some(working_dir) = working_dir {
|
if let Some(working_dir) = working_dir {
|
||||||
return Ok(VerbatimUrl::parse_path(url, working_dir).with_given(url.to_string()));
|
return Ok(
|
||||||
|
VerbatimUrl::parse_path(expanded.as_ref(), working_dir).with_given(url.to_string())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(VerbatimUrl::parse_absolute_path(url)
|
Ok(VerbatimUrl::parse_absolute_path(expanded.as_ref())
|
||||||
.map_err(|err| Pep508Error {
|
.map_err(|err| Pep508Error {
|
||||||
message: Pep508ErrorSource::UrlError(err),
|
message: Pep508ErrorSource::UrlError(err),
|
||||||
start,
|
start,
|
||||||
|
|
|
@ -30,12 +30,6 @@ pub struct VerbatimUrl {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VerbatimUrl {
|
impl VerbatimUrl {
|
||||||
/// Parse a URL from a string, expanding any environment variables.
|
|
||||||
pub fn parse(given: impl AsRef<str>) -> Result<Self, ParseError> {
|
|
||||||
let url = Url::parse(&expand_env_vars(given.as_ref(), Escape::Url))?;
|
|
||||||
Ok(Self { url, given: None })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a [`VerbatimUrl`] from a [`Url`].
|
/// Create a [`VerbatimUrl`] from a [`Url`].
|
||||||
pub fn from_url(url: Url) -> Self {
|
pub fn from_url(url: Url) -> Self {
|
||||||
Self { url, given: None }
|
Self { url, given: None }
|
||||||
|
@ -48,15 +42,18 @@ impl VerbatimUrl {
|
||||||
Self { url, given: None }
|
Self { url, given: None }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a URL from a string, expanding any environment variables.
|
||||||
|
pub fn parse_url(given: impl AsRef<str>) -> Result<Self, ParseError> {
|
||||||
|
let url = Url::parse(given.as_ref())?;
|
||||||
|
Ok(Self { url, given: None })
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse a URL from an absolute or relative path.
|
/// Parse a URL from an absolute or relative path.
|
||||||
#[cfg(feature = "non-pep508-extensions")] // PEP 508 arguably only allows absolute file URLs.
|
#[cfg(feature = "non-pep508-extensions")] // PEP 508 arguably only allows absolute file URLs.
|
||||||
pub fn parse_path(path: impl AsRef<str>, working_dir: impl AsRef<Path>) -> Self {
|
pub fn parse_path(path: impl AsRef<Path>, working_dir: impl AsRef<Path>) -> Self {
|
||||||
// Expand any environment variables.
|
|
||||||
let path = PathBuf::from(expand_env_vars(path.as_ref(), Escape::Path).as_ref());
|
|
||||||
|
|
||||||
// Convert the path to an absolute path, if necessary.
|
// Convert the path to an absolute path, if necessary.
|
||||||
let path = if path.is_absolute() {
|
let path = if path.as_ref().is_absolute() {
|
||||||
path
|
path.as_ref().to_path_buf()
|
||||||
} else {
|
} else {
|
||||||
working_dir.as_ref().join(path)
|
working_dir.as_ref().join(path)
|
||||||
};
|
};
|
||||||
|
@ -71,15 +68,12 @@ impl VerbatimUrl {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a URL from an absolute path.
|
/// Parse a URL from an absolute path.
|
||||||
pub fn parse_absolute_path(path: impl AsRef<str>) -> Result<Self, VerbatimUrlError> {
|
pub fn parse_absolute_path(path: impl AsRef<Path>) -> Result<Self, VerbatimUrlError> {
|
||||||
// Expand any environment variables.
|
|
||||||
let path = PathBuf::from(expand_env_vars(path.as_ref(), Escape::Path).as_ref());
|
|
||||||
|
|
||||||
// Convert the path to an absolute path, if necessary.
|
// Convert the path to an absolute path, if necessary.
|
||||||
let path = if path.is_absolute() {
|
let path = if path.as_ref().is_absolute() {
|
||||||
path
|
path.as_ref().to_path_buf()
|
||||||
} else {
|
} else {
|
||||||
return Err(VerbatimUrlError::RelativePath(path));
|
return Err(VerbatimUrlError::RelativePath(path.as_ref().to_path_buf()));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Normalize the path.
|
// Normalize the path.
|
||||||
|
@ -128,9 +122,7 @@ impl std::str::FromStr for VerbatimUrl {
|
||||||
type Err = VerbatimUrlError;
|
type Err = VerbatimUrlError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
Self::parse(s)
|
Ok(Self::parse_url(s).map(|url| url.with_given(s.to_owned()))?)
|
||||||
.map(|url| url.with_given(s.to_owned()))
|
|
||||||
.map_err(|e| VerbatimUrlError::Url(s.to_owned(), e))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,23 +144,14 @@ impl Deref for VerbatimUrl {
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum VerbatimUrlError {
|
pub enum VerbatimUrlError {
|
||||||
/// Failed to parse a URL.
|
/// Failed to parse a URL.
|
||||||
#[error("{0}")]
|
#[error(transparent)]
|
||||||
Url(String, #[source] ParseError),
|
Url(#[from] ParseError),
|
||||||
|
|
||||||
/// Received a relative path, but no working directory was provided.
|
/// Received a relative path, but no working directory was provided.
|
||||||
#[error("relative path without a working directory: {0}")]
|
#[error("relative path without a working directory: {0}")]
|
||||||
RelativePath(PathBuf),
|
RelativePath(PathBuf),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether to apply percent-encoding when expanding environment variables.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
enum Escape {
|
|
||||||
/// Apply percent-encoding.
|
|
||||||
Url,
|
|
||||||
/// Do not apply percent-encoding.
|
|
||||||
Path,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Expand all available environment variables.
|
/// Expand all available environment variables.
|
||||||
///
|
///
|
||||||
/// This is modeled off of pip's environment variable expansion, which states:
|
/// This is modeled off of pip's environment variable expansion, which states:
|
||||||
|
@ -184,7 +167,7 @@ enum Escape {
|
||||||
/// Valid characters in variable names follow the `POSIX standard
|
/// Valid characters in variable names follow the `POSIX standard
|
||||||
/// <http://pubs.opengroup.org/onlinepubs/9699919799/>`_ and are limited
|
/// <http://pubs.opengroup.org/onlinepubs/9699919799/>`_ and are limited
|
||||||
/// to uppercase letter, digits and the `_` (underscore).
|
/// to uppercase letter, digits and the `_` (underscore).
|
||||||
fn expand_env_vars(s: &str, escape: Escape) -> Cow<'_, str> {
|
pub fn expand_env_vars(s: &str) -> Cow<'_, str> {
|
||||||
// Generate the project root, to be used via the `${PROJECT_ROOT}`
|
// Generate the project root, to be used via the `${PROJECT_ROOT}`
|
||||||
// environment variable.
|
// environment variable.
|
||||||
static PROJECT_ROOT_FRAGMENT: Lazy<String> = Lazy::new(|| {
|
static PROJECT_ROOT_FRAGMENT: Lazy<String> = Lazy::new(|| {
|
||||||
|
@ -198,21 +181,12 @@ fn expand_env_vars(s: &str, escape: Escape) -> Cow<'_, str> {
|
||||||
RE.replace_all(s, |caps: ®ex::Captures<'_>| {
|
RE.replace_all(s, |caps: ®ex::Captures<'_>| {
|
||||||
let name = caps.name("name").unwrap().as_str();
|
let name = caps.name("name").unwrap().as_str();
|
||||||
std::env::var(name).unwrap_or_else(|_| match name {
|
std::env::var(name).unwrap_or_else(|_| match name {
|
||||||
// Ensure that the variable is URL-escaped, if necessary.
|
"PROJECT_ROOT" => PROJECT_ROOT_FRAGMENT.to_string(),
|
||||||
"PROJECT_ROOT" => match escape {
|
|
||||||
Escape::Url => PROJECT_ROOT_FRAGMENT.replace(' ', "%20"),
|
|
||||||
Escape::Path => PROJECT_ROOT_FRAGMENT.to_string(),
|
|
||||||
},
|
|
||||||
_ => caps["var"].to_owned(),
|
_ => caps["var"].to_owned(),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expand all available environment variables in a path-like string.
|
|
||||||
pub fn expand_path_vars(path: &str) -> Cow<'_, str> {
|
|
||||||
expand_env_vars(path, Escape::Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Like [`Url::parse`], but only splits the scheme. Derived from the `url` crate.
|
/// Like [`Url::parse`], but only splits the scheme. Derived from the `url` crate.
|
||||||
pub fn split_scheme(s: &str) -> Option<(&str, &str)> {
|
pub fn split_scheme(s: &str) -> Option<(&str, &str)> {
|
||||||
/// <https://url.spec.whatwg.org/#c0-controls-and-space>
|
/// <https://url.spec.whatwg.org/#c0-controls-and-space>
|
||||||
|
|
|
@ -46,7 +46,7 @@ use unscanny::{Pattern, Scanner};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use pep508_rs::{
|
use pep508_rs::{
|
||||||
expand_path_vars, split_scheme, Extras, Pep508Error, Pep508ErrorSource, Requirement, Scheme,
|
expand_env_vars, split_scheme, Extras, Pep508Error, Pep508ErrorSource, Requirement, Scheme,
|
||||||
VerbatimUrl,
|
VerbatimUrl,
|
||||||
};
|
};
|
||||||
use uv_client::Connectivity;
|
use uv_client::Connectivity;
|
||||||
|
@ -97,7 +97,10 @@ impl FindLink {
|
||||||
/// - `../ferris/`
|
/// - `../ferris/`
|
||||||
/// - `https://download.pytorch.org/whl/torch_stable.html`
|
/// - `https://download.pytorch.org/whl/torch_stable.html`
|
||||||
pub fn parse(given: &str, working_dir: impl AsRef<Path>) -> Result<Self, url::ParseError> {
|
pub fn parse(given: &str, working_dir: impl AsRef<Path>) -> Result<Self, url::ParseError> {
|
||||||
if let Some((scheme, path)) = split_scheme(given) {
|
// Expand environment variables.
|
||||||
|
let expanded = expand_env_vars(given);
|
||||||
|
|
||||||
|
if let Some((scheme, path)) = split_scheme(&expanded) {
|
||||||
match Scheme::parse(scheme) {
|
match Scheme::parse(scheme) {
|
||||||
// Ex) `file:///home/ferris/project/scripts/...` or `file:../ferris/`
|
// Ex) `file:///home/ferris/project/scripts/...` or `file:../ferris/`
|
||||||
Some(Scheme::File) => {
|
Some(Scheme::File) => {
|
||||||
|
@ -117,13 +120,13 @@ impl FindLink {
|
||||||
|
|
||||||
// Ex) `https://download.pytorch.org/whl/torch_stable.html`
|
// Ex) `https://download.pytorch.org/whl/torch_stable.html`
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
let url = Url::parse(given)?;
|
let url = Url::parse(&expanded)?;
|
||||||
Ok(Self::Url(url))
|
Ok(Self::Url(url))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ex) `C:/Users/ferris/wheel-0.42.0.tar.gz`
|
// Ex) `C:/Users/ferris/wheel-0.42.0.tar.gz`
|
||||||
_ => {
|
_ => {
|
||||||
let path = PathBuf::from(given);
|
let path = PathBuf::from(expanded.as_ref());
|
||||||
let path = if path.is_absolute() {
|
let path = if path.is_absolute() {
|
||||||
path
|
path
|
||||||
} else {
|
} else {
|
||||||
|
@ -134,7 +137,7 @@ impl FindLink {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Ex) `../ferris/`
|
// Ex) `../ferris/`
|
||||||
let path = PathBuf::from(given);
|
let path = PathBuf::from(expanded.as_ref());
|
||||||
let path = if path.is_absolute() {
|
let path = if path.is_absolute() {
|
||||||
path
|
path
|
||||||
} else {
|
} else {
|
||||||
|
@ -208,8 +211,11 @@ impl EditableRequirement {
|
||||||
(given, vec![])
|
(given, vec![])
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Expand environment variables.
|
||||||
|
let expanded = expand_env_vars(requirement);
|
||||||
|
|
||||||
// Create a `VerbatimUrl` to represent the editable requirement.
|
// Create a `VerbatimUrl` to represent the editable requirement.
|
||||||
let url = if let Some((scheme, path)) = split_scheme(requirement) {
|
let url = if let Some((scheme, path)) = split_scheme(&expanded) {
|
||||||
match Scheme::parse(scheme) {
|
match Scheme::parse(scheme) {
|
||||||
// Ex) `file:///home/ferris/project/scripts/...` or `file:../editable/`
|
// Ex) `file:///home/ferris/project/scripts/...` or `file:../editable/`
|
||||||
Some(Scheme::File) => {
|
Some(Scheme::File) => {
|
||||||
|
@ -218,28 +224,28 @@ impl EditableRequirement {
|
||||||
// Transform, e.g., `/C:/Users/ferris/wheel-0.42.0.tar.gz` to `C:\Users\ferris\wheel-0.42.0.tar.gz`.
|
// Transform, e.g., `/C:/Users/ferris/wheel-0.42.0.tar.gz` to `C:\Users\ferris\wheel-0.42.0.tar.gz`.
|
||||||
let path = normalize_url_path(path);
|
let path = normalize_url_path(path);
|
||||||
|
|
||||||
VerbatimUrl::parse_path(path, working_dir.as_ref())
|
VerbatimUrl::parse_path(path.as_ref(), working_dir.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ex) `https://download.pytorch.org/whl/torch_stable.html`
|
// Ex) `https://download.pytorch.org/whl/torch_stable.html`
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
return Err(RequirementsTxtParserError::UnsupportedUrl(
|
return Err(RequirementsTxtParserError::UnsupportedUrl(
|
||||||
requirement.to_string(),
|
expanded.to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ex) `C:/Users/ferris/wheel-0.42.0.tar.gz`
|
// Ex) `C:/Users/ferris/wheel-0.42.0.tar.gz`
|
||||||
_ => VerbatimUrl::parse_path(requirement, working_dir.as_ref()),
|
_ => VerbatimUrl::parse_path(expanded.as_ref(), working_dir.as_ref()),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Ex) `../editable/`
|
// Ex) `../editable/`
|
||||||
VerbatimUrl::parse_path(requirement, working_dir.as_ref())
|
VerbatimUrl::parse_path(expanded.as_ref(), working_dir.as_ref())
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create a `PathBuf`.
|
// Create a `PathBuf`.
|
||||||
let path = url.to_file_path().map_err(|()| {
|
let path = url
|
||||||
RequirementsTxtParserError::InvalidEditablePath(requirement.to_string())
|
.to_file_path()
|
||||||
})?;
|
.map_err(|()| RequirementsTxtParserError::InvalidEditablePath(expanded.to_string()))?;
|
||||||
|
|
||||||
// Add the verbatim representation of the URL to the `VerbatimUrl`.
|
// Add the verbatim representation of the URL to the `VerbatimUrl`.
|
||||||
let url = url.with_given(requirement.to_string());
|
let url = url.with_given(requirement.to_string());
|
||||||
|
@ -409,7 +415,7 @@ impl RequirementsTxt {
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
} => {
|
} => {
|
||||||
let filename = expand_path_vars(&filename);
|
let filename = expand_env_vars(&filename);
|
||||||
let sub_file =
|
let sub_file =
|
||||||
if filename.starts_with("http://") || filename.starts_with("https://") {
|
if filename.starts_with("http://") || filename.starts_with("https://") {
|
||||||
PathBuf::from(filename.as_ref())
|
PathBuf::from(filename.as_ref())
|
||||||
|
@ -447,7 +453,7 @@ impl RequirementsTxt {
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
} => {
|
} => {
|
||||||
let filename = expand_path_vars(&filename);
|
let filename = expand_env_vars(&filename);
|
||||||
let sub_file =
|
let sub_file =
|
||||||
if filename.starts_with("http://") || filename.starts_with("https://") {
|
if filename.starts_with("http://") || filename.starts_with("https://") {
|
||||||
PathBuf::from(filename.as_ref())
|
PathBuf::from(filename.as_ref())
|
||||||
|
@ -569,7 +575,8 @@ fn parse_entry(
|
||||||
RequirementsTxtStatement::EditableRequirement(editable_requirement)
|
RequirementsTxtStatement::EditableRequirement(editable_requirement)
|
||||||
} else if s.eat_if("-i") || s.eat_if("--index-url") {
|
} else if s.eat_if("-i") || s.eat_if("--index-url") {
|
||||||
let given = parse_value(content, s, |c: char| !['\n', '\r'].contains(&c))?;
|
let given = parse_value(content, s, |c: char| !['\n', '\r'].contains(&c))?;
|
||||||
let url = VerbatimUrl::parse(given)
|
let expanded = expand_env_vars(given);
|
||||||
|
let url = VerbatimUrl::parse_url(expanded.as_ref())
|
||||||
.map(|url| url.with_given(given.to_owned()))
|
.map(|url| url.with_given(given.to_owned()))
|
||||||
.map_err(|err| RequirementsTxtParserError::Url {
|
.map_err(|err| RequirementsTxtParserError::Url {
|
||||||
source: err,
|
source: err,
|
||||||
|
@ -580,7 +587,8 @@ fn parse_entry(
|
||||||
RequirementsTxtStatement::IndexUrl(url)
|
RequirementsTxtStatement::IndexUrl(url)
|
||||||
} else if s.eat_if("--extra-index-url") {
|
} else if s.eat_if("--extra-index-url") {
|
||||||
let given = parse_value(content, s, |c: char| !['\n', '\r'].contains(&c))?;
|
let given = parse_value(content, s, |c: char| !['\n', '\r'].contains(&c))?;
|
||||||
let url = VerbatimUrl::parse(given)
|
let expanded = expand_env_vars(given);
|
||||||
|
let url = VerbatimUrl::parse_url(expanded.as_ref())
|
||||||
.map(|url| url.with_given(given.to_owned()))
|
.map(|url| url.with_given(given.to_owned()))
|
||||||
.map_err(|err| RequirementsTxtParserError::Url {
|
.map_err(|err| RequirementsTxtParserError::Url {
|
||||||
source: err,
|
source: err,
|
||||||
|
@ -1289,7 +1297,7 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn invalid_requirement() -> Result<()> {
|
async fn invalid_requirement_version() -> Result<()> {
|
||||||
let temp_dir = assert_fs::TempDir::new()?;
|
let temp_dir = assert_fs::TempDir::new()?;
|
||||||
let requirements_txt = temp_dir.child("requirements.txt");
|
let requirements_txt = temp_dir.child("requirements.txt");
|
||||||
requirements_txt.write_str(indoc! {"
|
requirements_txt.write_str(indoc! {"
|
||||||
|
@ -1325,6 +1333,43 @@ mod test {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn invalid_requirement_url() -> Result<()> {
|
||||||
|
let temp_dir = assert_fs::TempDir::new()?;
|
||||||
|
let requirements_txt = temp_dir.child("requirements.txt");
|
||||||
|
requirements_txt.write_str(indoc! {"
|
||||||
|
numpy @ https:///
|
||||||
|
"})?;
|
||||||
|
|
||||||
|
let error = RequirementsTxt::parse(
|
||||||
|
requirements_txt.path(),
|
||||||
|
temp_dir.path(),
|
||||||
|
Connectivity::Offline,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
let errors = anyhow::Error::new(error).chain().join("\n");
|
||||||
|
|
||||||
|
let requirement_txt =
|
||||||
|
regex::escape(&requirements_txt.path().simplified_display().to_string());
|
||||||
|
let filters = vec![
|
||||||
|
(requirement_txt.as_str(), "<REQUIREMENTS_TXT>"),
|
||||||
|
(r"\\", "/"),
|
||||||
|
];
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => filters
|
||||||
|
}, {
|
||||||
|
insta::assert_snapshot!(errors, @r###"
|
||||||
|
Couldn't parse requirement in `<REQUIREMENTS_TXT>` at position 0
|
||||||
|
empty host
|
||||||
|
numpy @ https:///
|
||||||
|
^^^^^^^^^
|
||||||
|
"###);
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn unsupported_editable() -> Result<()> {
|
async fn unsupported_editable() -> Result<()> {
|
||||||
let temp_dir = assert_fs::TempDir::new()?;
|
let temp_dir = assert_fs::TempDir::new()?;
|
||||||
|
@ -1392,7 +1437,7 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn invalid_index_url() -> Result<()> {
|
async fn relative_index_url() -> Result<()> {
|
||||||
let temp_dir = assert_fs::TempDir::new()?;
|
let temp_dir = assert_fs::TempDir::new()?;
|
||||||
let requirements_txt = temp_dir.child("requirements.txt");
|
let requirements_txt = temp_dir.child("requirements.txt");
|
||||||
requirements_txt.write_str(indoc! {"
|
requirements_txt.write_str(indoc! {"
|
||||||
|
@ -1426,6 +1471,41 @@ mod test {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn invalid_index_url() -> Result<()> {
|
||||||
|
let temp_dir = assert_fs::TempDir::new()?;
|
||||||
|
let requirements_txt = temp_dir.child("requirements.txt");
|
||||||
|
requirements_txt.write_str(indoc! {"
|
||||||
|
--index-url https:////
|
||||||
|
"})?;
|
||||||
|
|
||||||
|
let error = RequirementsTxt::parse(
|
||||||
|
requirements_txt.path(),
|
||||||
|
temp_dir.path(),
|
||||||
|
Connectivity::Offline,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
let errors = anyhow::Error::new(error).chain().join("\n");
|
||||||
|
|
||||||
|
let requirement_txt =
|
||||||
|
regex::escape(&requirements_txt.path().simplified_display().to_string());
|
||||||
|
let filters = vec![
|
||||||
|
(requirement_txt.as_str(), "<REQUIREMENTS_TXT>"),
|
||||||
|
(r"\\", "/"),
|
||||||
|
];
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => filters
|
||||||
|
}, {
|
||||||
|
insta::assert_snapshot!(errors, @r###"
|
||||||
|
Invalid URL in `<REQUIREMENTS_TXT>` at position 0: `https:////`
|
||||||
|
empty host
|
||||||
|
"###);
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn missing_r() -> Result<()> {
|
async fn missing_r() -> Result<()> {
|
||||||
let temp_dir = assert_fs::TempDir::new()?;
|
let temp_dir = assert_fs::TempDir::new()?;
|
||||||
|
|
|
@ -44,12 +44,12 @@ mod tests {
|
||||||
fn test_apply_redirect() -> Result<(), url::ParseError> {
|
fn test_apply_redirect() -> Result<(), url::ParseError> {
|
||||||
// If there's no `@` in the original representation, we can just append the precise suffix
|
// If there's no `@` in the original representation, we can just append the precise suffix
|
||||||
// to the given representation.
|
// to the given representation.
|
||||||
let verbatim = VerbatimUrl::parse("https://github.com/flask.git")?
|
let verbatim = VerbatimUrl::parse_url("https://github.com/flask.git")?
|
||||||
.with_given("git+https://github.com/flask.git");
|
.with_given("git+https://github.com/flask.git");
|
||||||
let redirect =
|
let redirect =
|
||||||
Url::parse("https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe")?;
|
Url::parse("https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe")?;
|
||||||
|
|
||||||
let expected = VerbatimUrl::parse(
|
let expected = VerbatimUrl::parse_url(
|
||||||
"https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe",
|
"https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe",
|
||||||
)?
|
)?
|
||||||
.with_given("https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe");
|
.with_given("https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe");
|
||||||
|
@ -58,24 +58,24 @@ mod tests {
|
||||||
// If there's an `@` in the original representation, and it's stable between the parsed and
|
// If there's an `@` in the original representation, and it's stable between the parsed and
|
||||||
// given representations, we preserve everything that precedes the `@` in the precise
|
// given representations, we preserve everything that precedes the `@` in the precise
|
||||||
// representation.
|
// representation.
|
||||||
let verbatim = VerbatimUrl::parse("https://github.com/flask.git@main")?
|
let verbatim = VerbatimUrl::parse_url("https://github.com/flask.git@main")?
|
||||||
.with_given("git+https://${DOMAIN}.com/flask.git@main");
|
.with_given("git+https://${DOMAIN}.com/flask.git@main");
|
||||||
let redirect =
|
let redirect =
|
||||||
Url::parse("https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe")?;
|
Url::parse("https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe")?;
|
||||||
|
|
||||||
let expected = VerbatimUrl::parse(
|
let expected = VerbatimUrl::parse_url(
|
||||||
"https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe",
|
"https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe",
|
||||||
)?
|
)?
|
||||||
.with_given("https://${DOMAIN}.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe");
|
.with_given("https://${DOMAIN}.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe");
|
||||||
assert_eq!(apply_redirect(&verbatim, &redirect), expected);
|
assert_eq!(apply_redirect(&verbatim, &redirect), expected);
|
||||||
|
|
||||||
// If there's a conflict after the `@`, discard the original representation.
|
// If there's a conflict after the `@`, discard the original representation.
|
||||||
let verbatim = VerbatimUrl::parse("https://github.com/flask.git@main")?
|
let verbatim = VerbatimUrl::parse_url("https://github.com/flask.git@main")?
|
||||||
.with_given("git+https://github.com/flask.git@${TAG}".to_string());
|
.with_given("git+https://github.com/flask.git@${TAG}".to_string());
|
||||||
let redirect =
|
let redirect =
|
||||||
Url::parse("https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe")?;
|
Url::parse("https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe")?;
|
||||||
|
|
||||||
let expected = VerbatimUrl::parse(
|
let expected = VerbatimUrl::parse_url(
|
||||||
"https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe",
|
"https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe",
|
||||||
)?;
|
)?;
|
||||||
assert_eq!(apply_redirect(&verbatim, &redirect), expected);
|
assert_eq!(apply_redirect(&verbatim, &redirect), expected);
|
||||||
|
|
|
@ -200,28 +200,28 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn url_compatibility() -> Result<(), url::ParseError> {
|
fn url_compatibility() -> Result<(), url::ParseError> {
|
||||||
// Same repository, same tag.
|
// Same repository, same tag.
|
||||||
let previous = VerbatimUrl::parse("git+https://example.com/MyProject.git@v1.0")?;
|
let previous = VerbatimUrl::parse_url("git+https://example.com/MyProject.git@v1.0")?;
|
||||||
let url = VerbatimUrl::parse("git+https://example.com/MyProject.git@v1.0")?;
|
let url = VerbatimUrl::parse_url("git+https://example.com/MyProject.git@v1.0")?;
|
||||||
assert!(is_equal(&previous, &url));
|
assert!(is_equal(&previous, &url));
|
||||||
|
|
||||||
// Same repository, different tags.
|
// Same repository, different tags.
|
||||||
let previous = VerbatimUrl::parse("git+https://example.com/MyProject.git@v1.0")?;
|
let previous = VerbatimUrl::parse_url("git+https://example.com/MyProject.git@v1.0")?;
|
||||||
let url = VerbatimUrl::parse("git+https://example.com/MyProject.git@v1.1")?;
|
let url = VerbatimUrl::parse_url("git+https://example.com/MyProject.git@v1.1")?;
|
||||||
assert!(!is_equal(&previous, &url));
|
assert!(!is_equal(&previous, &url));
|
||||||
|
|
||||||
// Same repository (with and without `.git`), same tag.
|
// Same repository (with and without `.git`), same tag.
|
||||||
let previous = VerbatimUrl::parse("git+https://example.com/MyProject@v1.0")?;
|
let previous = VerbatimUrl::parse_url("git+https://example.com/MyProject@v1.0")?;
|
||||||
let url = VerbatimUrl::parse("git+https://example.com/MyProject.git@v1.0")?;
|
let url = VerbatimUrl::parse_url("git+https://example.com/MyProject.git@v1.0")?;
|
||||||
assert!(is_equal(&previous, &url));
|
assert!(is_equal(&previous, &url));
|
||||||
|
|
||||||
// Same repository, no tag on the previous URL.
|
// Same repository, no tag on the previous URL.
|
||||||
let previous = VerbatimUrl::parse("git+https://example.com/MyProject.git")?;
|
let previous = VerbatimUrl::parse_url("git+https://example.com/MyProject.git")?;
|
||||||
let url = VerbatimUrl::parse("git+https://example.com/MyProject.git@v1.0")?;
|
let url = VerbatimUrl::parse_url("git+https://example.com/MyProject.git@v1.0")?;
|
||||||
assert!(!is_equal(&previous, &url));
|
assert!(!is_equal(&previous, &url));
|
||||||
|
|
||||||
// Same repository, tag on the previous URL, no tag on the overriding URL.
|
// Same repository, tag on the previous URL, no tag on the overriding URL.
|
||||||
let previous = VerbatimUrl::parse("git+https://example.com/MyProject.git@v1.0")?;
|
let previous = VerbatimUrl::parse_url("git+https://example.com/MyProject.git@v1.0")?;
|
||||||
let url = VerbatimUrl::parse("git+https://example.com/MyProject.git")?;
|
let url = VerbatimUrl::parse_url("git+https://example.com/MyProject.git")?;
|
||||||
assert!(!is_equal(&previous, &url));
|
assert!(!is_equal(&previous, &url));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -230,29 +230,29 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn url_precision() -> Result<(), url::ParseError> {
|
fn url_precision() -> Result<(), url::ParseError> {
|
||||||
// Same repository, no tag on the previous URL, non-SHA on the overriding URL.
|
// Same repository, no tag on the previous URL, non-SHA on the overriding URL.
|
||||||
let previous = VerbatimUrl::parse("git+https://example.com/MyProject.git")?;
|
let previous = VerbatimUrl::parse_url("git+https://example.com/MyProject.git")?;
|
||||||
let url = VerbatimUrl::parse("git+https://example.com/MyProject.git@v1.0")?;
|
let url = VerbatimUrl::parse_url("git+https://example.com/MyProject.git@v1.0")?;
|
||||||
assert!(!is_precise(&previous, &url));
|
assert!(!is_precise(&previous, &url));
|
||||||
|
|
||||||
// Same repository, no tag on the previous URL, SHA on the overriding URL.
|
// Same repository, no tag on the previous URL, SHA on the overriding URL.
|
||||||
let previous = VerbatimUrl::parse("git+https://example.com/MyProject.git")?;
|
let previous = VerbatimUrl::parse_url("git+https://example.com/MyProject.git")?;
|
||||||
let url = VerbatimUrl::parse(
|
let url = VerbatimUrl::parse_url(
|
||||||
"git+https://example.com/MyProject.git@c3cd550a7a7c41b2c286ca52fbb6dec5fea195ef",
|
"git+https://example.com/MyProject.git@c3cd550a7a7c41b2c286ca52fbb6dec5fea195ef",
|
||||||
)?;
|
)?;
|
||||||
assert!(is_precise(&previous, &url));
|
assert!(is_precise(&previous, &url));
|
||||||
|
|
||||||
// Same repository, tag on the previous URL, SHA on the overriding URL.
|
// Same repository, tag on the previous URL, SHA on the overriding URL.
|
||||||
let previous = VerbatimUrl::parse("git+https://example.com/MyProject.git@v1.0")?;
|
let previous = VerbatimUrl::parse_url("git+https://example.com/MyProject.git@v1.0")?;
|
||||||
let url = VerbatimUrl::parse(
|
let url = VerbatimUrl::parse_url(
|
||||||
"git+https://example.com/MyProject.git@c3cd550a7a7c41b2c286ca52fbb6dec5fea195ef",
|
"git+https://example.com/MyProject.git@c3cd550a7a7c41b2c286ca52fbb6dec5fea195ef",
|
||||||
)?;
|
)?;
|
||||||
assert!(is_precise(&previous, &url));
|
assert!(is_precise(&previous, &url));
|
||||||
|
|
||||||
// Same repository, SHA on the previous URL, different SHA on the overriding URL.
|
// Same repository, SHA on the previous URL, different SHA on the overriding URL.
|
||||||
let previous = VerbatimUrl::parse(
|
let previous = VerbatimUrl::parse_url(
|
||||||
"git+https://example.com/MyProject.git@5ae5980c885e350a34ca019a84ba14a2a228d262",
|
"git+https://example.com/MyProject.git@5ae5980c885e350a34ca019a84ba14a2a228d262",
|
||||||
)?;
|
)?;
|
||||||
let url = VerbatimUrl::parse(
|
let url = VerbatimUrl::parse_url(
|
||||||
"git+https://example.com/MyProject.git@c3cd550a7a7c41b2c286ca52fbb6dec5fea195ef",
|
"git+https://example.com/MyProject.git@c3cd550a7a7c41b2c286ca52fbb6dec5fea195ef",
|
||||||
)?;
|
)?;
|
||||||
assert!(!is_precise(&previous, &url));
|
assert!(!is_precise(&previous, &url));
|
||||||
|
|
|
@ -2399,7 +2399,7 @@ fn preserve_url() -> Result<()> {
|
||||||
/// Resolve a dependency from a URL, preserving the unexpanded environment variable as specified in
|
/// Resolve a dependency from a URL, preserving the unexpanded environment variable as specified in
|
||||||
/// the requirements file.
|
/// the requirements file.
|
||||||
#[test]
|
#[test]
|
||||||
fn preserve_env_var() -> Result<()> {
|
fn preserve_project_root() -> Result<()> {
|
||||||
let context = TestContext::new("3.12");
|
let context = TestContext::new("3.12");
|
||||||
// Download a wheel.
|
// Download a wheel.
|
||||||
let response = reqwest::blocking::get("https://files.pythonhosted.org/packages/36/42/015c23096649b908c809c69388a805a571a3bea44362fe87e33fc3afa01f/flask-3.0.0-py3-none-any.whl")?;
|
let response = reqwest::blocking::get("https://files.pythonhosted.org/packages/36/42/015c23096649b908c809c69388a805a571a3bea44362fe87e33fc3afa01f/flask-3.0.0-py3-none-any.whl")?;
|
||||||
|
@ -2441,6 +2441,91 @@ fn preserve_env_var() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolve a dependency from a URL, passing in the entire URL as an environment variable.
|
||||||
|
#[test]
|
||||||
|
fn respect_http_env_var() -> Result<()> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
|
||||||
|
let requirements_in = context.temp_dir.child("requirements.in");
|
||||||
|
requirements_in.write_str("flask @ ${URL}")?;
|
||||||
|
|
||||||
|
uv_snapshot!(context.compile()
|
||||||
|
.arg("requirements.in")
|
||||||
|
.env("URL", "https://files.pythonhosted.org/packages/36/42/015c23096649b908c809c69388a805a571a3bea44362fe87e33fc3afa01f/flask-3.0.0-py3-none-any.whl"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
# This file was autogenerated by uv via the following command:
|
||||||
|
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2023-11-18T12:00:00Z requirements.in
|
||||||
|
blinker==1.7.0
|
||||||
|
# via flask
|
||||||
|
click==8.1.7
|
||||||
|
# via flask
|
||||||
|
flask @ ${URL}
|
||||||
|
itsdangerous==2.1.2
|
||||||
|
# via flask
|
||||||
|
jinja2==3.1.2
|
||||||
|
# via flask
|
||||||
|
markupsafe==2.1.3
|
||||||
|
# via
|
||||||
|
# jinja2
|
||||||
|
# werkzeug
|
||||||
|
werkzeug==3.0.1
|
||||||
|
# via flask
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 7 packages in [TIME]
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolve a dependency from a file path, passing in the entire path as an environment variable.
|
||||||
|
#[test]
|
||||||
|
fn respect_file_env_var() -> Result<()> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
// Download a wheel.
|
||||||
|
let response = reqwest::blocking::get("https://files.pythonhosted.org/packages/36/42/015c23096649b908c809c69388a805a571a3bea44362fe87e33fc3afa01f/flask-3.0.0-py3-none-any.whl")?;
|
||||||
|
let flask_wheel = context.temp_dir.child("flask-3.0.0-py3-none-any.whl");
|
||||||
|
let mut flask_wheel_file = std::fs::File::create(flask_wheel)?;
|
||||||
|
std::io::copy(&mut response.bytes()?.as_ref(), &mut flask_wheel_file)?;
|
||||||
|
|
||||||
|
let requirements_in = context.temp_dir.child("requirements.in");
|
||||||
|
requirements_in.write_str("flask @ ${FILE_PATH}")?;
|
||||||
|
|
||||||
|
uv_snapshot!(context.compile()
|
||||||
|
.arg("requirements.in")
|
||||||
|
.env("FILE_PATH", context.temp_dir.join("flask-3.0.0-py3-none-any.whl")), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
# This file was autogenerated by uv via the following command:
|
||||||
|
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2023-11-18T12:00:00Z requirements.in
|
||||||
|
blinker==1.7.0
|
||||||
|
# via flask
|
||||||
|
click==8.1.7
|
||||||
|
# via flask
|
||||||
|
flask @ ${FILE_PATH}
|
||||||
|
itsdangerous==2.1.2
|
||||||
|
# via flask
|
||||||
|
jinja2==3.1.2
|
||||||
|
# via flask
|
||||||
|
markupsafe==2.1.3
|
||||||
|
# via
|
||||||
|
# jinja2
|
||||||
|
# werkzeug
|
||||||
|
werkzeug==3.0.1
|
||||||
|
# via flask
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 7 packages in [TIME]
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "maturin")]
|
#[cfg(feature = "maturin")]
|
||||||
fn compile_editable() -> Result<()> {
|
fn compile_editable() -> Result<()> {
|
||||||
|
@ -3035,6 +3120,32 @@ fn find_links_url() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compile using `--find-links` with a URL passed via an environment variable.
|
||||||
|
#[test]
|
||||||
|
fn find_links_env_var() -> Result<()> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
let requirements_in = context.temp_dir.child("requirements.in");
|
||||||
|
requirements_in.write_str("tqdm\n--find-links ${URL}")?;
|
||||||
|
|
||||||
|
uv_snapshot!(context.compile()
|
||||||
|
.arg("requirements.in")
|
||||||
|
.arg("--no-index")
|
||||||
|
.env("URL", "https://download.pytorch.org/whl/torch_stable.html"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
# This file was autogenerated by uv via the following command:
|
||||||
|
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2023-11-18T12:00:00Z requirements.in --no-index
|
||||||
|
tqdm==4.64.1
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 1 package in [TIME]
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Compile using `--find-links` with a URL by resolving `tqdm` from the `PyTorch` wheels index,
|
/// Compile using `--find-links` with a URL by resolving `tqdm` from the `PyTorch` wheels index,
|
||||||
/// with the URL itself provided in a `requirements.txt` file.
|
/// with the URL itself provided in a `requirements.txt` file.
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue