# Reconciling \`packaging\` module & The Dependency Specifier Spec's non-version to version comparison rules

**URL:** <https://discuss.python.org/t/reconciling-packaging-module-the-dependency-specifier-specs-non-version-to-version-comparison-rules/100287>\
**Category:** Standards\
**Created:** [July 27, 2025, 5:40am UTC](https://discuss.python.org/t/reconciling-packaging-module-the-dependency-specifier-specs-non-version-to-version-comparison-rules/100287 "2025-07-27T05:40:32Z")\
**Posts on this page:** 18\
**Page:** 1

<div class="post-metadata">

**Author:** ![hal](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/hal/32/26837_2.png) [@hal](https://discuss.python.org/u/hal)\
**Post date:** [July 27, 2025, 5:40am UTC](https://discuss.python.org/t/reconciling-packaging-module-the-dependency-specifier-specs-non-version-to-version-comparison-rules/100287/1 "2025-07-27T05:40:32Z")

</div>

The [`packaging` module has a bug resulting in it raising an error when comparing non-version values with valid version values in dependency specifiers (#774)](https://github.com/pypa/packaging/issues/774). Its behaviour is different to the [Dependency Specifiers Spec](https://packaging.python.org/en/latest/specifications/dependency-specifiers/), but discussion of [the issue (#774)](https://github.com/pypa/packaging/issues/774)  
on GitHub has revealed that implementing the spec as-is would result in counter-intuitive behaviour when comparing the order of non-standard version values. **So some discussion is needed to decide how to bring `packaging` in line with the spec and whether the spec should be changed.**

## The bug

Say you want your package to require a dependency only on macOS 12 (Darwin 21). Your package metadata might contain:

```python
Requires-Dist: requests; platform_system == 'Darwin' and platform_release < '22'

```

On macOS, `import platform; print(platform.release())` is `22.1.0` (a valid [packaging version](https://packaging.python.org/en/latest/specifications/version-specifiers/)). Whereas on Linux, platform release may be `6.10.14-linuxkit` which isn’t a valid packaging version.

The spec requires that comparing two valid version values use the normalised version comparison rules. When one or both are not valid versions, regular Python operator rules are used (so lexicographic ordering of strings).

`packaging` currently raises an error when the right-hand side of an expression is a valid version and the left-hand side is not.

So in when evaluating this example on Linux, `platform_release` takes on the non-version value and it evaluates `'6.10.14-linuxkit' < '22'` which raises an error.

> **Reproduction instructions**
>
> For example, installing this `pyproject.toml` project with pip currently crashes:
> 
> ```toml
> # pyproject.toml
> [project]
> name = "version-comparison-example"
> version = "1.0.0"
> dependencies = [
> "requests; platform_system == 'Darwin' and platform_release < '22'",
> ]
> 
> [build-system]
> requires = ["hatchling >= 1.26"]
> build-backend = "hatchling.build"
> 
> ```
> 
> ```console
> $ python -c 'import platform; print(platform.release())'
> 6.10.14-linuxkit
> 
> $ touch version_comparison_example.py 
> 
> $ pip install .
> [...]
> ERROR: Exception:
> Traceback (most recent call last):
> [...]
> pip._vendor.packaging.version.InvalidVersion: Invalid version: '6.10.14-linuxkit'
> 
> $ pip --version
> pip 25.1.1 [...]
> 
> ```

Similarly, using an extra name like `v0` that is a valid version also crashes [(example project config)](https://github.com/h4l/v-extra-package-issue), as the package metadata ends up containing a specifier like this:

```python
Requires-Dist: typing-extensions; extra == 'v0'

```

This `v0` value is treated as a valid version and compared against non-version values, which crashes. (I opened a [PR to fix this (#883)](https://github.com/pypa/packaging/pull/883), which is how I came to this general issue.)

## The spec’s behaviour

The [spec requires](https://packaging.python.org/en/latest/specifications/dependency-specifiers/#environment-markers) that mark expressions (e.g. `platform_release < '22'`) are compared as normalised versions when both the mark’s constant value and the variable parse as a [packaging version](https://packaging.python.org/en/latest/specifications/version-specifiers/#normalization). When one or both do not parse as versions, the values are compared using the normal Python operator behaviour.

> **Spec's Wording**
>
> > Comparisons in marker expressions are typed by the comparison operator and the type of the marker value. The \<marker\_op\> operators that are not in \<version\_cmp\> perform the same as they do for strings or sets in Python based on whether the marker value is a string or set itself. The \<version\_cmp\> operators use the version comparison rules of the [Version specifier specification](https://packaging.python.org/en/latest/specifications/version-specifiers/#version-specifiers) when those are defined (that is when both sides have a valid version specifier). If there is no defined behaviour of this specification and the operator exists in Python, then the operator falls back to the Python behaviour for the types involved. Otherwise an error should be raised.

- `packaging` (and projects using it, like `pip` and most Python packaging tools) raise an error instead of using regular Python operators
- The `uv` project has its own implementation of dependency specifiers, and it follows the spec (`uv` can install the problematic example projects above).

## The issue with the spec’s behaviour

Implementing the spec’s behaviour results in counter-intuitive comparison results when comparing the order of version-like (but not valid packaging version) values.

* * *

A distribution (Python package)'s version is required to satisfy the version spec’s rules, but the values of several environment markers are provided by the OS, system software, or are not expected to be versions (e.g. `extras`).

Indeed the [spec has a table listing the environment marker names and their types](https://packaging.python.org/en/latest/specifications/dependency-specifiers/#environment-markers) (String, Version etc) and only `python_version`, `python_full_version` and `implementation_version` are defined to be Version, the rest are String (or sets of strings).

However the marker expression rules described above treat any value as a version if it parses as a version. This may happen to be the case (as in the macOS `platform_release` example and the `extra` example).

This results in most version comparisons using intuitive ordering based on numeric value of version components, but a hard cliff occurs where values are not valid versions, and the comparison instead uses regular string order by character-by-character value:

- `platform_release >= '20'`:
  - intuitively evaluates false when `platform_release` is `6.7.0` or `6.7.0+gentoo` as these are valid versions,
  - counter-intuitively it evaluates true when `platform_release` is `6.7.0-gentoo`, as this is not a valid version and so `'6'` \>= `'2'` is evaluated, which is true.

There are some examples of version-to-version and non-version-to-version equality and ordering comparisons in the tests for [MR #883](https://github.com/pypa/packaging/pull/883/files#diff-d09868fdb6e7989dd44be3064b2a75581d8fafcd2389c31f1031f37eca3c5cc9).

Equality comparisons (`==`, `!=`, etc) are also affected by this, but it’s not really a problem in practice, because the difference in behaviour is limited to normalisation of version components being used in version-to-version comparisons but not otherwise. E.g. `'v8-dev' == 'v8-dev.0'` is true because these are versions, but `'v8-foo' == 'v8-foo.0'` is false because they’re not versions.

## Previous `packaging` module behaviour

As [noted in pypa/packaging#774, packaging\<22 and (pip\<24.1)](https://github.com/pypa/packaging/issues/774#issuecomment-2216872147) had a [lenient LegacyVersion parsing fallback when a value failed to parse as a version](https://github.com/pypa/packaging/blob/21.3/packaging/version.py#L196). However when I read more of the code and tested marker evaluation, I found that the lenient version comparison was not used when evaluating markers, [comparisons involving LegacyVersion always evaluated false](https://github.com/pypa/packaging/blob/21.3/packaging/specifiers.py#L292-L293).

And even if it were used, LegacyVersion had an epoch of -1 (vs \>=0 for regular Version), so comparisons of non-versions with versions would always evaluate the non-version as smaller.

Although it wasn’t used to intuitively evaluate non-version marker expressions, the presence of this lenient version parsing logic meant that in past versions `packaging` (& pip & Co.) would not raise an error when evaluating a non-version-to-version expression. The current behaviour seems to be a result of removing the legacy lenient parsing, which now results in the non-version-to-version cases raising rather than always evaluating false.

## What to do?

In summary:

- The spec requires non-version to version markers to be evaluated as plain Python strings
- `packaging` currently raises an error evaluating non-version to version markers
- `uv` implements the spec’s behaviour
- Comparing the order of version-like strings with regular lexicographic string ordering results in unhelpful/counter-intuitive order, like `3.9` being greater than `3.10`
  - It’s not obvious to an average user under what circumstances versions will be compared with version semantics vs string semantics, and it’s also not under their control, as the logic depends on the value found in the environment

How should this situation be resolved?

- Update `packaging` to match the spec and accept the ordering issues with externally-sourced environment markers such as `platform_release`
- Update the spec(s) to improve handling of non-standard versions
- Something else?

Some pragmatic temporary workarounds/mitigations have also been proposed in [#774](https://github.com/pypa/packaging/issues/774) to avoid raising an error by evaluating mark expressions lazily. Maybe we could exclude environment names like `extras` from version parsing to avoid failing there.

---

<div class="post-metadata">

**Author:** ![sbidoul](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/sbidoul/32/244_2.png) [@sbidoul](https://discuss.python.org/u/sbidoul)\
**Post date:** [July 27, 2025, 9:05am UTC](https://discuss.python.org/t/reconciling-packaging-module-the-dependency-specifier-specs-non-version-to-version-comparison-rules/100287/2 "2025-07-27T09:05:46Z")

</div>

> [@hal](#):
>
> As [noted in pypa/packaging#774, packaging\<22 and (pip\<24.1)](https://github.com/pypa/packaging/issues/774#issuecomment-2216872147) had a [lenient LegacyVersion parsing fallback when a value failed to parse as a version](https://github.com/pypa/packaging/blob/21.3/packaging/version.py#L196). However when I read more of the code and tested marker evaluation, I found that the lenient version comparison was not used when evaluating markers, [comparisons involving LegacyVersion always evaluated false](https://github.com/pypa/packaging/blob/21.3/packaging/specifiers.py#L292-L293).

So it looks likes my comment was misguided. Thanks a lot for digging into this. It means (as you noted) the intuitive comparison could not be relied on in practice.

If there were no complaints about the non-intuitive comparison in pip (I’m not aware of any) and there are none in uv either, then updating `packaging` to match the spec is probably the right thing to do.

---

<div class="post-metadata">

**Author:** ![hal](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/hal/32/26837_2.png) [@hal](https://discuss.python.org/u/hal)\
**Post date:** [July 27, 2025, 9:27am UTC](https://discuss.python.org/t/reconciling-packaging-module-the-dependency-specifier-specs-non-version-to-version-comparison-rules/100287/3 "2025-07-27T09:27:41Z")

</div>

No problem! I’m tempted to do some more digging/testing to check I’m not missing anything about the old behaviour.

I think I’d agree that the current rules are probably good enough in practice, as although the non-version comparisons are unintuitive, they can be made in a known context by ANDing a version comparison with a known platform type to limit the scope they’ll be applied in.

If we did want to consider a spec change, I’ve got a suggestion for how we could resolve the comparison problem.

* * *

Modify the marker expression evaluation rules to work as follows:

Marker expressions are classified as involving Python Packaging Versions, Plain Strings, or Sets of Plain Strings based on their context, and different rules apply to each type.

### Python Package Version rules

Python Packaging Version expressions are always and only used for:

- The the version specifiers following a package name ([the `versionspec` part in the `name_req` grammar rule](https://packaging.python.org/en/latest/specifications/dependency-specifiers/#grammar)):
  - e.g in `Requires-Dist: importlib-metadata (>=4.6, <5)` the `>=4.6, <5` expression

- Any `marker_expr` grammar rule where the `marker_var` is [one of the markers defined to be a Version](https://packaging.python.org/en/latest/specifications/dependency-specifiers/#environment-markers) (`python_version`, `python_full_version`, `implementation_version`)
  - e.g: in `Requires-Dist: importlib-metadata (>=4.6) ; python_version <= "3.9"` the `python_version <= "3.9"` expression

Values in these contexts are required to be valid version values, so equality and comparison always uses the normalised version semantics. If a value is not a valid version, the expression evaluates false (which is what `packaging` used to do with `LegacyVersion`). However the `===` (triple equals) operator continues to act as an escape hatch to compare values as plain strings, without version semantics.

### Plain String rules

Values are treated as Plain Strings in marker expressions not known to be Python Packaging Versions, i.e. any of the environment markers [listed as type `String` in the markers table](https://packaging.python.org/en/latest/specifications/dependency-specifiers/#environment-markers) — `platform_release`, `sys_platform`, `extra`, etc.

Equality (`==`, `!=`) is regular str equality, even if both values are syntactically valid package versions. **However ordering comparisons** (`<`, `>=`, etc) **compare using a general version/natural sort algorithm** , so that expressions like `'3.9' < '3.10'` evaluate true, with no syntactic constraints on the values (see below for more details).

### Sets of Plain String rules

No change, evaluated as they are in Python.

## Version / Natural Sorting

[Debian dpkg has version sorting algorithm used to compare package versions](https://www.debian.org/doc/debian-policy/ch-controlfields.html#version), where the package versions have no specific syntax other than restricting the available set of characters. GNU Coreutils `ls --sort=version` and `sort --version-sort` [uses an algorithm based on the Debian dpkg one, with some adjustments](https://www.gnu.org/software/coreutils/manual/html_node/Version-sort-ordering.html) to make it more general purpose and suitable for sorting filenames (e.g. treating file extensions specially).

The rules are quite straightforward, and I think we could define a slight variation of the Debian dpkg scheme or a subset of the Coreutils rules (e.g. without the special handling of file extensions, single `.` etc.), and use it for ordering arbitrary strings that cannot be guaranteed to be Python Packaging Versions.

## Summary

- Comparison of valid version values is unchanged in Python Packaging Version contexts
- Non-version to version comparisons in Python Packaging Version contexts evaluate false, unless `===` is used.
- Arbitrary strings use a version/natural sorting scheme to compare order

The result should be that package versions compare as before, but environment marker comparisons for things like OS versions (`platform_release`) compare for order consistently and intuitively.

---

<div class="post-metadata">

**Author:** ![hal](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/hal/32/26837_2.png) [@hal](https://discuss.python.org/u/hal)\
**Post date:** [July 27, 2025, 1:34pm UTC](https://discuss.python.org/t/reconciling-packaging-module-the-dependency-specifier-specs-non-version-to-version-comparison-rules/100287/4 "2025-07-27T13:34:23Z")

</div>

Just to confirm, I did some testing on `packaging` 21.3 that pip used to use, I’ve got a throwaway branch in my packaging repo fork with some unit tests to demonstrate/verify how invalid versions were being evaluated: [Add throwaway tests to demo 21.3 invalid version evaluation by h4l · Pull Request #3 · h4l/pypa-packaging · GitHub](https://github.com/h4l/pypa-packaging/pull/3/files). This confirmed that the `LegacyVersion` is not used for environment marker evaluation.

I also manually tested/verified pip 24.0 behaviour on a machine with `platform_release` `6.10.14-linuxkit`.

> **Manual pip 24.0 dependency requirement evaluation test**
>
> - The `httpx` requirement IS installed because `'6.2.0-linuxkit'` is not a valid version in the right-hand side, so regular str comparison is used, and `6.1` (prefix) is less than `6.2`.
> - The `requests` requirement IS NOT installed because `'6'` is a valid version on the right-hand side, so version comparison semantics are used; however `6.10.14-linuxkit` is not a valid version, so it’s parsed with `LegacyVersion` and environment marker evaluation always returns false with `LegacyVersion`.
> 
> ```toml
> # pyproject.toml
> [project]
> name = "v-extra-example"
> version = "1.0.0"
> requires-python = ">=3.9"
> dependencies = [
> "httpx; platform_release < '6.2.0-linuxkit'",
> "requests; platform_release > '6'"
> ]
> 
> [build-system]
> requires = ["hatchling >= 1.26"]
> build-backend = "hatchling.build"
> 
> ```
> 
> ```console
> $ python -c 'import platform; print(platform.release())'
> 6.10.14-linuxkit
> 
> $ pip --version
> pip 24.0 from /home/vscode/.local/lib/python3.13/site-packages/pip (python 3.13)
> 
> $ pip install .
> Defaulting to user installation because normal site-packages is not writeable
> Processing /workspaces/v-extra-package-issue
> Installing build dependencies ... done
> Getting requirements to build wheel ... done
> Preparing metadata (pyproject.toml) ... done
> Collecting httpx (from v-extra-example==1.0.0)
> Using cached httpx-0.28.1-py3-none-any.whl.metadata (7.1 kB)
> Requirement already satisfied: anyio in /home/vscode/.local/lib/python3.13/site-packages (from httpx->v-extra-example==1.0.0) (4.9.0)
> Requirement already satisfied: certifi in /home/vscode/.local/lib/python3.13/site-packages (from httpx->v-extra-example==1.0.0) (2025.7.14)
> Requirement already satisfied: httpcore==1.* in /home/vscode/.local/lib/python3.13/site-packages (from httpx->v-extra-example==1.0.0) (1.0.9)
> Requirement already satisfied: idna in /home/vscode/.local/lib/python3.13/site-packages (from httpx->v-extra-example==1.0.0) (3.10)
> Requirement already satisfied: h11>=0.16 in /home/vscode/.local/lib/python3.13/site-packages (from httpcore==1.*->httpx->v-extra-example==1.0.0) (0.16.0)
> Requirement already satisfied: sniffio>=1.1 in /home/vscode/.local/lib/python3.13/site-packages (from anyio->httpx->v-extra-example==1.0.0) (1.3.1)
> Using cached httpx-0.28.1-py3-none-any.whl (73 kB)
> Building wheels for collected packages: v-extra-example
> Building wheel for v-extra-example (pyproject.toml) ... done
> Created wheel for v-extra-example: filename=v_extra_example-1.0.0-py3-none-any.whl size=1017 sha256=b21739ec62a46843023fc595797573b36572a2324b82bd212807ea3d9a653cde
> Stored in directory: /home/vscode/.cache/pip/wheels/0d/7f/ab/0927a9f4d4eb3859e98c10104f2cad4c8036afeec831e5ee80
> Successfully built v-extra-example
> Installing collected packages: httpx, v-extra-example
> Attempting uninstall: v-extra-example
> Found existing installation: v-extra-example 1.0.0
> Uninstalling v-extra-example-1.0.0:
> Successfully uninstalled v-extra-example-1.0.0
> Successfully installed httpx-0.28.1 v-extra-example-1.0.0
> 
> [notice] A new release of pip is available: 24.0 -> 25.1.1
> [notice] To update, run: python3.13 -m pip install --upgrade pip
> 
> ```

---

<div class="post-metadata">

**Author:** ![diazona](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/diazona/32/16414_2.png) [@diazona](https://discuss.python.org/u/diazona)\
**Post date:** [July 28, 2025, 8:04am UTC](https://discuss.python.org/t/reconciling-packaging-module-the-dependency-specifier-specs-non-version-to-version-comparison-rules/100287/5 "2025-07-28T08:04:27Z")

</div>

> [@sbidoul](#):
>
> If there were no complaints about the non-intuitive comparison in pip (I’m not aware of any)

[One of my comments on the original issue](https://github.com/pypa/packaging/issues/774#issuecomment-2211954535) was meant to be exactly this complaint. I didn’t emphasize it there because at the time changing the spec didn’t seem to be a realistic option - plus, the focus of the bug report was on `packaging` blocking installation of certain packages, not on whether the spec made sense - but now that changing the spec is on the table, yeah, I’ll go on record (for whatever little that’s worth) that I think the behavior described by the existing spec will lead to unintuitive results or is insufficiently flexible in enough realistic situations that it’s worth trying to change it. For example, if some package has a conditional dependency when running on version 6.9 or higher of the Linux kernel, the existing spec has no clean way to express that constraint, since kernels 6.10.X up through the current 6.16.X will fail the comparison `platform_release >= "6.9"`.

It looks like someone [made the same point on `uv`’s version of this discussion](https://github.com/astral-sh/uv/issues/3917#issuecomment-2141754917) as well.

FWIW if a change to the spec does go ahead, I like @hal’s suggestion of using version sorting semantics (as in e.g. Coreutils) for plain string fields. My original half-baked idea was to extract the longest prefix of the string that constitutes a PEP 440-compliant version number and use that for the initial comparison, but it makes more sense to stick with well-tested algorithms.

---

<div class="post-metadata">

**Author:** ![hal](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/hal/32/26837_2.png) [@hal](https://discuss.python.org/u/hal)\
**Post date:** [July 30, 2025, 8:30am UTC](https://discuss.python.org/t/reconciling-packaging-module-the-dependency-specifier-specs-non-version-to-version-comparison-rules/100287/6 "2025-07-30T08:30:00Z")

</div>

> [@hal](#):
>
> Just to confirm, I did some testing on `packaging` 21.3 that pip used to use

I should have made it more clear what the conclusion was:

In packaging packaging\<22 and pip\<24.1:

1. An invalid version in a dependency specifier environment marker would mean the condition was always evaluated with plain str semantics (even if the runtime value is a valid version).
  - e.g: This would always use plain str semantics to compare the runtime `platform_release` value with the constant `'20.7.0-gentoo'`.

```python
Requires-Dist: typing-extensions; platform_release >= '20.7.0-gentoo'

```

2. A valid version in a dependency specifier environment marker would mean the condition was always evaluated to be false when the runtime value was not a valid version.
  - e.g: This would always evaluate false when the runtime `platform_release` value is not a valid version, e.g. false when `platform.release() == '20.7.0-gentoo'`

```python
Requires-Dist: typing-extensions; platform_release >= '20'

```

---

<div class="post-metadata">

**Author:** ![hal](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/hal/32/26837_2.png) [@hal](https://discuss.python.org/u/hal)\
**Post date:** [July 30, 2025, 11:52am UTC](https://discuss.python.org/t/reconciling-packaging-module-the-dependency-specifier-specs-non-version-to-version-comparison-rules/100287/7 "2025-07-30T11:52:03Z")

</div>

I mentioned this discussion to one of the uv developers (who had previously talked about this problem) and they’ve commented here: [Different understanding of environment markers vs pip · Issue #3917 · astral-sh/uv · GitHub](https://github.com/astral-sh/uv/issues/3917#issuecomment-3135925392)

---

<div class="post-metadata">

**Author:** ![brettcannon](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/brettcannon/32/34895_2.png) [@brettcannon](https://discuss.python.org/u/brettcannon)\
**Post date:** [August 8, 2025, 5:01pm UTC](https://discuss.python.org/t/reconciling-packaging-module-the-dependency-specifier-specs-non-version-to-version-comparison-rules/100287/8 "2025-08-08T17:01:57Z")

</div>

I wanted to quickly thank @hal for the thorough write-up!

> [@hal](#):
>
> I mentioned this discussion to one of the uv developers (who had previously talked about this problem) and they’ve commented here

The summary is that `platform_release` and `platform_version` should be avoided (assuming I’m summarizing @konstin appropriately).

The options I’m thinking of are:

1. Update `packaging` to follow the spec and put a warning in the spec that comparisons won’t work the way users probably expect
2. Update the spec so only `==` and `!=` are allowed for string markers
3. The spec gains the concept of _external versions_ and we adopt Debian’s comparison algorithm for comparing those external versions

I think what we decide to do depends somewhat on how often these markers are used and what comparison is usually used (although we really only seem to be talking about `platform_release`). I have never used either marker nor have I seen them used, so my opinion is option 1 since it’s the easiest one to implement. But I’m very aware I personally might have a blind spot on the usage without analyzing PyPI metadata.

---

<div class="post-metadata">

**Author:** ![hal](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/hal/32/26837_2.png) [@hal](https://discuss.python.org/u/hal)\
**Post date:** [August 9, 2025, 6:08pm UTC](https://discuss.python.org/t/reconciling-packaging-module-the-dependency-specifier-specs-non-version-to-version-comparison-rules/100287/9 "2025-08-09T18:08:40Z")

</div>

I tried searching for uses of these markers with greater/less than operators, and it seems like they are very infrequently used, at least currently:

- [Code search results · GitHub](https://github.com/search?q=path%3A%2F%28%5E%7C%5C%2F%29%28pyproject%5C.toml%7Csetup.py%29%24%2F+%2F%28sys_platform%7Cplatform_release%7C+platform_version%29%5Cs*%5B%3C%3E%5D%2F&type=code&p=2)
- [context:global file:(^|/… - Sourcegraph](https://sourcegraph.com/search?q=context:global+file:%28%5E%7C/%29%28pyproject%5C.toml%7Csetup%5C.py%29%24+/%28sys_platform%7Cplatform_release%7Cplatform_version%29%5Cs*%5B%3C%3E%5D/&patternType=keyword&sm=0)

I would have thought that conditional dependencies by platform version should be largely unnecessary if the dependency used platform compatibility tags to control platform support.

Option 1. seems like a fairly safe option, as even if the comparisons can be confusing, very few people will be confused in practice if it’s a barely used feature. On the other hand, it would be cementing the sub-optimal behaviour, which until now has only been implemented in uv.

---

<div class="post-metadata">

**Author:** ![diazona](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/diazona/32/16414_2.png) [@diazona](https://discuss.python.org/u/diazona)\
**Post date:** [August 10, 2025, 8:02am UTC](https://discuss.python.org/t/reconciling-packaging-module-the-dependency-specifier-specs-non-version-to-version-comparison-rules/100287/10 "2025-08-10T08:02:40Z")

</div>

Fair. It seems like a bit of a shame to me to cement sub-optimal behavior when there is an opportunity to improve the behavior, but it’s still better than having no decision at all.

---

<div class="post-metadata">

**Author:** ![hal](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/hal/32/26837_2.png) [@hal](https://discuss.python.org/u/hal)\
**Post date:** [August 10, 2025, 9:26am UTC](https://discuss.python.org/t/reconciling-packaging-module-the-dependency-specifier-specs-non-version-to-version-comparison-rules/100287/11 "2025-08-10T09:26:01Z")

</div>

It does. I would be happy to volunteer to implement external version support if we did decide to take that approach and needed someone to work on it.

---

<div class="post-metadata">

**Author:** ![diazona](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/diazona/32/16414_2.png) [@diazona](https://discuss.python.org/u/diazona)\
**Post date:** [September 2, 2025, 10:59pm UTC](https://discuss.python.org/t/reconciling-packaging-module-the-dependency-specifier-specs-non-version-to-version-comparison-rules/100287/12 "2025-09-02T22:59:19Z")

</div>

Whoops, I didn’t see your reply when you posted it, my bad!

I don’t think there will be any problem getting an implementation coded up, once a decision is made about how to proceed. (I also would be happy to do it, as I’m sure many others would.)

So… I guess I’m wondering, where do we go from here? In other words, what’s the next step that would need to happen to move toward formally proposing a change to the spec? (Not to say that you, Hal, should necessarily know, but someone should.)

---

<div class="post-metadata">

**Author:** ![hal](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/hal/32/26837_2.png) [@hal](https://discuss.python.org/u/hal)\
**Post date:** [September 6, 2025, 11:58am UTC](https://discuss.python.org/t/reconciling-packaging-module-the-dependency-specifier-specs-non-version-to-version-comparison-rules/100287/13 "2025-09-06T11:58:13Z")

</div>

No problem! I’m indeed not sure myself, but I think we first need a decision on which of the options Brett summarises we’re going to go with.

---

<div class="post-metadata">

**Author:** ![henryiii](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/henryiii/32/3493_2.png) [@henryiii](https://discuss.python.org/u/henryiii)\
**Post date:** [December 2, 2025, 4:21am UTC](https://discuss.python.org/t/reconciling-packaging-module-the-dependency-specifier-specs-non-version-to-version-comparison-rules/100287/14 "2025-12-02T04:21:41Z")

</div>

Could we at least modify the spec to get rid of the `==` must be typed as a version part for everything except `platform_release`? `platform_release` is nearly useless if all you can do is `==` on it, but there’s no use for any of the other “string” typed values to be viewed as versions, it slows down `packaging` as creating `SpecifierSet`/`Version` is expensive, and it actively breaks things like extras, as `==` requires version comparisons, which breaks extras like `v0`.

Here’s my do-as-little-as-possible proposal:

- Change the spec to state all string values only support string comparisons. No more “convert to version if possible”.
- Make `platform_release` have a new type, `string | Version`, which has the current behavior (just punting the work on fixing this to later, as it’s the hard one) (Note: this is the current behavior of Version, the name just indicates that this might not always be a Version, while the three fields with Version are always supposed to be Versions).

Note that you can’t really use comparisons with `platform_release`, as there is no short-circuit evaluation requirement in the spec, so this will error on platforms that have a non-version-like `platform_release` if you use it, even if you protect it by checking the platform first.\[1\]

Longer term, things that might be useful to improve the situation for `platform_release` could be:

- Add short-circuit evaluation to the spec
- Make the version operators other than `==` and `!=` throw an error if `platform_release` isn’t convertible to a version. This would have to go with the above short-circuiting behavior; it would help reduce silent errors, for example when a “10something” follows a “9something”.
- Maybe find some way to make `platform_release` more usable across platforms (likely a new field that is stripped down in some way to just version-like numbers?)

(PS: to make matters worse, packaging does not follow the spec when it comes to the Python fallback behavior)

The way packaging used to do it (before LooseVersion was removed), and currently (unreleased) does it is comparisons like \< always return False if the value is not a valid Version. Changing the spec to match that is also an option. I can expand on this idea later.

* * *

1. Actually this is changing in the next version of packaging due to fixes in how Specifiers work with legacy versions, but it was true until now.

---

<div class="post-metadata">

**Author:** ![diazona](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/diazona/32/16414_2.png) [@diazona](https://discuss.python.org/u/diazona)\
**Post date:** [December 6, 2025, 11:28pm UTC](https://discuss.python.org/t/reconciling-packaging-module-the-dependency-specifier-specs-non-version-to-version-comparison-rules/100287/15 "2025-12-06T23:28:13Z")

</div>

Whoops, I had forgotten that I had typed up a reply to this in the past and had trouble posting it for some reason. Most of it probably isn’t worth resurrecting, but the one thing that I think might be worth some thought is the slight concern of backward compatibility with option 2 (only allowing `==` and `!=` for strings): suppose that we wind up having the spec, `packaging`, and `pip` updated to disallow ordered comparisons on `platform_release` and `platform_version`. What happens when someone uses a new version of `pip` (in this hypothetical future) to install an old version of some package that uses an ordered comparison on `platform_release`? Maybe this is a problem to solve later, though.

---

<div class="post-metadata">

**Author:** ![henryiii](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/henryiii/32/3493_2.png) [@henryiii](https://discuss.python.org/u/henryiii)\
**Post date:** [December 8, 2025, 5:01pm UTC](https://discuss.python.org/t/reconciling-packaging-module-the-dependency-specifier-specs-non-version-to-version-comparison-rules/100287/16 "2025-12-08T17:01:56Z")

</div>

> updated to disallow ordered comparisons

Packaging 22+ and pip 25+ currently crash if this happens and the value is not representable as a version, such as on Linux. So most of this backward incompatibility has already been either fixed (by packages updating to not crash), or the package is simply not installable anymore, or the package only supports Windows/macOS. That’s why there are so few examples on PyPI, you can see them ~~above~~ (Oh, sorry, wrong thread. You can see them in the new thread I opened with my specific proposal). That’s one of the reasons that pip took so long to update to packaging 22+, it broke packages.

That’s why this comes up now and needs to be resolved before the next packaging release - we are changing that crash so it’s no longer a error, which means we might start getting packages depending on whatever behavior we define again.

There is no `platform_version` field. And the change specifically does not allow any implementation to error on ordered comparisons on `platform_release`, it is treated exactly like the `_version` fields. All other fields would be allowed to error on ordered comparisons, though not required. So the very rare cases where they are used with strings would not crash yet (current packaging status), but could in the future. It’s mostly used with `>= "win32"` (which is wrong, even if there was a `"win64"`, it would fail on `"win128"` or pass on a platform that was alphabetically after `"win"`, so that’s not generally useful), and one package uses it for `arm` to define a range, which also isn’t needed, there are only a limited list (v7 and v8).

This was proposed in [Spec change/bugfix: dependency specifiers simplification (PEP 508)](https://discuss.python.org/t/spec-change-bugfix-dependency-specifiers-simplification-pep-508/105203) - please look at that and comment there, or in the PR at [fix(markers): Fix/change dependency specifiers by henryiii · Pull Request #1971 · pypa/packaging.python.org · GitHub](https://github.com/pypa/packaging.python.org/pull/1971).

---

<div class="post-metadata">

**Author:** ![henryiii](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/henryiii/32/3493_2.png) [@henryiii](https://discuss.python.org/u/henryiii)\
**Post date:** [December 8, 2025, 5:07pm UTC](https://discuss.python.org/t/reconciling-packaging-module-the-dependency-specifier-specs-non-version-to-version-comparison-rules/100287/17 "2025-12-08T17:07:24Z")

</div>

Ahh, no, I’m wrong, `platform_version` is a field, missed that it had a `_version` suffix. I might have missed checking that one when looking for ordered comparison. It’s so far from a representable version that I forgot about it having a `_version` suffix. Without a “string contains” operator, I think it’s pretty much useless as a marker field, but I can rerun looking for that and see if it is used anywhere on PyPI.

---

<div class="post-metadata">

**Author:** ![henryiii](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/henryiii/32/3493_2.png) [@henryiii](https://discuss.python.org/u/henryiii)\
**Post date:** [December 8, 2025, 5:30pm UTC](https://discuss.python.org/t/reconciling-packaging-module-the-dependency-specifier-specs-non-version-to-version-comparison-rules/100287/18 "2025-12-08T17:30:46Z")

</div>

There are 9 packages on PyPI that use `platform_version`.

Unaffected:

- `nanosurf`: `platform_version != "Nanosurf_Linux"` - this is fine.
- `inteltk`: `platform_version == "Windows"` - Weird, but not affected. Pretty sure this is never True.
- `flet`: \<long expression\> `"embedded" not in platform_version` Not sure if this works, but unaffected by this change - and it was removed in flet 0.26 (current version is 0.28)
- `toolio`: `platform_version == "arm64"` Was in 0.5.1, was changed to `platform_machine` in 0.5.2, current version is 0.6.0

I think these probably fail on pip 25+, and would become false. Not sure they work as expected, anyway, the version might not start with a number, and string comparison doesn’t work on version when they add a digit.

- `ouster-mapping` and `ouster-sdk`: `python_version <= "3.11" and platform_machine != "aarch64" and (platform_system != "Darwin" or platform_machine != "arm64" or platform_version >= "21.0.0")`
- `console`: `"colorama; os_name == "nt" and platform_version < "10.0.10586"`
- `global-logger`: same as above, but removed in current release (was in 0.4.5, removed in current 0.4.7)
- `out`: same as above, but removed in current release (was in 0.80, removed in current 0.81)

> **Script**
>
> ```python
> # /// script
> # dependencies = ["packaging"]
> # ///
> 
> import sqlite3
> from packaging.version import Version
> 
> con = sqlite3.connect("pypi-data.sqlite")
> cur = con.cursor()
> 
> rows = cur.execute("""
> SELECT name, version, requires_dist
> FROM projects
> WHERE requires_dist LIKE '%platform_version%'
> """).fetchall()
> 
> best = {} # name -> (name, version, requires_dist)
> 
> for name, version, requires_dist in rows:
> if name not in best or Version(version) > Version(best[name][1]):
> best[name] = (name, version, requires_dist)
> 
> # Convert dict → list for use
> results = list(best.values())
> 
> for r in results:
> print(*r)
> print()
> 
> ```
