[split out] Deprecating arbitrary equality

Are there any existing tools that support arbitrary equality as the spec describes it?

Trying the spec example of ===foobar, all of pip and uv and poetry fail, with an error message indicating that this is an invalid version number.

What are we even trying to be backwards-compatible with?

If no-one has implemented this for all these years then I guess that is no objection to changing the text.

However I am tempted to counter-propose that arbitrary equality just be removed altogether…

1 Like

That would require a PEP. Are you willing to write one?

2 Likes

Lets start with the other part! What are we even trying to be backwards compatible with?

While I sort of feel as though no-one will care what an unimplemented part of the spec says, I also feel that specs should not be moving targets.

If no-one is using this feature anyway, then I am not sure that changing its meaning is well justified.

I’m pretty sure this used to work on pip prior to v24.1, and as described in the OP, setuptool’s parse_version and packaging’s LegacyVersion are what I’m trying to match backwards compatibility with.

I could not tell you the real world usage, but from my personal experience there are a large number of users still on legacy versions of packaging tools.

Please start a new thread for that then, I don’t want this thread to get confused with splintering discussions.

1 Like

That is easily checked: and yes it did.

Nevertheless - if this has already been not working for 18 months (and my cursory search found no issue reports) - that somewhat weakens the case for backwards compatibility being important.

Do those actually currently support non-PEP 440 versions? packaging removed LooseVersion a year or two ago (I can’t find this mentioned in the changelog), and setuptools has rejected non-PEP 440 versions since v64, released in August 2022.

What’s the relevancy of that to the spec?

And in my own practical experience, I’ve worked with a team that has standardized on packaging tools over 5 years old, especially if they are using LTE redistributions of packaging tools.

I never made that case for backwards compatibility, I made the case (multiple times, including in the OP) that backward compatibility is important because the spec explicitly says that’s the intent of arbitrary equality:

This operator is special and acts as an escape hatch to allow someone using a tool which implements this specification to still install a legacy version which is otherwise incompatible with this specification.

In general, I think your points are will suited to arguing that arbitrary equality be removed from the spec, but aren’t really that relevant to the discussion of clarifying the intent of the existing spec.

packaging removed LegacyVersion not LooseVersion (that was an old distutils class), but Specifier still accepts arbitrary strings, and still accepts arbitrary equality.

As for setuptools, that’s a tool UX choice, and I am supportive of that, it is not, however it is not a specification question, which is what I am trying to address here.

1 Like

The arbitrary equality operator is “heavily discouraged” by spec and may display a warning (it’s implemented in uv but hidden by default). I’m not aware of it being used anyway, and over a decade after PEP 440, we’re not seeing non-PEP 440 legacy versions anymore. I’d favor deprecating arbitrary equality properly over investing into it.

2 Likes

I repeat - if you want to do that, by all means open a new thread.

For this thread, I would say “do nothing” - apart from the fact that the packaging behaviour is at a minimum not clearly allowed by the spec, and is arguably in violation. The minimum[1] reasonable fix is a quick text-only PEP change, and that involves very little investment, except for the time being spent addressing “let’s not do this, let’s deprecate arbitrary equality instead” comments :slightly_frowning_face:

I’m not against deprecating arbitrary equality, I just personally don’t care enough to invest time into doing so (although obviously as PEP delegate, I will invest time if someone else writes a PEP).

Edit: Updated this post to reflect that it was split off into a new thread.


  1. Changing packaging is a much bigger change ↩︎

6 Likes

In case anyone else is confused by this and other comments – @brettcannon split these posts into this thread and gave it the title!

2 Likes

Yeah, I appreciate trying to clean up the other thread my comments don’t make much sense in this context. I am not against deprecating arbitrary equality but it’s also not something I am trying to achieve or spend my time on.

I may delete all my comments in this thread, in case someone later reads this and wonders why I am being quoted but not appearing on this thread.

1 Like

Maybe edit the posts to add a footer, rather than remove them? My concern would be that things get even more confusing.


To this new thread, about deprecating arbitrary equality, it seems like a reasonable change, but I question the value of pursuing it.

Arbitrary equality doesn’t significantly impact the complexity of tooling, does it? So we’d be removing it in pursuit of spec purity, not for any practical gain?

If there’s a better reason than “theoretical purity” to remove it, I’d be willing to help write or co-author the requisite PEP and coach the change through tools.

5 Likes

Working on a static unsatisfiability check in packaging, arbitrary equality is a minor annoyance (and what led me to wanting to clarify the spec) but not a major one. If we’re getting rid of vestigial parts of the spec for cleanliness I would also remove epochs.

The real major annoyance for tooling is the complexity around exclusive ordering operators, pre-releases, and post-releases. For example:

  • The ordering 0.9b1 < 1.0b1 < 1.0b2 < 1.0 is true
  • Intuitively, out these versions, 0.9b1 and 1.0b1 matches the specifier <1.0b2
  • But, out of these versions, only 0.9b1 matches the specifier <1.0

These kind of quirky behaviors make implementations error prone, and it possibly difficult to translate to literature on satisfiability and other solutions in other ecosystems to Python versions and specifiers.

However, reworking these parts of the spec would need broad agreement from tooling authors, and tool design or other spec choices would need to be made to ensure a good user experience.

2 Likes

I would like to highlight one (two) use case where using arbitrary equality works (with pip and sort of with uv, with a caveat) and is necessary right now - handling build suffixes and sub-patch (internal patched) versions.
Example:

Index contains files:

  • torch-1.2.3.tar.gz (using tar.gz for example simplicity)
  • torch-1.2.3+cpu.tar.gz
  • torch-1.2.4.0+cpu.tar.gz
  • torch-1.2.4.0+cu128.tar.gz

Running “uv pip compile <(echo ‘torch==1.2.3’)” results in “torch-1.2.3+cpu” picked up. The only way to resolve “torch-1.2.3.tar.gz” is to use “pip install torch===1.2.3” or “uv pip compile <(echo ‘torch===1.2.3’) . This works.
The other case is - if user tries to install a package which is not in the index, but they need that exact package version. Trying to resolve “torch==1.2.4” results in torch-1.2.4.0+cu128.tar.gz. Here also === helps. However I found that behavior deviates between pip and uv - uv will pick up .0 version as long as suffix matches, even if === is used. Pip will not.

Some examples from real life:

(venv) [ /tmp/tmp34u1frv7/venv ]$ pip download torch==2.9.1 --no-deps
Looking in indexes: <redacted>
Collecting torch==2.9.1
  Downloading <redacted>/torch-2.9.1.0%2Bcu128-cp312-cp312-linux_x86_64.whl.metadata (29 kB)
Downloading <redacted>/torch-2.9.1.0%2Bcu128-cp312-cp312-linux_x86_64.whl (614.2 MB)
   ━━━━━━━━━━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━ 201.3/614.2 MB 106.8 MB/s eta 0:00:04
# Ctrl+C
ERROR: Operation cancelled by user

(venv) [ /tmp/tmp34u1frv7/venv ]$ pip download torch===2.9.1 --no-deps
Looking in indexes: <redacted>
ERROR: Could not find a version that satisfies the requirement torch===2.9.1 (from versions: 2.9.1.0+cpu, 2.9.1.0+cu128)
ERROR: No matching distribution found for torch===2.9.1

(venv) [ /tmp/tmp34u1frv7/venv ]$ pip download torch===2.9.1+cpu --no-deps
Looking in indexes: <redacted>
ERROR: Could not find a version that satisfies the requirement torch===2.9.1+cpu (from versions: 2.9.1.0+cpu, 2.9.1.0+cu128)
ERROR: No matching distribution found for torch===2.9.1+cpu

(venv) [ /tmp/tmp34u1frv7/venv ]$ pip download torch==2.9.1+cpu --no-deps
Looking in indexes: <redacted>
Collecting torch==2.9.1+cpu
  Downloading <redacted>/torch-2.9.1.0%2Bcpu-cp312-cp312-linux_x86_64.whl.metadata (29 kB)
Downloading <redacted>/torch-2.9.1.0%2Bcpu-cp312-cp312-linux_x86_64.whl (151.9 MB)
   ━━━━━━━━━━━━━━━━━━━╺━━━━━━━━━━━━━━━━━━━━ 73.7/151.9 MB 100.2 MB/s eta 0:00:01
# Ctrl+C
ERROR: Operation cancelled by user

(venv) [ /tmp/tmp34u1frv7/venv ]$ uv pip compile <(echo "torch==2.9.1")
Resolved 10 packages in 716ms
# ...
torch==2.9.1.0+cu128
# ...

(venv) [ /tmp/tmp34u1frv7/venv ]$ uv pip compile <(echo "torch===2.9.1")
  × No solution found when resolving dependencies:
  ╰─▶ Because there is no version of torch==2.9.1 and you require torch==2.9.1, we can conclude that your requirements are unsatisfiable.
  
(venv) [ /tmp/tmp34u1frv7/venv ]$ uv pip compile <(echo "torch==2.9.1+cpu")
Resolved 10 packages in 456ms
# ...
torch==2.9.1.0+cpu
# ...

# DEVIATION BETWEEN PIP AND UV:
(venv) [ /tmp/tmp34u1frv7/venv ]$ uv pip compile <(echo "torch===2.9.1+cpu")
Resolved 10 packages in 411ms
# ...
torch==2.9.1.0+cpu
# ...

3 Likes