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:
Charlie Marsh 2023-10-06 20:16:44 -04:00 committed by GitHub
parent 4fcdb3c045
commit c8477991a9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 359 additions and 1596 deletions

154
Cargo.lock generated
View file

@ -127,6 +127,12 @@ version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
[[package]]
name = "arc-swap"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6"
[[package]]
name = "async-compression"
version = "0.4.3"
@ -149,7 +155,7 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.38",
]
[[package]]
@ -392,7 +398,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
"syn 2.0.38",
]
[[package]]
@ -773,7 +779,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.38",
]
[[package]]
@ -1077,6 +1083,18 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "indoc"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306"
[[package]]
name = "indoc"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8"
[[package]]
name = "insta"
version = "1.33.0"
@ -1292,7 +1310,7 @@ checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.38",
]
[[package]]
@ -1422,7 +1440,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.38",
]
[[package]]
@ -1483,25 +1501,30 @@ dependencies = [
[[package]]
name = "pep440_rs"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "887f66cc62717ea72caac4f1eb4e6f392224da3ffff3f40ec13ab427802746d6"
dependencies = [
"lazy_static",
"indoc 2.0.4",
"once_cell",
"pyo3",
"regex",
"serde",
"tracing",
"unicode-width",
]
[[package]]
name = "pep508_rs"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4516b53d9ea6112ebb38b4af08d5707d30b994fb7f98ff133c5dcf7ed8fa854"
dependencies = [
"indoc 2.0.4",
"log",
"once_cell",
"pep440_rs",
"pyo3",
"pyo3-log",
"regex",
"serde",
"serde_json",
"testing_logger",
"thiserror",
"tracing",
"unicode-width",
@ -1734,6 +1757,77 @@ dependencies = [
"tracing",
]
[[package]]
name = "pyo3"
version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e681a6cfdc4adcc93b4d3cf993749a4552018ee0a9b65fc0ccfad74352c72a38"
dependencies = [
"cfg-if",
"indoc 1.0.9",
"libc",
"memoffset",
"parking_lot",
"pyo3-build-config",
"pyo3-ffi",
"pyo3-macros",
"unindent",
]
[[package]]
name = "pyo3-build-config"
version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "076c73d0bc438f7a4ef6fdd0c3bb4732149136abd952b110ac93e4edb13a6ba5"
dependencies = [
"once_cell",
"target-lexicon",
]
[[package]]
name = "pyo3-ffi"
version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e53cee42e77ebe256066ba8aa77eff722b3bb91f3419177cf4cd0f304d3284d9"
dependencies = [
"libc",
"pyo3-build-config",
]
[[package]]
name = "pyo3-log"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f47b0777feb17f61eea78667d61103758b243a871edc09a7786500a50467b605"
dependencies = [
"arc-swap",
"log",
"pyo3",
]
[[package]]
name = "pyo3-macros"
version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfeb4c99597e136528c6dd7d5e3de5434d1ceaf487436a3f03b2d56b6fc9efd1"
dependencies = [
"proc-macro2",
"pyo3-macros-backend",
"quote",
"syn 1.0.109",
]
[[package]]
name = "pyo3-macros-backend"
version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "947dc12175c254889edc0c02e399476c2f652b4b9ebd123aa655c224de259536"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "quick-xml"
version = "0.29.0"
@ -2069,7 +2163,7 @@ checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.38",
]
[[package]]
@ -2112,7 +2206,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.38",
]
[[package]]
@ -2257,6 +2351,17 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.38"
@ -2317,6 +2422,15 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "testing_logger"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d92b727cb45d33ae956f7f46b966b25f1bc712092aeef9dba5ac798fc89f720"
dependencies = [
"log",
]
[[package]]
name = "thiserror"
version = "1.0.49"
@ -2334,7 +2448,7 @@ checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.38",
]
[[package]]
@ -2425,7 +2539,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.38",
]
[[package]]
@ -2491,7 +2605,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.38",
]
[[package]]
@ -2593,6 +2707,12 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
[[package]]
name = "unindent"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c"
[[package]]
name = "url"
version = "2.4.1"
@ -2675,7 +2795,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn",
"syn 2.0.38",
"wasm-bindgen-shared",
]
@ -2709,7 +2829,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.38",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]

View file

@ -25,8 +25,6 @@ install-wheel-rs = { version = "0.0.1" }
mailparse = { version = "0.14.0" }
memchr = { version = "2.6.4" }
once_cell = { version = "1.18.0" }
pep440_rs = { version = "0.3.12" }
pep508_rs = { version = "0.2.3" }
platform-info = { version = "2.0.2" }
plist = { version = "1.5.0" }
regex = { version = "1.9.6" }
@ -44,4 +42,5 @@ tokio-util = { version = "0.7.9", features = ["compat"] }
tracing = { version = "0.1.37" }
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
tracing-tree = { version = "0.2.5" }
unicode-width = { version = "0.1.8" }
url = { version = "2.4.1" }

View file

@ -4,7 +4,6 @@ version = "0.3.12"
description = "A library for python version numbers and specifiers, implementing PEP 440"
edition = "2021"
include = ["/src", "Changelog.md", "License-Apache", "License-BSD", "Readme.md", "pyproject.toml"]
# Same license as pypa/packaging where the tests are from
license = "Apache-2.0 OR BSD-2-Clause"
repository = "https://github.com/konstin/pep440-rs"
readme = "Readme.md"
@ -14,12 +13,13 @@ name = "pep440_rs"
crate-type = ["rlib", "cdylib"]
[dependencies]
lazy_static = "1.4.0"
once_cell = { workspace = true }
regex = { workspace = true }
serde = { workspace = true, features = ["derive"], optional = true }
tracing = { workspace = true, optional = true }
unicode-width = { workspace = true }
pyo3 = { version = "0.19", optional = true, features = ["extension-module", "abi3-py37"] }
regex = { version = "1.8.1", default-features = false, features = ["std", "perf", "unicode-case", "unicode-perl"] }
serde = { version = "1.0.162", features = ["derive"], optional = true }
tracing = { version = "0.1.37", optional = true }
unicode-width = "0.1.10"
[dev-dependencies]
indoc = "2.0.1"

View file

@ -1,15 +0,0 @@
[project]
name = "pep440_rs"
readme = "python/Readme.md"
[build-system]
requires = ["maturin>=1.0.0,<2.0.0"]
build-backend = "maturin"
[tool.maturin]
features = ["pyo3"]
python-source = "python"
module-name = "pep440_rs._pep440_rs"
[tool.ruff.per-file-ignores]
"python/pep440_rs/__init__.py" = ["F403", "F405"]

View file

@ -1,4 +1,4 @@
use lazy_static::lazy_static;
use once_cell::sync::Lazy;
#[cfg(feature = "pyo3")]
use pyo3::{
basic::CompareOp, exceptions::PyValueError, pyclass, pymethods, FromPyObject, IntoPy, PyAny,
@ -53,12 +53,9 @@ pub(crate) const VERSION_RE_INNER: &str = r"
(?P<trailing_dot_star>\.\*)? # allow for version matching `.*`
";
lazy_static! {
/// Matches a python version, such as `1.19.a1`. Based on the PEP 440 regex
static ref VERSION_RE: Regex = Regex::new(&format!(
r#"(?xi)^(?:\s*){}(?:\s*)$"#, VERSION_RE_INNER
)).unwrap();
}
static VERSION_RE: Lazy<Regex> =
Lazy::new(|| Regex::new(&format!(r#"(?xi)^(?:\s*){VERSION_RE_INNER}(?:\s*)$"#)).unwrap());
/// One of `~=` `==` `!=` `<=` `>=` `<` `>` `===`
#[derive(Eq, PartialEq, Debug, Hash, Clone)]
@ -115,8 +112,7 @@ impl FromStr for Operator {
// Should be forbidden by the regex if called from normal parsing
other => {
return Err(format!(
"No such comparison operator '{}', must be one of ~= == != <= >= < > ===",
other
"No such comparison operator '{other}', must be one of ~= == != <= >= < > ===",
));
}
};
@ -125,7 +121,7 @@ impl FromStr for Operator {
}
impl Display for Operator {
/// Note the EqualStar is also `==`
/// Note the `EqualStar` is also `==`.
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let operator = match self {
Operator::Equal => "==",
@ -142,7 +138,7 @@ impl Display for Operator {
Operator::GreaterThanEqual => ">=",
};
write!(f, "{}", operator)
write!(f, "{operator}")
}
}
@ -181,8 +177,7 @@ impl FromStr for PreRelease {
"b" | "beta" => Ok(Self::Beta),
"c" | "rc" | "pre" | "preview" => Ok(Self::Rc),
_ => Err(format!(
"'{}' isn't recognized as alpha, beta or release candidate",
prerelease
"'{prerelease}' isn't recognized as alpha, beta or release candidate",
)),
}
}
@ -212,7 +207,7 @@ impl Display for PreRelease {
/// > shorter local versions segments match the beginning of the longer local versions segments
/// > exactly.
///
/// Luckily the default Ord impl for Vec<LocalSegment> matches the PEP 440 rules
/// Luckily the default `Ord` implementation for `Vec<LocalSegment>` matches the PEP 440 rules.
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
pub enum LocalSegment {
/// Not-parseable as integer segment of local version
@ -224,8 +219,8 @@ pub enum LocalSegment {
impl Display for LocalSegment {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::String(string) => write!(f, "{}", string),
Self::Number(number) => write!(f, "{}", number),
Self::String(string) => write!(f, "{string}"),
Self::Number(number) => write!(f, "{number}"),
}
}
}
@ -254,9 +249,9 @@ impl FromStr for LocalSegment {
///
/// Beware that the sorting implemented with [Ord] and [Eq] is not consistent with the operators
/// from PEP 440, i.e. compare two versions in rust with `>` gives a different result than a
/// VersionSpecifier with `>` as operator.
/// `VersionSpecifier` with `>` as operator.
///
/// Parse with [Version::from_str]:
/// Parse with [`Version::from_str`]:
///
/// ```rust
/// use std::str::FromStr;
@ -360,25 +355,25 @@ impl PyVersion {
#[getter]
#[allow(clippy::get_first)]
pub fn major(&self) -> usize {
self.release().get(0).cloned().unwrap_or_default()
self.release().get(0).copied().unwrap_or_default()
}
/// The second item of release or 0 if unavailable.
#[getter]
pub fn minor(&self) -> usize {
self.release().get(1).cloned().unwrap_or_default()
self.release().get(1).copied().unwrap_or_default()
}
/// The third item of release or 0 if unavailable.
#[getter]
pub fn micro(&self) -> usize {
self.release().get(2).cloned().unwrap_or_default()
self.release().get(2).copied().unwrap_or_default()
}
/// Parses a PEP 440 version string
#[cfg(feature = "pyo3")]
#[new]
pub fn parse(version: String) -> PyResult<Self> {
pub fn parse(version: &str) -> PyResult<Self> {
Ok(Self(
Version::from_str(&version).map_err(PyValueError::new_err)?,
Version::from_str(version).map_err(PyValueError::new_err)?,
))
}
@ -386,8 +381,8 @@ impl PyVersion {
/// Parse a PEP 440 version optionally ending with `.*`
#[cfg(feature = "pyo3")]
#[staticmethod]
pub fn parse_star(version_specifier: String) -> PyResult<(Self, bool)> {
Version::from_str_star(&version_specifier)
pub fn parse_star(version_specifier: &str) -> PyResult<(Self, bool)> {
Version::from_str_star(version_specifier)
.map_err(PyValueError::new_err)
.map(|(version, star)| (Self(version), star))
}
@ -422,7 +417,7 @@ impl PyVersion {
}
}
/// https://github.com/serde-rs/serde/issues/1316#issue-332908452
/// <https://github.com/serde-rs/serde/issues/1316#issue-332908452>
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for Version {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
@ -434,7 +429,7 @@ impl<'de> Deserialize<'de> for Version {
}
}
/// 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 Version {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
@ -500,29 +495,26 @@ impl Version {
impl Display for Version {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let epoch = if self.epoch == 0 {
"".to_string()
String::new()
} else {
format!("{}!", self.epoch)
};
let release = self
.release
.iter()
.map(|x| x.to_string())
.map(ToString::to_string)
.collect::<Vec<String>>()
.join(".");
let pre = self
.pre
.as_ref()
.map(|(pre_kind, pre_version)| format!("{}{}", pre_kind, pre_version))
.map(|(pre_kind, pre_version)| format!("{pre_kind}{pre_version}"))
.unwrap_or_default();
let post = self
.post
.map(|post| format!(".post{}", post))
.unwrap_or_default();
let dev = self
.dev
.map(|dev| format!(".dev{}", dev))
.map(|post| format!(".post{post}"))
.unwrap_or_default();
let dev = self.dev.map(|dev| format!(".dev{dev}")).unwrap_or_default();
let local = self
.local
.as_ref()
@ -531,19 +523,19 @@ impl Display for Version {
"+{}",
segments
.iter()
.map(|x| x.to_string())
.map(std::string::ToString::to_string)
.collect::<Vec<String>>()
.join(".")
)
})
.unwrap_or_default();
write!(f, "{}{}{}{}{}{}", epoch, release, pre, post, dev, local)
write!(f, "{epoch}{release}{pre}{post}{dev}{local}")
}
}
/// Compare the release parts of two versions, e.g. `4.3.1` > `4.2`, `1.1.0` == `1.1` and
/// `1.16` < `1.19`
pub fn compare_release(this: &[usize], other: &[usize]) -> Ordering {
pub(crate) fn compare_release(this: &[usize], other: &[usize]) -> Ordering {
// "When comparing release segments with different numbers of components, the shorter segment
// is padded out with additional zeros as necessary"
let iterator: Vec<(&usize, &usize)> = if this.len() < other.len() {
@ -632,7 +624,7 @@ impl PartialEq<Self> for Version {
impl Eq for Version {}
impl Hash for Version {
/// Custom implementation to ignoring trailing zero because PartialEq zero pads
/// Custom implementation to ignoring trailing zero because `PartialEq` zero pads
fn hash<H: Hasher>(&self, state: &mut H) {
self.epoch.hash(state);
// Skip trailing zeros
@ -693,11 +685,11 @@ impl FromStr for Version {
/// Parses a version such as `1.19`, `1.0a1`,`1.0+abc.5` or `1!2012.2`
///
/// Note that this variant doesn't allow the version to end with a star, see
/// [Self::from_str_star] if you want to parse versions for specifiers
/// [`Self::from_str_star`] if you want to parse versions for specifiers
fn from_str(version: &str) -> Result<Self, Self::Err> {
let captures = VERSION_RE
.captures(version)
.ok_or_else(|| format!("Version `{}` doesn't match PEP 440 rules", version))?;
.ok_or_else(|| format!("Version `{version}` doesn't match PEP 440 rules"))?;
let (version, star) = Version::parse_impl(&captures)?;
if star {
return Err("A star (`*`) must not be used in a fixed version (use `Version::from_string_star` otherwise)".to_string());
@ -707,7 +699,7 @@ impl FromStr for Version {
}
impl Version {
/// Like [Self::from_str], but also allows the version to end with a star and returns whether it
/// Like [`Self::from_str`], but also allows the version to end with a star and returns whether it
/// did. This variant is for use in specifiers.
/// * `1.2.3` -> false
/// * `1.2.3.*` -> true
@ -716,7 +708,7 @@ impl Version {
pub fn from_str_star(version: &str) -> Result<(Self, bool), String> {
let captures = VERSION_RE
.captures(version)
.ok_or_else(|| format!("Version `{}` doesn't match PEP 440 rules", version))?;
.ok_or_else(|| format!("Version `{version}` doesn't match PEP 440 rules"))?;
let (version, star) = Version::parse_impl(&captures)?;
Ok((version, star))
}
@ -895,7 +887,7 @@ mod test {
];
for version in versions {
Version::from_str(version).unwrap();
VersionSpecifier::from_str(&format!("=={}", version)).unwrap();
VersionSpecifier::from_str(&format!("=={version}")).unwrap();
}
}
@ -915,14 +907,11 @@ mod test {
for version in versions {
assert_eq!(
Version::from_str(version).unwrap_err(),
format!("Version `{}` doesn't match PEP 440 rules", version)
format!("Version `{version}` doesn't match PEP 440 rules")
);
assert_eq!(
VersionSpecifier::from_str(&format!("=={}", version)).unwrap_err(),
format!(
"Version specifier `=={}` doesn't match PEP 440 rules",
version
)
VersionSpecifier::from_str(&format!("=={version}")).unwrap_err(),
format!("Version specifier `=={version}` doesn't match PEP 440 rules")
);
}
}
@ -1048,19 +1037,17 @@ mod test {
let version = Version::from_str(version_str).unwrap();
let normalized = Version::from_str(normalized_str).unwrap();
// Just test version parsing again
assert_eq!(version, normalized, "{} {}", version_str, normalized_str);
assert_eq!(version, normalized, "{version_str} {normalized_str}");
// Test version normalization
assert_eq!(
version.to_string(),
normalized.to_string(),
"{} {}",
version_str,
normalized_str
"{version_str} {normalized_str}"
);
}
}
/// https://github.com/pypa/packaging/blob/237ff3aa348486cf835a980592af3a59fccd6101/tests/test_version.py#L229-L277
/// <https://github.com/pypa/packaging/blob/237ff3aa348486cf835a980592af3a59fccd6101/tests/test_version.py#L229-L277>
#[test]
fn test_equality_and_normalization2() {
let versions = [
@ -1115,22 +1102,18 @@ mod test {
for (version_str, normalized_str) in versions {
let version = Version::from_str(version_str).unwrap();
let normalized = Version::from_str(normalized_str).unwrap();
assert_eq!(version, normalized, "{} {}", version_str, normalized_str);
assert_eq!(version, normalized, "{version_str} {normalized_str}");
// Test version normalization
assert_eq!(
version.to_string(),
normalized_str,
"{} {}",
version_str,
normalized_str
"{version_str} {normalized_str}"
);
// Since we're already at it
assert_eq!(
version.to_string(),
normalized.to_string(),
"{} {}",
version_str,
normalized_str
"{version_str} {normalized_str}"
);
}
}

View file

@ -2,7 +2,7 @@
use crate::version::PyVersion;
use crate::version::VERSION_RE_INNER;
use crate::{version, Operator, Pep440Error, Version};
use lazy_static::lazy_static;
use once_cell::sync::Lazy;
#[cfg(feature = "pyo3")]
use pyo3::{
exceptions::{PyIndexError, PyNotImplementedError, PyValueError},
@ -27,14 +27,14 @@ use unicode_width::UnicodeWidthStr;
#[cfg(feature = "tracing")]
use tracing::warn;
lazy_static! {
/// Matches a python version specifier, such as `>=1.19.a1` or `4.1.*`. Extends the PEP 440
/// version regex to version specifiers
static ref VERSION_SPECIFIER_RE: Regex = Regex::new(&format!(
r#"(?xi)^(?:\s*)(?P<operator>(~=|==|!=|<=|>=|<|>|===))(?:\s*){}(?:\s*)$"#,
VERSION_RE_INNER
)).unwrap();
}
static VERSION_SPECIFIER_RE: Lazy<Regex> = Lazy::new(|| {
Regex::new(&format!(
r#"(?xi)^(?:\s*)(?P<operator>(~=|==|!=|<=|>=|<|>|===))(?:\s*){VERSION_RE_INNER}(?:\s*)$"#,
))
.unwrap()
});
/// A thin wrapper around `Vec<VersionSpecifier>` with a serde implementation
///
@ -91,10 +91,10 @@ impl Display for VersionSpecifiers {
for (idx, version_specifier) in self.0.iter().enumerate() {
// Separate version specifiers by comma, but we need one comma less than there are
// specifiers
if idx != 0 {
write!(f, ", {}", version_specifier)?;
if idx == 0 {
write!(f, "{version_specifier}")?;
} else {
write!(f, "{}", version_specifier)?;
write!(f, ", {version_specifier}")?;
}
}
Ok(())
@ -125,8 +125,8 @@ impl VersionSpecifiersIter {
impl VersionSpecifiers {
/// PEP 440 parsing
#[new]
pub fn __new__(version_specifiers: String) -> PyResult<Self> {
Self::from_str(&version_specifiers).map_err(|err| PyValueError::new_err(err.to_string()))
pub fn __new__(version_specifiers: &str) -> PyResult<Self> {
Self::from_str(version_specifiers).map_err(|err| PyValueError::new_err(err.to_string()))
}
/// PEP 440 serialization
@ -150,6 +150,7 @@ impl VersionSpecifiers {
})
}
#[allow(clippy::needless_pass_by_value)]
fn __iter__(slf: PyRef<'_, Self>) -> PyResult<Py<VersionSpecifiersIter>> {
let iter = VersionSpecifiersIter {
inner: slf.0.clone().into_iter(),
@ -207,8 +208,8 @@ impl Serialize for VersionSpecifiers {
/// let version_specifier = VersionSpecifier::from_str("== 1.*").unwrap();
/// assert!(version_specifier.contains(&version));
/// ```
#[cfg_attr(feature = "pyo3", pyclass(get_all))]
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
#[cfg_attr(feature = "pyo3", pyclass(get_all))]
pub struct VersionSpecifier {
/// ~=|==|!=|<=|>=|<|>|===, plus whether the version ended with a star
pub(crate) operator: Operator,
@ -222,8 +223,8 @@ impl VersionSpecifier {
// Since we don't bring FromStr to python
/// Parse a PEP 440 version
#[new]
pub fn parse(version_specifier: String) -> PyResult<Self> {
Self::from_str(&version_specifier).map_err(PyValueError::new_err)
pub fn parse(version_specifier: &str) -> PyResult<Self> {
Self::from_str(version_specifier).map_err(PyValueError::new_err)
}
/// See [VersionSpecifier::contains]
@ -244,7 +245,7 @@ impl VersionSpecifier {
/// Returns the normalized representation
pub fn __repr__(&self) -> String {
format!(r#"<VersionSpecifier("{}")>"#, self)
format!(r#"<VersionSpecifier("{self}")>"#)
}
fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult<bool> {
@ -265,7 +266,7 @@ impl VersionSpecifier {
}
}
/// https://github.com/serde-rs/serde/issues/1316#issue-332908452
/// <https://github.com/serde-rs/serde/issues/1316#issue-332908452>
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for VersionSpecifier {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
@ -277,7 +278,7 @@ impl<'de> Deserialize<'de> for VersionSpecifier {
}
}
/// 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 VersionSpecifier {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
@ -309,7 +310,7 @@ impl VersionSpecifier {
operator,
local
.iter()
.map(|x| x.to_string())
.map(std::string::ToString::to_string)
.collect::<Vec<String>>()
.join(".")
));
@ -323,8 +324,7 @@ impl VersionSpecifier {
Operator::NotEqual => Operator::NotEqualStar,
other => {
return Err(format!(
"Operator {} must not be used in version ending with a star",
other
"Operator {other} must not be used in version ending with a star"
))
}
}
@ -489,7 +489,7 @@ impl FromStr for VersionSpecifier {
fn from_str(spec: &str) -> Result<Self, Self::Err> {
let captures = VERSION_SPECIFIER_RE
.captures(spec)
.ok_or_else(|| format!("Version specifier `{}` doesn't match PEP 440 rules", spec))?;
.ok_or_else(|| format!("Version specifier `{spec}` doesn't match PEP 440 rules"))?;
let (version, star) = Version::parse_impl(&captures)?;
// operator but we don't know yet if it has a star
let operator = Operator::from_str(&captures["operator"])?;
@ -509,7 +509,7 @@ impl Display for VersionSpecifier {
/// Parses a list of specifiers such as `>= 1.0, != 1.3.*, < 2.0`.
///
/// I recommend using [VersionSpecifiers::from_str] instead.
/// I recommend using [`VersionSpecifiers::from_str`] instead.
///
/// ```rust
/// use std::str::FromStr;
@ -625,8 +625,8 @@ mod test {
"1!1.2.rev33+123456",
];
/// https://github.com/pypa/packaging/blob/237ff3aa348486cf835a980592af3a59fccd6101/tests/test_version.py#L666-L707
/// https://github.com/pypa/packaging/blob/237ff3aa348486cf835a980592af3a59fccd6101/tests/test_version.py#L709-L750
/// <https://github.com/pypa/packaging/blob/237ff3aa348486cf835a980592af3a59fccd6101/tests/test_version.py#L666-L707>
/// <https://github.com/pypa/packaging/blob/237ff3aa348486cf835a980592af3a59fccd6101/tests/test_version.py#L709-L750>
///
/// These tests are a lot shorter than the pypa/packaging version since we implement all
/// comparisons through one method
@ -667,7 +667,7 @@ mod test {
.collect();
for (a, b, ordering) in operations {
assert_eq!(a.cmp(b), ordering, "{} {:?} {}", a, ordering, b);
assert_eq!(a.cmp(b), ordering, "{a} {ordering:?} {b}");
}
}
@ -792,7 +792,7 @@ mod test {
/// Test for tilde equal (~=) and star equal (== x.y.*) recorded from pypa/packaging
///
/// Well, except for https://github.com/pypa/packaging/issues/617
/// Well, except for <https://github.com/pypa/packaging/issues/617>
#[test]
fn test_operators_other() {
let versions: Vec<Version> = VERSIONS_0
@ -809,9 +809,10 @@ mod test {
.iter()
.map(|specifier| specifier.contains(version))
.collect::<Vec<bool>>();
for ((actual, expected), specifier) in actual.iter().zip(expected).zip(SPECIFIERS_OTHER)
for ((actual, expected), _specifier) in
actual.iter().zip(expected).zip(SPECIFIERS_OTHER)
{
assert_eq!(actual, expected, "{} {}", version, specifier);
assert_eq!(actual, expected);
}
}
}
@ -924,9 +925,7 @@ mod test {
VersionSpecifier::from_str(specifier)
.unwrap()
.contains(&Version::from_str(version).unwrap()),
"{} {}",
version,
specifier
"{version} {specifier}"
);
}
}
@ -1027,9 +1026,7 @@ mod test {
!VersionSpecifier::from_str(specifier)
.unwrap()
.contains(&Version::from_str(version).unwrap()),
"{} {}",
version,
specifier
"{version} {specifier}"
);
}
}
@ -1246,15 +1243,12 @@ mod test {
];
for (specifier, error) in specifiers {
if let Some(error) = error {
assert_eq!(VersionSpecifier::from_str(specifier).unwrap_err(), error)
assert_eq!(VersionSpecifier::from_str(specifier).unwrap_err(), error);
} else {
assert_eq!(
VersionSpecifier::from_str(specifier).unwrap_err(),
format!(
"Version specifier `{}` doesn't match PEP 440 rules",
specifier
)
)
format!("Version specifier `{specifier}` doesn't match PEP 440 rules",)
);
}
}
}

View file

@ -1,177 +0,0 @@
name: Release
on:
push:
tags:
- v*
jobs:
crates-io:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
- name: Push to crates.io
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: cargo publish
macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.9
architecture: x64
- uses: dtolnay/rust-toolchain@stable
- name: Build wheels - x86_64
uses: PyO3/maturin-action@v1
with:
target: x86_64
args: --release --strip --out dist --sdist
env:
RUSTFLAGS: "-C link-arg=-undefined -C link-arg=dynamic_lookup"
- name: Build wheels - universal2
uses: PyO3/maturin-action@v1
with:
args: --release --strip --target --target universal2-apple-darwin --out dist
env:
RUSTFLAGS: "-C link-arg=-undefined -C link-arg=dynamic_lookup"
- name: Upload wheels
uses: actions/upload-artifact@v3
with:
name: wheels
path: dist
windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.9
- uses: dtolnay/rust-toolchain@stable
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
args: --release --strip --out dist
- name: Upload wheels
uses: actions/upload-artifact@v3
with:
name: wheels
path: dist
linux:
runs-on: ubuntu-latest
strategy:
matrix:
target: [ x86_64 ]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.9
architecture: x64
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.target }}
args: --release --strip --out dist
- name: Upload wheels
uses: actions/upload-artifact@v3
with:
name: wheels
path: dist
linux-cross:
runs-on: ubuntu-latest
strategy:
matrix:
target: [ aarch64, armv7, s390x, ppc64le, ppc64 ]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.9
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
rust-toolchain: nightly
target: ${{ matrix.target }}
args: --release --strip --out dist
- name: Upload wheels
uses: actions/upload-artifact@v3
with:
name: wheels
path: dist
musllinux:
runs-on: ubuntu-latest
strategy:
matrix:
target:
- x86_64-unknown-linux-musl
- i686-unknown-linux-musl
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.9
architecture: x64
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.target }}
manylinux: musllinux_1_2
args: --release --strip --out dist
- name: Upload wheels
uses: actions/upload-artifact@v3
with:
name: wheels
path: dist
musllinux-cross:
runs-on: ubuntu-latest
strategy:
matrix:
platform:
- target: aarch64-unknown-linux-musl
arch: aarch64
- target: armv7-unknown-linux-musleabihf
arch: armv7
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.9
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
rust-toolchain: nightly
target: ${{ matrix.platform.target }}
manylinux: musllinux_1_2
args: --release --strip --out dist
- name: Upload wheels
uses: actions/upload-artifact@v3
with:
name: wheels
path: dist
release:
name: Release
runs-on: ubuntu-latest
needs: [ macos, windows, linux, linux-cross, musllinux, musllinux-cross ]
environment: release
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v3
with:
name: wheels
- name: Publish to PyPI
uses: PyO3/maturin-action@v1
with:
command: upload
args: --skip-existing *

View file

@ -1,38 +0,0 @@
name: test
on: [ push, pull_request ]
env:
CARGO_TERM_COLOR: always
jobs:
test:
strategy:
matrix:
os: [ ubuntu-latest, windows-latest, macos-latest ]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- run: rustup toolchain install stable --profile minimal
- uses: Swatinem/rust-cache@v2
- name: Run tests
run: cargo test
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt
- uses: Swatinem/rust-cache@v2
- name: Ruff
run: pipx run ruff check --format github .
- name: black
run: pipx run black --check .
- name: Rustfmt
run: cargo fmt --all -- --check
- name: Clippy (pure rust)
run: cargo clippy --tests -- -D warnings
- name: Clippy (pyo3)
run: cargo clippy --tests --all-features -- -D warnings

View file

@ -1,6 +0,0 @@
.ipynb_checkpoints
.venv
/pypi_analysis/pipy_requires_dist.ndjson
/pypi_analysis/pypi_all.ndjson
__pycache__
target

View file

@ -4,25 +4,28 @@ version = "0.2.3"
description = "A library for python dependency specifiers, better known as PEP 508"
edition = "2021"
include = ["/src", "Changelog.md", "License-Apache", "License-BSD", "Readme.md", "pyproject.toml"]
# Same license as pypa/packaging where the tests are from
license = "Apache-2.0 OR BSD-2-Clause"
readme = "Readme.md"
repository = "https://github.com/konstin/pep508_rs"
[lib]
name = "pep508_rs"
crate-type = ["cdylib", "rlib"]
[dependencies]
anyhow = { version = "1.0.75", optional = true }
once_cell = "1.18.0"
pep440_rs = "0.3.11"
pep440_rs = { path = "../pep440-rs" }
once_cell = { workspace = true }
regex = { workspace = true }
serde = { workspace = true, features = ["derive"], optional = true }
serde_json = { workspace = true, optional = true }
thiserror = { workspace = true }
tracing = { workspace = true, features = ["log"] }
unicode-width = { workspace = true }
url = { workspace = true, features = ["serde"] }
pyo3 = { version = "0.19.2", optional = true, features = ["abi3", "extension-module"] }
pyo3-log = { version = "0.8.3", optional = true }
regex = { version = "1.9.5", default-features = false, features = ["std"] }
serde = { version = "1.0.188", features = ["derive"], optional = true }
serde_json = { version = "1.0.107", optional = true }
thiserror = "1.0.49"
toml = { version = "0.8.1", optional = true }
tracing = { version = "0.1.37", features = ["log"] }
unicode-width = "0.1.11"
url = { version = "2.4.1", features = ["serde"] }
[dev-dependencies]
indoc = "2.0.4"
@ -33,17 +36,4 @@ serde_json = "1.0.107"
[features]
pyo3 = ["dep:pyo3", "pep440_rs/pyo3", "pyo3-log"]
serde = ["dep:serde", "pep440_rs/serde"]
modern = ["serde", "toml", "anyhow"]
default = []
[lib]
name = "pep508_rs"
crate-type = ["cdylib", "rlib"]
[profile.release]
debug = true
[profile.maturin]
inherits = "release"
strip = true

View file

@ -1,580 +0,0 @@
# This file is automatically @generated by Poetry 1.5.0 and should not be changed by hand.
[[package]]
name = "appnope"
version = "0.1.3"
description = "Disable App Nap on macOS >= 10.9"
optional = false
python-versions = "*"
files = [
{file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"},
{file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"},
]
[[package]]
name = "backcall"
version = "0.2.0"
description = "Specifications for callback functions passed in to an API"
optional = false
python-versions = "*"
files = [
{file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"},
{file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"},
]
[[package]]
name = "black"
version = "23.3.0"
description = "The uncompromising code formatter."
optional = false
python-versions = ">=3.7"
files = [
{file = "black-23.3.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915"},
{file = "black-23.3.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9"},
{file = "black-23.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2"},
{file = "black-23.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c"},
{file = "black-23.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c"},
{file = "black-23.3.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6"},
{file = "black-23.3.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b"},
{file = "black-23.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d"},
{file = "black-23.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70"},
{file = "black-23.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326"},
{file = "black-23.3.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b"},
{file = "black-23.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2"},
{file = "black-23.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925"},
{file = "black-23.3.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27"},
{file = "black-23.3.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331"},
{file = "black-23.3.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5"},
{file = "black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"},
{file = "black-23.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8"},
{file = "black-23.3.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30"},
{file = "black-23.3.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"},
{file = "black-23.3.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266"},
{file = "black-23.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab"},
{file = "black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"},
{file = "black-23.3.0-py3-none-any.whl", hash = "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4"},
{file = "black-23.3.0.tar.gz", hash = "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940"},
]
[package.dependencies]
click = ">=8.0.0"
ipython = {version = ">=7.8.0", optional = true, markers = "extra == \"jupyter\""}
mypy-extensions = ">=0.4.3"
packaging = ">=22.0"
pathspec = ">=0.9.0"
platformdirs = ">=2"
tokenize-rt = {version = ">=3.2.0", optional = true, markers = "extra == \"jupyter\""}
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""}
typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
[package.extras]
colorama = ["colorama (>=0.4.3)"]
d = ["aiohttp (>=3.7.4)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"]
[[package]]
name = "click"
version = "8.1.3"
description = "Composable command line interface toolkit"
optional = false
python-versions = ">=3.7"
files = [
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
]
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
[[package]]
name = "colorama"
version = "0.4.6"
description = "Cross-platform colored terminal text."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
[[package]]
name = "decorator"
version = "5.1.1"
description = "Decorators for Humans"
optional = false
python-versions = ">=3.5"
files = [
{file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"},
{file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
]
[[package]]
name = "exceptiongroup"
version = "1.1.1"
description = "Backport of PEP 654 (exception groups)"
optional = false
python-versions = ">=3.7"
files = [
{file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"},
{file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"},
]
[package.extras]
test = ["pytest (>=6)"]
[[package]]
name = "importlib-metadata"
version = "6.6.0"
description = "Read metadata from Python packages"
optional = false
python-versions = ">=3.7"
files = [
{file = "importlib_metadata-6.6.0-py3-none-any.whl", hash = "sha256:43dd286a2cd8995d5eaef7fee2066340423b818ed3fd70adf0bad5f1fac53fed"},
{file = "importlib_metadata-6.6.0.tar.gz", hash = "sha256:92501cdf9cc66ebd3e612f1b4f0c0765dfa42f0fa38ffb319b6bd84dd675d705"},
]
[package.dependencies]
typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
zipp = ">=0.5"
[package.extras]
docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
perf = ["ipython"]
testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"]
[[package]]
name = "iniconfig"
version = "2.0.0"
description = "brain-dead simple config-ini parsing"
optional = false
python-versions = ">=3.7"
files = [
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
]
[[package]]
name = "ipython"
version = "7.34.0"
description = "IPython: Productive Interactive Computing"
optional = false
python-versions = ">=3.7"
files = [
{file = "ipython-7.34.0-py3-none-any.whl", hash = "sha256:c175d2440a1caff76116eb719d40538fbb316e214eda85c5515c303aacbfb23e"},
{file = "ipython-7.34.0.tar.gz", hash = "sha256:af3bdb46aa292bce5615b1b2ebc76c2080c5f77f54bda2ec72461317273e7cd6"},
]
[package.dependencies]
appnope = {version = "*", markers = "sys_platform == \"darwin\""}
backcall = "*"
colorama = {version = "*", markers = "sys_platform == \"win32\""}
decorator = "*"
jedi = ">=0.16"
matplotlib-inline = "*"
pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""}
pickleshare = "*"
prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0"
pygments = "*"
setuptools = ">=18.5"
traitlets = ">=4.2"
[package.extras]
all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.17)", "pygments", "qtconsole", "requests", "testpath"]
doc = ["Sphinx (>=1.3)"]
kernel = ["ipykernel"]
nbconvert = ["nbconvert"]
nbformat = ["nbformat"]
notebook = ["ipywidgets", "notebook"]
parallel = ["ipyparallel"]
qtconsole = ["qtconsole"]
test = ["ipykernel", "nbformat", "nose (>=0.10.1)", "numpy (>=1.17)", "pygments", "requests", "testpath"]
[[package]]
name = "jedi"
version = "0.18.2"
description = "An autocompletion tool for Python that can be used for text editors."
optional = false
python-versions = ">=3.6"
files = [
{file = "jedi-0.18.2-py2.py3-none-any.whl", hash = "sha256:203c1fd9d969ab8f2119ec0a3342e0b49910045abe6af0a3ae83a5764d54639e"},
{file = "jedi-0.18.2.tar.gz", hash = "sha256:bae794c30d07f6d910d32a7048af09b5a39ed740918da923c6b780790ebac612"},
]
[package.dependencies]
parso = ">=0.8.0,<0.9.0"
[package.extras]
docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"]
qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"]
[[package]]
name = "matplotlib-inline"
version = "0.1.6"
description = "Inline Matplotlib backend for Jupyter"
optional = false
python-versions = ">=3.5"
files = [
{file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"},
{file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"},
]
[package.dependencies]
traitlets = "*"
[[package]]
name = "maturin"
version = "1.0.1"
description = "Build and publish crates with pyo3, rust-cpython and cffi bindings as well as rust binaries as python packages"
optional = false
python-versions = ">=3.7"
files = [
{file = "maturin-1.0.1-py3-none-linux_armv6l.whl", hash = "sha256:10097e2602330c0b9db16d7dfd002476f5e5cf99df58ba2f3abc6de64a69e9a6"},
{file = "maturin-1.0.1-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:9ecebccb111c9c870fb2f5eee17518fe106f676227bb16f204a51e7a162aceec"},
{file = "maturin-1.0.1-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:b39f9a42b3c8242e3f3ab990bd03ba989c6c07e4de9e21fcf877a2418119d445"},
{file = "maturin-1.0.1-py3-none-manylinux_2_12_i686.manylinux2010_i686.musllinux_1_1_i686.whl", hash = "sha256:c0b1efa47f8b7d15bc5945159764ce57316f9d1bfb7c8caa07cebdd41318359b"},
{file = "maturin-1.0.1-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:d392ec0578d9e6f03914837cef7bbb264d5708807e0b48176b6ff0b50083ba7c"},
{file = "maturin-1.0.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:d271b24febbfc020561984b1acdfc39b132df21f4e42d7af0fe274ea738c8000"},
{file = "maturin-1.0.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:8d88d1595d7514c27df96d5f4fe3dc5f24288528a746439403f27c3b448fca16"},
{file = "maturin-1.0.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:04c0279dd0d6ccd317018bd1a43f52cbda715822537ae1a68015c9171f18b2fd"},
{file = "maturin-1.0.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:787bb56c80eda482ece2dd4788d479dbd0e74d981b2e2c538228365c19290fb7"},
{file = "maturin-1.0.1-py3-none-win32.whl", hash = "sha256:6d9b4ff7c2d501e91886b859296f5c0478fc08bc7d537a72f98a69d51ff4f519"},
{file = "maturin-1.0.1-py3-none-win_amd64.whl", hash = "sha256:2907b345186a83db4bbe5571830509b3031784d08958b32d2ffa7857bd473725"},
{file = "maturin-1.0.1-py3-none-win_arm64.whl", hash = "sha256:6b020b9abbd1e9fef468c171216dc4be053834b5bf638075264ee090a993b0b0"},
{file = "maturin-1.0.1.tar.gz", hash = "sha256:71fdb2dbbd5bcc60bd91ddcbe34dba9f04cc53c2add089a95a79d0d8fc8337b8"},
]
[package.dependencies]
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
[package.extras]
patchelf = ["patchelf"]
zig = ["ziglang (>=0.10.0,<0.11.0)"]
[[package]]
name = "mypy-extensions"
version = "1.0.0"
description = "Type system extensions for programs checked with the mypy type checker."
optional = false
python-versions = ">=3.5"
files = [
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
]
[[package]]
name = "packaging"
version = "23.1"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.7"
files = [
{file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"},
{file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"},
]
[[package]]
name = "parso"
version = "0.8.3"
description = "A Python Parser"
optional = false
python-versions = ">=3.6"
files = [
{file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"},
{file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"},
]
[package.extras]
qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
testing = ["docopt", "pytest (<6.0.0)"]
[[package]]
name = "pathspec"
version = "0.11.1"
description = "Utility library for gitignore style pattern matching of file paths."
optional = false
python-versions = ">=3.7"
files = [
{file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"},
{file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"},
]
[[package]]
name = "pexpect"
version = "4.8.0"
description = "Pexpect allows easy control of interactive console applications."
optional = false
python-versions = "*"
files = [
{file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"},
{file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"},
]
[package.dependencies]
ptyprocess = ">=0.5"
[[package]]
name = "pickleshare"
version = "0.7.5"
description = "Tiny 'shelve'-like database with concurrency support"
optional = false
python-versions = "*"
files = [
{file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"},
{file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"},
]
[[package]]
name = "platformdirs"
version = "3.5.1"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
optional = false
python-versions = ">=3.7"
files = [
{file = "platformdirs-3.5.1-py3-none-any.whl", hash = "sha256:e2378146f1964972c03c085bb5662ae80b2b8c06226c54b2ff4aa9483e8a13a5"},
{file = "platformdirs-3.5.1.tar.gz", hash = "sha256:412dae91f52a6f84830f39a8078cecd0e866cb72294a5c66808e74d5e88d251f"},
]
[package.dependencies]
typing-extensions = {version = ">=4.5", markers = "python_version < \"3.8\""}
[package.extras]
docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.2.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"]
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
[[package]]
name = "pluggy"
version = "1.0.0"
description = "plugin and hook calling mechanisms for python"
optional = false
python-versions = ">=3.6"
files = [
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
]
[package.dependencies]
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
[package.extras]
dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "prompt-toolkit"
version = "3.0.38"
description = "Library for building powerful interactive command lines in Python"
optional = false
python-versions = ">=3.7.0"
files = [
{file = "prompt_toolkit-3.0.38-py3-none-any.whl", hash = "sha256:45ea77a2f7c60418850331366c81cf6b5b9cf4c7fd34616f733c5427e6abbb1f"},
{file = "prompt_toolkit-3.0.38.tar.gz", hash = "sha256:23ac5d50538a9a38c8bde05fecb47d0b403ecd0662857a86f886f798563d5b9b"},
]
[package.dependencies]
wcwidth = "*"
[[package]]
name = "ptyprocess"
version = "0.7.0"
description = "Run a subprocess in a pseudo terminal"
optional = false
python-versions = "*"
files = [
{file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"},
{file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"},
]
[[package]]
name = "pygments"
version = "2.15.1"
description = "Pygments is a syntax highlighting package written in Python."
optional = false
python-versions = ">=3.7"
files = [
{file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"},
{file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"},
]
[package.extras]
plugins = ["importlib-metadata"]
[[package]]
name = "pytest"
version = "7.3.1"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.7"
files = [
{file = "pytest-7.3.1-py3-none-any.whl", hash = "sha256:3799fa815351fea3a5e96ac7e503a96fa51cc9942c3753cda7651b93c1cfa362"},
{file = "pytest-7.3.1.tar.gz", hash = "sha256:434afafd78b1d78ed0addf160ad2b77a30d35d4bdf8af234fe621919d9ed15e3"},
]
[package.dependencies]
colorama = {version = "*", markers = "sys_platform == \"win32\""}
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
iniconfig = "*"
packaging = "*"
pluggy = ">=0.12,<2.0"
tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
[package.extras]
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
[[package]]
name = "ruff"
version = "0.0.270"
description = "An extremely fast Python linter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
{file = "ruff-0.0.270-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:f74c4d550f7b8e808455ac77bbce38daafc458434815ba0bc21ae4bdb276509b"},
{file = "ruff-0.0.270-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:643de865fd35cb76c4f0739aea5afe7b8e4d40d623df7e9e6ea99054e5cead0a"},
{file = "ruff-0.0.270-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eca02e709b3308eb7255b5f74e779be23b5980fca3862eae28bb23069cd61ae4"},
{file = "ruff-0.0.270-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3ed3b198768d2b3a2300fb18f730cd39948a5cc36ba29ae9d4639a11040880be"},
{file = "ruff-0.0.270-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:739495d2dbde87cf4e3110c8d27bc20febf93112539a968a4e02c26f0deccd1d"},
{file = "ruff-0.0.270-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:08188f8351f4c0b6216e8463df0a76eb57894ca59a3da65e4ed205db980fd3ae"},
{file = "ruff-0.0.270-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0827b074635d37984fc98d99316bfab5c8b1231bb83e60dacc83bd92883eedb4"},
{file = "ruff-0.0.270-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d61ae4841313f6eeb8292dc349bef27b4ce426e62c36e80ceedc3824e408734"},
{file = "ruff-0.0.270-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0eb412f20e77529a01fb94d578b19dcb8331b56f93632aa0cce4a2ea27b7aeba"},
{file = "ruff-0.0.270-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b775e2c5fc869359daf8c8b8aa0fd67240201ab2e8d536d14a0edf279af18786"},
{file = "ruff-0.0.270-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:21f00e47ab2308617c44435c8dfd9e2e03897461c9e647ec942deb2a235b4cfd"},
{file = "ruff-0.0.270-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0bbfbf6fd2436165566ca85f6e57be03ed2f0a994faf40180cfbb3604c9232ef"},
{file = "ruff-0.0.270-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8af391ef81f7be960be10886a3c1aac0b298bde7cb9a86ec2b05faeb2081ce6b"},
{file = "ruff-0.0.270-py3-none-win32.whl", hash = "sha256:b4c037fe2f75bcd9aed0c89c7c507cb7fa59abae2bd4c8b6fc331a28178655a4"},
{file = "ruff-0.0.270-py3-none-win_amd64.whl", hash = "sha256:0012f9b7dc137ab7f1f0355e3c4ca49b562baf6c9fa1180948deeb6648c52957"},
{file = "ruff-0.0.270-py3-none-win_arm64.whl", hash = "sha256:9613456b0b375766244c25045e353bc8890c856431cd97893c97b10cc93bd28d"},
{file = "ruff-0.0.270.tar.gz", hash = "sha256:95db07b7850b30ebf32b27fe98bc39e0ab99db3985edbbf0754d399eb2f0e690"},
]
[[package]]
name = "setuptools"
version = "67.8.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false
python-versions = ">=3.7"
files = [
{file = "setuptools-67.8.0-py3-none-any.whl", hash = "sha256:5df61bf30bb10c6f756eb19e7c9f3b473051f48db77fddbe06ff2ca307df9a6f"},
{file = "setuptools-67.8.0.tar.gz", hash = "sha256:62642358adc77ffa87233bc4d2354c4b2682d214048f500964dbe760ccedf102"},
]
[package.extras]
docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
[[package]]
name = "tokenize-rt"
version = "5.0.0"
description = "A wrapper around the stdlib `tokenize` which roundtrips."
optional = false
python-versions = ">=3.7"
files = [
{file = "tokenize_rt-5.0.0-py2.py3-none-any.whl", hash = "sha256:c67772c662c6b3dc65edf66808577968fb10badfc2042e3027196bed4daf9e5a"},
{file = "tokenize_rt-5.0.0.tar.gz", hash = "sha256:3160bc0c3e8491312d0485171dea861fc160a240f5f5766b72a1165408d10740"},
]
[[package]]
name = "tomli"
version = "2.0.1"
description = "A lil' TOML parser"
optional = false
python-versions = ">=3.7"
files = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
[[package]]
name = "traitlets"
version = "5.9.0"
description = "Traitlets Python configuration system"
optional = false
python-versions = ">=3.7"
files = [
{file = "traitlets-5.9.0-py3-none-any.whl", hash = "sha256:9e6ec080259b9a5940c797d58b613b5e31441c2257b87c2e795c5228ae80d2d8"},
{file = "traitlets-5.9.0.tar.gz", hash = "sha256:f6cde21a9c68cf756af02035f72d5a723bf607e862e7be33ece505abf4a3bad9"},
]
[package.extras]
docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"]
test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"]
[[package]]
name = "typed-ast"
version = "1.5.4"
description = "a fork of Python 2 and 3 ast modules with type comment support"
optional = false
python-versions = ">=3.6"
files = [
{file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"},
{file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"},
{file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"},
{file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"},
{file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"},
{file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"},
{file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"},
{file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"},
{file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"},
{file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"},
{file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"},
{file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"},
{file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"},
{file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"},
{file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"},
{file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"},
{file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"},
{file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"},
{file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"},
{file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"},
{file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"},
{file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"},
{file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"},
{file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"},
]
[[package]]
name = "typing-extensions"
version = "4.6.2"
description = "Backported and Experimental Type Hints for Python 3.7+"
optional = false
python-versions = ">=3.7"
files = [
{file = "typing_extensions-4.6.2-py3-none-any.whl", hash = "sha256:3a8b36f13dd5fdc5d1b16fe317f5668545de77fa0b8e02006381fd49d731ab98"},
{file = "typing_extensions-4.6.2.tar.gz", hash = "sha256:06006244c70ac8ee83fa8282cb188f697b8db25bc8b4df07be1873c43897060c"},
]
[[package]]
name = "wcwidth"
version = "0.2.6"
description = "Measures the displayed width of unicode strings in a terminal"
optional = false
python-versions = "*"
files = [
{file = "wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"},
{file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"},
]
[[package]]
name = "zipp"
version = "3.15.0"
description = "Backport of pathlib-compatible object wrapper for zip files"
optional = false
python-versions = ">=3.7"
files = [
{file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"},
{file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"},
]
[package.extras]
docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
[metadata]
lock-version = "2.0"
python-versions = ">=3.7"
content-hash = "f64653737d40fcf3c7b9f9fea186e98322fea9e7ee1889b8f8ee181570b26e17"

View file

@ -1,32 +0,0 @@
[project]
name = "pep508_rs"
version = "0.2.2"
description = "A library for python dependency specifiers, better known as PEP 508"
readme = "Readme.md"
[tool.poetry]
name = "pep508_rs"
version = "0.1.1"
description = ""
authors = ["konstin <konstin@mailbox.org>"]
readme = "Readme.md"
[tool.poetry.dependencies]
python = ">=3.7"
[tool.poetry.group.dev.dependencies]
black = { extras = ["jupyter"], version = "^23.1.0" }
maturin = "^1.0.0"
pytest = "^7.2.0"
ruff = "^0.0.270"
[tool.maturin]
features = ["pyo3"]
[tool.pytest.ini_options]
minversion = "7.2.0"
addopts = "--tb=short"
[build-system]
requires = ["maturin>=1.0,<2.0"]
build-backend = "maturin"

View file

@ -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,12 +452,12 @@ impl<'a> CharIter<'a> {
while let Some(char) = self.peek_char() {
if !condition(char) {
break;
} else {
}
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,13 +567,12 @@ 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,
@ -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::{

View file

@ -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(),

View file

@ -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)
}
}

View file

@ -1,97 +0,0 @@
from collections import namedtuple
from unittest import mock
import pytest
from pep508_rs import Requirement, MarkerEnvironment, Pep508Error, VersionSpecifier
def test_pep508():
req = Requirement("numpy; python_version >= '3.7'")
assert req.name == "numpy"
env = MarkerEnvironment.current()
assert req.evaluate_markers(env, [])
req2 = Requirement("numpy; python_version < '3.7'")
assert not req2.evaluate_markers(env, [])
requests = Requirement(
'requests [security,tests] >=2.8.1, ==2.8.* ; python_version > "3.8"'
)
assert requests.name == "requests"
assert requests.extras == ["security", "tests"]
assert requests.version_or_url == [
VersionSpecifier(">=2.8.1"),
VersionSpecifier("==2.8.*"),
]
assert requests.marker == "python_version > '3.8'"
def test_marker():
env = MarkerEnvironment.current()
assert not Requirement("numpy; extra == 'science'").evaluate_markers(env, [])
assert Requirement("numpy; extra == 'science'").evaluate_markers(env, ["science"])
assert not Requirement(
"numpy; extra == 'science' and extra == 'arrays'"
).evaluate_markers(env, ["science"])
assert Requirement(
"numpy; extra == 'science' or extra == 'arrays'"
).evaluate_markers(env, ["science"])
class FakeVersionInfo(
namedtuple("FakeVersionInfo", ["major", "minor", "micro", "releaselevel", "serial"])
):
pass
@pytest.mark.parametrize(
("version", "version_str"),
[
(FakeVersionInfo(3, 10, 11, "final", 0), "3.10.11"),
(FakeVersionInfo(3, 10, 11, "rc", 1), "3.10.11rc1"),
],
)
def test_marker_values(version, version_str):
with mock.patch("sys.implementation.version", version):
env = MarkerEnvironment.current()
assert str(env.implementation_version.version) == version_str
def test_marker_values_current_platform():
MarkerEnvironment.current()
def test_errors():
with pytest.raises(
Pep508Error,
match="Expected an alphanumeric character starting the extra name, found 'ö'",
):
Requirement("numpy[ö]; python_version < '3.7'")
def test_warnings(caplog):
env = MarkerEnvironment.current()
assert not Requirement("numpy; '3.6' < '3.7'").evaluate_markers(env, [])
assert caplog.messages == [
"Comparing two quoted strings with each other doesn't make sense: "
"'3.6' < '3.7', evaluating to false"
]
caplog.clear()
assert not Requirement("numpy; 'a' < 'b'").evaluate_markers(env, [])
assert caplog.messages == [
"Comparing two quoted strings with each other doesn't make sense: "
"'a' < 'b', evaluating to false"
]
caplog.clear()
Requirement("numpy; python_version >= '3.9.'").evaluate_markers(env, [])
assert caplog.messages == [
"Expected PEP 440 version to compare with python_version, found '3.9.', "
"evaluating to false: Version `3.9.` doesn't match PEP 440 rules"
]
caplog.clear()
# pickleshare 0.7.5
Requirement("numpy; python_version in '2.6 2.7 3.2 3.3'").evaluate_markers(env, [])
assert caplog.messages == [
"Expected PEP 440 version to compare with python_version, "
"found '2.6 2.7 3.2 3.3', "
"evaluating to false: Version `2.6 2.7 3.2 3.3` doesn't match PEP 440 rules"
]

View file

@ -17,8 +17,8 @@ colored = { workspace = true }
directories = { workspace = true }
futures = { workspace = true }
install-wheel-rs = { workspace = true }
pep508_rs = { workspace = true }
pep440_rs = { workspace = true }
pep508_rs = { path = "../pep508-rs" }
pep440_rs = { path = "../pep440-rs" }
tracing = { workspace = true }
tracing-tree = { workspace = true }
tracing-subscriber = { workspace = true }

View file

@ -10,10 +10,10 @@ authors.workspace = true
license.workspace = true
[dependencies]
pep440_rs = { path = "../pep440-rs" }
pep508_rs = { path = "../pep508-rs" }
puffin-platform = { path = "../puffin-platform" }
anyhow = { workspace = true }
pep440_rs = { workspace = true }
pep508_rs = { workspace = true }
serde_json = { workspace = true }
tracing = { workspace = true }

View file

@ -4,14 +4,14 @@ version = "0.1.0"
edition = "2021"
[dependencies]
pep440_rs = { path = "../pep440-rs", features = ["serde"] }
pep508_rs = { path = "../pep508-rs", features = ["serde"] }
puffin-platform = { path = "../puffin-platform" }
anyhow = { workspace = true }
mailparse = { workspace = true }
memchr = { workspace = true }
once_cell = { workspace = true }
pep440_rs = { workspace = true, features = ["serde"] }
pep508_rs = { workspace = true, features = ["serde"] }
regex = { workspace = true }
rfc2047-decoder = { workspace = true }
serde = { workspace = true }

View file

@ -12,7 +12,7 @@ license.workspace = true
[dependencies]
glibc_version = { workspace = true }
goblin = { workspace = true }
pep440_rs = { workspace = true }
pep440_rs = { path = "../pep440-rs" }
platform-info = { workspace = true }
plist = { workspace = true }
regex = { workspace = true }

View file

@ -17,6 +17,6 @@ puffin-package = { path = "../puffin-package" }
anyhow = { workspace = true }
bitflags = { workspace = true }
futures = { workspace = true }
pep440_rs = { workspace = true }
pep508_rs = { workspace = true }
pep440_rs = { path = "../pep440-rs" }
pep508_rs = { path = "../pep508-rs" }
tracing = { workspace = true }

View file

@ -156,8 +156,7 @@ pub async fn resolve(
for dependency in metadata.requires_dist {
if !dependency.evaluate_markers(
markers,
// TODO(charlie): Remove this clone.
requirement.extras.clone().unwrap_or_default(),
requirement.extras.as_ref().map_or(&[], Vec::as_slice),
) {
debug!("--> ignoring {dependency} due to environment mismatch");
continue;