-
Notifications
You must be signed in to change notification settings - Fork 908
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
6 changed files
with
136 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
# PEP440 in rust | ||
|
||
[![Crates.io](https://img.shields.io/crates/v/pep440_rs.svg?logo=rust&style=flat-square)](https://crates.io/crates/pep440_rs) | ||
[![PyPI](https://img.shields.io/pypi/v/pep440_rs.svg?logo=python&style=flat-square)](https://pypi.org/project/pep440_rs) | ||
|
||
A library for python version numbers and specifiers, implementing | ||
[PEP 440](https://peps.python.org/pep-0440). See [Reimplementing PEP 440](https://cohost.org/konstin/post/514863-reimplementing-pep-4) for some background. | ||
|
||
Higher level bindings to the requirements syntax are available in [pep508_rs](https://github.com/konstin/pep508_rs). | ||
|
||
```rust | ||
use std::str::FromStr; | ||
use pep440_rs::{parse_version_specifiers, Version, VersionSpecifier}; | ||
|
||
let version = Version::from_str("1.19").unwrap(); | ||
let version_specifier = VersionSpecifier::from_str("==1.*").unwrap(); | ||
assert!(version_specifier.contains(&version)); | ||
let version_specifiers = parse_version_specifiers(">=1.16, <2.0").unwrap(); | ||
assert!(version_specifiers.iter().all(|specifier| specifier.contains(&version))); | ||
``` | ||
|
||
In python (`pip install pep440_rs`): | ||
|
||
```python | ||
from pep440_rs import Version, VersionSpecifier | ||
|
||
assert Version("1.1a1").any_prerelease() | ||
assert Version("1.1.dev2").any_prerelease() | ||
assert not Version("1.1").any_prerelease() | ||
assert VersionSpecifier(">=1.0").contains(Version("1.1a1")) | ||
assert not VersionSpecifier(">=1.1").contains(Version("1.1a1")) | ||
# Note that python comparisons are the version ordering, not the version specifiers operators | ||
assert Version("1.1") >= Version("1.1a1") | ||
assert Version("2.0") in VersionSpecifier("==2") | ||
``` | ||
|
||
PEP 440 has a lot of unintuitive features, including: | ||
|
||
* An epoch that you can prefix the version which, e.g. `1!1.2.3`. Lower epoch always means lower | ||
version (`1.0 <=2!0.1`) | ||
* post versions, which can be attached to both stable releases and prereleases | ||
* dev versions, which can be attached to sbpth table releases and prereleases. When attached to a | ||
prerelease the dev version is ordered just below the normal prerelease, however when attached | ||
to a stable version, the dev version is sorted before a prereleases | ||
* prerelease handling is a mess: "Pre-releases of any kind, including developmental releases, | ||
are implicitly excluded from all version specifiers, unless they are already present on the | ||
system, explicitly requested by the user, or if the only available version that satisfies | ||
the version specifier is a pre-release.". This means that we can't say whether a specifier | ||
matches without also looking at the environment | ||
* prelease vs. prerelease incl. dev is fuzzy | ||
* local versions on top of all the others, which are added with a + and have implicitly typed | ||
string and number segments | ||
* no semver-caret (`^`), but a pseudo-semver tilde (`~=`) | ||
* ordering contradicts matching: We have e.g. `1.0+local > 1.0` when sorting, | ||
but `==1.0` matches `1.0+local`. While the ordering of versions itself is a total order | ||
the version matching needs to catch all sorts of special cases |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
# Dependency specifiers (PEP 508) in Rust | ||
|
||
[![Crates.io](https://img.shields.io/crates/v/pep508_rs.svg?logo=rust&style=flat-square)](https://crates.io/crates/pep508_rs) | ||
[![PyPI](https://img.shields.io/pypi/v/pep508_rs.svg?logo=python&style=flat-square)](https://pypi.org/project/pep508_rs) | ||
|
||
A library for python [dependency specifiers](https://packaging.python.org/en/latest/specifications/dependency-specifiers/), better known as [PEP 508](https://peps.python.org/pep-0508/). | ||
|
||
## Usage | ||
|
||
**In Rust** | ||
|
||
```rust | ||
use std::str::FromStr; | ||
use pep508_rs::Requirement; | ||
|
||
let marker = r#"requests [security,tests] >= 2.8.1, == 2.8.* ; python_version > "3.8""#; | ||
let dependency_specification = Requirement::from_str(marker).unwrap(); | ||
assert_eq!(dependency_specification.name, "requests"); | ||
assert_eq!(dependency_specification.extras, Some(vec!["security".to_string(), "tests".to_string()])); | ||
``` | ||
|
||
**In Python** | ||
|
||
```python | ||
from pep508_rs import Requirement | ||
|
||
requests = Requirement( | ||
'requests [security,tests] >= 2.8.1, == 2.8.* ; python_version > "3.8"' | ||
) | ||
assert requests.name == "requests" | ||
assert requests.extras == ["security", "tests"] | ||
assert [str(i) for i in requests.version_or_url] == [">= 2.8.1", "== 2.8.*"] | ||
``` | ||
|
||
Python bindings are built with [maturin](https://github.com/PyO3/maturin), but you can also use the normal `pip install .` | ||
|
||
`Version` and `VersionSpecifier` from [pep440_rs](https://github.com/konstin/pep440-rs) are reexported to avoid type mismatches. | ||
|
||
## Markers | ||
|
||
Markers allow you to install dependencies only in specific environments (python version, operating system, architecture, etc.) or when a specific feature is activated. E.g. you can say `importlib-metadata ; python_version < "3.8"` or `itsdangerous (>=1.1.0) ; extra == 'security'`. Unfortunately, the marker grammar has some oversights (e.g. <https://github.com/pypa/packaging.python.org/pull/1181>) and the design of comparisons (PEP 440 comparisons with lexicographic fallback) leads to confusing outcomes. This implementation tries to carefully validate everything and emit warnings whenever bogus comparisons with unintended semantics are made. | ||
|
||
In python, warnings are by default sent to the normal python logging infrastructure: | ||
|
||
```python | ||
from pep508_rs import Requirement, MarkerEnvironment | ||
|
||
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"]) | ||
``` | ||
|
||
|
||
```python | ||
from pep508_rs import Requirement, MarkerEnvironment | ||
|
||
env = MarkerEnvironment.current() | ||
Requirement("numpy; python_version >= '3.9.'").evaluate_markers(env, []) | ||
# This will log: | ||
# "Expected PEP 440 version to compare with python_version, found '3.9.', " | ||
# "evaluating to false: Version `3.9.` doesn't match PEP 440 rules" | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters