uv/crates/uv-pep440
Jacob Woliver f5c3601445
Better error message for version specifier with missing operator (#13803)
## Summary

When missing an operator for version parsing, it would give an error
that was hard to know how to fix if you were not familiar with version
specifiers / PEP-440:

```
Unexpected end of version specifier, expected operator
```

Now, it will attempt to provide a more useful hint if it can parse the
version from the remaining scanner:

```
Unexpected end of version specifier, expected operator (did you mean "==3.12"?)
```
## Test Plan

Unit tests in `version_specifier.rs` were added/updated for the
following cases:
- `test_parse_specifier_missing_operator_error`
- `test_parse_specifier_missing_operator_invalid_version_error`
- `test_invalid_word`
- `test_invalid_specifier`
- `error_message_version_specifiers_parse_error`

A test in `edit.rs` for failing to parse the `pyproject.toml` when using
`add` was also included to match the request in the original issue:
- `add_invalid_requires_python`

I didn't add cases where no version specifier is provided because it
seemed like it doesn't get to the point of parsing in that case, so it
should not happen.


## Reference

Fixes #13126

---------

Co-authored-by: Jacob Woliver <jacob@jmw.sh>
Co-authored-by: konstin <konstin@mailbox.org>
2025-06-03 19:18:19 +00:00
..
src Better error message for version specifier with missing operator (#13803) 2025-06-03 19:18:19 +00:00
CHANGELOG.md Add `uv-` prefix to all internal crates (#7853) 2024-10-01 20:15:32 -04:00
Cargo.toml Remove uv-pep440 cdylib (#10058) 2024-12-20 15:38:13 +00:00
License-Apache Add `uv-` prefix to all internal crates (#7853) 2024-10-01 20:15:32 -04:00
License-BSD Add `uv-` prefix to all internal crates (#7853) 2024-10-01 20:15:32 -04:00
Readme.md Use consistent commas around i.e. and e.g. (#12157) 2025-03-13 23:42:10 +00:00

Readme.md

PEP440 in rust

Crates.io PyPI

A library for python version numbers and specifiers, implementing PEP 440. See Reimplementing PEP 440 for some background.

Higher level bindings to the requirements syntax are available in pep508_rs.

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.contains(&version));

PEP 440 has a lot of unintuitive features, including:

  • An epoch that you can prefix the version with, 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 pre-releases
  • dev versions, which can be attached to sbpth table releases and pre-releases. When attached to a pre-release the dev version is ordered just below the normal pre-release, however when attached to a stable version, the dev version is sorted before a pre-releases
  • pre-release 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
  • pre-release vs. pre-release 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