Skip to content
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

Refactor constraints structure #482

Merged
Merged
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
20 changes: 20 additions & 0 deletions src/poetry/core/constraints/generic/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from __future__ import annotations

from poetry.core.constraints.generic.any_constraint import AnyConstraint
from poetry.core.constraints.generic.base_constraint import BaseConstraint
from poetry.core.constraints.generic.constraint import Constraint
from poetry.core.constraints.generic.empty_constraint import EmptyConstraint
from poetry.core.constraints.generic.multi_constraint import MultiConstraint
from poetry.core.constraints.generic.parser import parse_constraint
from poetry.core.constraints.generic.union_constraint import UnionConstraint


__all__ = [
"AnyConstraint",
"BaseConstraint",
"Constraint",
"EmptyConstraint",
"MultiConstraint",
"UnionConstraint",
"parse_constraint",
mkniewallner marked this conversation as resolved.
Show resolved Hide resolved
]
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

from poetry.core.packages.constraints.base_constraint import BaseConstraint
from poetry.core.packages.constraints.empty_constraint import EmptyConstraint
from poetry.core.constraints.generic.base_constraint import BaseConstraint
from poetry.core.constraints.generic.empty_constraint import EmptyConstraint


class AnyConstraint(BaseConstraint):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

import operator

from poetry.core.packages.constraints import AnyConstraint
from poetry.core.packages.constraints.base_constraint import BaseConstraint
from poetry.core.packages.constraints.empty_constraint import EmptyConstraint
from poetry.core.constraints.generic.any_constraint import AnyConstraint
from poetry.core.constraints.generic.base_constraint import BaseConstraint
from poetry.core.constraints.generic.empty_constraint import EmptyConstraint


class Constraint(BaseConstraint):
Expand Down Expand Up @@ -78,7 +78,7 @@ def difference(self, other: BaseConstraint) -> Constraint | EmptyConstraint:
return self

def intersect(self, other: BaseConstraint) -> BaseConstraint:
from poetry.core.packages.constraints.multi_constraint import MultiConstraint
from poetry.core.constraints.generic.multi_constraint import MultiConstraint

if isinstance(other, Constraint):
if other == self:
Expand All @@ -99,9 +99,7 @@ def intersect(self, other: BaseConstraint) -> BaseConstraint:

def union(self, other: BaseConstraint) -> BaseConstraint:
if isinstance(other, Constraint):
from poetry.core.packages.constraints.union_constraint import (
UnionConstraint,
)
from poetry.core.constraints.generic.union_constraint import UnionConstraint

if other == self:
return self
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from poetry.core.packages.constraints.base_constraint import BaseConstraint
from poetry.core.constraints.generic.base_constraint import BaseConstraint


class EmptyConstraint(BaseConstraint):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

from poetry.core.packages.constraints.base_constraint import BaseConstraint
from poetry.core.packages.constraints.constraint import Constraint
from poetry.core.constraints.generic.base_constraint import BaseConstraint
from poetry.core.constraints.generic.constraint import Constraint


class MultiConstraint(BaseConstraint):
Expand Down
64 changes: 64 additions & 0 deletions src/poetry/core/constraints/generic/parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from __future__ import annotations

import re

from typing import TYPE_CHECKING

from poetry.core.constraints.generic.any_constraint import AnyConstraint
from poetry.core.constraints.generic.constraint import Constraint
from poetry.core.constraints.generic.union_constraint import UnionConstraint


if TYPE_CHECKING:
from poetry.core.constraints.generic.base_constraint import BaseConstraint


BASIC_CONSTRAINT = re.compile(r"^(!?==?)?\s*([^\s]+?)\s*$")


def parse_constraint(constraints: str) -> BaseConstraint:
if constraints == "*":
return AnyConstraint()

or_constraints = re.split(r"\s*\|\|?\s*", constraints.strip())
or_groups = []
for constraints in or_constraints:
and_constraints = re.split(
r"(?<!^)(?<![=>< ,]) *(?<!-)[, ](?!-) *(?!,|$)", constraints
)
constraint_objects = []

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

if len(constraint_objects) == 1:
constraint = constraint_objects[0]
else:
constraint = constraint_objects[0]
for next_constraint in constraint_objects[1:]:
constraint = constraint.intersect(next_constraint)

or_groups.append(constraint)

if len(or_groups) == 1:
return or_groups[0]
else:
return UnionConstraint(*or_groups)


def parse_single_constraint(constraint: str) -> Constraint:
# Basic comparator
m = BASIC_CONSTRAINT.match(constraint)
if m:
op = m.group(1)
if op is None:
op = "=="

version = m.group(2).strip()

return Constraint(version, op)

raise ValueError(f"Could not parse version constraint: {constraint}")
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from __future__ import annotations

from poetry.core.packages.constraints.base_constraint import BaseConstraint
from poetry.core.packages.constraints.constraint import Constraint
from poetry.core.packages.constraints.empty_constraint import EmptyConstraint
from poetry.core.packages.constraints.multi_constraint import MultiConstraint
from poetry.core.constraints.generic.base_constraint import BaseConstraint
from poetry.core.constraints.generic.constraint import Constraint
from poetry.core.constraints.generic.empty_constraint import EmptyConstraint
from poetry.core.constraints.generic.multi_constraint import MultiConstraint


class UnionConstraint(BaseConstraint):
Expand Down
24 changes: 24 additions & 0 deletions src/poetry/core/constraints/version/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from __future__ import annotations

from poetry.core.constraints.version.empty_constraint import EmptyConstraint
from poetry.core.constraints.version.parser import parse_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
from poetry.core.constraints.version.version_range import VersionRange
from poetry.core.constraints.version.version_range_constraint import (
VersionRangeConstraint,
)
from poetry.core.constraints.version.version_union import VersionUnion


__all__ = [
"EmptyConstraint",
"Version",
"VersionConstraint",
"VersionRange",
"VersionRangeConstraint",
"VersionUnion",
"constraint_regions",
"parse_constraint",
]
53 changes: 53 additions & 0 deletions src/poetry/core/constraints/version/empty_constraint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from __future__ import annotations

from typing import TYPE_CHECKING

from poetry.core.constraints.version.version_constraint import VersionConstraint


if TYPE_CHECKING:
from poetry.core.constraints.version.version import Version
from poetry.core.constraints.version.version_range_constraint import (
VersionRangeConstraint,
)


class EmptyConstraint(VersionConstraint):
def is_empty(self) -> bool:
return True

def is_any(self) -> bool:
return False

def is_simple(self) -> bool:
return True

def allows(self, version: Version) -> bool:
return False

def allows_all(self, other: VersionConstraint) -> bool:
return other.is_empty()

def allows_any(self, other: VersionConstraint) -> bool:
return False

def intersect(self, other: VersionConstraint) -> EmptyConstraint:
return self

def union(self, other: VersionConstraint) -> VersionConstraint:
return other

def difference(self, other: VersionConstraint) -> EmptyConstraint:
return self

def flatten(self) -> list[VersionRangeConstraint]:
return []

def __str__(self) -> str:
return "<empty>"

def __eq__(self, other: object) -> bool:
if not isinstance(other, VersionConstraint):
return False

return other.is_empty()
5 changes: 5 additions & 0 deletions src/poetry/core/constraints/version/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from __future__ import annotations


class ParseConstraintError(ValueError):
pass
147 changes: 147 additions & 0 deletions src/poetry/core/constraints/version/parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
from __future__ import annotations

import re

from typing import TYPE_CHECKING


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


def parse_constraint(constraints: str) -> VersionConstraint:
if constraints == "*":
from poetry.core.constraints.version.version_range import VersionRange

return VersionRange()

or_constraints = re.split(r"\s*\|\|?\s*", constraints.strip())
or_groups = []
for constraints in or_constraints:
# allow trailing commas for robustness (even though it may not be
# standard-compliant it seems to occur in some packages)
constraints = constraints.rstrip(",").rstrip()
and_constraints = re.split(
"(?<!^)(?<![~=>< ,]) *(?<!-)[, ](?!-) *(?!,|$)", constraints
)
constraint_objects = []

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

if len(constraint_objects) == 1:
constraint = constraint_objects[0]
else:
constraint = constraint_objects[0]
for next_constraint in constraint_objects[1:]:
constraint = constraint.intersect(next_constraint)

or_groups.append(constraint)

if len(or_groups) == 1:
return or_groups[0]
else:
from poetry.core.constraints.version.version_union import VersionUnion

return VersionUnion.of(*or_groups)


def parse_single_constraint(constraint: str) -> 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
from poetry.core.constraints.version.patterns import TILDE_PEP440_CONSTRAINT
from poetry.core.constraints.version.patterns import X_CONSTRAINT
from poetry.core.constraints.version.version import Version
from poetry.core.constraints.version.version_range import VersionRange
from poetry.core.constraints.version.version_union import VersionUnion

m = re.match(r"(?i)^v?[xX*](\.[xX*])*$", constraint)
if m:
return VersionRange()

# Tilde range
m = TILDE_CONSTRAINT.match(constraint)
if m:
version = Version.parse(m.group("version"))
high = version.stable.next_minor()
if version.release.precision == 1:
high = version.stable.next_major()

return VersionRange(version, high, include_min=True)

# PEP 440 Tilde range (~=)
m = TILDE_PEP440_CONSTRAINT.match(constraint)
if m:
version = Version.parse(m.group("version"))
if version.release.precision == 2:
high = version.stable.next_major()
else:
high = version.stable.next_minor()

return VersionRange(version, high, include_min=True)

# Caret range
m = CARET_CONSTRAINT.match(constraint)
if m:
version = Version.parse(m.group("version"))

return VersionRange(version, version.next_breaking(), include_min=True)

# X Range
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
)
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

# Basic comparator
m = BASIC_CONSTRAINT.match(constraint)
if m:
op = m.group("op")
version_string = m.group("version")

if version_string == "dev":
version_string = "0.0-dev"

try:
version = Version.parse(version_string)
except ValueError:
raise ValueError(f"Could not parse version constraint: {constraint}")

if op == "<":
return VersionRange(max=version)
if op == "<=":
return VersionRange(max=version, include_max=True)
if op == ">":
return VersionRange(min=version)
if op == ">=":
return VersionRange(min=version, include_min=True)
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}")
Loading