Skip to content

Commit

Permalink
markers: add new method to reduce a marker by a Python constraint
Browse files Browse the repository at this point in the history
That is useful for removing redundant information from markers.
For example, if the project's Python constraint is `>=3.9`,
a dependency's marker like `python_version >= 3.7` carries the
same information as an `AnyMarker` for this project.
(The dependency is required for all supported Python versions.)
  • Loading branch information
radoering committed Jul 29, 2024
1 parent 62b897c commit c3b86d2
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 0 deletions.
66 changes: 66 additions & 0 deletions src/poetry/core/version/markers.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from poetry.core.constraints.generic import MultiConstraint
from poetry.core.constraints.generic import UnionConstraint
from poetry.core.constraints.version import VersionConstraint
from poetry.core.constraints.version import VersionUnion
from poetry.core.constraints.version.exceptions import ParseConstraintError
from poetry.core.version.grammars import GRAMMAR_PEP_508_MARKERS
from poetry.core.version.parser import Parser
Expand Down Expand Up @@ -107,6 +108,12 @@ def exclude(self, marker_name: str) -> BaseMarker:
def only(self, *marker_names: str) -> BaseMarker:
raise NotImplementedError

@abstractmethod
def reduce_by_python_constraint(
self, python_constraint: VersionConstraint
) -> BaseMarker:
raise NotImplementedError

@abstractmethod
def invert(self) -> BaseMarker:
raise NotImplementedError
Expand Down Expand Up @@ -145,6 +152,11 @@ def exclude(self, marker_name: str) -> BaseMarker:
def only(self, *marker_names: str) -> BaseMarker:
return self

def reduce_by_python_constraint(
self, python_constraint: VersionConstraint
) -> BaseMarker:
return self

def invert(self) -> EmptyMarker:
return EmptyMarker()

Expand Down Expand Up @@ -186,6 +198,11 @@ def exclude(self, marker_name: str) -> EmptyMarker:
def only(self, *marker_names: str) -> BaseMarker:
return self

def reduce_by_python_constraint(
self, python_constraint: VersionConstraint
) -> BaseMarker:
return self

def invert(self) -> AnyMarker:
return AnyMarker()

Expand Down Expand Up @@ -283,6 +300,11 @@ def only(self, *marker_names: str) -> BaseMarker:

return self

def reduce_by_python_constraint(
self, python_constraint: VersionConstraint
) -> BaseMarker:
return self

def intersect(self, other: BaseMarker) -> BaseMarker:
if isinstance(other, SingleMarkerLike):
merged = _merge_single_markers(self, other, MultiMarker)
Expand Down Expand Up @@ -395,6 +417,18 @@ def value(self) -> str:
def _key(self) -> tuple[object, ...]:
return self._name, self._operator, self._value

def reduce_by_python_constraint(
self, python_constraint: VersionConstraint
) -> BaseMarker:
if self.name in PYTHON_VERSION_MARKERS:
assert isinstance(self._constraint, VersionConstraint)
if self._constraint.allows_all(python_constraint):
return AnyMarker()
elif not self._constraint.allows_any(python_constraint):
return EmptyMarker()

return self

def invert(self) -> BaseMarker:
if self._operator in ("===", "=="):
operator = "!="
Expand Down Expand Up @@ -670,6 +704,13 @@ def exclude(self, marker_name: str) -> BaseMarker:
def only(self, *marker_names: str) -> BaseMarker:
return self.of(*(m.only(*marker_names) for m in self._markers))

def reduce_by_python_constraint(
self, python_constraint: VersionConstraint
) -> BaseMarker:
return self.of(
*(m.reduce_by_python_constraint(python_constraint) for m in self._markers)
)

def invert(self) -> BaseMarker:
markers = [marker.invert() for marker in self._markers]

Expand Down Expand Up @@ -839,6 +880,31 @@ def exclude(self, marker_name: str) -> BaseMarker:
def only(self, *marker_names: str) -> BaseMarker:
return self.of(*(m.only(*marker_names) for m in self._markers))

def reduce_by_python_constraint(
self, python_constraint: VersionConstraint
) -> BaseMarker:
from poetry.core.packages.utils.utils import get_python_constraint_from_marker

markers: Iterable[BaseMarker] = self._markers
if isinstance(python_constraint, VersionUnion):
python_only_markers = []
other_markers = []
for m in self._markers:
if m == m.only(*PYTHON_VERSION_MARKERS):
python_only_markers.append(m)
else:
other_markers.append(m)
if get_python_constraint_from_marker(
self.of(*python_only_markers)
).allows_all(python_constraint):
if not other_markers:
return AnyMarker()
markers = other_markers

return self.of(
*(m.reduce_by_python_constraint(python_constraint) for m in markers)
)

def invert(self) -> BaseMarker:
markers = [marker.invert() for marker in self._markers]
return MultiMarker(*markers)
Expand Down
83 changes: 83 additions & 0 deletions tests/version/test_markers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from poetry.core.constraints.generic import UnionConstraint
from poetry.core.constraints.generic import parse_constraint as parse_generic_constraint
from poetry.core.constraints.version import parse_constraint as parse_version_constraint
from poetry.core.version.markers import AnyMarker
from poetry.core.version.markers import AtomicMarkerUnion
from poetry.core.version.markers import EmptyMarker
Expand Down Expand Up @@ -1175,6 +1176,88 @@ def test_only(marker: str, only: list[str], expected: str) -> None:
assert str(m.only(*only)) == expected


@pytest.mark.parametrize(
("marker", "constraint", "expected"),
[
("", "~3.8", ""),
("<empty>", "~3.8", "<empty>"),
('sys_platform == "linux"', "~3.8", 'sys_platform == "linux"'),
('python_version >= "3.8"', "~3.8", ""),
('python_version > "3.8"', "~3.8", 'python_version > "3.8"'),
('python_version >= "3.9"', "~3.8", "<empty>"),
('python_full_version >= "3.8.0"', "~3.8", ""),
('python_full_version >= "3.8.1"', "~3.8", 'python_full_version >= "3.8.1"'),
('python_full_version < "3.8.0"', "~3.8", "<empty>"),
('python_version >= "3.8" and python_version < "3.9"', "~3.8", ""),
('python_version >= "3.7" and python_version < "4.0"', "~3.8", ""),
(
'python_full_version >= "3.8.1" and python_version < "3.9"',
"~3.8",
'python_full_version >= "3.8.1"',
),
(
'python_version >= "3.8" and python_full_version < "3.8.2"',
"~3.8",
'python_full_version < "3.8.2"',
),
(
'python_version >= "3.8" and sys_platform == "linux" and python_version < "3.9"',
"~3.8",
'sys_platform == "linux"',
),
('python_version < "3.8" or python_version >= "3.9"', "~3.9", ""),
(
'python_version < "3.8" or python_version >= "3.9"',
">=3.7",
'python_version < "3.8" or python_version >= "3.9"',
),
('python_version < "3.8" or python_version >= "3.9"', "~3.7", ""),
(
'python_version < "3.8" or python_version >= "3.9"',
"<=3.10",
'python_version < "3.8" or python_version >= "3.9"',
),
(
(
'python_version < "3.8"'
' or python_version >= "3.9" and sys_platform == "linux"'
),
"~3.9",
'sys_platform == "linux"',
),
('python_version < "3.8" or python_version >= "3.9"', "~3.7 || ~3.9", ""),
(
'python_version < "3.8" or python_version >= "3.9"',
"~3.6 || ~3.8",
'python_version < "3.8"',
),
(
(
'python_version < "3.8" or sys_platform == "linux"'
' or python_version >= "3.9"'
),
"~3.7 || ~3.9",
'sys_platform == "linux"',
),
(
(
'python_version < "3.8" or sys_platform == "linux"'
' or python_version >= "3.9" or sys_platform == "win32"'
),
"~3.7 || ~3.9",
'sys_platform == "linux" or sys_platform == "win32"',
),
],
)
def test_reduce_by_python_constraint(
marker: str, constraint: str, expected: str
) -> None:
m = parse_marker(marker)
c = parse_version_constraint(constraint)

assert str(m.reduce_by_python_constraint(c)) == expected


def test_union_of_a_single_marker_is_the_single_marker() -> None:
union = MarkerUnion.of(SingleMarker("python_version", ">= 2.7"))

Expand Down

0 comments on commit c3b86d2

Please sign in to comment.