Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/poetry/core/constraints/version/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,13 @@ def __str__(self) -> str:
return self.text

def __eq__(self, other: object) -> bool:
# Common case: comparing against another Version. Handle it first
# (inlining the semantics of the dataclass-generated __eq__) to avoid
# extra call layers and the (otherwise per-call) import of VersionRange
# below.
if self.__class__ is other.__class__:
return self._compare_key == other._compare_key

from poetry.core.constraints.version.version_range import VersionRange

if isinstance(other, VersionRange):
Expand Down
78 changes: 23 additions & 55 deletions src/poetry/core/version/pep440/version.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import annotations

import dataclasses
import functools
import warnings

from typing import TYPE_CHECKING
Expand All @@ -21,44 +20,22 @@
from poetry.core.version.pep440.segments import LocalSegmentType


@functools.total_ordering
class AlwaysSmaller:
def __lt__(self, other: object) -> bool:
return True

def __eq__(self, other: object) -> bool:
return isinstance(other, AlwaysSmaller)

def __hash__(self) -> int:
return id(AlwaysSmaller)


@functools.total_ordering
class AlwaysGreater:
def __gt__(self, other: object) -> bool:
return True

def __eq__(self, other: object) -> bool:
return isinstance(other, AlwaysGreater)

def __hash__(self) -> int:
return id(AlwaysGreater)


class Infinity(AlwaysGreater, int):
pass


class NegativeInfinity(AlwaysSmaller, int):
pass


T = TypeVar("T", bound="PEP440Version")

# The compare key only contains built-in types (ints, floats, strings and
# tuples thereof), so that comparing and hashing versions stays entirely in
# C code. Infinity sentinels are expressed as floats; phases as strings that
# sort correctly against the real phase identifiers:
# we use the phase "z" to ensure we always sort this after other phases
_INF_TAG = ReleaseTag("z", Infinity())
_INF_TAG = ("z", float("inf"))
# we use the phase "" to ensure we always sort this before other phases
_NEG_INF_TAG = ReleaseTag("", NegativeInfinity())
_NEG_INF_TAG = ("", float("-inf"))

_CompareTag = tuple[str, int | float]
_CompareLocal = tuple[tuple[int | float, str], ...]
_CompareKey = tuple[
int, tuple[int, ...], _CompareTag, _CompareTag, _CompareTag, _CompareLocal
]


@dataclasses.dataclass(frozen=True, eq=True, order=True)
Expand All @@ -70,9 +47,7 @@ class PEP440Version:
dev: ReleaseTag | None = dataclasses.field(default=None, compare=False)
local: LocalSegmentType = dataclasses.field(default=None, compare=False)
text: str = dataclasses.field(init=False, compare=False)
_compare_key: tuple[
int, Release, ReleaseTag, ReleaseTag, ReleaseTag, tuple[int | str, ...]
] = dataclasses.field(init=False, compare=True)
_compare_key: _CompareKey = dataclasses.field(init=False, compare=True)

def __post_init__(self) -> None:
if self.local is not None and not isinstance(self.local, tuple):
Expand All @@ -85,16 +60,7 @@ def __post_init__(self) -> None:

object.__setattr__(self, "_compare_key", self._make_compare_key())

def _make_compare_key(
self,
) -> tuple[
int,
Release,
ReleaseTag,
ReleaseTag,
ReleaseTag,
tuple[tuple[int, int | str], ...],
]:
def _make_compare_key(self) -> _CompareKey:
"""
This code is based on the implementation of packaging.version._cmpkey(..)
"""
Expand All @@ -109,18 +75,20 @@ def _make_compare_key(
elif self.pre is None:
_pre = _INF_TAG
else:
_pre = self.pre
_pre = (self.pre.phase, self.pre.number)

# Versions without a post segment should sort before those with one.
_post = _NEG_INF_TAG if self.post is None else self.post
_post = (
_NEG_INF_TAG if self.post is None else (self.post.phase, self.post.number)
)

# Versions without a development segment should sort after those with one.
_dev = _INF_TAG if self.dev is None else self.dev
_dev = _INF_TAG if self.dev is None else (self.dev.phase, self.dev.number)

_local: tuple[tuple[int, int | str], ...]
_local: _CompareLocal
if self.local is None:
# Versions without a local segment should sort before those with one.
_local = ((NegativeInfinity(), ""),)
_local = ((float("-inf"), ""),)
else:
# Versions with a local segment need that segment parsed to implement
# the sorting rules in PEP440.
Expand All @@ -132,10 +100,10 @@ def _make_compare_key(
assert isinstance(self.local, tuple)
# We convert strings that are integers so that they can be compared
_local = tuple(
(int(i), "") if str(i).isnumeric() else (NegativeInfinity(), i)
(int(i), "") if (s_i := str(i)).isnumeric() else (float("-inf"), s_i)
for i in self.local
)
return self.epoch, self.release, _pre, _post, _dev, _local
return self.epoch, self.release._compare_key, _pre, _post, _dev, _local

@property
def major(self) -> int:
Expand Down
Loading