Skip to content

Commit ec280ef

Browse files
committed
markers: add new method to reduce a marker by a Python constraint
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.)
1 parent 94f933f commit ec280ef

File tree

2 files changed

+149
-0
lines changed

2 files changed

+149
-0
lines changed

src/poetry/core/version/markers.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from poetry.core.constraints.generic import MultiConstraint
2121
from poetry.core.constraints.generic import UnionConstraint
2222
from poetry.core.constraints.version import VersionConstraint
23+
from poetry.core.constraints.version import VersionUnion
2324
from poetry.core.constraints.version.exceptions import ParseConstraintError
2425
from poetry.core.version.grammars import GRAMMAR_PEP_508_MARKERS
2526
from poetry.core.version.parser import Parser
@@ -107,6 +108,12 @@ def exclude(self, marker_name: str) -> BaseMarker:
107108
def only(self, *marker_names: str) -> BaseMarker:
108109
raise NotImplementedError
109110

111+
@abstractmethod
112+
def reduce_by_python_constraint(
113+
self, python_constraint: VersionConstraint
114+
) -> BaseMarker:
115+
raise NotImplementedError
116+
110117
@abstractmethod
111118
def invert(self) -> BaseMarker:
112119
raise NotImplementedError
@@ -145,6 +152,11 @@ def exclude(self, marker_name: str) -> BaseMarker:
145152
def only(self, *marker_names: str) -> BaseMarker:
146153
return self
147154

155+
def reduce_by_python_constraint(
156+
self, python_constraint: VersionConstraint
157+
) -> BaseMarker:
158+
return self
159+
148160
def invert(self) -> EmptyMarker:
149161
return EmptyMarker()
150162

@@ -186,6 +198,11 @@ def exclude(self, marker_name: str) -> EmptyMarker:
186198
def only(self, *marker_names: str) -> BaseMarker:
187199
return self
188200

201+
def reduce_by_python_constraint(
202+
self, python_constraint: VersionConstraint
203+
) -> BaseMarker:
204+
return self
205+
189206
def invert(self) -> AnyMarker:
190207
return AnyMarker()
191208

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

284301
return self
285302

303+
def reduce_by_python_constraint(
304+
self, python_constraint: VersionConstraint
305+
) -> BaseMarker:
306+
return self
307+
286308
def intersect(self, other: BaseMarker) -> BaseMarker:
287309
if isinstance(other, SingleMarkerLike):
288310
merged = _merge_single_markers(self, other, MultiMarker)
@@ -395,6 +417,18 @@ def value(self) -> str:
395417
def _key(self) -> tuple[object, ...]:
396418
return self._name, self._operator, self._value
397419

420+
def reduce_by_python_constraint(
421+
self, python_constraint: VersionConstraint
422+
) -> BaseMarker:
423+
if self.name in PYTHON_VERSION_MARKERS:
424+
assert isinstance(self._constraint, VersionConstraint)
425+
if self._constraint.allows_all(python_constraint):
426+
return AnyMarker()
427+
elif not self._constraint.allows_any(python_constraint):
428+
return EmptyMarker()
429+
430+
return self
431+
398432
def invert(self) -> BaseMarker:
399433
if self._operator in ("===", "=="):
400434
operator = "!="
@@ -670,6 +704,13 @@ def exclude(self, marker_name: str) -> BaseMarker:
670704
def only(self, *marker_names: str) -> BaseMarker:
671705
return self.of(*(m.only(*marker_names) for m in self._markers))
672706

707+
def reduce_by_python_constraint(
708+
self, python_constraint: VersionConstraint
709+
) -> BaseMarker:
710+
return self.of(
711+
*(m.reduce_by_python_constraint(python_constraint) for m in self._markers)
712+
)
713+
673714
def invert(self) -> BaseMarker:
674715
markers = [marker.invert() for marker in self._markers]
675716

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

883+
def reduce_by_python_constraint(
884+
self, python_constraint: VersionConstraint
885+
) -> BaseMarker:
886+
from poetry.core.packages.utils.utils import get_python_constraint_from_marker
887+
888+
markers: Iterable[BaseMarker] = self._markers
889+
if isinstance(python_constraint, VersionUnion):
890+
python_only_markers = []
891+
other_markers = []
892+
for m in self._markers:
893+
if m == m.only(*PYTHON_VERSION_MARKERS):
894+
python_only_markers.append(m)
895+
else:
896+
other_markers.append(m)
897+
if get_python_constraint_from_marker(
898+
self.of(*python_only_markers)
899+
).allows_all(python_constraint):
900+
if not other_markers:
901+
return AnyMarker()
902+
markers = other_markers
903+
904+
return self.of(
905+
*(m.reduce_by_python_constraint(python_constraint) for m in markers)
906+
)
907+
842908
def invert(self) -> BaseMarker:
843909
markers = [marker.invert() for marker in self._markers]
844910
return MultiMarker(*markers)

tests/version/test_markers.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from poetry.core.constraints.generic import UnionConstraint
1010
from poetry.core.constraints.generic import parse_constraint as parse_generic_constraint
11+
from poetry.core.constraints.version import parse_constraint as parse_version_constraint
1112
from poetry.core.version.markers import AnyMarker
1213
from poetry.core.version.markers import AtomicMarkerUnion
1314
from poetry.core.version.markers import EmptyMarker
@@ -1175,6 +1176,88 @@ def test_only(marker: str, only: list[str], expected: str) -> None:
11751176
assert str(m.only(*only)) == expected
11761177

11771178

1179+
@pytest.mark.parametrize(
1180+
("marker", "constraint", "expected"),
1181+
[
1182+
("", "~3.8", ""),
1183+
("<empty>", "~3.8", "<empty>"),
1184+
('sys_platform == "linux"', "~3.8", 'sys_platform == "linux"'),
1185+
('python_version >= "3.8"', "~3.8", ""),
1186+
('python_version > "3.8"', "~3.8", 'python_version > "3.8"'),
1187+
('python_version >= "3.9"', "~3.8", "<empty>"),
1188+
('python_full_version >= "3.8.0"', "~3.8", ""),
1189+
('python_full_version >= "3.8.1"', "~3.8", 'python_full_version >= "3.8.1"'),
1190+
('python_full_version < "3.8.0"', "~3.8", "<empty>"),
1191+
('python_version >= "3.8" and python_version < "3.9"', "~3.8", ""),
1192+
('python_version >= "3.7" and python_version < "4.0"', "~3.8", ""),
1193+
(
1194+
'python_full_version >= "3.8.1" and python_version < "3.9"',
1195+
"~3.8",
1196+
'python_full_version >= "3.8.1"',
1197+
),
1198+
(
1199+
'python_version >= "3.8" and python_full_version < "3.8.2"',
1200+
"~3.8",
1201+
'python_full_version < "3.8.2"',
1202+
),
1203+
(
1204+
'python_version >= "3.8" and sys_platform == "linux" and python_version < "3.9"',
1205+
"~3.8",
1206+
'sys_platform == "linux"',
1207+
),
1208+
('python_version < "3.8" or python_version >= "3.9"', "~3.9", ""),
1209+
(
1210+
'python_version < "3.8" or python_version >= "3.9"',
1211+
">=3.7",
1212+
'python_version < "3.8" or python_version >= "3.9"',
1213+
),
1214+
('python_version < "3.8" or python_version >= "3.9"', "~3.7", ""),
1215+
(
1216+
'python_version < "3.8" or python_version >= "3.9"',
1217+
"<=3.10",
1218+
'python_version < "3.8" or python_version >= "3.9"',
1219+
),
1220+
(
1221+
(
1222+
'python_version < "3.8"'
1223+
' or python_version >= "3.9" and sys_platform == "linux"'
1224+
),
1225+
"~3.9",
1226+
'sys_platform == "linux"',
1227+
),
1228+
('python_version < "3.8" or python_version >= "3.9"', "~3.7 || ~3.9", ""),
1229+
(
1230+
'python_version < "3.8" or python_version >= "3.9"',
1231+
"~3.6 || ~3.8",
1232+
'python_version < "3.8"',
1233+
),
1234+
(
1235+
(
1236+
'python_version < "3.8" or sys_platform == "linux"'
1237+
' or python_version >= "3.9"'
1238+
),
1239+
"~3.7 || ~3.9",
1240+
'sys_platform == "linux"',
1241+
),
1242+
(
1243+
(
1244+
'python_version < "3.8" or sys_platform == "linux"'
1245+
' or python_version >= "3.9" or sys_platform == "win32"'
1246+
),
1247+
"~3.7 || ~3.9",
1248+
'sys_platform == "linux" or sys_platform == "win32"',
1249+
),
1250+
],
1251+
)
1252+
def test_reduce_by_python_constraint(
1253+
marker: str, constraint: str, expected: str
1254+
) -> None:
1255+
m = parse_marker(marker)
1256+
c = parse_version_constraint(constraint)
1257+
1258+
assert str(m.reduce_by_python_constraint(c)) == expected
1259+
1260+
11781261
def test_union_of_a_single_marker_is_the_single_marker() -> None:
11791262
union = MarkerUnion.of(SingleMarker("python_version", ">= 2.7"))
11801263

0 commit comments

Comments
 (0)