Summary
For vers:pypi/1.0.0|2.0.0, version 1.5.0 is outside both the original range and the result of invert(). This violates the documented inverse/complement purpose of the method and can leave versions unclassified when a caller partitions them using a range and its complement.
Reproduction
Save the following as repro.py.
from univers.version_range import VersionRange
from univers.versions import PypiVersion
r = VersionRange.from_string("vers:pypi/1.0.0|2.0.0")
inverse = r.invert()
print("range:", r)
print("inverse:", inverse)
for text in ("1.0.0", "1.5.0", "2.0.0"):
v = PypiVersion(text)
print(text, "in range:", v in r, "in inverse:", v in inverse)
Run:
python -m pip install univers==32.0.1
python repro.py
Actual result
range: vers:pypi/1.0.0|2.0.0
inverse: vers:pypi/!=1.0.0|!=2.0.0
1.0.0 in range: True in inverse: False
1.5.0 in range: False in inverse: False
2.0.0 in range: True in inverse: False
Expected result and contract
For each version, membership in a range and its complement should be opposite. In particular, 1.5.0 should be in the inverse because it is not in the original enumerated range; the two enumerated versions should remain outside the inverse.
The API's complement purpose is described in issue #91 and the implementing PR #92:
#91
#92
Code-level observation
invert() produces an all-!= constraint list. In the containment path, after testing the explicitly excluded versions, the =/!= constraints are filtered out and the empty remainder returns false. As a result, a version that matches none of the exclusions is still rejected. The report concerns the semantics of the returned complement, irrespective of how the constraint list's connective is represented internally.
I also reviewed the related containment issue #137; I could not establish that it covers this enumerated-inverse case:
#137
Environment
- Package: univers 32.0.1
- Runtime: CPython 3.13.3
- OS: macOS 26.6.2, Apple Silicon / arm64
- Reproduced: 2026-09-11
univers-reproduction.zip
Summary
For
vers:pypi/1.0.0|2.0.0, version1.5.0is outside both the original range and the result ofinvert(). This violates the documented inverse/complement purpose of the method and can leave versions unclassified when a caller partitions them using a range and its complement.Reproduction
Save the following as
repro.py.Run:
Actual result
Expected result and contract
For each version, membership in a range and its complement should be opposite. In particular,
1.5.0should be in the inverse because it is not in the original enumerated range; the two enumerated versions should remain outside the inverse.The API's complement purpose is described in issue #91 and the implementing PR #92:
#91
#92
Code-level observation
invert()produces an all-!=constraint list. In the containment path, after testing the explicitly excluded versions, the=/!=constraints are filtered out and the empty remainder returns false. As a result, a version that matches none of the exclusions is still rejected. The report concerns the semantics of the returned complement, irrespective of how the constraint list's connective is represented internally.I also reviewed the related containment issue #137; I could not establish that it covers this enumerated-inverse case:
#137
Environment
univers-reproduction.zip