Skip to content

Commit

Permalink
wip: pypa/packaging compliance
Browse files Browse the repository at this point in the history
  • Loading branch information
abn authored and radoering committed Oct 11, 2022
1 parent ed8c17d commit e088c1b
Show file tree
Hide file tree
Showing 15 changed files with 486 additions and 123 deletions.
2 changes: 2 additions & 0 deletions src/poetry/core/constraints/version/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from poetry.core.constraints.version.empty_constraint import EmptyConstraint
from poetry.core.constraints.version.parser import parse_constraint
from poetry.core.constraints.version.parser import parse_marker_version_constraint
from poetry.core.constraints.version.util import constraint_regions
from poetry.core.constraints.version.version import Version
from poetry.core.constraints.version.version_constraint import VersionConstraint
Expand All @@ -21,4 +22,5 @@
"VersionUnion",
"constraint_regions",
"parse_constraint",
"parse_marker_version_constraint",
]
93 changes: 72 additions & 21 deletions src/poetry/core/constraints/version/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,21 @@


if TYPE_CHECKING:
from poetry.core.constraints.version.version import Version
from poetry.core.constraints.version.version_constraint import VersionConstraint


def parse_constraint(constraints: str) -> VersionConstraint:
return _parse_constraint(constraints=constraints)


def parse_marker_version_constraint(constraints: str) -> VersionConstraint:
return _parse_constraint(constraints=constraints, is_marker_constraint=True)


def _parse_constraint(
constraints: str, is_marker_constraint: bool = False
) -> VersionConstraint:
if constraints == "*":
from poetry.core.constraints.version.version_range import VersionRange

Expand All @@ -28,9 +39,13 @@ def parse_constraint(constraints: str) -> VersionConstraint:

if len(and_constraints) > 1:
for constraint in and_constraints:
constraint_objects.append(parse_single_constraint(constraint))
constraint_objects.append(
parse_single_constraint(constraint, is_marker_constraint)
)
else:
constraint_objects.append(parse_single_constraint(and_constraints[0]))
constraint_objects.append(
parse_single_constraint(and_constraints[0], is_marker_constraint)
)

if len(constraint_objects) == 1:
constraint = constraint_objects[0]
Expand All @@ -49,7 +64,9 @@ def parse_constraint(constraints: str) -> VersionConstraint:
return VersionUnion.of(*or_groups)


def parse_single_constraint(constraint: str) -> VersionConstraint:
def parse_single_constraint(
constraint: str, is_marker_constraint: bool = False
) -> VersionConstraint:
from poetry.core.constraints.version.patterns import BASIC_CONSTRAINT
from poetry.core.constraints.version.patterns import CARET_CONSTRAINT
from poetry.core.constraints.version.patterns import TILDE_CONSTRAINT
Expand Down Expand Up @@ -95,26 +112,15 @@ def parse_single_constraint(constraint: str) -> VersionConstraint:
m = X_CONSTRAINT.match(constraint)
if m:
op = m.group("op")
major = int(m.group(2))
minor = m.group(3)

if minor is not None:
version = Version.from_parts(major, int(minor), 0)
result: VersionConstraint = VersionRange(
version, version.next_minor(), include_min=True
try:
return _make_x_constraint_range(
version=Version.parse(m.group("version")),
invert=op == "!=",
is_marker_constraint=is_marker_constraint,
)
else:
if major == 0:
result = VersionRange(max=Version.from_parts(1, 0, 0))
else:
version = Version.from_parts(major, 0, 0)

result = VersionRange(version, version.next_major(), include_min=True)

if op == "!=":
result = VersionRange().difference(result)

return result
except ValueError:
raise ValueError(f"Could not parse version constraint: {constraint}")

# Basic comparator
m = BASIC_CONSTRAINT.match(constraint)
Expand All @@ -138,10 +144,55 @@ def parse_single_constraint(constraint: str) -> VersionConstraint:
return VersionRange(min=version)
if op == ">=":
return VersionRange(min=version, include_min=True)

if m.group("wildcard") is not None:
return _make_x_constraint_range(
version=version,
invert=op == "!=",
is_marker_constraint=is_marker_constraint,
)

if op == "!=":
return VersionUnion(VersionRange(max=version), VersionRange(min=version))

return version

from poetry.core.constraints.version.exceptions import ParseConstraintError

raise ParseConstraintError(f"Could not parse version constraint: {constraint}")


def _make_x_constraint_range(
version: Version, invert: bool = False, is_marker_constraint: bool = False
) -> VersionConstraint:
from poetry.core.constraints.version.version_range import VersionRange

_is_zero_major = version.major == 0 and version.precision == 1

if version.is_postrelease():
_next = version.next_postrelease()
_is_zero_major = False
elif version.is_stable():
_next = version.next_stable()
elif version.is_prerelease():
_next = version.next_prerelease()
_is_zero_major = False
elif version.is_devrelease():
_next = version.next_devrelease()
_is_zero_major = False

if _is_zero_major:
result = VersionRange(max=_next.with_precision(3))
else:
_min = version.with_precision(max(version.precision, 3))

if not is_marker_constraint and not _next.is_unstable():
_min = _min.next_devrelease()

_max = _next.with_precision(max(version.precision, 3))
result = VersionRange(_min, _max, include_min=True)

if invert:
return VersionRange().difference(result)

return result
4 changes: 2 additions & 2 deletions src/poetry/core/constraints/version/patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@
rf"^~=\s*(?P<version>{VERSION_PATTERN})$", re.VERBOSE | re.IGNORECASE
)
X_CONSTRAINT = re.compile(
r"^(?P<op>!=|==)?\s*v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.[xX*])+$"
r"^(?P<op>!=|==)?\s*v?(?P<version>(\d+)(?:\.(\d+))?(?:\.(\d+))?)(?:\.[xX*])+$"
)

# note that we also allow technically incorrect version patterns with astrix (eg: 3.5.*)
# as this is supported by pip and appears in metadata within python packages
BASIC_CONSTRAINT = re.compile(
rf"^(?P<op><>|!=|>=?|<=?|==?)?\s*(?P<version>{VERSION_PATTERN}|dev)(\.\*)?$",
rf"^(?P<op><>|!=|>=?|<=?|==?)?\s*(?P<version>{VERSION_PATTERN}|dev)(?P<wildcard>\.\*)?$",
re.VERBOSE | re.IGNORECASE,
)
45 changes: 18 additions & 27 deletions src/poetry/core/constraints/version/version_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,9 @@ def __init__(
max: Version | None = None,
include_min: bool = False,
include_max: bool = False,
always_include_max_prerelease: bool = False,
) -> None:
full_max = max
if (
not always_include_max_prerelease
and not include_max
and full_max is not None
and full_max.is_stable()
and not full_max.is_postrelease()
and (min is None or min.is_stable() or min.release != full_max.release)
):
full_max = full_max.first_prerelease()

self._min = min
self._max = max
self._full_max = full_max
self._min = min
self._include_min = include_min
self._include_max = include_max

Expand All @@ -48,10 +35,6 @@ def min(self) -> Version | None:
def max(self) -> Version | None:
return self._max

@property
def full_max(self) -> Version | None:
return self._full_max

@property
def include_min(self) -> bool:
return self._include_min
Expand All @@ -71,27 +54,35 @@ def is_simple(self) -> bool:

def allows(self, other: Version) -> bool:
if self._min is not None:
if other < self._min:
_this, _other = self.allowed_min, other

assert _this is not None

if not _this.is_postrelease() and _other.is_postrelease():
_other = _other.without_postrelease()

if not _this.is_local() and _other.is_local():
_other = other.without_local()

if _other < _this:
return False

if not self._include_min and other == self._min:
if not self._include_min and (_other == self._min or _other == _this):
return False

if self.full_max is not None:
_this, _other = self.full_max, other
if self.max is not None:
_this, _other = self.allowed_max, other

assert _this is not None

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()

if not _this.is_postrelease() and _other.is_postrelease():
# allow weak equality to allow `3.0.0-1` for `<=3.0.0`
_other = _other.without_postrelease()

if _other > _this:
return False

if not self._include_max and _other == _this:
if not self._include_max and (_other == self._max or _other == _this):
return False

return True
Expand Down
65 changes: 47 additions & 18 deletions src/poetry/core/constraints/version/version_range_constraint.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,6 @@ def min(self) -> Version | None:
def max(self) -> Version | None:
raise NotImplementedError()

@property
@abstractmethod
def full_max(self) -> Version | None:
raise NotImplementedError()

@property
@abstractmethod
def include_min(self) -> bool:
Expand All @@ -36,44 +31,78 @@ def include_min(self) -> bool:
def include_max(self) -> bool:
raise NotImplementedError()

def allows_lower(self, other: VersionRangeConstraint) -> bool:
@property
def allowed_min(self) -> Version | None:
if self.min is None:
return other.min is not None
return None

if not self.include_min or self.min.is_unstable():
return self.min

if self.min == self.max and (self.include_min or self.include_max):
# this is an equality range
return self.min

if other.min is None:
return self.min

@property
def allowed_max(self) -> Version | None:
if self.max is None:
return None

if self.include_max or self.max.is_unstable():
return self.max

if self.min == self.max and (self.include_min or self.include_max):
# this is an equality range
return self.max

return self.max.next_devrelease()

def allows_lower(self, other: VersionRangeConstraint) -> bool:
_this, _other = self.allowed_min, other.allowed_min

if _this is None:
return _other is not None

if _other is None:
return False

if self.min < other.min:
if _this < _other:
return True

if self.min > other.min:
if _this > _other:
return False

return self.include_min and not other.include_min

def allows_higher(self, other: VersionRangeConstraint) -> bool:
if self.full_max is None:
return other.max is not None
_this, _other = self.allowed_max, other.allowed_max

if other.full_max is None:
if _this is None:
return _other is not None

if _other is None:
return False

if self.full_max < other.full_max:
if _this < _other:
return False

if self.full_max > other.full_max:
if _this > _other:
return True

return self.include_max and not other.include_max

def is_strictly_lower(self, other: VersionRangeConstraint) -> bool:
if self.full_max is None or other.min is None:
_this, _other = self.allowed_max, other.allowed_min

if _this is None or _other is None:
return False

if self.full_max < other.min:
if _this < _other:
return True

if self.full_max > other.min:
if _this > _other:
return False

return not self.include_max or not other.include_min
Expand Down
Loading

0 comments on commit e088c1b

Please sign in to comment.