Skip to content

Commit 69a8b76

Browse files
committed
markers: reintroduce merging of python_version and python_full_version markers
1 parent c29d741 commit 69a8b76

File tree

3 files changed

+211
-28
lines changed

3 files changed

+211
-28
lines changed

src/poetry/core/version/markers.py

+53
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ class UndefinedEnvironmentName(ValueError):
4747
"python_implementation": "platform_python_implementation",
4848
}
4949

50+
PYTHON_VERSION_MARKERS = {"python_version", "python_full_version"}
51+
5052
# Parser: PEP 508 Environment Markers
5153
_parser = Parser(GRAMMAR_PEP_508_MARKERS, "lalr")
5254

@@ -414,6 +416,10 @@ def of(cls, *markers: BaseMarker) -> BaseMarker:
414416
for i, mark in enumerate(new_markers):
415417
if isinstance(mark, SingleMarker) and (
416418
mark.name == marker.name
419+
or (
420+
mark.name in PYTHON_VERSION_MARKERS
421+
and marker.name in PYTHON_VERSION_MARKERS
422+
)
417423
):
418424
new_marker = _merge_single_markers(mark, marker, cls)
419425
if new_marker is not None:
@@ -617,6 +623,10 @@ def of(cls, *markers: BaseMarker) -> BaseMarker:
617623
for i, mark in enumerate(new_markers):
618624
if isinstance(mark, SingleMarker) and (
619625
mark.name == marker.name
626+
or (
627+
mark.name in PYTHON_VERSION_MARKERS
628+
and marker.name in PYTHON_VERSION_MARKERS
629+
)
620630
):
621631
new_marker = _merge_single_markers(mark, marker, cls)
622632
if new_marker is not None:
@@ -847,6 +857,9 @@ def _merge_single_markers(
847857
marker2: SingleMarker,
848858
merge_class: type[MultiMarker | MarkerUnion],
849859
) -> BaseMarker | None:
860+
if {marker1.name, marker2.name} == {"python_version", "python_full_version"}:
861+
return _merge_python_version_single_markers(marker1, marker2, merge_class)
862+
850863
if merge_class == MultiMarker:
851864
merge_method = marker1.constraint.intersect
852865
else:
@@ -870,3 +883,43 @@ def _merge_single_markers(
870883
):
871884
return SingleMarker(marker1.name, result_constraint)
872885
return result_marker
886+
887+
888+
def _merge_python_version_single_markers(
889+
marker1: SingleMarker,
890+
marker2: SingleMarker,
891+
merge_class: type[MultiMarker | MarkerUnion],
892+
) -> BaseMarker | None:
893+
from poetry.core.packages.utils.utils import get_python_constraint_from_marker
894+
895+
if marker1.name == "python_version":
896+
marker = marker1
897+
full_marker = marker2
898+
else:
899+
marker = marker2
900+
full_marker = marker1
901+
902+
normalized_constraint = get_python_constraint_from_marker(marker)
903+
if marker.constraint != normalized_constraint:
904+
normalized_marker = SingleMarker("python_full_version", normalized_constraint)
905+
else:
906+
normalized_marker = SingleMarker("python_full_version", normalized_constraint)
907+
908+
merged_marker = _merge_single_markers(normalized_marker, full_marker, merge_class)
909+
if merged_marker == normalized_marker:
910+
# prefer original marker to avoid unnecessary changes
911+
return marker
912+
elif merged_marker and isinstance(merged_marker, SingleMarker):
913+
# We have to fix markers like 'python_full_version == "3.6"'
914+
# to receive 'python_full_version == "3.6.0"'.
915+
# It seems a bit hacky to convert to string and back to marker,
916+
# but it's probably much simpler than to consider the different constraint
917+
# classes (mostly VersonRangeConstraint, but VersionUnion for "!=") and
918+
# since this conversion is only required for python_full_version markers
919+
# it may be sufficient to handle it here.
920+
marker_string = str(merged_marker)
921+
precision = marker_string.count(".") + 1
922+
if precision != 3:
923+
marker_string = marker_string[:-1] + ".0" * (3 - precision) + '"'
924+
merged_marker = parse_marker(marker_string)
925+
return merged_marker

tests/packages/utils/test_utils.py

+1-6
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,7 @@
4444
(
4545
'(python_version < "2.7" or python_full_version >= "3.0.0") and'
4646
' python_full_version < "3.6.0"',
47-
{
48-
"python_version": [
49-
[("<", "2.7"), ("<", "3.6.0")],
50-
[(">=", "3.0.0"), ("<", "3.6.0")],
51-
]
52-
},
47+
{"python_version": [[("<", "2.7")], [(">=", "3.0.0"), ("<", "3.6.0")]]},
5348
),
5449
(
5550
'(python_version < "2.7" or python_full_version >= "3.0.0") and'

tests/version/test_markers.py

+157-22
Original file line numberDiff line numberDiff line change
@@ -166,18 +166,6 @@ def test_single_marker_not_in_python_intersection() -> None:
166166
assert str(intersection) == 'python_version not in "2.7, 3.0, 3.1, 3.2"'
167167

168168

169-
def test_marker_intersection_of_python_version_and_python_full_version() -> None:
170-
m = parse_marker('python_version > "3.6"')
171-
m2 = parse_marker('python_full_version >= "3.6.2"')
172-
intersection = m.intersect(m2)
173-
174-
# 'python_version > "3.6"' would be good, but not
175-
# 'python_full_version >= "3.6.2"'.
176-
assert (
177-
str(intersection) == 'python_version > "3.6" and python_full_version >= "3.6.2"'
178-
)
179-
180-
181169
def test_single_marker_union() -> None:
182170
m = parse_marker('sys_platform == "darwin"')
183171

@@ -542,16 +530,6 @@ def test_marker_union_deduplicate() -> None:
542530
assert str(m) == 'sys_platform == "darwin" or implementation_name == "cpython"'
543531

544532

545-
def test_marker_union_of_python_version_and_python_full_version() -> None:
546-
m = parse_marker('python_version > "3.6"')
547-
m2 = parse_marker('python_full_version >= "3.6.2"')
548-
union = m.union(m2)
549-
550-
# 'python_full_version >= "3.6.2"' would be good, but not
551-
# 'python_version > "3.6"'.
552-
assert str(union) == 'python_version > "3.6" or python_full_version >= "3.6.2"'
553-
554-
555533
def test_marker_union_intersect_single_marker() -> None:
556534
m = parse_marker('sys_platform == "darwin" or python_version < "3.4"')
557535

@@ -1216,3 +1194,160 @@ def test_single_markers_are_found_in_complex_intersection() -> None:
12161194
str(intersection)
12171195
== 'implementation_name == "cpython" and python_version == "3.6"'
12181196
)
1197+
1198+
1199+
@pytest.mark.parametrize(
1200+
"python_version, python_full_version, "
1201+
"expected_intersection_version, expected_union_version",
1202+
[
1203+
# python_version > 3.6 (equal to python_full_version >= 3.7.0)
1204+
('> "3.6"', '> "3.5.2"', '> "3.6"', '> "3.5.2"'),
1205+
('> "3.6"', '>= "3.5.2"', '> "3.6"', '>= "3.5.2"'),
1206+
('> "3.6"', '> "3.6.2"', '> "3.6"', '> "3.6.2"'),
1207+
('> "3.6"', '>= "3.6.2"', '> "3.6"', '>= "3.6.2"'),
1208+
('> "3.6"', '> "3.7.0"', '> "3.7.0"', '> "3.6"'),
1209+
('> "3.6"', '>= "3.7.0"', '> "3.6"', '> "3.6"'),
1210+
('> "3.6"', '> "3.7.1"', '> "3.7.1"', '> "3.6"'),
1211+
('> "3.6"', '>= "3.7.1"', '>= "3.7.1"', '> "3.6"'),
1212+
('> "3.6"', '== "3.6.2"', "<empty>", None),
1213+
('> "3.6"', '== "3.7.0"', '== "3.7.0"', '> "3.6"'),
1214+
('> "3.6"', '== "3.7.1"', '== "3.7.1"', '> "3.6"'),
1215+
('> "3.6"', '!= "3.6.2"', '> "3.6"', '!= "3.6.2"'),
1216+
('> "3.6"', '!= "3.7.0"', '> "3.7.0"', ""),
1217+
('> "3.6"', '!= "3.7.1"', None, ""),
1218+
('> "3.6"', '< "3.7.0"', "<empty>", ""),
1219+
('> "3.6"', '<= "3.7.0"', '== "3.7.0"', ""),
1220+
('> "3.6"', '< "3.7.1"', None, ""),
1221+
('> "3.6"', '<= "3.7.1"', None, ""),
1222+
# python_version >= 3.6 (equal to python_full_version >= 3.6.0)
1223+
('>= "3.6"', '> "3.5.2"', '>= "3.6"', '> "3.5.2"'),
1224+
('>= "3.6"', '>= "3.5.2"', '>= "3.6"', '>= "3.5.2"'),
1225+
('>= "3.6"', '> "3.6.0"', '> "3.6.0"', '>= "3.6"'),
1226+
('>= "3.6"', '>= "3.6.0"', '>= "3.6"', '>= "3.6"'),
1227+
('>= "3.6"', '> "3.6.1"', '> "3.6.1"', '>= "3.6"'),
1228+
('>= "3.6"', '>= "3.6.1"', '>= "3.6.1"', '>= "3.6"'),
1229+
('>= "3.6"', '== "3.5.2"', "<empty>", None),
1230+
('>= "3.6"', '== "3.6.0"', '== "3.6.0"', '>= "3.6"'),
1231+
('>= "3.6"', '!= "3.5.2"', '>= "3.6"', '!= "3.5.2"'),
1232+
('>= "3.6"', '!= "3.6.0"', '> "3.6.0"', ""),
1233+
('>= "3.6"', '!= "3.6.1"', None, ""),
1234+
('>= "3.6"', '!= "3.7.1"', None, ""),
1235+
('>= "3.6"', '< "3.6.0"', "<empty>", ""),
1236+
('>= "3.6"', '<= "3.6.0"', '== "3.6.0"', ""),
1237+
('>= "3.6"', '< "3.6.1"', None, ""), # '== "3.6.0"'
1238+
('>= "3.6"', '<= "3.6.1"', None, ""),
1239+
# python_version < 3.6 (equal to python_full_version < 3.6.0)
1240+
('< "3.6"', '< "3.5.2"', '< "3.5.2"', '< "3.6"'),
1241+
('< "3.6"', '<= "3.5.2"', '<= "3.5.2"', '< "3.6"'),
1242+
('< "3.6"', '< "3.6.0"', '< "3.6"', '< "3.6"'),
1243+
('< "3.6"', '<= "3.6.0"', '< "3.6"', '<= "3.6.0"'),
1244+
('< "3.6"', '< "3.6.1"', '< "3.6"', '< "3.6.1"'),
1245+
('< "3.6"', '<= "3.6.1"', '< "3.6"', '<= "3.6.1"'),
1246+
('< "3.6"', '== "3.5.2"', '== "3.5.2"', '< "3.6"'),
1247+
('< "3.6"', '== "3.6.0"', "<empty>", '<= "3.6.0"'),
1248+
('< "3.6"', '!= "3.5.2"', None, ""),
1249+
('< "3.6"', '!= "3.6.0"', '< "3.6"', '!= "3.6.0"'),
1250+
('< "3.6"', '> "3.6.0"', "<empty>", '!= "3.6.0"'),
1251+
('< "3.6"', '>= "3.6.0"', "<empty>", ""),
1252+
('< "3.6"', '> "3.5.2"', None, ""),
1253+
('< "3.6"', '>= "3.5.2"', None, ""),
1254+
# python_version <= 3.6 (equal to python_full_version < 3.7.0)
1255+
('<= "3.6"', '< "3.6.1"', '< "3.6.1"', '<= "3.6"'),
1256+
('<= "3.6"', '<= "3.6.1"', '<= "3.6.1"', '<= "3.6"'),
1257+
('<= "3.6"', '< "3.7.0"', '<= "3.6"', '<= "3.6"'),
1258+
('<= "3.6"', '<= "3.7.0"', '<= "3.6"', '<= "3.7.0"'),
1259+
('<= "3.6"', '== "3.6.1"', '== "3.6.1"', '<= "3.6"'),
1260+
('<= "3.6"', '== "3.7.0"', "<empty>", '<= "3.7.0"'),
1261+
('<= "3.6"', '!= "3.6.1"', None, ""),
1262+
('<= "3.6"', '!= "3.7.0"', '<= "3.6"', '!= "3.7.0"'),
1263+
('<= "3.6"', '> "3.7.0"', "<empty>", '!= "3.7.0"'),
1264+
('<= "3.6"', '>= "3.7.0"', "<empty>", ""),
1265+
('<= "3.6"', '> "3.6.2"', None, ""),
1266+
('<= "3.6"', '>= "3.6.2"', None, ""),
1267+
# python_version == 3.6 # noqa: E800
1268+
# (equal to python_full_version >= 3.6.0 and python_full_version < 3.7.0)
1269+
('== "3.6"', '< "3.5.2"', "<empty>", None),
1270+
('== "3.6"', '<= "3.5.2"', "<empty>", None),
1271+
('== "3.6"', '> "3.5.2"', '== "3.6"', '> "3.5.2"'),
1272+
('== "3.6"', '>= "3.5.2"', '== "3.6"', '>= "3.5.2"'),
1273+
('== "3.6"', '!= "3.5.2"', '== "3.6"', '!= "3.5.2"'),
1274+
('== "3.6"', '< "3.6.0"', "<empty>", '< "3.7.0"'),
1275+
('== "3.6"', '<= "3.6.0"', '== "3.6.0"', '< "3.7.0"'),
1276+
('== "3.6"', '> "3.6.0"', None, '>= "3.6.0"'),
1277+
('== "3.6"', '>= "3.6.0"', '== "3.6"', '>= "3.6.0"'),
1278+
('== "3.6"', '!= "3.6.0"', None, ""),
1279+
('== "3.6"', '< "3.6.1"', None, '< "3.7.0"'),
1280+
('== "3.6"', '<= "3.6.1"', None, '< "3.7.0"'),
1281+
('== "3.6"', '> "3.6.1"', None, '>= "3.6.0"'),
1282+
('== "3.6"', '>= "3.6.1"', None, '>= "3.6.0"'),
1283+
('== "3.6"', '!= "3.6.1"', None, ""),
1284+
('== "3.6"', '< "3.7.0"', '== "3.6"', '< "3.7.0"'),
1285+
('== "3.6"', '<= "3.7.0"', '== "3.6"', '<= "3.7.0"'),
1286+
('== "3.6"', '> "3.7.0"', "<empty>", None),
1287+
('== "3.6"', '>= "3.7.0"', "<empty>", '>= "3.6.0"'),
1288+
('== "3.6"', '!= "3.7.0"', '== "3.6"', '!= "3.7.0"'),
1289+
('== "3.6"', '<= "3.7.1"', '== "3.6"', '<= "3.7.1"'),
1290+
('== "3.6"', '< "3.7.1"', '== "3.6"', '< "3.7.1"'),
1291+
('== "3.6"', '> "3.7.1"', "<empty>", None),
1292+
('== "3.6"', '>= "3.7.1"', "<empty>", None),
1293+
('== "3.6"', '!= "3.7.1"', '== "3.6"', '!= "3.7.1"'),
1294+
# python_version != 3.6 # noqa: E800
1295+
# (equal to python_full_version < 3.6.0 or python_full_version >= 3.7.0)
1296+
('!= "3.6"', '< "3.5.2"', '< "3.5.2"', '!= "3.6"'),
1297+
('!= "3.6"', '<= "3.5.2"', '<= "3.5.2"', '!= "3.6"'),
1298+
('!= "3.6"', '> "3.5.2"', None, ""),
1299+
('!= "3.6"', '>= "3.5.2"', None, ""),
1300+
('!= "3.6"', '!= "3.5.2"', None, ""),
1301+
('!= "3.6"', '< "3.6.0"', '< "3.6.0"', '!= "3.6"'),
1302+
('!= "3.6"', '<= "3.6.0"', '< "3.6.0"', None),
1303+
('!= "3.6"', '> "3.6.0"', '>= "3.7.0"', '!= "3.6.0"'),
1304+
('!= "3.6"', '>= "3.6.0"', '>= "3.7.0"', ""),
1305+
('!= "3.6"', '!= "3.6.0"', '!= "3.6"', '!= "3.6.0"'),
1306+
('!= "3.6"', '< "3.6.1"', '< "3.6.0"', None),
1307+
('!= "3.6"', '<= "3.6.1"', '< "3.6.0"', None),
1308+
('!= "3.6"', '> "3.6.1"', '>= "3.7.0"', None),
1309+
('!= "3.6"', '>= "3.6.1"', '>= "3.7.0"', None),
1310+
('!= "3.6"', '!= "3.6.1"', '!= "3.6"', '!= "3.6.1"'),
1311+
('!= "3.6"', '< "3.7.0"', '< "3.6.0"', ""),
1312+
('!= "3.6"', '<= "3.7.0"', None, ""),
1313+
('!= "3.6"', '> "3.7.0"', '> "3.7.0"', '!= "3.6"'),
1314+
('!= "3.6"', '>= "3.7.0"', '>= "3.7.0"', '!= "3.6"'),
1315+
('!= "3.6"', '!= "3.7.0"', None, ""),
1316+
('!= "3.6"', '<= "3.7.1"', None, ""),
1317+
('!= "3.6"', '< "3.7.1"', None, ""),
1318+
('!= "3.6"', '> "3.7.1"', '> "3.7.1"', '!= "3.6"'),
1319+
('!= "3.6"', '>= "3.7.1"', '>= "3.7.1"', '!= "3.6"'),
1320+
('!= "3.6"', '!= "3.7.1"', None, ""),
1321+
],
1322+
)
1323+
def test_merging_python_version_and_python_full_version(
1324+
python_version: str,
1325+
python_full_version: str,
1326+
expected_intersection_version: str,
1327+
expected_union_version: str,
1328+
) -> None:
1329+
m = f"python_version {python_version}"
1330+
m2 = f"python_full_version {python_full_version}"
1331+
1332+
def get_expected_marker(expected_version: str, op: str) -> str:
1333+
if expected_version is None:
1334+
expected = f"{m} {op} {m2}"
1335+
elif expected_version in ("", "<empty>"):
1336+
expected = expected_version
1337+
else:
1338+
expected_marker_name = (
1339+
"python_version"
1340+
if expected_version.count(".") == 1
1341+
else "python_full_version"
1342+
)
1343+
expected = f"{expected_marker_name} {expected_version}"
1344+
return expected
1345+
1346+
expected_intersection = get_expected_marker(expected_intersection_version, "and")
1347+
expected_union = get_expected_marker(expected_union_version, "or")
1348+
1349+
intersection = parse_marker(m).intersect(parse_marker(m2))
1350+
assert str(intersection) == expected_intersection
1351+
1352+
union = parse_marker(m).union(parse_marker(m2))
1353+
assert str(union) == expected_union

0 commit comments

Comments
 (0)