Skip to content

Commit

Permalink
Simplify error
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Mar 12, 2024
1 parent 38650d2 commit a8cf59d
Show file tree
Hide file tree
Showing 7 changed files with 218 additions and 31 deletions.
2 changes: 1 addition & 1 deletion crates/distribution-types/src/index_url.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use url::Url;

use pep508_rs::{split_scheme, Scheme, VerbatimUrl, expand_env_vars};
use pep508_rs::{expand_env_vars, split_scheme, Scheme, VerbatimUrl};
use uv_fs::normalize_url_path;

use crate::Verbatim;
Expand Down
30 changes: 16 additions & 14 deletions crates/pep508-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ pub use marker::{
use pep440_rs::{Version, VersionSpecifier, VersionSpecifiers};
use uv_fs::normalize_url_path;
// Parity with the crates.io version of pep508_rs
use crate::verbatim_url::VerbatimUrlError;
pub use uv_normalize::{ExtraName, InvalidNameError, PackageName};
pub use verbatim_url::{expand_env_vars, split_scheme, Scheme, VerbatimUrl};
use crate::verbatim_url::VerbatimUrlError;

mod marker;
mod verbatim_url;
Expand Down Expand Up @@ -818,9 +818,8 @@ fn preprocess_url(

#[cfg(feature = "non-pep508-extensions")]
if let Some(working_dir) = working_dir {
return Ok(
VerbatimUrl::parse_path(path.as_ref(), working_dir).with_given(url.to_string())
);
return Ok(VerbatimUrl::parse_path(path.as_ref(), working_dir)
.with_given(url.to_string()));
}

Ok(VerbatimUrl::parse_absolute_path(path.as_ref())
Expand All @@ -835,21 +834,22 @@ fn preprocess_url(
// Ex) `https://download.pytorch.org/whl/torch_stable.html`
Some(_) => {
// Ex) `https://download.pytorch.org/whl/torch_stable.html`
Ok(VerbatimUrl::parse_url(expanded.as_ref()).map_err(|err| Pep508Error {
message: Pep508ErrorSource::UrlError(VerbatimUrlError::Url(expanded.to_string(), err)),
start,
len,
input: cursor.to_string(),
})?.with_given(url.to_string()))
Ok(VerbatimUrl::parse_url(expanded.as_ref())
.map_err(|err| Pep508Error {
message: Pep508ErrorSource::UrlError(VerbatimUrlError::Url(err)),
start,
len,
input: cursor.to_string(),
})?
.with_given(url.to_string()))
}

// Ex) `C:\Users\ferris\wheel-0.42.0.tar.gz`
_ => {
#[cfg(feature = "non-pep508-extensions")]
if let Some(working_dir) = working_dir {
return Ok(
VerbatimUrl::parse_path(expanded.as_ref(), 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(expanded.as_ref())
Expand All @@ -866,7 +866,9 @@ fn preprocess_url(
// Ex) `../editable/`
#[cfg(feature = "non-pep508-extensions")]
if let Some(working_dir) = working_dir {
return Ok(VerbatimUrl::parse_path(expanded.as_ref(), 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(expanded.as_ref())
Expand Down
8 changes: 3 additions & 5 deletions crates/pep508-rs/src/verbatim_url.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,7 @@ impl std::str::FromStr for VerbatimUrl {
type Err = VerbatimUrlError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse_url(s)
.map(|url| url.with_given(s.to_owned()))
.map_err(|e| VerbatimUrlError::Url(s.to_owned(), e))
Ok(Self::parse_url(s).map(|url| url.with_given(s.to_owned()))?)
}
}

Expand All @@ -146,8 +144,8 @@ impl Deref for VerbatimUrl {
#[derive(thiserror::Error, Debug)]
pub enum VerbatimUrlError {
/// Failed to parse a URL.
#[error("{0}")]
Url(String, #[source] ParseError),
#[error(transparent)]
Url(#[from] ParseError),

/// Received a relative path, but no working directory was provided.
#[error("relative path without a working directory: {0}")]
Expand Down
94 changes: 86 additions & 8 deletions crates/requirements-txt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ use tracing::instrument;
use unscanny::{Pattern, Scanner};
use url::Url;

use pep508_rs::{split_scheme, Extras, Pep508Error, Pep508ErrorSource, Requirement, Scheme, VerbatimUrl, expand_env_vars};
use pep508_rs::{
expand_env_vars, split_scheme, Extras, Pep508Error, Pep508ErrorSource, Requirement, Scheme,
VerbatimUrl,
};
use uv_client::Connectivity;
use uv_fs::{normalize_url_path, Simplified};
use uv_normalize::ExtraName;
Expand Down Expand Up @@ -240,9 +243,9 @@ impl EditableRequirement {
};

// Create a `PathBuf`.
let path = url.to_file_path().map_err(|()| {
RequirementsTxtParserError::InvalidEditablePath(expanded.to_string())
})?;
let path = url
.to_file_path()
.map_err(|()| RequirementsTxtParserError::InvalidEditablePath(expanded.to_string()))?;

// Add the verbatim representation of the URL to the `VerbatimUrl`.
let url = url.with_given(requirement.to_string());
Expand Down Expand Up @@ -572,7 +575,8 @@ fn parse_entry(
RequirementsTxtStatement::EditableRequirement(editable_requirement)
} else if s.eat_if("-i") || s.eat_if("--index-url") {
let given = parse_value(content, s, |c: char| !['\n', '\r'].contains(&c))?;
let url = VerbatimUrl::parse_url(given)
let expanded = expand_env_vars(given);
let url = VerbatimUrl::parse_url(expanded.as_ref())
.map(|url| url.with_given(given.to_owned()))
.map_err(|err| RequirementsTxtParserError::Url {
source: err,
Expand All @@ -583,7 +587,8 @@ fn parse_entry(
RequirementsTxtStatement::IndexUrl(url)
} else if s.eat_if("--extra-index-url") {
let given = parse_value(content, s, |c: char| !['\n', '\r'].contains(&c))?;
let url = VerbatimUrl::parse_url(given)
let expanded = expand_env_vars(given);
let url = VerbatimUrl::parse_url(expanded.as_ref())
.map(|url| url.with_given(given.to_owned()))
.map_err(|err| RequirementsTxtParserError::Url {
source: err,
Expand Down Expand Up @@ -1292,7 +1297,7 @@ mod test {
}

#[tokio::test]
async fn invalid_requirement() -> Result<()> {
async fn invalid_requirement_version() -> Result<()> {
let temp_dir = assert_fs::TempDir::new()?;
let requirements_txt = temp_dir.child("requirements.txt");
requirements_txt.write_str(indoc! {"
Expand Down Expand Up @@ -1328,6 +1333,43 @@ mod test {
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]
async fn unsupported_editable() -> Result<()> {
let temp_dir = assert_fs::TempDir::new()?;
Expand Down Expand Up @@ -1395,7 +1437,7 @@ mod test {
}

#[tokio::test]
async fn invalid_index_url() -> Result<()> {
async fn relative_index_url() -> Result<()> {
let temp_dir = assert_fs::TempDir::new()?;
let requirements_txt = temp_dir.child("requirements.txt");
requirements_txt.write_str(indoc! {"
Expand Down Expand Up @@ -1429,6 +1471,42 @@ mod test {
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]
async fn missing_r() -> Result<()> {
let temp_dir = assert_fs::TempDir::new()?;
Expand Down
113 changes: 112 additions & 1 deletion crates/uv/tests/pip_compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2390,7 +2390,7 @@ fn preserve_url() -> Result<()> {
/// Resolve a dependency from a URL, preserving the unexpanded environment variable as specified in
/// the requirements file.
#[test]
fn preserve_env_var() -> Result<()> {
fn preserve_project_root() -> 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")?;
Expand Down Expand Up @@ -2432,6 +2432,91 @@ fn preserve_env_var() -> Result<()> {
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]
#[cfg(feature = "maturin")]
fn compile_editable() -> Result<()> {
Expand Down Expand Up @@ -3026,6 +3111,32 @@ fn find_links_url() -> Result<()> {
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,
/// with the URL itself provided in a `requirements.txt` file.
#[test]
Expand Down
1 change: 0 additions & 1 deletion foo bar/requirements.txt

This file was deleted.

1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ classifiers = [
"Topic :: Software Development :: Libraries",
]
readme = "README.md"
dependencies = ["-e ./scripts/editable-installs/black_editable"]

[project.urls]
Repository = "https://github.com/astral-sh/uv"
Expand Down

0 comments on commit a8cf59d

Please sign in to comment.