mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 10:58:28 +00:00
Use VerbatimParsedUrl
in pep508_rs
(#3758)
When parsing requirements from any source, directly parse the url parts (and reject unsupported urls) instead of parsing url parts at a later stage. This removes a bunch of error branches and concludes the work parsing url parts once and passing them around everywhere. Many usages of the assembled `VerbatimUrl` remain, but these can be removed incrementally. Please review commit-by-commit.
This commit is contained in:
parent
0d2f3fc4e4
commit
4db468e27f
56 changed files with 877 additions and 656 deletions
9
Cargo.lock
generated
9
Cargo.lock
generated
|
@ -1104,7 +1104,6 @@ dependencies = [
|
|||
"cache-key",
|
||||
"distribution-filename",
|
||||
"fs-err",
|
||||
"git2",
|
||||
"indexmap",
|
||||
"itertools 0.13.0",
|
||||
"once_cell",
|
||||
|
@ -2860,7 +2859,9 @@ dependencies = [
|
|||
name = "pypi-types"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"git2",
|
||||
"indexmap",
|
||||
"mailparse",
|
||||
"once_cell",
|
||||
|
@ -2873,6 +2874,7 @@ dependencies = [
|
|||
"toml",
|
||||
"tracing",
|
||||
"url",
|
||||
"uv-git",
|
||||
"uv-normalize",
|
||||
]
|
||||
|
||||
|
@ -3076,12 +3078,12 @@ dependencies = [
|
|||
"insta",
|
||||
"itertools 0.13.0",
|
||||
"pep508_rs",
|
||||
"pypi-types",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"reqwest-middleware",
|
||||
"tempfile",
|
||||
"test-case",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"unscanny",
|
||||
|
@ -4505,6 +4507,7 @@ dependencies = [
|
|||
"pep508_rs",
|
||||
"platform-tags",
|
||||
"predicates",
|
||||
"pypi-types",
|
||||
"rayon",
|
||||
"regex",
|
||||
"requirements-txt",
|
||||
|
@ -4579,6 +4582,7 @@ dependencies = [
|
|||
"once_cell",
|
||||
"pep440_rs",
|
||||
"pep508_rs",
|
||||
"pypi-types",
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
|
@ -4704,6 +4708,7 @@ dependencies = [
|
|||
"pep508_rs",
|
||||
"poloto",
|
||||
"pretty_assertions",
|
||||
"pypi-types",
|
||||
"resvg",
|
||||
"rustc-hash",
|
||||
"schemars",
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use bench::criterion::black_box;
|
||||
use bench::criterion::{criterion_group, criterion_main, measurement::WallTime, Criterion};
|
||||
use distribution_types::Requirement;
|
||||
|
@ -15,9 +17,9 @@ fn resolve_warm_jupyter(c: &mut Criterion<WallTime>) {
|
|||
let cache = &Cache::from_path("../../.cache").unwrap().init().unwrap();
|
||||
let venv = PythonEnvironment::from_virtualenv(cache).unwrap();
|
||||
let client = &RegistryClientBuilder::new(cache.clone()).build();
|
||||
let manifest = &Manifest::simple(vec![
|
||||
Requirement::from_pep508("jupyter".parse().unwrap()).unwrap()
|
||||
]);
|
||||
let manifest = &Manifest::simple(vec![Requirement::from(
|
||||
pep508_rs::Requirement::from_str("jupyter").unwrap(),
|
||||
)]);
|
||||
|
||||
let run = || {
|
||||
runtime
|
||||
|
@ -45,13 +47,10 @@ fn resolve_warm_airflow(c: &mut Criterion<WallTime>) {
|
|||
let venv = PythonEnvironment::from_virtualenv(cache).unwrap();
|
||||
let client = &RegistryClientBuilder::new(cache.clone()).build();
|
||||
let manifest = &Manifest::simple(vec![
|
||||
Requirement::from_pep508("apache-airflow[all]".parse().unwrap()).unwrap(),
|
||||
Requirement::from_pep508(
|
||||
"apache-airflow-providers-apache-beam>3.0.0"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap(),
|
||||
Requirement::from(pep508_rs::Requirement::from_str("apache-airflow[all]").unwrap()),
|
||||
Requirement::from(
|
||||
pep508_rs::Requirement::from_str("apache-airflow-providers-apache-beam>3.0.0").unwrap(),
|
||||
),
|
||||
]);
|
||||
|
||||
let run = || {
|
||||
|
@ -73,10 +72,10 @@ criterion_main!(uv);
|
|||
|
||||
mod resolver {
|
||||
use anyhow::Result;
|
||||
use install_wheel_rs::linker::LinkMode;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use distribution_types::IndexLocations;
|
||||
use install_wheel_rs::linker::LinkMode;
|
||||
use pep508_rs::{MarkerEnvironment, MarkerEnvironmentBuilder};
|
||||
use platform_tags::{Arch, Os, Platform, Tags};
|
||||
use uv_cache::Cache;
|
||||
|
|
|
@ -25,7 +25,6 @@ uv-normalize = { workspace = true }
|
|||
|
||||
anyhow = { workspace = true }
|
||||
fs-err = { workspace = true }
|
||||
git2 = { workspace = true }
|
||||
indexmap = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
|
|
|
@ -105,9 +105,9 @@ impl std::fmt::Display for DirectSourceUrl<'_> {
|
|||
pub struct GitSourceUrl<'a> {
|
||||
/// The URL with the revision and subdirectory fragment.
|
||||
pub url: &'a VerbatimUrl,
|
||||
pub git: &'a GitUrl,
|
||||
/// The URL without the revision and subdirectory fragment.
|
||||
pub git: Cow<'a, GitUrl>,
|
||||
pub subdirectory: Option<Cow<'a, Path>>,
|
||||
pub subdirectory: Option<&'a Path>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for GitSourceUrl<'_> {
|
||||
|
@ -120,8 +120,8 @@ impl<'a> From<&'a GitSourceDist> for GitSourceUrl<'a> {
|
|||
fn from(dist: &'a GitSourceDist) -> Self {
|
||||
Self {
|
||||
url: &dist.url,
|
||||
git: Cow::Borrowed(&dist.git),
|
||||
subdirectory: dist.subdirectory.as_deref().map(Cow::Borrowed),
|
||||
git: &dist.git,
|
||||
subdirectory: dist.subdirectory.as_deref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,12 +4,12 @@ use anyhow::{anyhow, Result};
|
|||
|
||||
use distribution_filename::WheelFilename;
|
||||
use pep508_rs::VerbatimUrl;
|
||||
use pypi_types::HashDigest;
|
||||
use pypi_types::{HashDigest, ParsedPathUrl};
|
||||
use uv_normalize::PackageName;
|
||||
|
||||
use crate::{
|
||||
BuiltDist, Dist, DistributionMetadata, Hashed, InstalledMetadata, InstalledVersion, Name,
|
||||
ParsedPathUrl, ParsedUrl, SourceDist, VersionOrUrlRef,
|
||||
ParsedUrl, SourceDist, VersionOrUrlRef,
|
||||
};
|
||||
|
||||
/// A built distribution (wheel) that exists in the local cache.
|
||||
|
|
|
@ -42,6 +42,7 @@ use url::Url;
|
|||
use distribution_filename::WheelFilename;
|
||||
use pep440_rs::Version;
|
||||
use pep508_rs::{Pep508Url, VerbatimUrl};
|
||||
use pypi_types::{ParsedUrl, VerbatimParsedUrl};
|
||||
use uv_git::GitUrl;
|
||||
use uv_normalize::PackageName;
|
||||
|
||||
|
@ -57,7 +58,6 @@ pub use crate::hash::*;
|
|||
pub use crate::id::*;
|
||||
pub use crate::index_url::*;
|
||||
pub use crate::installed::*;
|
||||
pub use crate::parsed_url::*;
|
||||
pub use crate::prioritized_distribution::*;
|
||||
pub use crate::requirement::*;
|
||||
pub use crate::resolution::*;
|
||||
|
@ -77,7 +77,6 @@ mod hash;
|
|||
mod id;
|
||||
mod index_url;
|
||||
mod installed;
|
||||
mod parsed_url;
|
||||
mod prioritized_distribution;
|
||||
mod requirement;
|
||||
mod resolution;
|
||||
|
|
|
@ -9,7 +9,7 @@ use pep508_rs::{MarkerEnvironment, MarkerTree, RequirementOrigin, VerbatimUrl, V
|
|||
use uv_git::{GitReference, GitSha};
|
||||
use uv_normalize::{ExtraName, PackageName};
|
||||
|
||||
use crate::{ParsedUrl, ParsedUrlError};
|
||||
use crate::{ParsedUrl, VerbatimParsedUrl};
|
||||
|
||||
/// The requirements of a distribution, an extension over PEP 508's requirements.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
|
@ -44,9 +44,11 @@ impl Requirement {
|
|||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<pep508_rs::Requirement<VerbatimParsedUrl>> for Requirement {
|
||||
/// Convert a [`pep508_rs::Requirement`] to a [`Requirement`].
|
||||
pub fn from_pep508(requirement: pep508_rs::Requirement) -> Result<Self, Box<ParsedUrlError>> {
|
||||
fn from(requirement: pep508_rs::Requirement<VerbatimParsedUrl>) -> Self {
|
||||
let source = match requirement.version_or_url {
|
||||
None => RequirementSource::Registry {
|
||||
specifier: VersionSpecifiers::empty(),
|
||||
|
@ -58,17 +60,16 @@ impl Requirement {
|
|||
index: None,
|
||||
},
|
||||
Some(VersionOrUrl::Url(url)) => {
|
||||
let direct_url = ParsedUrl::try_from(url.to_url())?;
|
||||
RequirementSource::from_parsed_url(direct_url, url)
|
||||
RequirementSource::from_parsed_url(url.parsed_url, url.verbatim)
|
||||
}
|
||||
};
|
||||
Ok(Requirement {
|
||||
Requirement {
|
||||
name: requirement.name,
|
||||
extras: requirement.extras,
|
||||
marker: requirement.marker,
|
||||
source,
|
||||
origin: requirement.origin,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ use std::fmt::{Display, Formatter};
|
|||
use pep508_rs::{MarkerEnvironment, UnnamedRequirement};
|
||||
use uv_normalize::ExtraName;
|
||||
|
||||
use crate::{ParsedUrl, ParsedUrlError, Requirement, RequirementSource};
|
||||
use crate::{Requirement, RequirementSource, VerbatimParsedUrl};
|
||||
|
||||
/// An [`UnresolvedRequirement`] with additional metadata from `requirements.txt`, currently only
|
||||
/// hashes but in the future also editable and similar information.
|
||||
|
@ -29,7 +29,7 @@ pub enum UnresolvedRequirement {
|
|||
/// `tool.uv.sources`.
|
||||
Named(Requirement),
|
||||
/// A PEP 508-like, direct URL dependency specifier.
|
||||
Unnamed(UnnamedRequirement),
|
||||
Unnamed(UnnamedRequirement<VerbatimParsedUrl>),
|
||||
}
|
||||
|
||||
impl Display for UnresolvedRequirement {
|
||||
|
@ -64,17 +64,13 @@ impl UnresolvedRequirement {
|
|||
}
|
||||
|
||||
/// Return the version specifier or URL for the requirement.
|
||||
pub fn source(&self) -> Result<Cow<'_, RequirementSource>, Box<ParsedUrlError>> {
|
||||
// TODO(konsti): This is a bad place to raise errors, we should have parsed the url earlier.
|
||||
pub fn source(&self) -> Cow<'_, RequirementSource> {
|
||||
match self {
|
||||
Self::Named(requirement) => Ok(Cow::Borrowed(&requirement.source)),
|
||||
Self::Unnamed(requirement) => {
|
||||
let parsed_url = ParsedUrl::try_from(requirement.url.to_url())?;
|
||||
Ok(Cow::Owned(RequirementSource::from_parsed_url(
|
||||
parsed_url,
|
||||
requirement.url.clone(),
|
||||
)))
|
||||
}
|
||||
Self::Named(requirement) => Cow::Borrowed(&requirement.source),
|
||||
Self::Unnamed(requirement) => Cow::Owned(RequirementSource::from_parsed_url(
|
||||
requirement.url.parsed_url.clone(),
|
||||
requirement.url.verbatim.clone(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use cursor::Cursor;
|
||||
#[cfg(feature = "pyo3")]
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::collections::HashSet;
|
||||
|
@ -39,18 +38,18 @@ use thiserror::Error;
|
|||
use unicode_width::UnicodeWidthChar;
|
||||
use url::Url;
|
||||
|
||||
use cursor::Cursor;
|
||||
pub use marker::{
|
||||
ExtraOperator, MarkerEnvironment, MarkerEnvironmentBuilder, MarkerExpression, MarkerOperator,
|
||||
MarkerTree, MarkerValue, MarkerValueString, MarkerValueVersion, MarkerWarningKind,
|
||||
StringVersion,
|
||||
};
|
||||
pub use origin::RequirementOrigin;
|
||||
#[cfg(feature = "pyo3")]
|
||||
use pep440_rs::PyVersion;
|
||||
use pep440_rs::{Version, VersionSpecifier, VersionSpecifiers};
|
||||
#[cfg(feature = "non-pep508-extensions")]
|
||||
pub use unnamed::UnnamedRequirement;
|
||||
// Parity with the crates.io version of pep508_rs
|
||||
pub use origin::RequirementOrigin;
|
||||
pub use unnamed::{UnnamedRequirement, UnnamedRequirementUrl};
|
||||
pub use uv_normalize::{ExtraName, InvalidNameError, PackageName};
|
||||
pub use verbatim_url::{
|
||||
expand_env_vars, split_scheme, strip_host, Scheme, VerbatimUrl, VerbatimUrlError,
|
||||
|
@ -123,7 +122,7 @@ impl<T: Pep508Url> Display for Pep508Error<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// We need this to allow e.g. anyhow's `.context()`
|
||||
/// We need this to allow anyhow's `.context()` and `AsDynError`.
|
||||
impl<E: Error + Debug, T: Pep508Url<Err = E>> std::error::Error for Pep508Error<T> {}
|
||||
|
||||
#[cfg(feature = "pyo3")]
|
||||
|
@ -155,17 +154,6 @@ pub struct Requirement<T: Pep508Url = VerbatimUrl> {
|
|||
pub origin: Option<RequirementOrigin>,
|
||||
}
|
||||
|
||||
impl Requirement {
|
||||
/// Set the source file containing the requirement.
|
||||
#[must_use]
|
||||
pub fn with_origin(self, origin: RequirementOrigin) -> Self {
|
||||
Self {
|
||||
origin: Some(origin),
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Pep508Url + Display> Display for Requirement<T> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.name)?;
|
||||
|
@ -453,10 +441,19 @@ impl<T: Pep508Url> Requirement<T> {
|
|||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the source file containing the requirement.
|
||||
#[must_use]
|
||||
pub fn with_origin(self, origin: RequirementOrigin) -> Self {
|
||||
Self {
|
||||
origin: Some(origin),
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Type to parse URLs from `name @ <url>` into. Defaults to [`url::Url`].
|
||||
pub trait Pep508Url: Clone + Display + Debug {
|
||||
pub trait Pep508Url: Display + Debug + Sized {
|
||||
/// String to URL parsing error
|
||||
type Err: Error + Debug;
|
||||
|
||||
|
@ -1136,7 +1133,7 @@ mod tests {
|
|||
|
||||
#[cfg(feature = "non-pep508-extensions")]
|
||||
fn parse_unnamed_err(input: &str) -> String {
|
||||
crate::UnnamedRequirement::from_str(input)
|
||||
crate::UnnamedRequirement::<VerbatimUrl>::from_str(input)
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
}
|
||||
|
@ -1256,7 +1253,7 @@ mod tests {
|
|||
#[test]
|
||||
#[cfg(feature = "non-pep508-extensions")]
|
||||
fn direct_url_no_extras() {
|
||||
let numpy = crate::UnnamedRequirement::from_str("https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl").unwrap();
|
||||
let numpy = crate::UnnamedRequirement::<VerbatimUrl>::from_str("https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl").unwrap();
|
||||
assert_eq!(numpy.url.to_string(), "https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl");
|
||||
assert_eq!(numpy.extras, vec![]);
|
||||
}
|
||||
|
@ -1264,8 +1261,9 @@ mod tests {
|
|||
#[test]
|
||||
#[cfg(all(unix, feature = "non-pep508-extensions"))]
|
||||
fn direct_url_extras() {
|
||||
let numpy =
|
||||
crate::UnnamedRequirement::from_str("/path/to/numpy-1.26.4-cp312-cp312-win32.whl[dev]")
|
||||
let numpy = crate::UnnamedRequirement::<VerbatimUrl>::from_str(
|
||||
"/path/to/numpy-1.26.4-cp312-cp312-win32.whl[dev]",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
numpy.url.to_string(),
|
||||
|
@ -1277,7 +1275,7 @@ mod tests {
|
|||
#[test]
|
||||
#[cfg(all(windows, feature = "non-pep508-extensions"))]
|
||||
fn direct_url_extras() {
|
||||
let numpy = crate::UnnamedRequirement::from_str(
|
||||
let numpy = crate::UnnamedRequirement::<VerbatimUrl>::from_str(
|
||||
"C:\\path\\to\\numpy-1.26.4-cp312-cp312-win32.whl[dev]",
|
||||
)
|
||||
.unwrap();
|
||||
|
@ -1459,7 +1457,8 @@ mod tests {
|
|||
fn test_marker_parsing() {
|
||||
let marker = r#"python_version == "2.7" and (sys_platform == "win32" or (os_name == "linux" and implementation_name == 'cpython'))"#;
|
||||
let actual =
|
||||
parse_markers_cursor::<Url>(&mut Cursor::new(marker), &mut TracingReporter).unwrap();
|
||||
parse_markers_cursor::<VerbatimUrl>(&mut Cursor::new(marker), &mut TracingReporter)
|
||||
.unwrap();
|
||||
let expected = MarkerTree::And(vec![
|
||||
MarkerTree::Expression(MarkerExpression::Version {
|
||||
key: MarkerValueVersion::PythonVersion,
|
||||
|
|
|
@ -1550,6 +1550,11 @@ impl FromStr for MarkerTree {
|
|||
}
|
||||
|
||||
impl MarkerTree {
|
||||
/// Like [`FromStr::from_str`], but the caller chooses the return type generic.
|
||||
pub fn parse_str<T: Pep508Url>(markers: &str) -> Result<Self, Pep508Error<T>> {
|
||||
parse_markers(markers, &mut TracingReporter)
|
||||
}
|
||||
|
||||
/// Parse a [`MarkerTree`] from a string with the given reporter.
|
||||
pub fn parse_reporter(
|
||||
markers: &str,
|
||||
|
|
|
@ -1,28 +1,74 @@
|
|||
use std::fmt::{Display, Formatter};
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::hash::Hash;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
use uv_fs::normalize_url_path;
|
||||
use uv_normalize::ExtraName;
|
||||
|
||||
use crate::marker::parse_markers_cursor;
|
||||
use crate::{
|
||||
expand_env_vars, parse_extras_cursor, split_extras, split_scheme, strip_host, Cursor,
|
||||
MarkerEnvironment, MarkerTree, Pep508Error, Pep508ErrorSource, Reporter, RequirementOrigin,
|
||||
Scheme, TracingReporter, VerbatimUrl, VerbatimUrlError,
|
||||
MarkerEnvironment, MarkerTree, Pep508Error, Pep508ErrorSource, Pep508Url, Reporter,
|
||||
RequirementOrigin, Scheme, TracingReporter, VerbatimUrl, VerbatimUrlError,
|
||||
};
|
||||
|
||||
/// An extension over [`Pep508Url`] that also supports parsing unnamed requirements, namely paths.
|
||||
///
|
||||
/// The error type is fixed to the same as the [`Pep508Url`] impl error.
|
||||
pub trait UnnamedRequirementUrl: Pep508Url {
|
||||
/// Parse a URL from a relative or absolute path.
|
||||
fn parse_path(path: impl AsRef<Path>, working_dir: impl AsRef<Path>)
|
||||
-> Result<Self, Self::Err>;
|
||||
|
||||
/// Parse a URL from an absolute path.
|
||||
fn parse_absolute_path(path: impl AsRef<Path>) -> Result<Self, Self::Err>;
|
||||
|
||||
/// Parse a URL from a string.
|
||||
fn parse_unnamed_url(given: impl AsRef<str>) -> Result<Self, Self::Err>;
|
||||
|
||||
/// Set the verbatim representation of the URL.
|
||||
#[must_use]
|
||||
fn with_given(self, given: impl Into<String>) -> Self;
|
||||
|
||||
/// Return the original string as given by the user, if available.
|
||||
fn given(&self) -> Option<&str>;
|
||||
}
|
||||
|
||||
impl UnnamedRequirementUrl for VerbatimUrl {
|
||||
fn parse_path(
|
||||
path: impl AsRef<Path>,
|
||||
working_dir: impl AsRef<Path>,
|
||||
) -> Result<Self, VerbatimUrlError> {
|
||||
Self::parse_path(path, working_dir)
|
||||
}
|
||||
|
||||
fn parse_absolute_path(path: impl AsRef<Path>) -> Result<Self, Self::Err> {
|
||||
Self::parse_absolute_path(path)
|
||||
}
|
||||
|
||||
fn parse_unnamed_url(given: impl AsRef<str>) -> Result<Self, Self::Err> {
|
||||
Ok(Self::parse_url(given)?)
|
||||
}
|
||||
|
||||
fn with_given(self, given: impl Into<String>) -> Self {
|
||||
self.with_given(given)
|
||||
}
|
||||
|
||||
fn given(&self) -> Option<&str> {
|
||||
self.given()
|
||||
}
|
||||
}
|
||||
|
||||
/// A PEP 508-like, direct URL dependency specifier without a package name.
|
||||
///
|
||||
/// In a `requirements.txt` file, the name of the package is optional for direct URL
|
||||
/// dependencies. This isn't compliant with PEP 508, but is common in `requirements.txt`, which
|
||||
/// is implementation-defined.
|
||||
#[derive(Hash, Debug, Clone, Eq, PartialEq)]
|
||||
pub struct UnnamedRequirement {
|
||||
pub struct UnnamedRequirement<Url: UnnamedRequirementUrl = VerbatimUrl> {
|
||||
/// The direct URL that defines the version specifier.
|
||||
pub url: VerbatimUrl,
|
||||
pub url: Url,
|
||||
/// The list of extras such as `security`, `tests` in
|
||||
/// `requests [security,tests] >= 2.8.1, == 2.8.* ; python_version > "3.8"`.
|
||||
pub extras: Vec<ExtraName>,
|
||||
|
@ -34,7 +80,7 @@ pub struct UnnamedRequirement {
|
|||
pub origin: Option<RequirementOrigin>,
|
||||
}
|
||||
|
||||
impl UnnamedRequirement {
|
||||
impl<Url: UnnamedRequirementUrl> UnnamedRequirement<Url> {
|
||||
/// Returns whether the markers apply for the given environment
|
||||
pub fn evaluate_markers(&self, env: &MarkerEnvironment, extras: &[ExtraName]) -> bool {
|
||||
self.evaluate_optional_environment(Some(env), extras)
|
||||
|
@ -61,9 +107,22 @@ impl UnnamedRequirement {
|
|||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a PEP 508-like direct URL requirement without a package name.
|
||||
pub fn parse(
|
||||
input: &str,
|
||||
working_dir: impl AsRef<Path>,
|
||||
reporter: &mut impl Reporter,
|
||||
) -> Result<Self, Pep508Error<Url>> {
|
||||
parse_unnamed_requirement(
|
||||
&mut Cursor::new(input),
|
||||
Some(working_dir.as_ref()),
|
||||
reporter,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for UnnamedRequirement {
|
||||
impl<Url: UnnamedRequirementUrl> Display for UnnamedRequirement<Url> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.url)?;
|
||||
if !self.extras.is_empty() {
|
||||
|
@ -84,29 +143,8 @@ impl Display for UnnamedRequirement {
|
|||
}
|
||||
}
|
||||
|
||||
/// <https://github.com/serde-rs/serde/issues/908#issuecomment-298027413>
|
||||
impl<'de> Deserialize<'de> for UnnamedRequirement {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
FromStr::from_str(&s).map_err(de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://github.com/serde-rs/serde/issues/1316#issue-332908452>
|
||||
impl Serialize for UnnamedRequirement {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.collect_str(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for UnnamedRequirement {
|
||||
type Err = Pep508Error<VerbatimUrl>;
|
||||
impl<Url: UnnamedRequirementUrl> FromStr for UnnamedRequirement<Url> {
|
||||
type Err = Pep508Error<Url>;
|
||||
|
||||
/// Parse a PEP 508-like direct URL requirement without a package name.
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
|
@ -114,33 +152,18 @@ impl FromStr for UnnamedRequirement {
|
|||
}
|
||||
}
|
||||
|
||||
impl UnnamedRequirement {
|
||||
/// Parse a PEP 508-like direct URL requirement without a package name.
|
||||
pub fn parse(
|
||||
input: &str,
|
||||
working_dir: impl AsRef<Path>,
|
||||
reporter: &mut impl Reporter,
|
||||
) -> Result<Self, Pep508Error<VerbatimUrl>> {
|
||||
parse_unnamed_requirement(
|
||||
&mut Cursor::new(input),
|
||||
Some(working_dir.as_ref()),
|
||||
reporter,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a PEP 508-like direct URL specifier without a package name.
|
||||
///
|
||||
/// Unlike pip, we allow extras on URLs and paths.
|
||||
fn parse_unnamed_requirement(
|
||||
fn parse_unnamed_requirement<Url: UnnamedRequirementUrl>(
|
||||
cursor: &mut Cursor,
|
||||
working_dir: Option<&Path>,
|
||||
reporter: &mut impl Reporter,
|
||||
) -> Result<UnnamedRequirement, Pep508Error<VerbatimUrl>> {
|
||||
) -> Result<UnnamedRequirement<Url>, Pep508Error<Url>> {
|
||||
cursor.eat_whitespace();
|
||||
|
||||
// Parse the URL itself, along with any extras.
|
||||
let (url, extras) = parse_unnamed_url(cursor, working_dir)?;
|
||||
let (url, extras) = parse_unnamed_url::<Url>(cursor, working_dir)?;
|
||||
let requirement_end = cursor.pos();
|
||||
|
||||
// wsp*
|
||||
|
@ -191,13 +214,13 @@ fn parse_unnamed_requirement(
|
|||
|
||||
/// Create a `VerbatimUrl` to represent the requirement, and extracts any extras at the end of the
|
||||
/// URL, to comply with the non-PEP 508 extensions.
|
||||
fn preprocess_unnamed_url(
|
||||
fn preprocess_unnamed_url<Url: UnnamedRequirementUrl>(
|
||||
url: &str,
|
||||
#[cfg_attr(not(feature = "non-pep508-extensions"), allow(unused))] working_dir: Option<&Path>,
|
||||
cursor: &Cursor,
|
||||
start: usize,
|
||||
len: usize,
|
||||
) -> Result<(VerbatimUrl, Vec<ExtraName>), Pep508Error<VerbatimUrl>> {
|
||||
) -> Result<(Url, Vec<ExtraName>), Pep508Error<Url>> {
|
||||
// Split extras _before_ expanding the URL. We assume that the extras are not environment
|
||||
// variables. If we parsed the extras after expanding the URL, then the verbatim representation
|
||||
// of the URL itself would be ambiguous, since it would consist of the environment variable,
|
||||
|
@ -235,9 +258,9 @@ fn preprocess_unnamed_url(
|
|||
|
||||
#[cfg(feature = "non-pep508-extensions")]
|
||||
if let Some(working_dir) = working_dir {
|
||||
let url = VerbatimUrl::parse_path(path.as_ref(), working_dir)
|
||||
let url = Url::parse_path(path.as_ref(), working_dir)
|
||||
.map_err(|err| Pep508Error {
|
||||
message: Pep508ErrorSource::<VerbatimUrl>::UrlError(err),
|
||||
message: Pep508ErrorSource::UrlError(err),
|
||||
start,
|
||||
len,
|
||||
input: cursor.to_string(),
|
||||
|
@ -246,9 +269,9 @@ fn preprocess_unnamed_url(
|
|||
return Ok((url, extras));
|
||||
}
|
||||
|
||||
let url = VerbatimUrl::parse_absolute_path(path.as_ref())
|
||||
let url = Url::parse_absolute_path(path.as_ref())
|
||||
.map_err(|err| Pep508Error {
|
||||
message: Pep508ErrorSource::<VerbatimUrl>::UrlError(err),
|
||||
message: Pep508ErrorSource::UrlError(err),
|
||||
start,
|
||||
len,
|
||||
input: cursor.to_string(),
|
||||
|
@ -259,11 +282,9 @@ fn preprocess_unnamed_url(
|
|||
// Ex) `https://download.pytorch.org/whl/torch_stable.html`
|
||||
Some(_) => {
|
||||
// Ex) `https://download.pytorch.org/whl/torch_stable.html`
|
||||
let url = VerbatimUrl::parse_url(expanded.as_ref())
|
||||
let url = Url::parse_unnamed_url(expanded.as_ref())
|
||||
.map_err(|err| Pep508Error {
|
||||
message: Pep508ErrorSource::<VerbatimUrl>::UrlError(VerbatimUrlError::Url(
|
||||
err,
|
||||
)),
|
||||
message: Pep508ErrorSource::UrlError(err),
|
||||
start,
|
||||
len,
|
||||
input: cursor.to_string(),
|
||||
|
@ -275,9 +296,9 @@ fn preprocess_unnamed_url(
|
|||
// Ex) `C:\Users\ferris\wheel-0.42.0.tar.gz`
|
||||
_ => {
|
||||
if let Some(working_dir) = working_dir {
|
||||
let url = VerbatimUrl::parse_path(expanded.as_ref(), working_dir)
|
||||
let url = Url::parse_path(expanded.as_ref(), working_dir)
|
||||
.map_err(|err| Pep508Error {
|
||||
message: Pep508ErrorSource::<VerbatimUrl>::UrlError(err),
|
||||
message: Pep508ErrorSource::UrlError(err),
|
||||
start,
|
||||
len,
|
||||
input: cursor.to_string(),
|
||||
|
@ -286,7 +307,7 @@ fn preprocess_unnamed_url(
|
|||
return Ok((url, extras));
|
||||
}
|
||||
|
||||
let url = VerbatimUrl::parse_absolute_path(expanded.as_ref())
|
||||
let url = Url::parse_absolute_path(expanded.as_ref())
|
||||
.map_err(|err| Pep508Error {
|
||||
message: Pep508ErrorSource::UrlError(err),
|
||||
start,
|
||||
|
@ -300,9 +321,9 @@ fn preprocess_unnamed_url(
|
|||
} else {
|
||||
// Ex) `../editable/`
|
||||
if let Some(working_dir) = working_dir {
|
||||
let url = VerbatimUrl::parse_path(expanded.as_ref(), working_dir)
|
||||
let url = Url::parse_path(expanded.as_ref(), working_dir)
|
||||
.map_err(|err| Pep508Error {
|
||||
message: Pep508ErrorSource::<VerbatimUrl>::UrlError(err),
|
||||
message: Pep508ErrorSource::UrlError(err),
|
||||
start,
|
||||
len,
|
||||
input: cursor.to_string(),
|
||||
|
@ -311,7 +332,7 @@ fn preprocess_unnamed_url(
|
|||
return Ok((url, extras));
|
||||
}
|
||||
|
||||
let url = VerbatimUrl::parse_absolute_path(expanded.as_ref())
|
||||
let url = Url::parse_absolute_path(expanded.as_ref())
|
||||
.map_err(|err| Pep508Error {
|
||||
message: Pep508ErrorSource::UrlError(err),
|
||||
start,
|
||||
|
@ -329,10 +350,10 @@ fn preprocess_unnamed_url(
|
|||
/// For example:
|
||||
/// - `https://download.pytorch.org/whl/torch_stable.html[dev]`
|
||||
/// - `../editable[dev]`
|
||||
fn parse_unnamed_url(
|
||||
fn parse_unnamed_url<Url: UnnamedRequirementUrl>(
|
||||
cursor: &mut Cursor,
|
||||
working_dir: Option<&Path>,
|
||||
) -> Result<(VerbatimUrl, Vec<ExtraName>), Pep508Error<VerbatimUrl>> {
|
||||
) -> Result<(Url, Vec<ExtraName>), Pep508Error<Url>> {
|
||||
// wsp*
|
||||
cursor.eat_whitespace();
|
||||
// <URI_reference>
|
||||
|
|
|
@ -16,8 +16,11 @@ workspace = true
|
|||
pep440_rs = { workspace = true }
|
||||
pep508_rs = { workspace = true }
|
||||
uv-normalize = { workspace = true }
|
||||
uv-git = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
git2 = { workspace = true }
|
||||
indexmap = { workspace = true, features = ["serde"] }
|
||||
mailparse = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
|
|
|
@ -7,7 +7,9 @@ use serde::{de, Deserialize, Deserializer, Serialize};
|
|||
use tracing::warn;
|
||||
|
||||
use pep440_rs::{VersionSpecifiers, VersionSpecifiersParseError};
|
||||
use pep508_rs::{Pep508Error, Pep508Url, Requirement, VerbatimUrl};
|
||||
use pep508_rs::{Pep508Error, Pep508Url, Requirement};
|
||||
|
||||
use crate::VerbatimParsedUrl;
|
||||
|
||||
/// Ex) `>=7.2.0<8.0.0`
|
||||
static MISSING_COMMA: Lazy<Regex> = Lazy::new(|| Regex::new(r"(\d)([<>=~^!])").unwrap());
|
||||
|
@ -114,7 +116,7 @@ fn parse_with_fixups<Err, T: FromStr<Err = Err>>(input: &str, type_name: &str) -
|
|||
|
||||
/// Like [`Requirement`], but attempts to correct some common errors in user-provided requirements.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||
pub struct LenientRequirement<T: Pep508Url = VerbatimUrl>(Requirement<T>);
|
||||
pub struct LenientRequirement<T: Pep508Url = VerbatimParsedUrl>(Requirement<T>);
|
||||
|
||||
impl<T: Pep508Url> FromStr for LenientRequirement<T> {
|
||||
type Err = Pep508Error<T>;
|
||||
|
|
|
@ -2,6 +2,7 @@ pub use base_url::*;
|
|||
pub use direct_url::*;
|
||||
pub use lenient_requirement::*;
|
||||
pub use metadata::*;
|
||||
pub use parsed_url::*;
|
||||
pub use scheme::*;
|
||||
pub use simple_json::*;
|
||||
|
||||
|
@ -9,5 +10,6 @@ mod base_url;
|
|||
mod direct_url;
|
||||
mod lenient_requirement;
|
||||
mod metadata;
|
||||
mod parsed_url;
|
||||
mod scheme;
|
||||
mod simple_json;
|
||||
|
|
|
@ -9,11 +9,11 @@ use thiserror::Error;
|
|||
use tracing::warn;
|
||||
|
||||
use pep440_rs::{Version, VersionParseError, VersionSpecifiers, VersionSpecifiersParseError};
|
||||
use pep508_rs::{Pep508Error, Requirement, VerbatimUrl};
|
||||
use pep508_rs::{Pep508Error, Requirement};
|
||||
use uv_normalize::{ExtraName, InvalidNameError, PackageName};
|
||||
|
||||
use crate::lenient_requirement::LenientRequirement;
|
||||
use crate::LenientVersionSpecifiers;
|
||||
use crate::{LenientVersionSpecifiers, VerbatimParsedUrl};
|
||||
|
||||
/// Python Package Metadata 2.3 as specified in
|
||||
/// <https://packaging.python.org/specifications/core-metadata/>.
|
||||
|
@ -29,7 +29,7 @@ pub struct Metadata23 {
|
|||
pub name: PackageName,
|
||||
pub version: Version,
|
||||
// Optional fields
|
||||
pub requires_dist: Vec<Requirement<VerbatimUrl>>,
|
||||
pub requires_dist: Vec<Requirement<VerbatimParsedUrl>>,
|
||||
pub requires_python: Option<VersionSpecifiers>,
|
||||
pub provides_extras: Vec<ExtraName>,
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ pub enum MetadataError {
|
|||
#[error(transparent)]
|
||||
Pep440Error(#[from] VersionSpecifiersParseError),
|
||||
#[error(transparent)]
|
||||
Pep508Error(#[from] Pep508Error<VerbatimUrl>),
|
||||
Pep508Error(#[from] Box<Pep508Error<VerbatimParsedUrl>>),
|
||||
#[error(transparent)]
|
||||
InvalidName(#[from] InvalidNameError),
|
||||
#[error("Invalid `Metadata-Version` field: {0}")]
|
||||
|
@ -61,6 +61,12 @@ pub enum MetadataError {
|
|||
DynamicField(&'static str),
|
||||
}
|
||||
|
||||
impl From<Pep508Error<VerbatimParsedUrl>> for MetadataError {
|
||||
fn from(error: Pep508Error<VerbatimParsedUrl>) -> Self {
|
||||
Self::Pep508Error(Box::new(error))
|
||||
}
|
||||
}
|
||||
|
||||
/// From <https://github.com/PyO3/python-pkginfo-rs/blob/d719988323a0cfea86d4737116d7917f30e819e2/src/metadata.rs#LL78C2-L91C26>
|
||||
impl Metadata23 {
|
||||
/// Parse the [`Metadata23`] from a `METADATA` file, as included in a built distribution (wheel).
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
use std::path::PathBuf;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{Error, Result};
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
use url::{ParseError, Url};
|
||||
|
||||
use pep508_rs::VerbatimUrl;
|
||||
use pep508_rs::{Pep508Url, UnnamedRequirementUrl, VerbatimUrl, VerbatimUrlError};
|
||||
use uv_git::{GitSha, GitUrl};
|
||||
|
||||
use crate::{ArchiveInfo, DirInfo, DirectUrl, VcsInfo, VcsKind};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ParsedUrlError {
|
||||
#[error("Unsupported URL prefix `{prefix}` in URL: `{url}` ({message})")]
|
||||
|
@ -20,7 +22,9 @@ pub enum ParsedUrlError {
|
|||
#[error("Failed to parse Git reference from URL: `{0}`")]
|
||||
GitShaParse(Url, #[source] git2::Error),
|
||||
#[error("Not a valid URL: `{0}`")]
|
||||
UrlParse(String, #[source] url::ParseError),
|
||||
UrlParse(String, #[source] ParseError),
|
||||
#[error(transparent)]
|
||||
VerbatimUrl(#[from] VerbatimUrlError),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, PartialOrd, Eq, Ord)]
|
||||
|
@ -29,6 +33,105 @@ pub struct VerbatimParsedUrl {
|
|||
pub verbatim: VerbatimUrl,
|
||||
}
|
||||
|
||||
impl Pep508Url for VerbatimParsedUrl {
|
||||
type Err = ParsedUrlError;
|
||||
|
||||
fn parse_url(url: &str, working_dir: Option<&Path>) -> Result<Self, Self::Err> {
|
||||
let verbatim_url = <VerbatimUrl as Pep508Url>::parse_url(url, working_dir)?;
|
||||
Ok(Self {
|
||||
parsed_url: ParsedUrl::try_from(verbatim_url.to_url())?,
|
||||
verbatim: verbatim_url,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl UnnamedRequirementUrl for VerbatimParsedUrl {
|
||||
fn parse_path(
|
||||
path: impl AsRef<Path>,
|
||||
working_dir: impl AsRef<Path>,
|
||||
) -> Result<Self, Self::Err> {
|
||||
let verbatim = VerbatimUrl::parse_path(&path, &working_dir)?;
|
||||
let parsed_path_url = ParsedPathUrl {
|
||||
url: verbatim.to_url(),
|
||||
path: working_dir.as_ref().join(path),
|
||||
editable: false,
|
||||
};
|
||||
Ok(Self {
|
||||
parsed_url: ParsedUrl::Path(parsed_path_url),
|
||||
verbatim,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_absolute_path(path: impl AsRef<Path>) -> Result<Self, Self::Err> {
|
||||
let verbatim = VerbatimUrl::parse_absolute_path(&path)?;
|
||||
let parsed_path_url = ParsedPathUrl {
|
||||
url: verbatim.to_url(),
|
||||
path: path.as_ref().to_path_buf(),
|
||||
editable: false,
|
||||
};
|
||||
Ok(Self {
|
||||
parsed_url: ParsedUrl::Path(parsed_path_url),
|
||||
verbatim,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_unnamed_url(url: impl AsRef<str>) -> Result<Self, Self::Err> {
|
||||
let verbatim = <VerbatimUrl as UnnamedRequirementUrl>::parse_unnamed_url(&url)?;
|
||||
Ok(Self {
|
||||
parsed_url: ParsedUrl::try_from(verbatim.to_url())?,
|
||||
verbatim,
|
||||
})
|
||||
}
|
||||
|
||||
fn with_given(self, given: impl Into<String>) -> Self {
|
||||
Self {
|
||||
verbatim: self.verbatim.with_given(given),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
fn given(&self) -> Option<&str> {
|
||||
self.verbatim.given()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for VerbatimParsedUrl {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
Display::fmt(&self.verbatim, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<VerbatimUrl> for VerbatimParsedUrl {
|
||||
type Error = ParsedUrlError;
|
||||
|
||||
fn try_from(verbatim_url: VerbatimUrl) -> Result<Self, Self::Error> {
|
||||
let parsed_url = ParsedUrl::try_from(verbatim_url.to_url())?;
|
||||
Ok(Self {
|
||||
parsed_url,
|
||||
verbatim: verbatim_url,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::ser::Serialize for VerbatimParsedUrl {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::ser::Serializer,
|
||||
{
|
||||
self.verbatim.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::de::Deserialize<'de> for VerbatimParsedUrl {
|
||||
fn deserialize<D>(deserializer: D) -> Result<VerbatimParsedUrl, D::Error>
|
||||
where
|
||||
D: serde::de::Deserializer<'de>,
|
||||
{
|
||||
let verbatim_url = VerbatimUrl::deserialize(deserializer)?;
|
||||
Self::try_from(verbatim_url).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
/// We support three types of URLs for distributions:
|
||||
/// * The path to a file or directory (`file://`)
|
||||
/// * A Git repository (`git+https://` or `git+ssh://`), optionally with a subdirectory and/or
|
||||
|
@ -124,7 +227,7 @@ fn get_subdirectory(url: &Url) -> Option<PathBuf> {
|
|||
}
|
||||
|
||||
/// Return the Git reference of the given URL, if it exists.
|
||||
pub fn git_reference(url: Url) -> Result<Option<GitSha>, Error> {
|
||||
pub fn git_reference(url: Url) -> Result<Option<GitSha>, Box<ParsedUrlError>> {
|
||||
let ParsedGitUrl { url, .. } = ParsedGitUrl::try_from(url)?;
|
||||
Ok(url.precise())
|
||||
}
|
||||
|
@ -172,10 +275,10 @@ impl TryFrom<Url> for ParsedUrl {
|
|||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&ParsedUrl> for pypi_types::DirectUrl {
|
||||
type Error = Error;
|
||||
impl TryFrom<&ParsedUrl> for DirectUrl {
|
||||
type Error = ParsedUrlError;
|
||||
|
||||
fn try_from(value: &ParsedUrl) -> std::result::Result<Self, Self::Error> {
|
||||
fn try_from(value: &ParsedUrl) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
ParsedUrl::Path(value) => Self::try_from(value),
|
||||
ParsedUrl::Git(value) => Self::try_from(value),
|
||||
|
@ -184,26 +287,26 @@ impl TryFrom<&ParsedUrl> for pypi_types::DirectUrl {
|
|||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&ParsedPathUrl> for pypi_types::DirectUrl {
|
||||
type Error = Error;
|
||||
impl TryFrom<&ParsedPathUrl> for DirectUrl {
|
||||
type Error = ParsedUrlError;
|
||||
|
||||
fn try_from(value: &ParsedPathUrl) -> Result<Self, Self::Error> {
|
||||
Ok(Self::LocalDirectory {
|
||||
url: value.url.to_string(),
|
||||
dir_info: pypi_types::DirInfo {
|
||||
dir_info: DirInfo {
|
||||
editable: value.editable.then_some(true),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&ParsedArchiveUrl> for pypi_types::DirectUrl {
|
||||
type Error = Error;
|
||||
impl TryFrom<&ParsedArchiveUrl> for DirectUrl {
|
||||
type Error = ParsedUrlError;
|
||||
|
||||
fn try_from(value: &ParsedArchiveUrl) -> Result<Self, Self::Error> {
|
||||
Ok(Self::ArchiveUrl {
|
||||
url: value.url.to_string(),
|
||||
archive_info: pypi_types::ArchiveInfo {
|
||||
archive_info: ArchiveInfo {
|
||||
hash: None,
|
||||
hashes: None,
|
||||
},
|
||||
|
@ -212,14 +315,14 @@ impl TryFrom<&ParsedArchiveUrl> for pypi_types::DirectUrl {
|
|||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&ParsedGitUrl> for pypi_types::DirectUrl {
|
||||
type Error = Error;
|
||||
impl TryFrom<&ParsedGitUrl> for DirectUrl {
|
||||
type Error = ParsedUrlError;
|
||||
|
||||
fn try_from(value: &ParsedGitUrl) -> Result<Self, Self::Error> {
|
||||
Ok(Self::VcsUrl {
|
||||
url: value.url.repository().to_string(),
|
||||
vcs_info: pypi_types::VcsInfo {
|
||||
vcs: pypi_types::VcsKind::Git,
|
||||
vcs_info: VcsInfo {
|
||||
vcs: VcsKind::Git,
|
||||
commit_id: value.url.precise().as_ref().map(ToString::to_string),
|
||||
requested_revision: value.url.reference().as_str().map(ToString::to_string),
|
||||
},
|
|
@ -15,6 +15,7 @@ workspace = true
|
|||
[dependencies]
|
||||
distribution-types = { workspace = true }
|
||||
pep508_rs = { workspace = true }
|
||||
pypi-types = { workspace = true }
|
||||
uv-client = { workspace = true }
|
||||
uv-fs = { workspace = true }
|
||||
uv-normalize = { workspace = true }
|
||||
|
@ -25,7 +26,6 @@ fs-err = { workspace = true }
|
|||
regex = { workspace = true }
|
||||
reqwest = { workspace = true, optional = true }
|
||||
reqwest-middleware = { workspace = true, optional = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
unscanny = { workspace = true }
|
||||
url = { workspace = true }
|
||||
|
|
|
@ -44,13 +44,12 @@ use tracing::instrument;
|
|||
use unscanny::{Pattern, Scanner};
|
||||
use url::Url;
|
||||
|
||||
use distribution_types::{
|
||||
ParsedUrlError, Requirement, UnresolvedRequirement, UnresolvedRequirementSpecification,
|
||||
};
|
||||
use distribution_types::{Requirement, UnresolvedRequirement, UnresolvedRequirementSpecification};
|
||||
use pep508_rs::{
|
||||
expand_env_vars, split_scheme, strip_host, Extras, MarkerTree, Pep508Error, Pep508ErrorSource,
|
||||
RequirementOrigin, Scheme, VerbatimUrl,
|
||||
};
|
||||
use pypi_types::VerbatimParsedUrl;
|
||||
#[cfg(feature = "http")]
|
||||
use uv_client::BaseClient;
|
||||
use uv_client::BaseClientBuilder;
|
||||
|
@ -59,7 +58,7 @@ use uv_fs::{normalize_url_path, Simplified};
|
|||
use uv_normalize::ExtraName;
|
||||
use uv_warnings::warn_user;
|
||||
|
||||
pub use crate::requirement::{RequirementsTxtRequirement, RequirementsTxtRequirementError};
|
||||
pub use crate::requirement::RequirementsTxtRequirement;
|
||||
|
||||
mod requirement;
|
||||
|
||||
|
@ -203,7 +202,7 @@ impl EditableRequirement {
|
|||
) -> Result<Self, RequirementsTxtParserError> {
|
||||
// Identify the markers.
|
||||
let (given, marker) = if let Some((requirement, marker)) = Self::split_markers(given) {
|
||||
let marker = MarkerTree::from_str(marker).map_err(|err| {
|
||||
let marker = MarkerTree::parse_str(marker).map_err(|err| {
|
||||
// Map from error on the markers to error on the whole requirement.
|
||||
let err = Pep508Error {
|
||||
message: err.message,
|
||||
|
@ -216,14 +215,14 @@ impl EditableRequirement {
|
|||
RequirementsTxtParserError::Pep508 {
|
||||
start: err.start,
|
||||
end: err.start + err.len,
|
||||
source: err,
|
||||
source: Box::new(err),
|
||||
}
|
||||
}
|
||||
Pep508ErrorSource::UnsupportedRequirement(_) => {
|
||||
RequirementsTxtParserError::UnsupportedRequirement {
|
||||
start: err.start,
|
||||
end: err.start + err.len,
|
||||
source: err,
|
||||
source: Box::new(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -248,14 +247,14 @@ impl EditableRequirement {
|
|||
RequirementsTxtParserError::Pep508 {
|
||||
start: err.start,
|
||||
end: err.start + err.len,
|
||||
source: err,
|
||||
source: Box::new(err),
|
||||
}
|
||||
}
|
||||
Pep508ErrorSource::UnsupportedRequirement(_) => {
|
||||
RequirementsTxtParserError::UnsupportedRequirement {
|
||||
start: err.start,
|
||||
end: err.start + err.len,
|
||||
source: err,
|
||||
source: Box::new(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -403,21 +402,19 @@ pub struct RequirementEntry {
|
|||
// We place the impl here instead of next to `UnresolvedRequirementSpecification` because
|
||||
// `UnresolvedRequirementSpecification` is defined in `distribution-types` and `requirements-txt`
|
||||
// depends on `distribution-types`.
|
||||
impl TryFrom<RequirementEntry> for UnresolvedRequirementSpecification {
|
||||
type Error = Box<ParsedUrlError>;
|
||||
|
||||
fn try_from(value: RequirementEntry) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
impl From<RequirementEntry> for UnresolvedRequirementSpecification {
|
||||
fn from(value: RequirementEntry) -> Self {
|
||||
Self {
|
||||
requirement: match value.requirement {
|
||||
RequirementsTxtRequirement::Named(named) => {
|
||||
UnresolvedRequirement::Named(Requirement::from_pep508(named)?)
|
||||
UnresolvedRequirement::Named(Requirement::from(named))
|
||||
}
|
||||
RequirementsTxtRequirement::Unnamed(unnamed) => {
|
||||
UnresolvedRequirement::Unnamed(unnamed)
|
||||
}
|
||||
},
|
||||
hashes: value.hashes,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -427,7 +424,7 @@ pub struct RequirementsTxt {
|
|||
/// The actual requirements with the hashes.
|
||||
pub requirements: Vec<RequirementEntry>,
|
||||
/// Constraints included with `-c`.
|
||||
pub constraints: Vec<pep508_rs::Requirement>,
|
||||
pub constraints: Vec<pep508_rs::Requirement<VerbatimParsedUrl>>,
|
||||
/// Editables with `-e`.
|
||||
pub editables: Vec<EditableRequirement>,
|
||||
/// The index URL, specified with `--index-url`.
|
||||
|
@ -914,30 +911,10 @@ fn parse_requirement_and_hashes(
|
|||
requirement
|
||||
}
|
||||
})
|
||||
.map_err(|err| match err {
|
||||
RequirementsTxtRequirementError::ParsedUrl(err) => {
|
||||
RequirementsTxtParserError::ParsedUrl {
|
||||
.map_err(|err| RequirementsTxtParserError::Pep508 {
|
||||
source: err,
|
||||
start,
|
||||
end,
|
||||
}
|
||||
}
|
||||
RequirementsTxtRequirementError::Pep508(err) => match err.message {
|
||||
Pep508ErrorSource::String(_) | Pep508ErrorSource::UrlError(_) => {
|
||||
RequirementsTxtParserError::Pep508 {
|
||||
source: err,
|
||||
start,
|
||||
end,
|
||||
}
|
||||
}
|
||||
Pep508ErrorSource::UnsupportedRequirement(_) => {
|
||||
RequirementsTxtParserError::UnsupportedRequirement {
|
||||
source: err,
|
||||
start,
|
||||
end,
|
||||
}
|
||||
}
|
||||
},
|
||||
})?;
|
||||
|
||||
let hashes = if has_hashes {
|
||||
|
@ -1068,17 +1045,17 @@ pub enum RequirementsTxtParserError {
|
|||
column: usize,
|
||||
},
|
||||
UnsupportedRequirement {
|
||||
source: Pep508Error,
|
||||
source: Box<Pep508Error<VerbatimParsedUrl>>,
|
||||
start: usize,
|
||||
end: usize,
|
||||
},
|
||||
Pep508 {
|
||||
source: Pep508Error,
|
||||
source: Box<Pep508Error<VerbatimParsedUrl>>,
|
||||
start: usize,
|
||||
end: usize,
|
||||
},
|
||||
ParsedUrl {
|
||||
source: Box<ParsedUrlError>,
|
||||
source: Box<Pep508Error<VerbatimParsedUrl>>,
|
||||
start: usize,
|
||||
end: usize,
|
||||
},
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
use std::path::Path;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use distribution_types::ParsedUrlError;
|
||||
use pep508_rs::{
|
||||
Pep508Error, Pep508ErrorSource, RequirementOrigin, TracingReporter, UnnamedRequirement,
|
||||
};
|
||||
use pypi_types::VerbatimParsedUrl;
|
||||
|
||||
/// A requirement specifier in a `requirements.txt` file.
|
||||
///
|
||||
|
@ -15,9 +13,9 @@ use pep508_rs::{
|
|||
pub enum RequirementsTxtRequirement {
|
||||
/// The uv-specific superset over PEP 508 requirements specifier incorporating
|
||||
/// `tool.uv.sources`.
|
||||
Named(pep508_rs::Requirement),
|
||||
Named(pep508_rs::Requirement<VerbatimParsedUrl>),
|
||||
/// A PEP 508-like, direct URL dependency specifier.
|
||||
Unnamed(UnnamedRequirement),
|
||||
Unnamed(UnnamedRequirement<VerbatimParsedUrl>),
|
||||
}
|
||||
|
||||
impl RequirementsTxtRequirement {
|
||||
|
@ -31,20 +29,12 @@ impl RequirementsTxtRequirement {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum RequirementsTxtRequirementError {
|
||||
#[error(transparent)]
|
||||
ParsedUrl(#[from] Box<ParsedUrlError>),
|
||||
#[error(transparent)]
|
||||
Pep508(#[from] Pep508Error),
|
||||
}
|
||||
|
||||
impl RequirementsTxtRequirement {
|
||||
/// Parse a requirement as seen in a `requirements.txt` file.
|
||||
pub fn parse(
|
||||
input: &str,
|
||||
working_dir: impl AsRef<Path>,
|
||||
) -> Result<Self, RequirementsTxtRequirementError> {
|
||||
) -> Result<Self, Box<Pep508Error<VerbatimParsedUrl>>> {
|
||||
// Attempt to parse as a PEP 508-compliant requirement.
|
||||
match pep508_rs::Requirement::parse(input, &working_dir) {
|
||||
Ok(requirement) => Ok(Self::Named(requirement)),
|
||||
|
@ -57,8 +47,9 @@ impl RequirementsTxtRequirement {
|
|||
&mut TracingReporter,
|
||||
)?))
|
||||
}
|
||||
_ => Err(RequirementsTxtRequirementError::Pep508(err)),
|
||||
_ => Err(err),
|
||||
},
|
||||
}
|
||||
.map_err(Box::new)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,28 @@ RequirementsTxt {
|
|||
],
|
||||
version_or_url: Some(
|
||||
Url(
|
||||
VerbatimUrl {
|
||||
VerbatimParsedUrl {
|
||||
parsed_url: Archive(
|
||||
ParsedArchiveUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"github.com",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/pandas-dev/pandas",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
subdirectory: None,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
|
@ -55,6 +76,7 @@ RequirementsTxt {
|
|||
"https://github.com/pandas-dev/pandas",
|
||||
),
|
||||
},
|
||||
},
|
||||
),
|
||||
),
|
||||
marker: None,
|
||||
|
|
|
@ -7,7 +7,25 @@ RequirementsTxt {
|
|||
RequirementEntry {
|
||||
requirement: Unnamed(
|
||||
UnnamedRequirement {
|
||||
url: VerbatimUrl {
|
||||
url: VerbatimParsedUrl {
|
||||
parsed_url: Path(
|
||||
ParsedPathUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
path: "<REQUIREMENTS_DIR>/./scripts/packages/black_editable",
|
||||
editable: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
|
@ -23,6 +41,7 @@ RequirementsTxt {
|
|||
"./scripts/packages/black_editable",
|
||||
),
|
||||
},
|
||||
},
|
||||
extras: [],
|
||||
marker: None,
|
||||
origin: Some(
|
||||
|
@ -37,7 +56,25 @@ RequirementsTxt {
|
|||
RequirementEntry {
|
||||
requirement: Unnamed(
|
||||
UnnamedRequirement {
|
||||
url: VerbatimUrl {
|
||||
url: VerbatimParsedUrl {
|
||||
parsed_url: Path(
|
||||
ParsedPathUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
path: "<REQUIREMENTS_DIR>/./scripts/packages/black_editable",
|
||||
editable: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
|
@ -53,6 +90,7 @@ RequirementsTxt {
|
|||
"./scripts/packages/black_editable",
|
||||
),
|
||||
},
|
||||
},
|
||||
extras: [
|
||||
ExtraName(
|
||||
"dev",
|
||||
|
@ -71,7 +109,25 @@ RequirementsTxt {
|
|||
RequirementEntry {
|
||||
requirement: Unnamed(
|
||||
UnnamedRequirement {
|
||||
url: VerbatimUrl {
|
||||
url: VerbatimParsedUrl {
|
||||
parsed_url: Path(
|
||||
ParsedPathUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/scripts/packages/black_editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
path: "/scripts/packages/black_editable",
|
||||
editable: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
|
@ -87,6 +143,7 @@ RequirementsTxt {
|
|||
"file:///scripts/packages/black_editable",
|
||||
),
|
||||
},
|
||||
},
|
||||
extras: [],
|
||||
marker: None,
|
||||
origin: Some(
|
||||
|
|
|
@ -35,7 +35,28 @@ RequirementsTxt {
|
|||
],
|
||||
version_or_url: Some(
|
||||
Url(
|
||||
VerbatimUrl {
|
||||
VerbatimParsedUrl {
|
||||
parsed_url: Archive(
|
||||
ParsedArchiveUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"github.com",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/pandas-dev/pandas",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
subdirectory: None,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
|
@ -55,6 +76,7 @@ RequirementsTxt {
|
|||
"https://github.com/pandas-dev/pandas",
|
||||
),
|
||||
},
|
||||
},
|
||||
),
|
||||
),
|
||||
marker: None,
|
||||
|
|
|
@ -7,7 +7,25 @@ RequirementsTxt {
|
|||
RequirementEntry {
|
||||
requirement: Unnamed(
|
||||
UnnamedRequirement {
|
||||
url: VerbatimUrl {
|
||||
url: VerbatimParsedUrl {
|
||||
parsed_url: Path(
|
||||
ParsedPathUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
path: "<REQUIREMENTS_DIR>/./scripts/packages/black_editable",
|
||||
editable: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
|
@ -23,6 +41,7 @@ RequirementsTxt {
|
|||
"./scripts/packages/black_editable",
|
||||
),
|
||||
},
|
||||
},
|
||||
extras: [],
|
||||
marker: None,
|
||||
origin: Some(
|
||||
|
@ -37,7 +56,25 @@ RequirementsTxt {
|
|||
RequirementEntry {
|
||||
requirement: Unnamed(
|
||||
UnnamedRequirement {
|
||||
url: VerbatimUrl {
|
||||
url: VerbatimParsedUrl {
|
||||
parsed_url: Path(
|
||||
ParsedPathUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
path: "<REQUIREMENTS_DIR>/./scripts/packages/black_editable",
|
||||
editable: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
|
@ -53,6 +90,7 @@ RequirementsTxt {
|
|||
"./scripts/packages/black_editable",
|
||||
),
|
||||
},
|
||||
},
|
||||
extras: [
|
||||
ExtraName(
|
||||
"dev",
|
||||
|
@ -71,7 +109,25 @@ RequirementsTxt {
|
|||
RequirementEntry {
|
||||
requirement: Unnamed(
|
||||
UnnamedRequirement {
|
||||
url: VerbatimUrl {
|
||||
url: VerbatimParsedUrl {
|
||||
parsed_url: Path(
|
||||
ParsedPathUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
path: "<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
editable: false,
|
||||
},
|
||||
),
|
||||
verbatim: VerbatimUrl {
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
|
@ -87,6 +143,7 @@ RequirementsTxt {
|
|||
"file:///scripts/packages/black_editable",
|
||||
),
|
||||
},
|
||||
},
|
||||
extras: [],
|
||||
marker: None,
|
||||
origin: Some(
|
||||
|
|
|
@ -17,6 +17,7 @@ workspace = true
|
|||
distribution-types = { workspace = true }
|
||||
pep440_rs = { workspace = true }
|
||||
pep508_rs = { workspace = true }
|
||||
pypi-types = { workspace = true }
|
||||
uv-fs = { workspace = true }
|
||||
uv-interpreter = { workspace = true }
|
||||
uv-types = { workspace = true }
|
||||
|
|
|
@ -25,9 +25,10 @@ use tokio::process::Command;
|
|||
use tokio::sync::{Mutex, Semaphore};
|
||||
use tracing::{debug, info_span, instrument, Instrument};
|
||||
|
||||
use distribution_types::{ParsedUrlError, Requirement, Resolution};
|
||||
use distribution_types::{Requirement, Resolution};
|
||||
use pep440_rs::Version;
|
||||
use pep508_rs::PackageName;
|
||||
use pypi_types::VerbatimParsedUrl;
|
||||
use uv_configuration::{BuildKind, ConfigSettings, SetupPyStrategy};
|
||||
use uv_fs::{PythonExt, Simplified};
|
||||
use uv_interpreter::{Interpreter, PythonEnvironment};
|
||||
|
@ -66,18 +67,16 @@ static WHEEL_NOT_FOUND_RE: Lazy<Regex> =
|
|||
static DEFAULT_BACKEND: Lazy<Pep517Backend> = Lazy::new(|| Pep517Backend {
|
||||
backend: "setuptools.build_meta:__legacy__".to_string(),
|
||||
backend_path: None,
|
||||
requirements: vec![Requirement::from_pep508(
|
||||
requirements: vec![Requirement::from(
|
||||
pep508_rs::Requirement::from_str("setuptools >= 40.8.0").unwrap(),
|
||||
)
|
||||
.unwrap()],
|
||||
)],
|
||||
});
|
||||
|
||||
/// The requirements for `--legacy-setup-py` builds.
|
||||
static SETUP_PY_REQUIREMENTS: Lazy<[Requirement; 2]> = Lazy::new(|| {
|
||||
[
|
||||
Requirement::from_pep508(pep508_rs::Requirement::from_str("setuptools >= 40.8.0").unwrap())
|
||||
.unwrap(),
|
||||
Requirement::from_pep508(pep508_rs::Requirement::from_str("wheel").unwrap()).unwrap(),
|
||||
Requirement::from(pep508_rs::Requirement::from_str("setuptools >= 40.8.0").unwrap()),
|
||||
Requirement::from(pep508_rs::Requirement::from_str("wheel").unwrap()),
|
||||
]
|
||||
});
|
||||
|
||||
|
@ -116,8 +115,6 @@ pub enum Error {
|
|||
},
|
||||
#[error("Failed to build PATH for build script")]
|
||||
BuildScriptPath(#[source] env::JoinPathsError),
|
||||
#[error("Failed to parse requirements from build backend")]
|
||||
DirectUrl(#[source] Box<ParsedUrlError>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -244,7 +241,7 @@ pub struct Project {
|
|||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct BuildSystem {
|
||||
/// PEP 508 dependencies required to execute the build system.
|
||||
pub requires: Vec<pep508_rs::Requirement>,
|
||||
pub requires: Vec<pep508_rs::Requirement<VerbatimParsedUrl>>,
|
||||
/// A string naming a Python object that will be used to perform the build.
|
||||
pub build_backend: Option<String>,
|
||||
/// Specify that their backend code is hosted in-tree, this key contains a list of directories.
|
||||
|
@ -601,9 +598,8 @@ impl SourceBuild {
|
|||
requirements: build_system
|
||||
.requires
|
||||
.into_iter()
|
||||
.map(Requirement::from_pep508)
|
||||
.collect::<Result<_, _>>()
|
||||
.map_err(|err| Box::new(Error::DirectUrl(err)))?,
|
||||
.map(Requirement::from)
|
||||
.collect(),
|
||||
}
|
||||
} else {
|
||||
// If a `pyproject.toml` is present, but `[build-system]` is missing, proceed with
|
||||
|
@ -982,7 +978,7 @@ async fn create_pep517_build_environment(
|
|||
})?;
|
||||
|
||||
// Deserialize the requirements from the output file.
|
||||
let extra_requires: Vec<pep508_rs::Requirement> = serde_json::from_slice::<Vec<pep508_rs::Requirement>>(&contents).map_err(|err| {
|
||||
let extra_requires: Vec<pep508_rs::Requirement<VerbatimParsedUrl>> = serde_json::from_slice::<Vec<pep508_rs::Requirement<VerbatimParsedUrl>>>(&contents).map_err(|err| {
|
||||
Error::from_command_output(
|
||||
format!(
|
||||
"Build backend failed to return extra requires with `get_requires_for_build_{build_kind}`: {err}"
|
||||
|
@ -991,11 +987,7 @@ async fn create_pep517_build_environment(
|
|||
version_id,
|
||||
)
|
||||
})?;
|
||||
let extra_requires: Vec<_> = extra_requires
|
||||
.into_iter()
|
||||
.map(Requirement::from_pep508)
|
||||
.collect::<Result<_, _>>()
|
||||
.map_err(Error::DirectUrl)?;
|
||||
let extra_requires: Vec<_> = extra_requires.into_iter().map(Requirement::from).collect();
|
||||
|
||||
// Some packages (such as tqdm 4.66.1) list only extra requires that have already been part of
|
||||
// the pyproject.toml requires (in this case, `wheel`). We can skip doing the whole resolution
|
||||
|
|
|
@ -20,6 +20,7 @@ distribution-filename = { workspace = true }
|
|||
distribution-types = { workspace = true }
|
||||
install-wheel-rs = { workspace = true }
|
||||
pep508_rs = { workspace = true }
|
||||
pypi-types = { workspace = true }
|
||||
uv-build = { workspace = true }
|
||||
uv-cache = { workspace = true, features = ["clap"] }
|
||||
uv-client = { workspace = true }
|
||||
|
|
|
@ -5,8 +5,9 @@ use anyhow::{bail, Result};
|
|||
use clap::Parser;
|
||||
|
||||
use distribution_filename::WheelFilename;
|
||||
use distribution_types::{BuiltDist, DirectUrlBuiltDist, ParsedUrl, RemoteSource};
|
||||
use distribution_types::{BuiltDist, DirectUrlBuiltDist, RemoteSource};
|
||||
use pep508_rs::VerbatimUrl;
|
||||
use pypi_types::ParsedUrl;
|
||||
use uv_cache::{Cache, CacheArgs};
|
||||
use uv_client::RegistryClientBuilder;
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ use tokio::task::JoinError;
|
|||
use zip::result::ZipError;
|
||||
|
||||
use distribution_filename::WheelFilenameError;
|
||||
use distribution_types::ParsedUrlError;
|
||||
use pep440_rs::Version;
|
||||
use pypi_types::HashDigest;
|
||||
use uv_client::BetterReqwestError;
|
||||
|
@ -28,8 +27,6 @@ pub enum Error {
|
|||
#[error("Git operation failed")]
|
||||
Git(#[source] anyhow::Error),
|
||||
#[error(transparent)]
|
||||
DirectUrl(#[from] Box<ParsedUrlError>),
|
||||
#[error(transparent)]
|
||||
Reqwest(#[from] BetterReqwestError),
|
||||
#[error(transparent)]
|
||||
Client(#[from] uv_client::Error),
|
||||
|
|
|
@ -8,7 +8,7 @@ use tracing::debug;
|
|||
use url::Url;
|
||||
|
||||
use cache_key::{CanonicalUrl, RepositoryUrl};
|
||||
use distribution_types::ParsedGitUrl;
|
||||
use pypi_types::ParsedGitUrl;
|
||||
use uv_cache::{Cache, CacheBucket};
|
||||
use uv_fs::LockedFile;
|
||||
use uv_git::{Fetch, GitReference, GitSha, GitSource, GitUrl};
|
||||
|
|
|
@ -17,12 +17,11 @@ use zip::ZipArchive;
|
|||
use distribution_filename::WheelFilename;
|
||||
use distribution_types::{
|
||||
BuildableSource, DirectorySourceDist, DirectorySourceUrl, Dist, FileLocation, GitSourceUrl,
|
||||
HashPolicy, Hashed, LocalEditable, ParsedArchiveUrl, PathSourceUrl, RemoteSource, SourceDist,
|
||||
SourceUrl,
|
||||
HashPolicy, Hashed, LocalEditable, PathSourceUrl, RemoteSource, SourceDist, SourceUrl,
|
||||
};
|
||||
use install_wheel_rs::metadata::read_archive_metadata;
|
||||
use platform_tags::Tags;
|
||||
use pypi_types::{HashDigest, Metadata23};
|
||||
use pypi_types::{HashDigest, Metadata23, ParsedArchiveUrl};
|
||||
use uv_cache::{
|
||||
ArchiveTimestamp, CacheBucket, CacheEntry, CacheShard, CachedByTimestamp, Freshness, Timestamp,
|
||||
WheelCache,
|
||||
|
@ -1026,7 +1025,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
|
||||
// Resolve to a precise Git SHA.
|
||||
let url = if let Some(url) = resolve_precise(
|
||||
&resource.git,
|
||||
resource.git,
|
||||
self.build_context.cache(),
|
||||
self.reporter.as_ref(),
|
||||
)
|
||||
|
@ -1034,11 +1033,9 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
{
|
||||
Cow::Owned(url)
|
||||
} else {
|
||||
Cow::Borrowed(resource.git.as_ref())
|
||||
Cow::Borrowed(resource.git)
|
||||
};
|
||||
|
||||
let subdirectory = resource.subdirectory.as_deref();
|
||||
|
||||
// Fetch the Git repository.
|
||||
let fetch =
|
||||
fetch_git_archive(&url, self.build_context.cache(), self.reporter.as_ref()).await?;
|
||||
|
@ -1062,7 +1059,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
.map(|reporter| reporter.on_build_start(source));
|
||||
|
||||
let (disk_filename, filename, metadata) = self
|
||||
.build_distribution(source, fetch.path(), subdirectory, &cache_shard)
|
||||
.build_distribution(source, fetch.path(), resource.subdirectory, &cache_shard)
|
||||
.await?;
|
||||
|
||||
if let Some(task) = task {
|
||||
|
@ -1102,7 +1099,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
|
||||
// Resolve to a precise Git SHA.
|
||||
let url = if let Some(url) = resolve_precise(
|
||||
&resource.git,
|
||||
resource.git,
|
||||
self.build_context.cache(),
|
||||
self.reporter.as_ref(),
|
||||
)
|
||||
|
@ -1110,11 +1107,9 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
{
|
||||
Cow::Owned(url)
|
||||
} else {
|
||||
Cow::Borrowed(resource.git.as_ref())
|
||||
Cow::Borrowed(resource.git)
|
||||
};
|
||||
|
||||
let subdirectory = resource.subdirectory.as_deref();
|
||||
|
||||
// Fetch the Git repository.
|
||||
let fetch =
|
||||
fetch_git_archive(&url, self.build_context.cache(), self.reporter.as_ref()).await?;
|
||||
|
@ -1143,7 +1138,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
|
||||
// If the backend supports `prepare_metadata_for_build_wheel`, use it.
|
||||
if let Some(metadata) = self
|
||||
.build_metadata(source, fetch.path(), subdirectory)
|
||||
.build_metadata(source, fetch.path(), resource.subdirectory)
|
||||
.boxed_local()
|
||||
.await?
|
||||
{
|
||||
|
@ -1165,7 +1160,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
.map(|reporter| reporter.on_build_start(source));
|
||||
|
||||
let (_disk_filename, _filename, metadata) = self
|
||||
.build_distribution(source, fetch.path(), subdirectory, &cache_shard)
|
||||
.build_distribution(source, fetch.path(), resource.subdirectory, &cache_shard)
|
||||
.await?;
|
||||
|
||||
if let Some(task) = task {
|
||||
|
|
|
@ -12,6 +12,7 @@ use distribution_types::{
|
|||
UnresolvedRequirementSpecification,
|
||||
};
|
||||
use pep440_rs::{Version, VersionSpecifiers};
|
||||
use pypi_types::VerbatimParsedUrl;
|
||||
use requirements_txt::EditableRequirement;
|
||||
use uv_cache::{ArchiveTarget, ArchiveTimestamp};
|
||||
use uv_interpreter::PythonEnvironment;
|
||||
|
@ -341,9 +342,9 @@ impl SitePackages {
|
|||
&requirement.extras,
|
||||
) {
|
||||
let dependency = UnresolvedRequirementSpecification {
|
||||
requirement: UnresolvedRequirement::Named(
|
||||
Requirement::from_pep508(dependency)?,
|
||||
),
|
||||
requirement: UnresolvedRequirement::Named(Requirement::from(
|
||||
dependency,
|
||||
)),
|
||||
hashes: vec![],
|
||||
};
|
||||
if seen.insert(dependency.clone()) {
|
||||
|
@ -363,7 +364,9 @@ impl SitePackages {
|
|||
while let Some(entry) = stack.pop() {
|
||||
let installed = match &entry.requirement {
|
||||
UnresolvedRequirement::Named(requirement) => self.get_packages(&requirement.name),
|
||||
UnresolvedRequirement::Unnamed(requirement) => self.get_urls(requirement.url.raw()),
|
||||
UnresolvedRequirement::Unnamed(requirement) => {
|
||||
self.get_urls(requirement.url.verbatim.raw())
|
||||
}
|
||||
};
|
||||
match installed.as_slice() {
|
||||
[] => {
|
||||
|
@ -373,7 +376,7 @@ impl SitePackages {
|
|||
[distribution] => {
|
||||
match RequirementSatisfaction::check(
|
||||
distribution,
|
||||
entry.requirement.source()?.as_ref(),
|
||||
entry.requirement.source().as_ref(),
|
||||
)? {
|
||||
RequirementSatisfaction::Mismatch | RequirementSatisfaction::OutOfDate => {
|
||||
return Ok(SatisfiesResult::Unsatisfied(entry.requirement.to_string()))
|
||||
|
@ -405,9 +408,9 @@ impl SitePackages {
|
|||
entry.requirement.extras(),
|
||||
) {
|
||||
let dependency = UnresolvedRequirementSpecification {
|
||||
requirement: UnresolvedRequirement::Named(
|
||||
Requirement::from_pep508(dependency)?,
|
||||
),
|
||||
requirement: UnresolvedRequirement::Named(Requirement::from(
|
||||
dependency,
|
||||
)),
|
||||
hashes: vec![],
|
||||
};
|
||||
if seen.insert(dependency.clone()) {
|
||||
|
@ -471,7 +474,7 @@ pub enum SitePackagesDiagnostic {
|
|||
/// The package that is missing a dependency.
|
||||
package: PackageName,
|
||||
/// The dependency that is missing.
|
||||
requirement: pep508_rs::Requirement,
|
||||
requirement: pep508_rs::Requirement<VerbatimParsedUrl>,
|
||||
},
|
||||
IncompatibleDependency {
|
||||
/// The package that has an incompatible dependency.
|
||||
|
@ -479,7 +482,7 @@ pub enum SitePackagesDiagnostic {
|
|||
/// The version of the package that is installed.
|
||||
version: Version,
|
||||
/// The dependency that is incompatible.
|
||||
requirement: pep508_rs::Requirement,
|
||||
requirement: pep508_rs::Requirement<VerbatimParsedUrl>,
|
||||
},
|
||||
DuplicatePackage {
|
||||
/// The package that has multiple installed distributions.
|
||||
|
|
|
@ -24,8 +24,6 @@ pub enum LookaheadError {
|
|||
DownloadAndBuild(SourceDist, #[source] uv_distribution::Error),
|
||||
#[error(transparent)]
|
||||
UnsupportedUrl(#[from] distribution_types::Error),
|
||||
#[error(transparent)]
|
||||
InvalidRequirement(#[from] Box<distribution_types::ParsedUrlError>),
|
||||
}
|
||||
|
||||
/// A resolver for resolving lookahead requirements from direct URLs.
|
||||
|
@ -211,8 +209,8 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> {
|
|||
.requires_dist
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(Requirement::from_pep508)
|
||||
.collect::<Result<_, _>>()?
|
||||
.map(Requirement::from)
|
||||
.collect()
|
||||
} else {
|
||||
// Run the PEP 517 build process to extract metadata from the source distribution.
|
||||
let archive = self
|
||||
|
@ -233,10 +231,7 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> {
|
|||
.distributions()
|
||||
.done(id, Arc::new(MetadataResponse::Found(archive)));
|
||||
|
||||
requires_dist
|
||||
.into_iter()
|
||||
.map(Requirement::from_pep508)
|
||||
.collect::<Result<_, _>>()?
|
||||
requires_dist.into_iter().map(Requirement::from).collect()
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -20,9 +20,10 @@ use serde::{Deserialize, Serialize};
|
|||
use thiserror::Error;
|
||||
use url::Url;
|
||||
|
||||
use distribution_types::{ParsedUrlError, Requirement, RequirementSource, Requirements};
|
||||
use distribution_types::{Requirement, RequirementSource, Requirements};
|
||||
use pep440_rs::VersionSpecifiers;
|
||||
use pep508_rs::{RequirementOrigin, VerbatimUrl, VersionOrUrl};
|
||||
use pep508_rs::{Pep508Error, RequirementOrigin, VerbatimUrl, VersionOrUrl};
|
||||
use pypi_types::VerbatimParsedUrl;
|
||||
use uv_configuration::PreviewMode;
|
||||
use uv_fs::Simplified;
|
||||
use uv_git::GitReference;
|
||||
|
@ -34,7 +35,7 @@ use crate::ExtrasSpecification;
|
|||
#[derive(Debug, Error)]
|
||||
pub enum Pep621Error {
|
||||
#[error(transparent)]
|
||||
Pep508(#[from] pep508_rs::Pep508Error),
|
||||
Pep508(#[from] Box<Pep508Error<VerbatimParsedUrl>>),
|
||||
#[error("Must specify a `[project]` section alongside `[tool.uv.sources]`")]
|
||||
MissingProjectSection,
|
||||
#[error("pyproject.toml section is declared as dynamic, but must be static: `{0}`")]
|
||||
|
@ -43,12 +44,16 @@ pub enum Pep621Error {
|
|||
LoweringError(PackageName, #[source] LoweringError),
|
||||
}
|
||||
|
||||
impl From<Pep508Error<VerbatimParsedUrl>> for Pep621Error {
|
||||
fn from(error: Pep508Error<VerbatimParsedUrl>) -> Self {
|
||||
Self::Pep508(Box::new(error))
|
||||
}
|
||||
}
|
||||
|
||||
/// An error parsing and merging `tool.uv.sources` with
|
||||
/// `project.{dependencies,optional-dependencies}`.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum LoweringError {
|
||||
#[error("Invalid URL structure")]
|
||||
DirectUrl(#[from] Box<ParsedUrlError>),
|
||||
#[error("Unsupported path (can't convert to URL): `{}`", _0.user_display())]
|
||||
PathToUrl(PathBuf),
|
||||
#[error("Package is not included as workspace package in `tool.uv.workspace`")]
|
||||
|
@ -385,7 +390,7 @@ pub(crate) fn lower_requirements(
|
|||
|
||||
/// Combine `project.dependencies` or `project.optional-dependencies` with `tool.uv.sources`.
|
||||
pub(crate) fn lower_requirement(
|
||||
requirement: pep508_rs::Requirement,
|
||||
requirement: pep508_rs::Requirement<VerbatimParsedUrl>,
|
||||
project_name: &PackageName,
|
||||
project_dir: &Path,
|
||||
project_sources: &BTreeMap<PackageName, Source>,
|
||||
|
@ -420,7 +425,7 @@ pub(crate) fn lower_requirement(
|
|||
requirement.name
|
||||
);
|
||||
}
|
||||
return Ok(Requirement::from_pep508(requirement)?);
|
||||
return Ok(Requirement::from(requirement));
|
||||
};
|
||||
|
||||
if preview.is_disabled() {
|
||||
|
|
|
@ -11,6 +11,7 @@ use distribution_types::{
|
|||
BuildableSource, DirectorySourceUrl, HashPolicy, Requirement, SourceUrl, VersionId,
|
||||
};
|
||||
use pep508_rs::RequirementOrigin;
|
||||
use pypi_types::VerbatimParsedUrl;
|
||||
use uv_distribution::{DistributionDatabase, Reporter};
|
||||
use uv_fs::Simplified;
|
||||
use uv_resolver::{InMemoryIndex, MetadataResponse};
|
||||
|
@ -74,12 +75,15 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> {
|
|||
Ok(requirements
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(Requirement::from_pep508)
|
||||
.collect::<Result<_, _>>()?)
|
||||
.map(Requirement::from)
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Infer the package name for a given "unnamed" requirement.
|
||||
async fn resolve_source_tree(&self, path: &Path) -> Result<Vec<pep508_rs::Requirement>> {
|
||||
async fn resolve_source_tree(
|
||||
&self,
|
||||
path: &Path,
|
||||
) -> Result<Vec<pep508_rs::Requirement<VerbatimParsedUrl>>> {
|
||||
// Convert to a buildable source.
|
||||
let source_tree = fs_err::canonicalize(path).with_context(|| {
|
||||
format!(
|
||||
|
|
|
@ -11,7 +11,8 @@ use distribution_types::{
|
|||
FlatIndexLocation, IndexUrl, Requirement, RequirementSource, UnresolvedRequirement,
|
||||
UnresolvedRequirementSpecification,
|
||||
};
|
||||
use pep508_rs::{UnnamedRequirement, VerbatimUrl};
|
||||
use pep508_rs::{UnnamedRequirement, UnnamedRequirementUrl};
|
||||
use pypi_types::VerbatimParsedUrl;
|
||||
use requirements_txt::{
|
||||
EditableRequirement, FindLink, RequirementEntry, RequirementsTxt, RequirementsTxtRequirement,
|
||||
};
|
||||
|
@ -67,12 +68,12 @@ impl RequirementsSpecification {
|
|||
let requirement = RequirementsTxtRequirement::parse(name, std::env::current_dir()?)
|
||||
.with_context(|| format!("Failed to parse: `{name}`"))?;
|
||||
Self {
|
||||
requirements: vec![UnresolvedRequirementSpecification::try_from(
|
||||
requirements: vec![UnresolvedRequirementSpecification::from(
|
||||
RequirementEntry {
|
||||
requirement,
|
||||
hashes: vec![],
|
||||
},
|
||||
)?],
|
||||
)],
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
|
@ -96,8 +97,8 @@ impl RequirementsSpecification {
|
|||
constraints: requirements_txt
|
||||
.constraints
|
||||
.into_iter()
|
||||
.map(Requirement::from_pep508)
|
||||
.collect::<Result<_, _>>()?,
|
||||
.map(Requirement::from)
|
||||
.collect(),
|
||||
editables: requirements_txt.editables,
|
||||
index_url: requirements_txt.index_url.map(IndexUrl::from),
|
||||
extra_index_urls: requirements_txt
|
||||
|
@ -132,7 +133,7 @@ impl RequirementsSpecification {
|
|||
project: None,
|
||||
requirements: vec![UnresolvedRequirementSpecification {
|
||||
requirement: UnresolvedRequirement::Unnamed(UnnamedRequirement {
|
||||
url: VerbatimUrl::from_path(path)?,
|
||||
url: VerbatimParsedUrl::parse_absolute_path(path)?,
|
||||
extras: vec![],
|
||||
marker: None,
|
||||
origin: None,
|
||||
|
|
|
@ -11,12 +11,12 @@ use tracing::debug;
|
|||
|
||||
use distribution_filename::{SourceDistFilename, WheelFilename};
|
||||
use distribution_types::{
|
||||
BuildableSource, DirectSourceUrl, DirectorySourceUrl, GitSourceUrl, ParsedGitUrl,
|
||||
PathSourceUrl, RemoteSource, Requirement, SourceUrl, UnresolvedRequirement,
|
||||
BuildableSource, DirectSourceUrl, DirectorySourceUrl, GitSourceUrl, PathSourceUrl,
|
||||
RemoteSource, Requirement, SourceUrl, UnresolvedRequirement,
|
||||
UnresolvedRequirementSpecification, VersionId,
|
||||
};
|
||||
use pep508_rs::{Scheme, UnnamedRequirement, VersionOrUrl};
|
||||
use pypi_types::Metadata10;
|
||||
use pep508_rs::{UnnamedRequirement, VersionOrUrl};
|
||||
use pypi_types::{Metadata10, ParsedUrl, VerbatimParsedUrl};
|
||||
use uv_distribution::{DistributionDatabase, Reporter};
|
||||
use uv_normalize::PackageName;
|
||||
use uv_resolver::{InMemoryIndex, MetadataResponse};
|
||||
|
@ -72,9 +72,9 @@ impl<'a, Context: BuildContext> NamedRequirementsResolver<'a, Context> {
|
|||
.map(|entry| async {
|
||||
match entry.requirement {
|
||||
UnresolvedRequirement::Named(requirement) => Ok(requirement),
|
||||
UnresolvedRequirement::Unnamed(requirement) => Ok(Requirement::from_pep508(
|
||||
UnresolvedRequirement::Unnamed(requirement) => Ok(Requirement::from(
|
||||
Self::resolve_requirement(requirement, hasher, index, &database).await?,
|
||||
)?),
|
||||
)),
|
||||
}
|
||||
})
|
||||
.collect::<FuturesOrdered<_>>()
|
||||
|
@ -84,19 +84,19 @@ impl<'a, Context: BuildContext> NamedRequirementsResolver<'a, Context> {
|
|||
|
||||
/// Infer the package name for a given "unnamed" requirement.
|
||||
async fn resolve_requirement(
|
||||
requirement: UnnamedRequirement,
|
||||
requirement: UnnamedRequirement<VerbatimParsedUrl>,
|
||||
hasher: &HashStrategy,
|
||||
index: &InMemoryIndex,
|
||||
database: &DistributionDatabase<'a, Context>,
|
||||
) -> Result<pep508_rs::Requirement> {
|
||||
) -> Result<pep508_rs::Requirement<VerbatimParsedUrl>> {
|
||||
// If the requirement is a wheel, extract the package name from the wheel filename.
|
||||
//
|
||||
// Ex) `anyio-4.3.0-py3-none-any.whl`
|
||||
if Path::new(requirement.url.path())
|
||||
if Path::new(requirement.url.verbatim.path())
|
||||
.extension()
|
||||
.is_some_and(|ext| ext.eq_ignore_ascii_case("whl"))
|
||||
{
|
||||
let filename = WheelFilename::from_str(&requirement.url.filename()?)?;
|
||||
let filename = WheelFilename::from_str(&requirement.url.verbatim.filename()?)?;
|
||||
return Ok(pep508_rs::Requirement {
|
||||
name: filename.name,
|
||||
extras: requirement.extras,
|
||||
|
@ -112,6 +112,7 @@ impl<'a, Context: BuildContext> NamedRequirementsResolver<'a, Context> {
|
|||
// Ex) `anyio-4.3.0.tar.gz`
|
||||
if let Some(filename) = requirement
|
||||
.url
|
||||
.verbatim
|
||||
.filename()
|
||||
.ok()
|
||||
.and_then(|filename| SourceDistFilename::parsed_normalized_filename(&filename).ok())
|
||||
|
@ -125,23 +126,17 @@ impl<'a, Context: BuildContext> NamedRequirementsResolver<'a, Context> {
|
|||
});
|
||||
}
|
||||
|
||||
let source = match Scheme::parse(requirement.url.scheme()) {
|
||||
Some(Scheme::File) => {
|
||||
let path = requirement
|
||||
.url
|
||||
.to_file_path()
|
||||
.expect("URL to be a file path");
|
||||
|
||||
let source = match &requirement.url.parsed_url {
|
||||
// If the path points to a directory, attempt to read the name from static metadata.
|
||||
if path.is_dir() {
|
||||
ParsedUrl::Path(parsed_path_url) if parsed_path_url.path.is_dir() => {
|
||||
// Attempt to read a `PKG-INFO` from the directory.
|
||||
if let Some(metadata) = fs_err::read(path.join("PKG-INFO"))
|
||||
if let Some(metadata) = fs_err::read(parsed_path_url.path.join("PKG-INFO"))
|
||||
.ok()
|
||||
.and_then(|contents| Metadata10::parse_pkg_info(&contents).ok())
|
||||
{
|
||||
debug!(
|
||||
"Found PKG-INFO metadata for {path} ({name})",
|
||||
path = path.display(),
|
||||
path = parsed_path_url.path.display(),
|
||||
name = metadata.name
|
||||
);
|
||||
return Ok(pep508_rs::Requirement {
|
||||
|
@ -154,7 +149,7 @@ impl<'a, Context: BuildContext> NamedRequirementsResolver<'a, Context> {
|
|||
}
|
||||
|
||||
// Attempt to read a `pyproject.toml` file.
|
||||
let project_path = path.join("pyproject.toml");
|
||||
let project_path = parsed_path_url.path.join("pyproject.toml");
|
||||
if let Some(pyproject) = fs_err::read_to_string(project_path)
|
||||
.ok()
|
||||
.and_then(|contents| toml::from_str::<PyProjectToml>(&contents).ok())
|
||||
|
@ -163,7 +158,7 @@ impl<'a, Context: BuildContext> NamedRequirementsResolver<'a, Context> {
|
|||
if let Some(project) = pyproject.project {
|
||||
debug!(
|
||||
"Found PEP 621 metadata for {path} in `pyproject.toml` ({name})",
|
||||
path = path.display(),
|
||||
path = parsed_path_url.path.display(),
|
||||
name = project.name
|
||||
);
|
||||
return Ok(pep508_rs::Requirement {
|
||||
|
@ -181,7 +176,7 @@ impl<'a, Context: BuildContext> NamedRequirementsResolver<'a, Context> {
|
|||
if let Some(name) = poetry.name {
|
||||
debug!(
|
||||
"Found Poetry metadata for {path} in `pyproject.toml` ({name})",
|
||||
path = path.display(),
|
||||
path = parsed_path_url.path.display(),
|
||||
name = name
|
||||
);
|
||||
return Ok(pep508_rs::Requirement {
|
||||
|
@ -197,7 +192,8 @@ impl<'a, Context: BuildContext> NamedRequirementsResolver<'a, Context> {
|
|||
}
|
||||
|
||||
// Attempt to read a `setup.cfg` from the directory.
|
||||
if let Some(setup_cfg) = fs_err::read_to_string(path.join("setup.cfg"))
|
||||
if let Some(setup_cfg) =
|
||||
fs_err::read_to_string(parsed_path_url.path.join("setup.cfg"))
|
||||
.ok()
|
||||
.and_then(|contents| {
|
||||
let mut ini = Ini::new_cs();
|
||||
|
@ -210,7 +206,7 @@ impl<'a, Context: BuildContext> NamedRequirementsResolver<'a, Context> {
|
|||
if let Ok(name) = PackageName::from_str(name) {
|
||||
debug!(
|
||||
"Found setuptools metadata for {path} in `setup.cfg` ({name})",
|
||||
path = path.display(),
|
||||
path = parsed_path_url.path.display(),
|
||||
name = name
|
||||
);
|
||||
return Ok(pep508_rs::Requirement {
|
||||
|
@ -226,33 +222,23 @@ impl<'a, Context: BuildContext> NamedRequirementsResolver<'a, Context> {
|
|||
}
|
||||
|
||||
SourceUrl::Directory(DirectorySourceUrl {
|
||||
url: &requirement.url,
|
||||
path: Cow::Owned(path),
|
||||
})
|
||||
} else {
|
||||
SourceUrl::Path(PathSourceUrl {
|
||||
url: &requirement.url,
|
||||
path: Cow::Owned(path),
|
||||
url: &requirement.url.verbatim,
|
||||
path: Cow::Borrowed(&parsed_path_url.path),
|
||||
})
|
||||
}
|
||||
}
|
||||
Some(Scheme::Http | Scheme::Https) => SourceUrl::Direct(DirectSourceUrl {
|
||||
url: &requirement.url,
|
||||
// If it's not a directory, assume it's a file.
|
||||
ParsedUrl::Path(parsed_path_url) => SourceUrl::Path(PathSourceUrl {
|
||||
url: &requirement.url.verbatim,
|
||||
path: Cow::Borrowed(&parsed_path_url.path),
|
||||
}),
|
||||
ParsedUrl::Archive(parsed_archive_url) => SourceUrl::Direct(DirectSourceUrl {
|
||||
url: &parsed_archive_url.url,
|
||||
}),
|
||||
ParsedUrl::Git(parsed_git_url) => SourceUrl::Git(GitSourceUrl {
|
||||
url: &requirement.url.verbatim,
|
||||
git: &parsed_git_url.url,
|
||||
subdirectory: parsed_git_url.subdirectory.as_deref(),
|
||||
}),
|
||||
Some(Scheme::GitSsh | Scheme::GitHttps | Scheme::GitHttp) => {
|
||||
let git = ParsedGitUrl::try_from(requirement.url.to_url())?;
|
||||
SourceUrl::Git(GitSourceUrl {
|
||||
git: Cow::Owned(git.url),
|
||||
subdirectory: git.subdirectory.map(Cow::Owned),
|
||||
url: &requirement.url,
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Unsupported scheme for unnamed requirement: {}",
|
||||
requirement.url
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
// Fetch the metadata for the distribution.
|
||||
|
|
|
@ -9,7 +9,7 @@ use pubgrub::report::{DefaultStringReporter, DerivationTree, External, Reporter}
|
|||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use dashmap::DashMap;
|
||||
use distribution_types::{BuiltDist, IndexLocations, InstalledDist, ParsedUrlError, SourceDist};
|
||||
use distribution_types::{BuiltDist, IndexLocations, InstalledDist, SourceDist};
|
||||
use pep440_rs::Version;
|
||||
use pep508_rs::Requirement;
|
||||
use uv_normalize::PackageName;
|
||||
|
@ -96,10 +96,6 @@ pub enum ResolveError {
|
|||
#[error("In `--require-hashes` mode, all requirements must be pinned upfront with `==`, but found: `{0}`")]
|
||||
UnhashedPackage(PackageName),
|
||||
|
||||
// TODO(konsti): Attach the distribution that contained the invalid requirement as error source.
|
||||
#[error("Failed to parse requirements")]
|
||||
DirectUrl(#[from] Box<ParsedUrlError>),
|
||||
|
||||
/// Something unexpected happened.
|
||||
#[error("{0}")]
|
||||
Failure(String),
|
||||
|
|
|
@ -12,14 +12,13 @@ use url::Url;
|
|||
use distribution_filename::WheelFilename;
|
||||
use distribution_types::{
|
||||
BuiltDist, DirectUrlBuiltDist, DirectUrlSourceDist, DirectorySourceDist, Dist, FileLocation,
|
||||
GitSourceDist, IndexUrl, ParsedArchiveUrl, ParsedGitUrl, PathBuiltDist, PathSourceDist,
|
||||
RegistryBuiltDist, RegistryBuiltWheel, RegistrySourceDist, RemoteSource, Resolution,
|
||||
ResolvedDist, ToUrlError,
|
||||
GitSourceDist, IndexUrl, PathBuiltDist, PathSourceDist, RegistryBuiltDist, RegistryBuiltWheel,
|
||||
RegistrySourceDist, RemoteSource, Resolution, ResolvedDist, ToUrlError,
|
||||
};
|
||||
use pep440_rs::Version;
|
||||
use pep508_rs::{MarkerEnvironment, VerbatimUrl};
|
||||
use platform_tags::{TagCompatibility, TagPriority, Tags};
|
||||
use pypi_types::HashDigest;
|
||||
use pypi_types::{HashDigest, ParsedArchiveUrl, ParsedGitUrl};
|
||||
use uv_git::{GitReference, GitSha};
|
||||
use uv_normalize::PackageName;
|
||||
|
||||
|
|
|
@ -4,21 +4,19 @@ use std::sync::Arc;
|
|||
use rustc_hash::FxHashMap;
|
||||
use tracing::trace;
|
||||
|
||||
use distribution_types::{ParsedUrlError, Requirement, RequirementSource};
|
||||
use distribution_types::{Requirement, RequirementSource};
|
||||
use pep440_rs::{Operator, Version};
|
||||
use pep508_rs::{MarkerEnvironment, UnnamedRequirement};
|
||||
use pypi_types::{HashDigest, HashError};
|
||||
use pypi_types::{HashDigest, HashError, VerbatimParsedUrl};
|
||||
use requirements_txt::{RequirementEntry, RequirementsTxtRequirement};
|
||||
use uv_normalize::PackageName;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum PreferenceError {
|
||||
#[error("direct URL requirements without package names are not supported: `{0}`")]
|
||||
Bare(UnnamedRequirement),
|
||||
Bare(UnnamedRequirement<VerbatimParsedUrl>),
|
||||
#[error(transparent)]
|
||||
Hash(#[from] HashError),
|
||||
#[error(transparent)]
|
||||
ParsedUrl(#[from] Box<ParsedUrlError>),
|
||||
}
|
||||
|
||||
/// A pinned requirement, as extracted from a `requirements.txt` file.
|
||||
|
@ -33,9 +31,7 @@ impl Preference {
|
|||
pub fn from_entry(entry: RequirementEntry) -> Result<Self, PreferenceError> {
|
||||
Ok(Self {
|
||||
requirement: match entry.requirement {
|
||||
RequirementsTxtRequirement::Named(requirement) => {
|
||||
Requirement::from_pep508(requirement)?
|
||||
}
|
||||
RequirementsTxtRequirement::Named(requirement) => Requirement::from(requirement),
|
||||
RequirementsTxtRequirement::Unnamed(requirement) => {
|
||||
return Err(PreferenceError::Bare(requirement));
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use distribution_types::{DistributionMetadata, Name, VerbatimParsedUrl, VersionOrUrlRef};
|
||||
use distribution_types::{DistributionMetadata, Name, VersionOrUrlRef};
|
||||
use pep440_rs::Version;
|
||||
use pypi_types::VerbatimParsedUrl;
|
||||
use uv_normalize::PackageName;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use distribution_types::VerbatimParsedUrl;
|
||||
use pep508_rs::MarkerTree;
|
||||
use pypi_types::VerbatimParsedUrl;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use url::Url;
|
||||
|
||||
use distribution_types::{ParsedGitUrl, ParsedUrl, VerbatimParsedUrl};
|
||||
use pep508_rs::VerbatimUrl;
|
||||
use pypi_types::{ParsedGitUrl, ParsedUrl, VerbatimParsedUrl};
|
||||
use uv_distribution::git_url_to_precise;
|
||||
use uv_git::GitReference;
|
||||
|
||||
|
|
|
@ -7,12 +7,12 @@ use pubgrub::type_aliases::SelectedDependencies;
|
|||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use distribution_types::{
|
||||
Dist, DistributionMetadata, Name, ParsedUrlError, Requirement, ResolutionDiagnostic,
|
||||
ResolvedDist, VersionId, VersionOrUrlRef,
|
||||
Dist, DistributionMetadata, Name, Requirement, ResolutionDiagnostic, ResolvedDist, VersionId,
|
||||
VersionOrUrlRef,
|
||||
};
|
||||
use pep440_rs::{Version, VersionSpecifier};
|
||||
use pep508_rs::MarkerEnvironment;
|
||||
use pypi_types::Yanked;
|
||||
use pypi_types::{ParsedUrlError, Yanked};
|
||||
use uv_normalize::PackageName;
|
||||
|
||||
use crate::dependency_provider::UvDependencyProvider;
|
||||
|
@ -512,8 +512,8 @@ impl ResolutionGraph {
|
|||
.requires_dist
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(Requirement::from_pep508)
|
||||
.collect::<anyhow::Result<_, _>>()?;
|
||||
.map(Requirement::from)
|
||||
.collect();
|
||||
for req in manifest.apply(requirements.iter()) {
|
||||
let Some(ref marker_tree) = req.marker else {
|
||||
continue;
|
||||
|
|
|
@ -203,9 +203,10 @@ mod tests {
|
|||
use anyhow::Result;
|
||||
use url::Url;
|
||||
|
||||
use distribution_types::{ParsedUrl, RequirementSource};
|
||||
use distribution_types::RequirementSource;
|
||||
use pep440_rs::{Operator, Version, VersionSpecifier, VersionSpecifiers};
|
||||
use pep508_rs::VerbatimUrl;
|
||||
use pypi_types::ParsedUrl;
|
||||
|
||||
use crate::resolver::locals::{iter_locals, Locals};
|
||||
|
||||
|
|
|
@ -1063,8 +1063,8 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
.requires_dist
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(Requirement::from_pep508)
|
||||
.collect::<Result<_, _>>()?;
|
||||
.map(Requirement::from)
|
||||
.collect();
|
||||
let dependencies = PubGrubDependencies::from_requirements(
|
||||
&requirements,
|
||||
&self.constraints,
|
||||
|
@ -1170,8 +1170,8 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
.requires_dist
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(Requirement::from_pep508)
|
||||
.collect::<Result<_, _>>()?;
|
||||
.map(Requirement::from)
|
||||
.collect();
|
||||
let dependencies = PubGrubDependencies::from_requirements(
|
||||
&requirements,
|
||||
&self.constraints,
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
use distribution_types::{RequirementSource, Verbatim};
|
||||
use rustc_hash::FxHashMap;
|
||||
use tracing::debug;
|
||||
|
||||
use distribution_types::{
|
||||
ParsedArchiveUrl, ParsedGitUrl, ParsedPathUrl, ParsedUrl, RequirementSource, Verbatim,
|
||||
VerbatimParsedUrl,
|
||||
};
|
||||
use pep508_rs::{MarkerEnvironment, VerbatimUrl};
|
||||
use pypi_types::{ParsedArchiveUrl, ParsedGitUrl, ParsedPathUrl, ParsedUrl, VerbatimParsedUrl};
|
||||
use uv_distribution::is_same_reference;
|
||||
use uv_git::GitUrl;
|
||||
use uv_normalize::PackageName;
|
||||
|
|
|
@ -167,10 +167,9 @@ macro_rules! assert_snapshot {
|
|||
|
||||
#[tokio::test]
|
||||
async fn black() -> Result<()> {
|
||||
let manifest = Manifest::simple(vec![Requirement::from_pep508(
|
||||
let manifest = Manifest::simple(vec![Requirement::from(
|
||||
pep508_rs::Requirement::from_str("black<=23.9.1").unwrap(),
|
||||
)
|
||||
.unwrap()]);
|
||||
)]);
|
||||
let options = OptionsBuilder::new()
|
||||
.exclude_newer(Some(*EXCLUDE_NEWER))
|
||||
.build();
|
||||
|
@ -196,10 +195,9 @@ async fn black() -> Result<()> {
|
|||
|
||||
#[tokio::test]
|
||||
async fn black_colorama() -> Result<()> {
|
||||
let manifest = Manifest::simple(vec![Requirement::from_pep508(
|
||||
let manifest = Manifest::simple(vec![Requirement::from(
|
||||
pep508_rs::Requirement::from_str("black[colorama]<=23.9.1").unwrap(),
|
||||
)
|
||||
.unwrap()]);
|
||||
)]);
|
||||
let options = OptionsBuilder::new()
|
||||
.exclude_newer(Some(*EXCLUDE_NEWER))
|
||||
.build();
|
||||
|
@ -228,10 +226,9 @@ async fn black_colorama() -> Result<()> {
|
|||
/// Resolve Black with an invalid extra. The resolver should ignore the extra.
|
||||
#[tokio::test]
|
||||
async fn black_tensorboard() -> Result<()> {
|
||||
let manifest = Manifest::simple(vec![Requirement::from_pep508(
|
||||
let manifest = Manifest::simple(vec![Requirement::from(
|
||||
pep508_rs::Requirement::from_str("black[tensorboard]<=23.9.1").unwrap(),
|
||||
)
|
||||
.unwrap()]);
|
||||
)]);
|
||||
let options = OptionsBuilder::new()
|
||||
.exclude_newer(Some(*EXCLUDE_NEWER))
|
||||
.build();
|
||||
|
@ -257,10 +254,9 @@ async fn black_tensorboard() -> Result<()> {
|
|||
|
||||
#[tokio::test]
|
||||
async fn black_python_310() -> Result<()> {
|
||||
let manifest = Manifest::simple(vec![Requirement::from_pep508(
|
||||
let manifest = Manifest::simple(vec![Requirement::from(
|
||||
pep508_rs::Requirement::from_str("black<=23.9.1").unwrap(),
|
||||
)
|
||||
.unwrap()]);
|
||||
)]);
|
||||
let options = OptionsBuilder::new()
|
||||
.exclude_newer(Some(*EXCLUDE_NEWER))
|
||||
.build();
|
||||
|
@ -293,14 +289,12 @@ async fn black_python_310() -> Result<()> {
|
|||
#[tokio::test]
|
||||
async fn black_mypy_extensions() -> Result<()> {
|
||||
let manifest = Manifest::new(
|
||||
vec![
|
||||
Requirement::from_pep508(pep508_rs::Requirement::from_str("black<=23.9.1").unwrap())
|
||||
.unwrap(),
|
||||
],
|
||||
Constraints::from_requirements(vec![Requirement::from_pep508(
|
||||
vec![Requirement::from(
|
||||
pep508_rs::Requirement::from_str("black<=23.9.1").unwrap(),
|
||||
)],
|
||||
Constraints::from_requirements(vec![Requirement::from(
|
||||
pep508_rs::Requirement::from_str("mypy-extensions<0.4.4").unwrap(),
|
||||
)
|
||||
.unwrap()]),
|
||||
)]),
|
||||
Overrides::default(),
|
||||
vec![],
|
||||
None,
|
||||
|
@ -336,14 +330,12 @@ async fn black_mypy_extensions() -> Result<()> {
|
|||
#[tokio::test]
|
||||
async fn black_mypy_extensions_extra() -> Result<()> {
|
||||
let manifest = Manifest::new(
|
||||
vec![
|
||||
Requirement::from_pep508(pep508_rs::Requirement::from_str("black<=23.9.1").unwrap())
|
||||
.unwrap(),
|
||||
],
|
||||
Constraints::from_requirements(vec![Requirement::from_pep508(
|
||||
vec![Requirement::from(
|
||||
pep508_rs::Requirement::from_str("black<=23.9.1").unwrap(),
|
||||
)],
|
||||
Constraints::from_requirements(vec![Requirement::from(
|
||||
pep508_rs::Requirement::from_str("mypy-extensions[extra]<0.4.4").unwrap(),
|
||||
)
|
||||
.unwrap()]),
|
||||
)]),
|
||||
Overrides::default(),
|
||||
vec![],
|
||||
None,
|
||||
|
@ -379,14 +371,12 @@ async fn black_mypy_extensions_extra() -> Result<()> {
|
|||
#[tokio::test]
|
||||
async fn black_flake8() -> Result<()> {
|
||||
let manifest = Manifest::new(
|
||||
vec![
|
||||
Requirement::from_pep508(pep508_rs::Requirement::from_str("black<=23.9.1").unwrap())
|
||||
.unwrap(),
|
||||
],
|
||||
Constraints::from_requirements(vec![Requirement::from_pep508(
|
||||
vec![Requirement::from(
|
||||
pep508_rs::Requirement::from_str("black<=23.9.1").unwrap(),
|
||||
)],
|
||||
Constraints::from_requirements(vec![Requirement::from(
|
||||
pep508_rs::Requirement::from_str("flake8<1").unwrap(),
|
||||
)
|
||||
.unwrap()]),
|
||||
)]),
|
||||
Overrides::default(),
|
||||
vec![],
|
||||
None,
|
||||
|
@ -419,10 +409,9 @@ async fn black_flake8() -> Result<()> {
|
|||
|
||||
#[tokio::test]
|
||||
async fn black_lowest() -> Result<()> {
|
||||
let manifest = Manifest::simple(vec![Requirement::from_pep508(
|
||||
let manifest = Manifest::simple(vec![Requirement::from(
|
||||
pep508_rs::Requirement::from_str("black>21").unwrap(),
|
||||
)
|
||||
.unwrap()]);
|
||||
)]);
|
||||
let options = OptionsBuilder::new()
|
||||
.resolution_mode(ResolutionMode::Lowest)
|
||||
.exclude_newer(Some(*EXCLUDE_NEWER))
|
||||
|
@ -449,10 +438,9 @@ async fn black_lowest() -> Result<()> {
|
|||
|
||||
#[tokio::test]
|
||||
async fn black_lowest_direct() -> Result<()> {
|
||||
let manifest = Manifest::simple(vec![Requirement::from_pep508(
|
||||
let manifest = Manifest::simple(vec![Requirement::from(
|
||||
pep508_rs::Requirement::from_str("black>21").unwrap(),
|
||||
)
|
||||
.unwrap()]);
|
||||
)]);
|
||||
let options = OptionsBuilder::new()
|
||||
.resolution_mode(ResolutionMode::LowestDirect)
|
||||
.exclude_newer(Some(*EXCLUDE_NEWER))
|
||||
|
@ -480,12 +468,14 @@ async fn black_lowest_direct() -> Result<()> {
|
|||
#[tokio::test]
|
||||
async fn black_respect_preference() -> Result<()> {
|
||||
let manifest = Manifest::new(
|
||||
vec![Requirement::from_pep508(pep508_rs::Requirement::from_str("black<=23.9.1")?).unwrap()],
|
||||
vec![Requirement::from(pep508_rs::Requirement::from_str(
|
||||
"black<=23.9.1",
|
||||
)?)],
|
||||
Constraints::default(),
|
||||
Overrides::default(),
|
||||
vec![Preference::from_requirement(
|
||||
Requirement::from_pep508(pep508_rs::Requirement::from_str("black==23.9.0")?).unwrap(),
|
||||
)],
|
||||
vec![Preference::from_requirement(Requirement::from(
|
||||
pep508_rs::Requirement::from_str("black==23.9.0")?,
|
||||
))],
|
||||
None,
|
||||
vec![],
|
||||
Exclusions::default(),
|
||||
|
@ -518,12 +508,14 @@ async fn black_respect_preference() -> Result<()> {
|
|||
#[tokio::test]
|
||||
async fn black_ignore_preference() -> Result<()> {
|
||||
let manifest = Manifest::new(
|
||||
vec![Requirement::from_pep508(pep508_rs::Requirement::from_str("black<=23.9.1")?).unwrap()],
|
||||
vec![Requirement::from(pep508_rs::Requirement::from_str(
|
||||
"black<=23.9.1",
|
||||
)?)],
|
||||
Constraints::default(),
|
||||
Overrides::default(),
|
||||
vec![Preference::from_requirement(
|
||||
Requirement::from_pep508(pep508_rs::Requirement::from_str("black==23.9.2")?).unwrap(),
|
||||
)],
|
||||
vec![Preference::from_requirement(Requirement::from(
|
||||
pep508_rs::Requirement::from_str("black==23.9.2")?,
|
||||
))],
|
||||
None,
|
||||
vec![],
|
||||
Exclusions::default(),
|
||||
|
@ -554,10 +546,9 @@ async fn black_ignore_preference() -> Result<()> {
|
|||
|
||||
#[tokio::test]
|
||||
async fn black_disallow_prerelease() -> Result<()> {
|
||||
let manifest = Manifest::simple(vec![Requirement::from_pep508(
|
||||
let manifest = Manifest::simple(vec![Requirement::from(
|
||||
pep508_rs::Requirement::from_str("black<=20.0").unwrap(),
|
||||
)
|
||||
.unwrap()]);
|
||||
)]);
|
||||
let options = OptionsBuilder::new()
|
||||
.prerelease_mode(PreReleaseMode::Disallow)
|
||||
.exclude_newer(Some(*EXCLUDE_NEWER))
|
||||
|
@ -578,10 +569,9 @@ async fn black_disallow_prerelease() -> Result<()> {
|
|||
|
||||
#[tokio::test]
|
||||
async fn black_allow_prerelease_if_necessary() -> Result<()> {
|
||||
let manifest = Manifest::simple(vec![Requirement::from_pep508(
|
||||
let manifest = Manifest::simple(vec![Requirement::from(
|
||||
pep508_rs::Requirement::from_str("black<=20.0").unwrap(),
|
||||
)
|
||||
.unwrap()]);
|
||||
)]);
|
||||
let options = OptionsBuilder::new()
|
||||
.prerelease_mode(PreReleaseMode::IfNecessary)
|
||||
.exclude_newer(Some(*EXCLUDE_NEWER))
|
||||
|
@ -602,10 +592,9 @@ async fn black_allow_prerelease_if_necessary() -> Result<()> {
|
|||
|
||||
#[tokio::test]
|
||||
async fn pylint_disallow_prerelease() -> Result<()> {
|
||||
let manifest = Manifest::simple(vec![Requirement::from_pep508(
|
||||
let manifest = Manifest::simple(vec![Requirement::from(
|
||||
pep508_rs::Requirement::from_str("pylint==2.3.0").unwrap(),
|
||||
)
|
||||
.unwrap()]);
|
||||
)]);
|
||||
let options = OptionsBuilder::new()
|
||||
.prerelease_mode(PreReleaseMode::Disallow)
|
||||
.exclude_newer(Some(*EXCLUDE_NEWER))
|
||||
|
@ -628,10 +617,9 @@ async fn pylint_disallow_prerelease() -> Result<()> {
|
|||
|
||||
#[tokio::test]
|
||||
async fn pylint_allow_prerelease() -> Result<()> {
|
||||
let manifest = Manifest::simple(vec![Requirement::from_pep508(
|
||||
let manifest = Manifest::simple(vec![Requirement::from(
|
||||
pep508_rs::Requirement::from_str("pylint==2.3.0").unwrap(),
|
||||
)
|
||||
.unwrap()]);
|
||||
)]);
|
||||
let options = OptionsBuilder::new()
|
||||
.prerelease_mode(PreReleaseMode::Allow)
|
||||
.exclude_newer(Some(*EXCLUDE_NEWER))
|
||||
|
@ -655,10 +643,8 @@ async fn pylint_allow_prerelease() -> Result<()> {
|
|||
#[tokio::test]
|
||||
async fn pylint_allow_explicit_prerelease_without_marker() -> Result<()> {
|
||||
let manifest = Manifest::simple(vec![
|
||||
Requirement::from_pep508(pep508_rs::Requirement::from_str("pylint==2.3.0").unwrap())
|
||||
.unwrap(),
|
||||
Requirement::from_pep508(pep508_rs::Requirement::from_str("isort>=5.0.0").unwrap())
|
||||
.unwrap(),
|
||||
Requirement::from(pep508_rs::Requirement::from_str("pylint==2.3.0").unwrap()),
|
||||
Requirement::from(pep508_rs::Requirement::from_str("isort>=5.0.0").unwrap()),
|
||||
]);
|
||||
let options = OptionsBuilder::new()
|
||||
.prerelease_mode(PreReleaseMode::Explicit)
|
||||
|
@ -683,10 +669,8 @@ async fn pylint_allow_explicit_prerelease_without_marker() -> Result<()> {
|
|||
#[tokio::test]
|
||||
async fn pylint_allow_explicit_prerelease_with_marker() -> Result<()> {
|
||||
let manifest = Manifest::simple(vec![
|
||||
Requirement::from_pep508(pep508_rs::Requirement::from_str("pylint==2.3.0").unwrap())
|
||||
.unwrap(),
|
||||
Requirement::from_pep508(pep508_rs::Requirement::from_str("isort>=5.0.0b").unwrap())
|
||||
.unwrap(),
|
||||
Requirement::from(pep508_rs::Requirement::from_str("pylint==2.3.0").unwrap()),
|
||||
Requirement::from(pep508_rs::Requirement::from_str("isort>=5.0.0b").unwrap()),
|
||||
]);
|
||||
let options = OptionsBuilder::new()
|
||||
.prerelease_mode(PreReleaseMode::Explicit)
|
||||
|
@ -712,10 +696,9 @@ async fn pylint_allow_explicit_prerelease_with_marker() -> Result<()> {
|
|||
/// fail with a pre-release-centric hint.
|
||||
#[tokio::test]
|
||||
async fn msgraph_sdk() -> Result<()> {
|
||||
let manifest = Manifest::simple(vec![Requirement::from_pep508(
|
||||
let manifest = Manifest::simple(vec![Requirement::from(
|
||||
pep508_rs::Requirement::from_str("msgraph-sdk==1.0.0").unwrap(),
|
||||
)
|
||||
.unwrap()]);
|
||||
)]);
|
||||
let options = OptionsBuilder::new()
|
||||
.exclude_newer(Some(*EXCLUDE_NEWER))
|
||||
.build();
|
||||
|
|
|
@ -110,7 +110,7 @@ impl HashStrategy {
|
|||
}
|
||||
UnresolvedRequirement::Unnamed(requirement) => {
|
||||
// Direct URLs are always allowed.
|
||||
PackageId::from_url(&requirement.url)
|
||||
PackageId::from_url(&requirement.url.verbatim)
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ install-wheel-rs = { workspace = true, features = ["clap"], default-features = f
|
|||
pep440_rs = { workspace = true }
|
||||
pep508_rs = { workspace = true }
|
||||
platform-tags = { workspace = true }
|
||||
pypi-types = { workspace = true }
|
||||
requirements-txt = { workspace = true, features = ["http"] }
|
||||
uv-auth = { workspace = true }
|
||||
uv-cache = { workspace = true, features = ["clap"] }
|
||||
|
|
|
@ -16,8 +16,7 @@ use tempfile::tempdir_in;
|
|||
use tracing::debug;
|
||||
|
||||
use distribution_types::{
|
||||
IndexLocations, LocalEditable, LocalEditables, ParsedUrlError, SourceAnnotation,
|
||||
SourceAnnotations, Verbatim,
|
||||
IndexLocations, LocalEditable, LocalEditables, SourceAnnotation, SourceAnnotations, Verbatim,
|
||||
};
|
||||
use distribution_types::{Requirement, Requirements};
|
||||
use install_wheel_rs::linker::LinkMode;
|
||||
|
@ -472,17 +471,17 @@ pub(crate) async fn pip_compile(
|
|||
.requires_dist
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(Requirement::from_pep508)
|
||||
.collect::<Result<_, _>>()?,
|
||||
.map(Requirement::from)
|
||||
.collect(),
|
||||
optional_dependencies: IndexMap::default(),
|
||||
};
|
||||
Ok::<_, Box<ParsedUrlError>>(BuiltEditableMetadata {
|
||||
BuiltEditableMetadata {
|
||||
built: built_editable.editable,
|
||||
metadata: built_editable.metadata,
|
||||
requirements,
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
.collect();
|
||||
|
||||
// Validate that the editables are compatible with the target Python version.
|
||||
for editable in &editables {
|
||||
|
|
|
@ -2,12 +2,12 @@ use std::borrow::Cow;
|
|||
use std::fmt::Write;
|
||||
|
||||
use anstream::eprint;
|
||||
use distribution_types::{IndexLocations, Resolution};
|
||||
use fs_err as fs;
|
||||
use itertools::Itertools;
|
||||
use owo_colors::OwoColorize;
|
||||
use tracing::{debug, enabled, Level};
|
||||
|
||||
use distribution_types::{IndexLocations, Resolution};
|
||||
use install_wheel_rs::linker::LinkMode;
|
||||
use platform_tags::Tags;
|
||||
use uv_auth::store_credentials_from_url;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
//! Common operations shared across the `pip` API and subcommands.
|
||||
|
||||
use pypi_types::{ParsedUrl, ParsedUrlError};
|
||||
use std::fmt::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
@ -14,7 +15,7 @@ use distribution_types::{
|
|||
};
|
||||
use distribution_types::{
|
||||
DistributionMetadata, IndexLocations, InstalledMetadata, InstalledVersion, LocalDist, Name,
|
||||
ParsedUrl, RequirementSource, Resolution,
|
||||
RequirementSource, Resolution,
|
||||
};
|
||||
use install_wheel_rs::linker::LinkMode;
|
||||
use pep440_rs::{VersionSpecifier, VersionSpecifiers};
|
||||
|
@ -177,7 +178,7 @@ pub(crate) async fn resolve<InstalledPackages: InstalledPackagesProvider>(
|
|||
let python_requirement = PythonRequirement::from_marker_environment(interpreter, markers);
|
||||
|
||||
// Map the editables to their metadata.
|
||||
let editables = editables.as_metadata().map_err(Error::ParsedUrl)?;
|
||||
let editables = editables.as_metadata();
|
||||
|
||||
// Determine any lookahead requirements.
|
||||
let lookaheads = match options.dependency_mode {
|
||||
|
@ -769,12 +770,9 @@ pub(crate) enum Error {
|
|||
#[error(transparent)]
|
||||
Lookahead(#[from] uv_requirements::LookaheadError),
|
||||
|
||||
#[error(transparent)]
|
||||
ParsedUrl(Box<distribution_types::ParsedUrlError>),
|
||||
|
||||
#[error(transparent)]
|
||||
Anyhow(#[from] anyhow::Error),
|
||||
|
||||
#[error("Installed distribution has unsupported type")]
|
||||
UnsupportedInstalledDist(#[source] Box<distribution_types::ParsedUrlError>),
|
||||
UnsupportedInstalledDist(#[source] Box<ParsedUrlError>),
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ use tracing::debug;
|
|||
|
||||
use distribution_types::{InstalledMetadata, Name, Requirement, UnresolvedRequirement};
|
||||
use pep508_rs::UnnamedRequirement;
|
||||
use pypi_types::VerbatimParsedUrl;
|
||||
use uv_cache::Cache;
|
||||
use uv_client::{BaseClientBuilder, Connectivity};
|
||||
use uv_configuration::{KeyringProviderType, PreviewMode};
|
||||
|
@ -94,7 +95,7 @@ pub(crate) async fn pip_uninstall(
|
|||
let site_packages = uv_installer::SitePackages::from_executable(&venv)?;
|
||||
|
||||
// Partition the requirements into named and unnamed requirements.
|
||||
let (named, unnamed): (Vec<Requirement>, Vec<UnnamedRequirement>) = spec
|
||||
let (named, unnamed): (Vec<Requirement>, Vec<UnnamedRequirement<VerbatimParsedUrl>>) = spec
|
||||
.requirements
|
||||
.into_iter()
|
||||
.partition_map(|entry| match entry.requirement {
|
||||
|
@ -118,7 +119,7 @@ pub(crate) async fn pip_uninstall(
|
|||
let urls = {
|
||||
let mut urls = unnamed
|
||||
.into_iter()
|
||||
.map(|requirement| requirement.url.to_url())
|
||||
.map(|requirement| requirement.url.verbatim.to_url())
|
||||
.collect::<Vec<_>>();
|
||||
urls.sort_unstable();
|
||||
urls.dedup();
|
||||
|
|
|
@ -225,16 +225,14 @@ async fn venv_impl(
|
|||
let requirements = if interpreter.python_tuple() < (3, 12) {
|
||||
// Only include `setuptools` and `wheel` on Python <3.12
|
||||
vec![
|
||||
Requirement::from_pep508(pep508_rs::Requirement::from_str("pip").unwrap()).unwrap(),
|
||||
Requirement::from_pep508(pep508_rs::Requirement::from_str("setuptools").unwrap())
|
||||
.unwrap(),
|
||||
Requirement::from_pep508(pep508_rs::Requirement::from_str("wheel").unwrap())
|
||||
.unwrap(),
|
||||
Requirement::from(pep508_rs::Requirement::from_str("pip").unwrap()),
|
||||
Requirement::from(pep508_rs::Requirement::from_str("setuptools").unwrap()),
|
||||
Requirement::from(pep508_rs::Requirement::from_str("wheel").unwrap()),
|
||||
]
|
||||
} else {
|
||||
vec![
|
||||
Requirement::from_pep508(pep508_rs::Requirement::from_str("pip").unwrap()).unwrap(),
|
||||
]
|
||||
vec![Requirement::from(
|
||||
pep508_rs::Requirement::from_str("pip").unwrap(),
|
||||
)]
|
||||
};
|
||||
|
||||
// Resolve and install the requirements.
|
||||
|
|
|
@ -6,7 +6,7 @@ use indexmap::IndexMap;
|
|||
use owo_colors::OwoColorize;
|
||||
|
||||
use distribution_types::{
|
||||
InstalledDist, LocalEditable, LocalEditables, Name, ParsedUrlError, Requirement, Requirements,
|
||||
InstalledDist, LocalEditable, LocalEditables, Name, Requirement, Requirements,
|
||||
};
|
||||
use platform_tags::Tags;
|
||||
use requirements_txt::EditableRequirement;
|
||||
|
@ -159,7 +159,7 @@ impl ResolvedEditables {
|
|||
})
|
||||
}
|
||||
|
||||
pub(crate) fn as_metadata(&self) -> Result<Vec<BuiltEditableMetadata>, Box<ParsedUrlError>> {
|
||||
pub(crate) fn as_metadata(&self) -> Vec<BuiltEditableMetadata> {
|
||||
self.iter()
|
||||
.map(|editable| {
|
||||
let dependencies: Vec<_> = editable
|
||||
|
@ -167,16 +167,16 @@ impl ResolvedEditables {
|
|||
.requires_dist
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(Requirement::from_pep508)
|
||||
.collect::<Result<_, _>>()?;
|
||||
Ok::<_, Box<ParsedUrlError>>(BuiltEditableMetadata {
|
||||
.map(Requirement::from)
|
||||
.collect();
|
||||
BuiltEditableMetadata {
|
||||
built: editable.local().clone(),
|
||||
metadata: editable.metadata().clone(),
|
||||
requirements: Requirements {
|
||||
dependencies,
|
||||
optional_dependencies: IndexMap::default(),
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
|
|
@ -5458,7 +5458,10 @@ fn unsupported_scheme() -> Result<()> {
|
|||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: Unsupported URL prefix `bzr` in URL: `bzr+https://example.com/anyio` (Bazaar is not supported)
|
||||
error: Couldn't parse requirement in `requirements.in` at position 0
|
||||
Caused by: Unsupported URL prefix `bzr` in URL: `bzr+https://example.com/anyio` (Bazaar is not supported)
|
||||
anyio @ bzr+https://example.com/anyio
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
"###
|
||||
);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue