mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-21 15:52:15 +00:00
Use local versions of PEP 440 and PEP 508 crates (#32)
This PR modifies the PEP 440 and PEP 508 crates to pass CI, primarily by fixing all lint violations. We're also now using these crates in the workspace via `path`. (Previously, we were still fetching them from Cargo.)
This commit is contained in:
parent
4fcdb3c045
commit
c8477991a9
22 changed files with 359 additions and 1596 deletions
|
@ -16,8 +16,6 @@
|
|||
#![deny(missing_docs)]
|
||||
|
||||
mod marker;
|
||||
#[cfg(feature = "modern")]
|
||||
pub mod modern;
|
||||
|
||||
pub use marker::{
|
||||
MarkerEnvironment, MarkerExpression, MarkerOperator, MarkerTree, MarkerValue,
|
||||
|
@ -125,8 +123,8 @@ create_exception!(
|
|||
);
|
||||
|
||||
/// A PEP 508 dependency specification
|
||||
#[cfg_attr(feature = "pyo3", pyclass(module = "pep508"))]
|
||||
#[derive(Hash, Debug, Clone, Eq, PartialEq)]
|
||||
#[cfg_attr(feature = "pyo3", pyclass(module = "pep508"))]
|
||||
pub struct Requirement {
|
||||
/// The distribution name such as `numpy` in
|
||||
/// `requests [security,tests] >= 2.8.1, == 2.8.* ; python_version > "3.8"`
|
||||
|
@ -159,18 +157,18 @@ impl Display for Requirement {
|
|||
}
|
||||
VersionOrUrl::Url(url) => {
|
||||
// We add the space for markers later if necessary
|
||||
write!(f, " @ {}", url)?;
|
||||
write!(f, " @ {url}")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(marker) = &self.marker {
|
||||
write!(f, " ; {}", marker)?;
|
||||
write!(f, " ; {marker}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// https://github.com/serde-rs/serde/issues/908#issuecomment-298027413
|
||||
/// <https://github.com/serde-rs/serde/issues/908#issuecomment-298027413>
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de> Deserialize<'de> for Requirement {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
|
@ -182,7 +180,7 @@ impl<'de> Deserialize<'de> for Requirement {
|
|||
}
|
||||
}
|
||||
|
||||
/// https://github.com/serde-rs/serde/issues/1316#issue-332908452
|
||||
/// <https://github.com/serde-rs/serde/issues/1316#issue-332908452>
|
||||
#[cfg(feature = "serde")]
|
||||
impl Serialize for Requirement {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
|
@ -214,7 +212,7 @@ impl Requirement {
|
|||
/// `requests [security,tests] >= 2.8.1, == 2.8.* ; python_version > "3.8"`
|
||||
#[getter]
|
||||
pub fn marker(&self) -> Option<String> {
|
||||
self.marker.as_ref().map(|m| m.to_string())
|
||||
self.marker.as_ref().map(std::string::ToString::to_string)
|
||||
}
|
||||
|
||||
/// Parses a PEP 440 string
|
||||
|
@ -241,7 +239,7 @@ impl Requirement {
|
|||
}
|
||||
|
||||
fn __repr__(&self) -> String {
|
||||
format!(r#""{}""#, self)
|
||||
format!(r#""{self}""#)
|
||||
}
|
||||
|
||||
fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult<bool> {
|
||||
|
@ -263,9 +261,10 @@ impl Requirement {
|
|||
}
|
||||
|
||||
/// Returns whether the markers apply for the given environment
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
#[pyo3(name = "evaluate_markers")]
|
||||
pub fn py_evaluate_markers(&self, env: &MarkerEnvironment, extras: Vec<String>) -> bool {
|
||||
self.evaluate_markers(env, extras)
|
||||
self.evaluate_markers(env, &extras)
|
||||
}
|
||||
|
||||
/// Returns whether the requirement would be satisfied, independent of environment markers, i.e.
|
||||
|
@ -274,6 +273,7 @@ impl Requirement {
|
|||
/// Note that unlike [Self::evaluate_markers] this does not perform any checks for bogus
|
||||
/// expressions but will simply return true. As caller you should separately perform a check
|
||||
/// with an environment and forward all warnings.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
#[pyo3(name = "evaluate_extras_and_python_version")]
|
||||
pub fn py_evaluate_extras_and_python_version(
|
||||
&self,
|
||||
|
@ -281,28 +281,29 @@ impl Requirement {
|
|||
python_versions: Vec<PyVersion>,
|
||||
) -> bool {
|
||||
self.evaluate_extras_and_python_version(
|
||||
extras,
|
||||
python_versions
|
||||
&extras,
|
||||
&python_versions
|
||||
.into_iter()
|
||||
.map(|py_version| py_version.0)
|
||||
.collect(),
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns whether the markers apply for the given environment
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
#[pyo3(name = "evaluate_markers_and_report")]
|
||||
pub fn py_evaluate_markers_and_report(
|
||||
&self,
|
||||
env: &MarkerEnvironment,
|
||||
extras: Vec<String>,
|
||||
) -> (bool, Vec<(MarkerWarningKind, String, String)>) {
|
||||
self.evaluate_markers_and_report(env, extras)
|
||||
self.evaluate_markers_and_report(env, &extras)
|
||||
}
|
||||
}
|
||||
|
||||
impl Requirement {
|
||||
/// Returns whether the markers apply for the given environment
|
||||
pub fn evaluate_markers(&self, env: &MarkerEnvironment, extras: Vec<String>) -> bool {
|
||||
pub fn evaluate_markers(&self, env: &MarkerEnvironment, extras: &[String]) -> bool {
|
||||
if let Some(marker) = &self.marker {
|
||||
marker.evaluate(
|
||||
env,
|
||||
|
@ -316,16 +317,16 @@ impl Requirement {
|
|||
/// Returns whether the requirement would be satisfied, independent of environment markers, i.e.
|
||||
/// if there is potentially an environment that could activate this requirement.
|
||||
///
|
||||
/// Note that unlike [Self::evaluate_markers] this does not perform any checks for bogus
|
||||
/// Note that unlike [`Self::evaluate_markers`] this does not perform any checks for bogus
|
||||
/// expressions but will simply return true. As caller you should separately perform a check
|
||||
/// with an environment and forward all warnings.
|
||||
pub fn evaluate_extras_and_python_version(
|
||||
&self,
|
||||
extras: HashSet<String>,
|
||||
python_versions: Vec<Version>,
|
||||
extras: &HashSet<String>,
|
||||
python_versions: &[Version],
|
||||
) -> bool {
|
||||
if let Some(marker) = &self.marker {
|
||||
marker.evaluate_extras_and_python_version(&extras, &python_versions)
|
||||
marker.evaluate_extras_and_python_version(extras, python_versions)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
@ -335,12 +336,15 @@ impl Requirement {
|
|||
pub fn evaluate_markers_and_report(
|
||||
&self,
|
||||
env: &MarkerEnvironment,
|
||||
extras: Vec<String>,
|
||||
extras: &[String],
|
||||
) -> (bool, Vec<(MarkerWarningKind, String, String)>) {
|
||||
if let Some(marker) = &self.marker {
|
||||
marker.evaluate_collect_warnings(
|
||||
env,
|
||||
&extras.iter().map(|x| x.as_str()).collect::<Vec<&str>>(),
|
||||
&extras
|
||||
.iter()
|
||||
.map(std::string::String::as_str)
|
||||
.collect::<Vec<&str>>(),
|
||||
)
|
||||
} else {
|
||||
(true, Vec::new())
|
||||
|
@ -448,11 +452,11 @@ impl<'a> CharIter<'a> {
|
|||
while let Some(char) = self.peek_char() {
|
||||
if !condition(char) {
|
||||
break;
|
||||
} else {
|
||||
substring.push(char);
|
||||
self.next();
|
||||
len += 1;
|
||||
}
|
||||
|
||||
substring.push(char);
|
||||
self.next();
|
||||
len += 1;
|
||||
}
|
||||
(substring, start, len)
|
||||
}
|
||||
|
@ -461,8 +465,7 @@ impl<'a> CharIter<'a> {
|
|||
match self.next() {
|
||||
None => Err(Pep508Error {
|
||||
message: Pep508ErrorSource::String(format!(
|
||||
"Expected '{}', found end of dependency specification",
|
||||
expected
|
||||
"Expected '{expected}', found end of dependency specification"
|
||||
)),
|
||||
start: span_start,
|
||||
len: 1,
|
||||
|
@ -471,8 +474,7 @@ impl<'a> CharIter<'a> {
|
|||
Some((_, value)) if value == expected => Ok(()),
|
||||
Some((pos, other)) => Err(Pep508Error {
|
||||
message: Pep508ErrorSource::String(format!(
|
||||
"Expected '{}', found '{}'",
|
||||
expected, other
|
||||
"Expected '{expected}', found '{other}'"
|
||||
)),
|
||||
start: pos,
|
||||
len: 1,
|
||||
|
@ -502,8 +504,7 @@ fn parse_name(chars: &mut CharIter) -> Result<String, Pep508Error> {
|
|||
} else {
|
||||
return Err(Pep508Error {
|
||||
message: Pep508ErrorSource::String(format!(
|
||||
"Expected package name starting with an alphanumeric character, found '{}'",
|
||||
char
|
||||
"Expected package name starting with an alphanumeric character, found '{char}'"
|
||||
)),
|
||||
start: index,
|
||||
len: 1,
|
||||
|
@ -528,8 +529,7 @@ fn parse_name(chars: &mut CharIter) -> Result<String, Pep508Error> {
|
|||
if chars.peek().is_none() && matches!(char, '.' | '-' | '_') {
|
||||
return Err(Pep508Error {
|
||||
message: Pep508ErrorSource::String(format!(
|
||||
"Package name must end with an alphanumeric character, not '{}'",
|
||||
char
|
||||
"Package name must end with an alphanumeric character, not '{char}'"
|
||||
)),
|
||||
start: index,
|
||||
len: 1,
|
||||
|
@ -544,9 +544,8 @@ fn parse_name(chars: &mut CharIter) -> Result<String, Pep508Error> {
|
|||
|
||||
/// parses extras in the `[extra1,extra2] format`
|
||||
fn parse_extras(chars: &mut CharIter) -> Result<Option<Vec<String>>, Pep508Error> {
|
||||
let bracket_pos = match chars.eat('[') {
|
||||
Some(pos) => pos,
|
||||
None => return Ok(None),
|
||||
let Some(bracket_pos) = chars.eat('[') else {
|
||||
return Ok(None);
|
||||
};
|
||||
let mut extras = Vec::new();
|
||||
|
||||
|
@ -568,14 +567,13 @@ fn parse_extras(chars: &mut CharIter) -> Result<Option<Vec<String>>, Pep508Error
|
|||
match chars.next() {
|
||||
// letterOrDigit
|
||||
Some((_, alphanumeric @ ('a'..='z' | 'A'..='Z' | '0'..='9'))) => {
|
||||
buffer.push(alphanumeric)
|
||||
buffer.push(alphanumeric);
|
||||
}
|
||||
Some((pos, other)) => {
|
||||
return Err(Pep508Error {
|
||||
message: Pep508ErrorSource::String(format!(
|
||||
"Expected an alphanumeric character starting the extra name, found '{}'",
|
||||
other
|
||||
)),
|
||||
"Expected an alphanumeric character starting the extra name, found '{other}'"
|
||||
)),
|
||||
start: pos,
|
||||
len: 1,
|
||||
input: chars.copy_chars(),
|
||||
|
@ -598,7 +596,7 @@ fn parse_extras(chars: &mut CharIter) -> Result<Option<Vec<String>>, Pep508Error
|
|||
Some((pos, char)) if char != ',' && char != ']' && !char.is_whitespace() => {
|
||||
return Err(Pep508Error {
|
||||
message: Pep508ErrorSource::String(format!(
|
||||
"Invalid character in extras name, expected an alphanumeric character, '-', '_', '.', ',' or ']', found '{}'", char
|
||||
"Invalid character in extras name, expected an alphanumeric character, '-', '_', '.', ',' or ']', found '{char}'"
|
||||
)),
|
||||
start: pos,
|
||||
len: 1,
|
||||
|
@ -786,8 +784,7 @@ fn parse(chars: &mut CharIter) -> Result<Requirement, Pep508Error> {
|
|||
Some(other) => {
|
||||
return Err(Pep508Error {
|
||||
message: Pep508ErrorSource::String(format!(
|
||||
"Expected one of `@`, `(`, `<`, `=`, `>`, `~`, `!`, `;`, found `{}`",
|
||||
other
|
||||
"Expected one of `@`, `(`, `<`, `=`, `>`, `~`, `!`, `;`, found `{other}`"
|
||||
)),
|
||||
start: chars.get_pos(),
|
||||
len: 1,
|
||||
|
@ -811,9 +808,9 @@ fn parse(chars: &mut CharIter) -> Result<Requirement, Pep508Error> {
|
|||
if let Some((pos, char)) = chars.next() {
|
||||
return Err(Pep508Error {
|
||||
message: Pep508ErrorSource::String(if marker.is_none() {
|
||||
format!(r#"Expected end of input or ';', found '{}'"#, char)
|
||||
format!(r#"Expected end of input or ';', found '{char}'"#)
|
||||
} else {
|
||||
format!(r#"Expected end of input, found '{}'"#, char)
|
||||
format!(r#"Expected end of input, found '{char}'"#)
|
||||
}),
|
||||
start: pos,
|
||||
len: 1,
|
||||
|
@ -854,7 +851,7 @@ pub fn python_module(py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Half of these tests are copied from https://github.com/pypa/packaging/pull/624
|
||||
/// Half of these tests are copied from <https://github.com/pypa/packaging/pull/624>
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::marker::{
|
||||
|
|
|
@ -24,8 +24,8 @@ use std::str::FromStr;
|
|||
use tracing::warn;
|
||||
|
||||
/// Ways in which marker evaluation can fail
|
||||
#[cfg_attr(feature = "pyo3", pyclass(module = "pep508"))]
|
||||
#[derive(Debug, Eq, Hash, Ord, PartialOrd, PartialEq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "pyo3", pyclass(module = "pep508"))]
|
||||
pub enum MarkerWarningKind {
|
||||
/// Using an old name from PEP 345 instead of the modern equivalent
|
||||
/// <https://peps.python.org/pep-0345/#environment-markers>
|
||||
|
@ -46,12 +46,14 @@ pub enum MarkerWarningKind {
|
|||
#[cfg(feature = "pyo3")]
|
||||
#[pymethods]
|
||||
impl MarkerWarningKind {
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
fn __hash__(&self) -> u8 {
|
||||
*self as u8
|
||||
}
|
||||
|
||||
fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool {
|
||||
op.matches(self.cmp(other))
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
fn __richcmp__(&self, other: Self, op: CompareOp) -> bool {
|
||||
op.matches(self.cmp(&other))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,15 +86,15 @@ pub enum MarkerValueString {
|
|||
ImplementationName,
|
||||
/// `os_name`
|
||||
OsName,
|
||||
/// /// Deprecated `os.name` from https://peps.python.org/pep-0345/#environment-markers
|
||||
/// Deprecated `os.name` from <https://peps.python.org/pep-0345/#environment-markers>
|
||||
OsNameDeprecated,
|
||||
/// `platform_machine`
|
||||
PlatformMachine,
|
||||
/// /// Deprecated `platform.machine` from https://peps.python.org/pep-0345/#environment-markers
|
||||
/// Deprecated `platform.machine` from <https://peps.python.org/pep-0345/#environment-markers>
|
||||
PlatformMachineDeprecated,
|
||||
/// `platform_python_implementation`
|
||||
PlatformPythonImplementation,
|
||||
/// /// Deprecated `platform.python_implementation` from https://peps.python.org/pep-0345/#environment-markers
|
||||
/// Deprecated `platform.python_implementation` from <https://peps.python.org/pep-0345/#environment-markers>
|
||||
PlatformPythonImplementationDeprecated,
|
||||
/// `platform_release`
|
||||
PlatformRelease,
|
||||
|
@ -100,11 +102,11 @@ pub enum MarkerValueString {
|
|||
PlatformSystem,
|
||||
/// `platform_version`
|
||||
PlatformVersion,
|
||||
/// /// Deprecated `platform.version` from https://peps.python.org/pep-0345/#environment-markers
|
||||
/// Deprecated `platform.version` from <https://peps.python.org/pep-0345/#environment-markers>
|
||||
PlatformVersionDeprecated,
|
||||
/// `sys_platform`
|
||||
SysPlatform,
|
||||
/// /// Deprecated `sys.platform` from https://peps.python.org/pep-0345/#environment-markers
|
||||
/// Deprecated `sys.platform` from <https://peps.python.org/pep-0345/#environment-markers>
|
||||
SysPlatformDeprecated,
|
||||
}
|
||||
|
||||
|
@ -184,7 +186,7 @@ impl FromStr for MarkerValue {
|
|||
"sys_platform" => Self::MarkerEnvString(MarkerValueString::SysPlatform),
|
||||
"sys.platform" => Self::MarkerEnvString(MarkerValueString::SysPlatformDeprecated),
|
||||
"extra" => Self::Extra,
|
||||
_ => return Err(format!("Invalid key: {}", s)),
|
||||
_ => return Err(format!("Invalid key: {s}")),
|
||||
};
|
||||
Ok(value)
|
||||
}
|
||||
|
@ -196,7 +198,7 @@ impl Display for MarkerValue {
|
|||
Self::MarkerEnvVersion(marker_value_version) => marker_value_version.fmt(f),
|
||||
Self::MarkerEnvString(marker_value_string) => marker_value_string.fmt(f),
|
||||
Self::Extra => f.write_str("extra"),
|
||||
Self::QuotedString(value) => write!(f, "'{}'", value),
|
||||
Self::QuotedString(value) => write!(f, "'{value}'"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -267,7 +269,7 @@ impl FromStr for MarkerOperator {
|
|||
{
|
||||
Self::NotIn
|
||||
}
|
||||
other => return Err(format!("Invalid comparator: {}", other)),
|
||||
other => return Err(format!("Invalid comparator: {other}")),
|
||||
};
|
||||
Ok(value)
|
||||
}
|
||||
|
@ -290,8 +292,8 @@ impl Display for MarkerOperator {
|
|||
}
|
||||
|
||||
/// Helper type with a [Version] and its original text
|
||||
#[cfg_attr(feature = "pyo3", pyclass(get_all, module = "pep508"))]
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
#[cfg_attr(feature = "pyo3", pyclass(get_all, module = "pep508"))]
|
||||
pub struct StringVersion {
|
||||
/// Original unchanged string
|
||||
pub string: String,
|
||||
|
@ -344,7 +346,7 @@ impl Deref for StringVersion {
|
|||
/// <https://packaging.python.org/en/latest/specifications/dependency-specifiers/#environment-markers>
|
||||
///
|
||||
/// Some are `(String, Version)` because we have to support version comparison
|
||||
#[allow(missing_docs)]
|
||||
#[allow(missing_docs, clippy::unsafe_derive_deserialize)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "pyo3", pyclass(get_all, module = "pep508"))]
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
|
@ -431,20 +433,17 @@ impl MarkerEnvironment {
|
|||
let implementation_version =
|
||||
StringVersion::from_str(implementation_version).map_err(|err| {
|
||||
PyValueError::new_err(format!(
|
||||
"implementation_version is not a valid PEP440 version: {}",
|
||||
err
|
||||
"implementation_version is not a valid PEP440 version: {err}"
|
||||
))
|
||||
})?;
|
||||
let python_full_version = StringVersion::from_str(python_full_version).map_err(|err| {
|
||||
PyValueError::new_err(format!(
|
||||
"python_full_version is not a valid PEP440 version: {}",
|
||||
err
|
||||
"python_full_version is not a valid PEP440 version: {err}"
|
||||
))
|
||||
})?;
|
||||
let python_version = StringVersion::from_str(python_version).map_err(|err| {
|
||||
PyValueError::new_err(format!(
|
||||
"python_version is not a valid PEP440 version: {}",
|
||||
err
|
||||
"python_version is not a valid PEP440 version: {err}"
|
||||
))
|
||||
})?;
|
||||
Ok(Self {
|
||||
|
@ -483,10 +482,10 @@ impl MarkerEnvironment {
|
|||
info.getattr("major")?.extract::<usize>()?,
|
||||
info.getattr("minor")?.extract::<usize>()?,
|
||||
info.getattr("micro")?.extract::<usize>()?,
|
||||
if kind != "final" {
|
||||
format!("{}{}", kind, info.getattr("serial")?.extract::<usize>()?)
|
||||
if kind == "final" {
|
||||
String::new()
|
||||
} else {
|
||||
"".to_string()
|
||||
format!("{}{}", kind, info.getattr("serial")?.extract::<usize>()?)
|
||||
}
|
||||
);
|
||||
let python_full_version: String = platform.getattr("python_version")?.call0()?.extract()?;
|
||||
|
@ -496,20 +495,17 @@ impl MarkerEnvironment {
|
|||
let implementation_version =
|
||||
StringVersion::from_str(&implementation_version).map_err(|err| {
|
||||
PyValueError::new_err(format!(
|
||||
"Broken python implementation, implementation_version is not a valid PEP440 version: {}",
|
||||
err
|
||||
"Broken python implementation, implementation_version is not a valid PEP440 version: {err}"
|
||||
))
|
||||
})?;
|
||||
let python_full_version = StringVersion::from_str(&python_full_version).map_err(|err| {
|
||||
PyValueError::new_err(format!(
|
||||
"Broken python implementation, python_full_version is not a valid PEP440 version: {}",
|
||||
err
|
||||
"Broken python implementation, python_full_version is not a valid PEP440 version: {err}"
|
||||
))
|
||||
})?;
|
||||
let python_version = StringVersion::from_str(&python_version).map_err(|err| {
|
||||
PyValueError::new_err(format!(
|
||||
"Broken python implementation, python_version is not a valid PEP440 version: {}",
|
||||
err
|
||||
"Broken python implementation, python_version is not a valid PEP440 version: {err}"
|
||||
))
|
||||
})?;
|
||||
Ok(Self {
|
||||
|
@ -546,7 +542,7 @@ pub struct MarkerExpression {
|
|||
}
|
||||
|
||||
impl MarkerExpression {
|
||||
/// Evaluate a <marker_value> <marker_op> <marker_value> expression
|
||||
/// Evaluate a <`marker_value`> <`marker_op`> <`marker_value`> expression
|
||||
fn evaluate(
|
||||
&self,
|
||||
env: &MarkerEnvironment,
|
||||
|
@ -592,7 +588,7 @@ impl MarkerExpression {
|
|||
Err(err) => {
|
||||
reporter(
|
||||
MarkerWarningKind::Pep440Error,
|
||||
format!("Invalid operator/version combination: {}", err),
|
||||
format!("Invalid operator/version combination: {err}"),
|
||||
self,
|
||||
);
|
||||
return false;
|
||||
|
@ -664,7 +660,7 @@ impl MarkerExpression {
|
|||
Err(err) => {
|
||||
reporter(
|
||||
MarkerWarningKind::Pep440Error,
|
||||
format!("Invalid operator/version combination: {}", err),
|
||||
format!("Invalid operator/version combination: {err}"),
|
||||
self,
|
||||
);
|
||||
return false;
|
||||
|
@ -685,8 +681,7 @@ impl MarkerExpression {
|
|||
// Not even pypa/packaging 22.0 supports this
|
||||
// https://github.com/pypa/packaging/issues/632
|
||||
reporter(MarkerWarningKind::StringStringComparison, format!(
|
||||
"Comparing two quoted strings with each other doesn't make sense: {}, evaluating to false",
|
||||
self
|
||||
"Comparing two quoted strings with each other doesn't make sense: {self}, evaluating to false"
|
||||
), self);
|
||||
false
|
||||
}
|
||||
|
@ -703,7 +698,7 @@ impl MarkerExpression {
|
|||
/// `python_version <pep PEP 440 operator> '...'` and
|
||||
/// `'...' <pep PEP 440 operator> python_version`.
|
||||
///
|
||||
/// Note that unlike [Self::evaluate] this does not perform any checks for bogus expressions but
|
||||
/// Note that unlike [`Self::evaluate`] this does not perform any checks for bogus expressions but
|
||||
/// will simply return true.
|
||||
///
|
||||
/// ```rust
|
||||
|
@ -809,7 +804,7 @@ impl MarkerExpression {
|
|||
MarkerOperator::GreaterThan => {
|
||||
reporter(
|
||||
MarkerWarningKind::LexicographicComparison,
|
||||
format!("Comparing {} and {} lexicographically", l_string, r_string),
|
||||
format!("Comparing {l_string} and {r_string} lexicographically"),
|
||||
self,
|
||||
);
|
||||
l_string > r_string
|
||||
|
@ -817,7 +812,7 @@ impl MarkerExpression {
|
|||
MarkerOperator::GreaterEqual => {
|
||||
reporter(
|
||||
MarkerWarningKind::LexicographicComparison,
|
||||
format!("Comparing {} and {} lexicographically", l_string, r_string),
|
||||
format!("Comparing {l_string} and {r_string} lexicographically"),
|
||||
self,
|
||||
);
|
||||
l_string >= r_string
|
||||
|
@ -825,7 +820,7 @@ impl MarkerExpression {
|
|||
MarkerOperator::LessThan => {
|
||||
reporter(
|
||||
MarkerWarningKind::LexicographicComparison,
|
||||
format!("Comparing {} and {} lexicographically", l_string, r_string),
|
||||
format!("Comparing {l_string} and {r_string} lexicographically"),
|
||||
self,
|
||||
);
|
||||
l_string < r_string
|
||||
|
@ -833,7 +828,7 @@ impl MarkerExpression {
|
|||
MarkerOperator::LessEqual => {
|
||||
reporter(
|
||||
MarkerWarningKind::LexicographicComparison,
|
||||
format!("Comparing {} and {} lexicographically", l_string, r_string),
|
||||
format!("Comparing {l_string} and {r_string} lexicographically"),
|
||||
self,
|
||||
);
|
||||
l_string <= r_string
|
||||
|
@ -841,7 +836,7 @@ impl MarkerExpression {
|
|||
MarkerOperator::TildeEqual => {
|
||||
reporter(
|
||||
MarkerWarningKind::LexicographicComparison,
|
||||
format!("Can't compare {} and {} with `~=`", l_string, r_string),
|
||||
format!("Can't compare {l_string} and {r_string} with `~=`"),
|
||||
self,
|
||||
);
|
||||
false
|
||||
|
@ -880,8 +875,7 @@ impl FromStr for MarkerExpression {
|
|||
if let Some((pos, unexpected)) = chars.next() {
|
||||
return Err(Pep508Error {
|
||||
message: Pep508ErrorSource::String(format!(
|
||||
"Unexpected character '{}', expected end of input",
|
||||
unexpected
|
||||
"Unexpected character '{unexpected}', expected end of input"
|
||||
)),
|
||||
start: pos,
|
||||
len: chars.chars.clone().count(),
|
||||
|
@ -937,7 +931,7 @@ impl MarkerTree {
|
|||
}
|
||||
}
|
||||
|
||||
/// Same as [Self::evaluate], but instead of using logging to warn, you can pass your own
|
||||
/// Same as [`Self::evaluate`], but instead of using logging to warn, you can pass your own
|
||||
/// handler for warnings
|
||||
pub fn evaluate_reporter(
|
||||
&self,
|
||||
|
@ -971,7 +965,7 @@ impl MarkerTree {
|
|||
/// environment markers, i.e. if there is potentially an environment that could activate this
|
||||
/// requirement.
|
||||
///
|
||||
/// Note that unlike [Self::evaluate] this does not perform any checks for bogus expressions but
|
||||
/// Note that unlike [`Self::evaluate`] this does not perform any checks for bogus expressions but
|
||||
/// will simply return true. As caller you should separately perform a check with an environment
|
||||
/// and forward all warnings.
|
||||
pub fn evaluate_extras_and_python_version(
|
||||
|
@ -992,7 +986,7 @@ impl MarkerTree {
|
|||
}
|
||||
}
|
||||
|
||||
/// Same as [Self::evaluate], but instead of using logging to warn, you get a Vec with all
|
||||
/// Same as [`Self::evaluate`], but instead of using logging to warn, you get a Vec with all
|
||||
/// warnings collected
|
||||
pub fn evaluate_collect_warnings(
|
||||
&self,
|
||||
|
@ -1001,7 +995,7 @@ impl MarkerTree {
|
|||
) -> (bool, Vec<(MarkerWarningKind, String, String)>) {
|
||||
let mut warnings = Vec::new();
|
||||
let mut reporter = |kind, warning, marker: &MarkerExpression| {
|
||||
warnings.push((kind, warning, marker.to_string()))
|
||||
warnings.push((kind, warning, marker.to_string()));
|
||||
};
|
||||
self.report_deprecated_options(&mut reporter);
|
||||
let result = self.evaluate_reporter_impl(env, extras, &mut reporter);
|
||||
|
@ -1066,12 +1060,12 @@ impl MarkerTree {
|
|||
}
|
||||
MarkerTree::And(expressions) => {
|
||||
for expression in expressions {
|
||||
expression.report_deprecated_options(reporter)
|
||||
expression.report_deprecated_options(reporter);
|
||||
}
|
||||
}
|
||||
MarkerTree::Or(expressions) => {
|
||||
for expression in expressions {
|
||||
expression.report_deprecated_options(reporter)
|
||||
expression.report_deprecated_options(reporter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1082,13 +1076,13 @@ impl Display for MarkerTree {
|
|||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let format_inner = |expression: &MarkerTree| {
|
||||
if matches!(expression, MarkerTree::Expression(_)) {
|
||||
format!("{}", expression)
|
||||
format!("{expression}")
|
||||
} else {
|
||||
format!("({})", expression)
|
||||
format!("({expression})")
|
||||
}
|
||||
};
|
||||
match self {
|
||||
MarkerTree::Expression(expression) => write!(f, "{}", expression),
|
||||
MarkerTree::Expression(expression) => write!(f, "{expression}"),
|
||||
MarkerTree::And(and_list) => f.write_str(
|
||||
&and_list
|
||||
.iter()
|
||||
|
@ -1131,8 +1125,7 @@ fn parse_marker_operator(chars: &mut CharIter) -> Result<MarkerOperator, Pep508E
|
|||
Some((pos, other)) => {
|
||||
return Err(Pep508Error {
|
||||
message: Pep508ErrorSource::String(format!(
|
||||
"Expected whitespace after 'not', found '{}'",
|
||||
other
|
||||
"Expected whitespace after 'not', found '{other}'"
|
||||
)),
|
||||
start: pos,
|
||||
len: 1,
|
||||
|
@ -1147,8 +1140,7 @@ fn parse_marker_operator(chars: &mut CharIter) -> Result<MarkerOperator, Pep508E
|
|||
}
|
||||
MarkerOperator::from_str(&operator).map_err(|_| Pep508Error {
|
||||
message: Pep508ErrorSource::String(format!(
|
||||
"Expected a valid marker operator (such as '>=' or 'not in'), found '{}'",
|
||||
operator
|
||||
"Expected a valid marker operator (such as '>=' or 'not in'), found '{operator}'"
|
||||
)),
|
||||
start,
|
||||
len,
|
||||
|
@ -1156,10 +1148,10 @@ fn parse_marker_operator(chars: &mut CharIter) -> Result<MarkerOperator, Pep508E
|
|||
})
|
||||
}
|
||||
|
||||
/// Either a single or double quoted string or one of 'python_version', 'python_full_version',
|
||||
/// 'os_name', 'sys_platform', 'platform_release', 'platform_system', 'platform_version',
|
||||
/// 'platform_machine', 'platform_python_implementation', 'implementation_name',
|
||||
/// 'implementation_version', 'extra'
|
||||
/// Either a single or double quoted string or one of '`python_version`', '`python_full_version`',
|
||||
/// '`os_name`', '`sys_platform`', '`platform_release`', '`platform_system`', '`platform_version`',
|
||||
/// '`platform_machine`', '`platform_python_implementation`', '`implementation_name`',
|
||||
/// '`implementation_version`', 'extra'
|
||||
fn parse_marker_value(chars: &mut CharIter) -> Result<MarkerValue, Pep508Error> {
|
||||
// > User supplied constants are always encoded as strings with either ' or " quote marks. Note
|
||||
// > that backslash escapes are not defined, but existing implementations do support them. They
|
||||
|
@ -1189,8 +1181,7 @@ fn parse_marker_value(chars: &mut CharIter) -> Result<MarkerValue, Pep508Error>
|
|||
});
|
||||
MarkerValue::from_str(&key).map_err(|_| Pep508Error {
|
||||
message: Pep508ErrorSource::String(format!(
|
||||
"Expected a valid marker name, found '{}'",
|
||||
key
|
||||
"Expected a valid marker name, found '{key}'"
|
||||
)),
|
||||
start,
|
||||
len,
|
||||
|
@ -1303,8 +1294,7 @@ pub(crate) fn parse_markers_impl(chars: &mut CharIter) -> Result<MarkerTree, Pep
|
|||
// character was neither "and" nor "or"
|
||||
return Err(Pep508Error {
|
||||
message: Pep508ErrorSource::String(format!(
|
||||
"Unexpected character '{}', expected 'and', 'or' or end of input",
|
||||
unexpected
|
||||
"Unexpected character '{unexpected}', expected 'and', 'or' or end of input"
|
||||
)),
|
||||
start: pos,
|
||||
len: chars.chars.clone().count(),
|
||||
|
@ -1337,14 +1327,14 @@ mod test {
|
|||
let v37 = StringVersion::from_str("3.7").unwrap();
|
||||
|
||||
MarkerEnvironment {
|
||||
implementation_name: "".to_string(),
|
||||
implementation_name: String::new(),
|
||||
implementation_version: v37.clone(),
|
||||
os_name: "linux".to_string(),
|
||||
platform_machine: "".to_string(),
|
||||
platform_python_implementation: "".to_string(),
|
||||
platform_release: "".to_string(),
|
||||
platform_system: "".to_string(),
|
||||
platform_version: "".to_string(),
|
||||
platform_machine: String::new(),
|
||||
platform_python_implementation: String::new(),
|
||||
platform_release: String::new(),
|
||||
platform_system: String::new(),
|
||||
platform_version: String::new(),
|
||||
python_full_version: v37.clone(),
|
||||
python_version: v37,
|
||||
sys_platform: "linux".to_string(),
|
||||
|
@ -1383,9 +1373,7 @@ mod test {
|
|||
assert_eq!(
|
||||
MarkerTree::from_str(a).unwrap(),
|
||||
MarkerTree::from_str(b).unwrap(),
|
||||
"{} {}",
|
||||
a,
|
||||
b
|
||||
"{a} {b}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1394,14 +1382,14 @@ mod test {
|
|||
fn test_marker_evaluation() {
|
||||
let v27 = StringVersion::from_str("2.7").unwrap();
|
||||
let env27 = MarkerEnvironment {
|
||||
implementation_name: "".to_string(),
|
||||
implementation_name: String::new(),
|
||||
implementation_version: v27.clone(),
|
||||
os_name: "linux".to_string(),
|
||||
platform_machine: "".to_string(),
|
||||
platform_python_implementation: "".to_string(),
|
||||
platform_release: "".to_string(),
|
||||
platform_system: "".to_string(),
|
||||
platform_version: "".to_string(),
|
||||
platform_machine: String::new(),
|
||||
platform_python_implementation: String::new(),
|
||||
platform_release: String::new(),
|
||||
platform_system: String::new(),
|
||||
platform_version: String::new(),
|
||||
python_full_version: v27.clone(),
|
||||
python_version: v27,
|
||||
sys_platform: "linux".to_string(),
|
||||
|
|
|
@ -1,362 +0,0 @@
|
|||
//! WIP Draft for a poetry/cargo like, modern dependency specification
|
||||
//!
|
||||
//! This still needs
|
||||
//! * Better VersionSpecifier (e.g. allowing `^1.19`) and it's sentry integration
|
||||
//! * PEP 440/PEP 508 translation
|
||||
//! * a json schema
|
||||
|
||||
#![cfg(feature = "modern")]
|
||||
|
||||
use crate::MarkerValue::QuotedString;
|
||||
use crate::{MarkerExpression, MarkerOperator, MarkerTree, MarkerValue, Requirement, VersionOrUrl};
|
||||
use anyhow::{bail, format_err, Context};
|
||||
use once_cell::sync::Lazy;
|
||||
use pep440_rs::{Operator, Pep440Error, Version, VersionSpecifier, VersionSpecifiers};
|
||||
use regex::Regex;
|
||||
use serde::{de, Deserialize, Deserializer, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
use url::Url;
|
||||
|
||||
/// Shared fields for version/git/file/path/url dependencies (`optional`, `extras`, `markers`)
|
||||
#[derive(Eq, PartialEq, Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct RequirementModernCommon {
|
||||
/// Whether this is an optional dependency. This is inverted from PEP 508 extras where the
|
||||
/// requirements has the extras attached, as here the extras has a table where each extra
|
||||
/// says which optional dependencies it activates
|
||||
#[serde(default)]
|
||||
pub optional: bool,
|
||||
/// The list of extras <https://packaging.python.org/en/latest/tutorials/installing-packages/#installing-extras>
|
||||
pub extras: Option<Vec<String>>,
|
||||
/// The list of markers <https://peps.python.org/pep-0508/#environment-markers>.
|
||||
/// Note that this will not accept extras.
|
||||
///
|
||||
/// TODO: Deserialize into `MarkerTree` that does not accept the extras key
|
||||
pub markers: Option<String>,
|
||||
}
|
||||
|
||||
/// Instead of only PEP 440 specifier, you can also set a single version (exact) or TODO use
|
||||
/// the semver caret
|
||||
#[derive(Eq, PartialEq, Debug, Clone, Serialize)]
|
||||
pub enum VersionSpecifierModern {
|
||||
/// e.g. `4.12.1-beta.1`
|
||||
Version(Version),
|
||||
/// e.g. `== 4.12.1-beta.1` or `>=3.8,<4.0`
|
||||
VersionSpecifier(VersionSpecifiers),
|
||||
}
|
||||
|
||||
impl VersionSpecifierModern {
|
||||
/// `4.12.1-beta.1` -> `== 4.12.1-beta.1`
|
||||
/// `== 4.12.1-beta.1` -> `== 4.12.1-beta.1`
|
||||
/// `>=3.8,<4.0` -> `>=3.8,<4.0`
|
||||
/// TODO: `^1.19` -> `>=1.19,<2.0`
|
||||
pub fn to_pep508_specifier(&self) -> VersionSpecifiers {
|
||||
match self {
|
||||
// unwrapping is safe here because we're using Operator::Equal
|
||||
VersionSpecifierModern::Version(version) => {
|
||||
[VersionSpecifier::new(Operator::Equal, version.clone(), false).unwrap()]
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
VersionSpecifierModern::VersionSpecifier(version_specifiers) => {
|
||||
version_specifiers.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for VersionSpecifierModern {
|
||||
/// TODO: Modern needs it's own error type
|
||||
type Err = Pep440Error;
|
||||
|
||||
/// dispatching between just a version and a version specifier set
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
// If it starts with
|
||||
if s.trim_start().starts_with(|x: char| x.is_ascii_digit()) {
|
||||
Ok(Self::Version(Version::from_str(s).map_err(|err| {
|
||||
// TODO: Fix this in pep440_rs
|
||||
Pep440Error {
|
||||
message: err,
|
||||
line: s.to_string(),
|
||||
start: 0,
|
||||
width: 1,
|
||||
}
|
||||
})?))
|
||||
} else if s.starts_with('^') {
|
||||
todo!("TODO caret operator is not supported yet");
|
||||
} else {
|
||||
Ok(Self::VersionSpecifier(VersionSpecifiers::from_str(s)?))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// https://github.com/serde-rs/serde/issues/908#issuecomment-298027413
|
||||
impl<'de> Deserialize<'de> for VersionSpecifierModern {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/// WIP Draft for a poetry/cargo like, modern dependency specification
|
||||
#[derive(Eq, PartialEq, Debug, Clone, Deserialize, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum RequirementModern {
|
||||
/// e.g. `numpy = "1.24.1"`
|
||||
Dependency(VersionSpecifierModern),
|
||||
/// e.g. `numpy = { version = "1.24.1" }` or `django-anymail = { version = "1.24.1", extras = ["sendgrid"], optional = true }`
|
||||
LongDependency {
|
||||
/// e.g. `1.2.3.beta1`
|
||||
version: VersionSpecifierModern,
|
||||
#[serde(flatten)]
|
||||
#[allow(missing_docs)]
|
||||
common: RequirementModernCommon,
|
||||
},
|
||||
/// e.g. `tqdm = { git = "https://github.com/tqdm/tqdm", rev = "0bb91857eca0d4aea08f66cf1c8949abe0cd6b7a" }`
|
||||
GitDependency {
|
||||
/// URL of the git repository e.g. `https://github.com/tqdm/tqdm`
|
||||
git: Url,
|
||||
/// The git branch to use
|
||||
branch: Option<String>,
|
||||
/// The git revision to use. Can be the short revision (`0bb9185`) or the long revision
|
||||
/// (`0bb91857eca0d4aea08f66cf1c8949abe0cd6b7a`)
|
||||
rev: Option<String>,
|
||||
#[serde(flatten)]
|
||||
#[allow(missing_docs)]
|
||||
common: RequirementModernCommon,
|
||||
},
|
||||
/// e.g. `tqdm = { file = "tqdm-4.65.0-py3-none-any.whl" }`
|
||||
FileDependency {
|
||||
/// Path to a source distribution (e.g. `tqdm-4.65.0.tar.gz`) or wheel (e.g. `tqdm-4.65.0-py3-none-any.whl`)
|
||||
file: String,
|
||||
#[serde(flatten)]
|
||||
#[allow(missing_docs)]
|
||||
common: RequirementModernCommon,
|
||||
},
|
||||
/// Path to a directory with source distributions and/or wheels e.g.
|
||||
/// `scilib_core = { path = "build_wheels/scilib_core/" }`.
|
||||
///
|
||||
/// Use this option if you e.g. have multiple platform platform dependent wheels or want to
|
||||
/// have a fallback to a source distribution for you wheel.
|
||||
PathDependency {
|
||||
/// e.g. `dist/`, `target/wheels` or `vendored`
|
||||
path: String,
|
||||
#[serde(flatten)]
|
||||
#[allow(missing_docs)]
|
||||
common: RequirementModernCommon,
|
||||
},
|
||||
/// e.g. `jax = { url = "https://storage.googleapis.com/jax-releases/cuda112/jaxlib-0.1.64+cuda112-cp39-none-manylinux2010_x86_64.whl" }`
|
||||
UrlDependency {
|
||||
/// URL to a source distribution or wheel. The file available there must be named
|
||||
/// appropriately for a source distribution or a wheel.
|
||||
url: Url,
|
||||
#[serde(flatten)]
|
||||
#[allow(missing_docs)]
|
||||
common: RequirementModernCommon,
|
||||
},
|
||||
}
|
||||
|
||||
/// Adopted from the grammar at <https://peps.python.org/pep-0508/#extras>
|
||||
static EXTRA_REGEX: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"^[a-zA-Z0-9]([-_.]*[a-zA-Z0-9])*$").unwrap());
|
||||
|
||||
impl RequirementModern {
|
||||
/// Check the things that serde doesn't check, namely that extra names are valid
|
||||
pub fn check(&self) -> anyhow::Result<()> {
|
||||
match self {
|
||||
Self::LongDependency { common, .. }
|
||||
| Self::GitDependency { common, .. }
|
||||
| Self::FileDependency { common, .. }
|
||||
| Self::PathDependency { common, .. }
|
||||
| Self::UrlDependency { common, .. } => {
|
||||
if let Some(extras) = &common.extras {
|
||||
for extra in extras {
|
||||
if !EXTRA_REGEX.is_match(extra) {
|
||||
bail!("Not a valid extra name: '{}'", extra)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// WIP Converts the modern format to PEP 508
|
||||
pub fn to_pep508(
|
||||
&self,
|
||||
name: &str,
|
||||
extras: &HashMap<String, Vec<String>>,
|
||||
) -> Result<Requirement, anyhow::Error> {
|
||||
let default = RequirementModernCommon {
|
||||
optional: false,
|
||||
extras: None,
|
||||
markers: None,
|
||||
};
|
||||
|
||||
let common = match self {
|
||||
RequirementModern::Dependency(..) => &default,
|
||||
RequirementModern::LongDependency { common, .. }
|
||||
| RequirementModern::GitDependency { common, .. }
|
||||
| RequirementModern::FileDependency { common, .. }
|
||||
| RequirementModern::PathDependency { common, .. }
|
||||
| RequirementModern::UrlDependency { common, .. } => common,
|
||||
};
|
||||
|
||||
let marker = if common.optional {
|
||||
// invert the extras table from the modern format
|
||||
// extra1 -> optional_dep1, optional_dep2, ...
|
||||
// to the PEP 508 format
|
||||
// optional_dep1; extra == "extra1" or extra == "extra2"
|
||||
let dep_markers = extras
|
||||
.iter()
|
||||
.filter(|(_marker, dependencies)| dependencies.contains(&name.to_string()))
|
||||
.map(|(marker, _dependencies)| {
|
||||
MarkerTree::Expression(MarkerExpression {
|
||||
l_value: MarkerValue::Extra,
|
||||
operator: MarkerOperator::Equal,
|
||||
r_value: QuotedString(marker.to_string()),
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
// any of these extras activates the dependency -> or clause
|
||||
let dep_markers = MarkerTree::Or(dep_markers);
|
||||
let joined_marker = if let Some(user_markers) = &common.markers {
|
||||
let user_markers = MarkerTree::from_str(user_markers)
|
||||
.context("TODO: parse this in serde already")?;
|
||||
// but the dependency needs to be activated and match the other markers
|
||||
// -> and clause
|
||||
MarkerTree::And(vec![user_markers, dep_markers])
|
||||
} else {
|
||||
dep_markers
|
||||
};
|
||||
Some(joined_marker)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(extras) = &common.extras {
|
||||
debug_assert!(extras.iter().all(|extra| EXTRA_REGEX.is_match(extra)));
|
||||
}
|
||||
|
||||
let version_or_url = match self {
|
||||
RequirementModern::Dependency(version) => {
|
||||
VersionOrUrl::VersionSpecifier(version.to_pep508_specifier())
|
||||
}
|
||||
RequirementModern::LongDependency { version, .. } => {
|
||||
VersionOrUrl::VersionSpecifier(version.to_pep508_specifier())
|
||||
}
|
||||
RequirementModern::GitDependency {
|
||||
git, branch, rev, ..
|
||||
} => {
|
||||
// TODO: Read https://peps.python.org/pep-0440/#direct-references properly
|
||||
// set_scheme doesn't like us adding `git+` to https, therefore this hack
|
||||
let mut url =
|
||||
Url::parse(&format!("git+{}", git)).expect("TODO: Better url validation");
|
||||
match (branch, rev) {
|
||||
(Some(_branch), Some(_rev)) => {
|
||||
bail!("You can set both branch and rev (for {})", name)
|
||||
}
|
||||
(Some(branch), None) => url.set_path(&format!("{}@{}", url.path(), branch)),
|
||||
(None, Some(rev)) => url.set_path(&format!("{}@{}", url.path(), rev)),
|
||||
(None, None) => {}
|
||||
}
|
||||
|
||||
VersionOrUrl::Url(url)
|
||||
}
|
||||
RequirementModern::FileDependency { file, .. } => VersionOrUrl::Url(
|
||||
Url::from_file_path(file)
|
||||
.map_err(|()| format_err!("File must be absolute (for {})", name))?,
|
||||
),
|
||||
RequirementModern::PathDependency { path, .. } => VersionOrUrl::Url(
|
||||
Url::from_directory_path(path)
|
||||
.map_err(|()| format_err!("Path must be absolute (for {})", name))?,
|
||||
),
|
||||
RequirementModern::UrlDependency { url, .. } => VersionOrUrl::Url(url.clone()),
|
||||
};
|
||||
|
||||
Ok(Requirement {
|
||||
name: name.to_string(),
|
||||
extras: common.extras.clone(),
|
||||
version_or_url: Some(version_or_url),
|
||||
marker,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::modern::{RequirementModern, VersionSpecifierModern};
|
||||
use crate::Requirement;
|
||||
use indoc::indoc;
|
||||
use pep440_rs::VersionSpecifiers;
|
||||
use serde::Deserialize;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn test_basic() {
|
||||
let deps: HashMap<String, RequirementModern> =
|
||||
toml::from_str(r#"numpy = "==1.19""#).unwrap();
|
||||
assert_eq!(
|
||||
deps["numpy"],
|
||||
RequirementModern::Dependency(VersionSpecifierModern::VersionSpecifier(
|
||||
VersionSpecifiers::from_str("==1.19").unwrap()
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
deps["numpy"].to_pep508("numpy", &HashMap::new()).unwrap(),
|
||||
Requirement::from_str("numpy== 1.19").unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_conversion() {
|
||||
#[derive(Deserialize)]
|
||||
struct PyprojectToml {
|
||||
// BTreeMap to keep the order
|
||||
#[serde(rename = "modern-dependencies")]
|
||||
modern_dependencies: BTreeMap<String, RequirementModern>,
|
||||
extras: HashMap<String, Vec<String>>,
|
||||
}
|
||||
|
||||
let pyproject_toml = indoc! {r#"
|
||||
[modern-dependencies]
|
||||
pydantic = "1.10.5"
|
||||
numpy = ">=1.24.2, <2.0.0"
|
||||
pandas = { version = ">=1.5.3, <2.0.0" }
|
||||
flask = { version = "2.2.3 ", extras = ["dotenv"], optional = true }
|
||||
tqdm = { git = "https://github.com/tqdm/tqdm", rev = "0bb91857eca0d4aea08f66cf1c8949abe0cd6b7a" }
|
||||
jax = { url = "https://storage.googleapis.com/jax-releases/cuda112/jaxlib-0.1.64+cuda112-cp39-none-manylinux2010_x86_64.whl" }
|
||||
zstandard = { file = "/home/ferris/wheels/zstandard/zstandard-0.20.0.tar.gz" }
|
||||
h5py = { path = "/home/ferris/wheels/h5py/" }
|
||||
|
||||
[extras]
|
||||
internet = ["flask"]
|
||||
"#
|
||||
};
|
||||
|
||||
let deps: PyprojectToml = toml::from_str(pyproject_toml).unwrap();
|
||||
|
||||
let actual: Vec<String> = deps
|
||||
.modern_dependencies
|
||||
.iter()
|
||||
.map(|(name, spec)| spec.to_pep508(name, &deps.extras).unwrap().to_string())
|
||||
.collect();
|
||||
let expected: Vec<String> = vec![
|
||||
"flask[dotenv] ==2.2.3 ; extra == 'internet'".to_string(),
|
||||
"h5py @ file:///home/ferris/wheels/h5py/".to_string(),
|
||||
"jax @ https://storage.googleapis.com/jax-releases/cuda112/jaxlib-0.1.64+cuda112-cp39-none-manylinux2010_x86_64.whl".to_string(),
|
||||
"numpy >=1.24.2, <2.0.0".to_string(),
|
||||
"pandas >=1.5.3, <2.0.0".to_string(),
|
||||
"pydantic ==1.10.5".to_string(),
|
||||
"tqdm @ git+https://github.com/tqdm/tqdm@0bb91857eca0d4aea08f66cf1c8949abe0cd6b7a".to_string(),
|
||||
"zstandard @ file:///home/ferris/wheels/zstandard/zstandard-0.20.0.tar.gz".to_string()
|
||||
];
|
||||
assert_eq!(actual, expected)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue