-
Notifications
You must be signed in to change notification settings - Fork 251
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
version: fix local label comparison #379
base: main
Are you sure you want to change the base?
Conversation
Prior to this change, local label comparison was inconsistent with PEP 440. This change ensures that the local version label is checked for equivalence using a strict string equality comparison. Resolves: python-poetry/poetry#4729
cfdccfc
to
c4bb9bd
Compare
Kudos, SonarCloud Quality Gate passed! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that's completely correct yet. PEP440 says:
"Comparison and ordering of local versions considers each segment of the local version (divided by a .) separately. If a segment consists entirely of ASCII digits then that section should be considered an integer for comparison purposes and if a segment contains any ASCII letters then that segment is compared lexicographically with case insensitivity. When comparing a numeric and lexicographic segment, the numeric section always compares as greater than the lexicographic segment. Additionally a local version with a great number of segments will always compare as greater than a local version with fewer segments, as long as the shorter local version’s segments match the beginning of the longer local version’s segments exactly."
It seems that comparing local version segments is already prepared here:
poetry-core/src/poetry/core/version/pep440/version.py
Lines 108 to 125 in dfd1b39
_local: tuple[tuple[int, int | str], ...] | |
if self.local is None: | |
# Versions without a local segment should sort before those with one. | |
_local = ((NegativeInfinity(), ""),) | |
else: | |
# Versions with a local segment need that segment parsed to implement | |
# the sorting rules in PEP440. | |
# - Alpha numeric segments sort before numeric segments | |
# - Alpha numeric segments sort lexicographically | |
# - Numeric segments sort numerically | |
# - Shorter versions sort before longer versions when the prefixes | |
# match exactly | |
assert isinstance(self.local, tuple) | |
_local = tuple( | |
# We typecast strings that are integers so that they can be compared | |
(int(i), "") if str(i).isnumeric() else (NegativeInfinity(), i) | |
for i in self.local | |
) |
assert v.allows(Version.parse("1.2.3-1+build.1")) | ||
assert v.allows(Version.parse("1.2.3-1+build.1")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Duplicated line.
Maybe, it's worth adding assert not v.allows(Version.parse("1.2.3"))
since that's new.
@@ -69,6 +69,11 @@ def is_simple(self) -> bool: | |||
|
|||
def allows(self, other: Version) -> bool: | |||
if self._min is not None: | |||
if self._min.is_local() and ( | |||
not other.is_local() or self._min.local != other.local |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's not completely correct. It actually has to be something like self._min.local < other.local
. (However, this falls too short.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIRC, local labels are to be treated as exact strings, so they should not be ordered when comparing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it might not be suitable for stuff like 1.10.0+cu113
and 1.10.0+cpu
but that's what PEP440 says.
@@ -81,6 +86,10 @@ def allows(self, other: Version) -> bool: | |||
if not _this.is_local() and _other.is_local(): | |||
# allow weak equality to allow `3.0.0+local.1` for `<=3.0.0` | |||
_other = _other.without_local() | |||
elif _this.is_local() and ( | |||
not _other.is_local() or _this.local != _other.local |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above.
Prior to this change, local label comparison was inconsistent with PEP 440. This change ensures that the local version label is checked for equivalence using a strict string equality comparison.
Resolves: python-poetry/poetry#4729