Skip to content

Commit

Permalink
markers: reintroduce merging of python_version and python_full_versio…
Browse files Browse the repository at this point in the history
…n markers
  • Loading branch information
radoering committed Jun 2, 2022
1 parent c29d741 commit 69a8b76
Show file tree
Hide file tree
Showing 3 changed files with 211 additions and 28 deletions.
53 changes: 53 additions & 0 deletions src/poetry/core/version/markers.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ class UndefinedEnvironmentName(ValueError):
"python_implementation": "platform_python_implementation",
}

PYTHON_VERSION_MARKERS = {"python_version", "python_full_version"}

# Parser: PEP 508 Environment Markers
_parser = Parser(GRAMMAR_PEP_508_MARKERS, "lalr")

Expand Down Expand Up @@ -414,6 +416,10 @@ def of(cls, *markers: BaseMarker) -> BaseMarker:
for i, mark in enumerate(new_markers):
if isinstance(mark, SingleMarker) and (
mark.name == marker.name
or (
mark.name in PYTHON_VERSION_MARKERS
and marker.name in PYTHON_VERSION_MARKERS
)
):
new_marker = _merge_single_markers(mark, marker, cls)
if new_marker is not None:
Expand Down Expand Up @@ -617,6 +623,10 @@ def of(cls, *markers: BaseMarker) -> BaseMarker:
for i, mark in enumerate(new_markers):
if isinstance(mark, SingleMarker) and (
mark.name == marker.name
or (
mark.name in PYTHON_VERSION_MARKERS
and marker.name in PYTHON_VERSION_MARKERS
)
):
new_marker = _merge_single_markers(mark, marker, cls)
if new_marker is not None:
Expand Down Expand Up @@ -847,6 +857,9 @@ def _merge_single_markers(
marker2: SingleMarker,
merge_class: type[MultiMarker | MarkerUnion],
) -> BaseMarker | None:
if {marker1.name, marker2.name} == {"python_version", "python_full_version"}:
return _merge_python_version_single_markers(marker1, marker2, merge_class)

if merge_class == MultiMarker:
merge_method = marker1.constraint.intersect
else:
Expand All @@ -870,3 +883,43 @@ def _merge_single_markers(
):
return SingleMarker(marker1.name, result_constraint)
return result_marker


def _merge_python_version_single_markers(
marker1: SingleMarker,
marker2: SingleMarker,
merge_class: type[MultiMarker | MarkerUnion],
) -> BaseMarker | None:
from poetry.core.packages.utils.utils import get_python_constraint_from_marker

if marker1.name == "python_version":
marker = marker1
full_marker = marker2
else:
marker = marker2
full_marker = marker1

normalized_constraint = get_python_constraint_from_marker(marker)
if marker.constraint != normalized_constraint:
normalized_marker = SingleMarker("python_full_version", normalized_constraint)
else:
normalized_marker = SingleMarker("python_full_version", normalized_constraint)

merged_marker = _merge_single_markers(normalized_marker, full_marker, merge_class)
if merged_marker == normalized_marker:
# prefer original marker to avoid unnecessary changes
return marker
elif merged_marker and isinstance(merged_marker, SingleMarker):
# We have to fix markers like 'python_full_version == "3.6"'
# to receive 'python_full_version == "3.6.0"'.
# It seems a bit hacky to convert to string and back to marker,
# but it's probably much simpler than to consider the different constraint
# classes (mostly VersonRangeConstraint, but VersionUnion for "!=") and
# since this conversion is only required for python_full_version markers
# it may be sufficient to handle it here.
marker_string = str(merged_marker)
precision = marker_string.count(".") + 1
if precision != 3:
marker_string = marker_string[:-1] + ".0" * (3 - precision) + '"'
merged_marker = parse_marker(marker_string)
return merged_marker
7 changes: 1 addition & 6 deletions tests/packages/utils/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,7 @@
(
'(python_version < "2.7" or python_full_version >= "3.0.0") and'
' python_full_version < "3.6.0"',
{
"python_version": [
[("<", "2.7"), ("<", "3.6.0")],
[(">=", "3.0.0"), ("<", "3.6.0")],
]
},
{"python_version": [[("<", "2.7")], [(">=", "3.0.0"), ("<", "3.6.0")]]},
),
(
'(python_version < "2.7" or python_full_version >= "3.0.0") and'
Expand Down
179 changes: 157 additions & 22 deletions tests/version/test_markers.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,18 +166,6 @@ def test_single_marker_not_in_python_intersection() -> None:
assert str(intersection) == 'python_version not in "2.7, 3.0, 3.1, 3.2"'


def test_marker_intersection_of_python_version_and_python_full_version() -> None:
m = parse_marker('python_version > "3.6"')
m2 = parse_marker('python_full_version >= "3.6.2"')
intersection = m.intersect(m2)

# 'python_version > "3.6"' would be good, but not
# 'python_full_version >= "3.6.2"'.
assert (
str(intersection) == 'python_version > "3.6" and python_full_version >= "3.6.2"'
)


def test_single_marker_union() -> None:
m = parse_marker('sys_platform == "darwin"')

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


def test_marker_union_of_python_version_and_python_full_version() -> None:
m = parse_marker('python_version > "3.6"')
m2 = parse_marker('python_full_version >= "3.6.2"')
union = m.union(m2)

# 'python_full_version >= "3.6.2"' would be good, but not
# 'python_version > "3.6"'.
assert str(union) == 'python_version > "3.6" or python_full_version >= "3.6.2"'


def test_marker_union_intersect_single_marker() -> None:
m = parse_marker('sys_platform == "darwin" or python_version < "3.4"')

Expand Down Expand Up @@ -1216,3 +1194,160 @@ def test_single_markers_are_found_in_complex_intersection() -> None:
str(intersection)
== 'implementation_name == "cpython" and python_version == "3.6"'
)


@pytest.mark.parametrize(
"python_version, python_full_version, "
"expected_intersection_version, expected_union_version",
[
# python_version > 3.6 (equal to python_full_version >= 3.7.0)
('> "3.6"', '> "3.5.2"', '> "3.6"', '> "3.5.2"'),
('> "3.6"', '>= "3.5.2"', '> "3.6"', '>= "3.5.2"'),
('> "3.6"', '> "3.6.2"', '> "3.6"', '> "3.6.2"'),
('> "3.6"', '>= "3.6.2"', '> "3.6"', '>= "3.6.2"'),
('> "3.6"', '> "3.7.0"', '> "3.7.0"', '> "3.6"'),
('> "3.6"', '>= "3.7.0"', '> "3.6"', '> "3.6"'),
('> "3.6"', '> "3.7.1"', '> "3.7.1"', '> "3.6"'),
('> "3.6"', '>= "3.7.1"', '>= "3.7.1"', '> "3.6"'),
('> "3.6"', '== "3.6.2"', "<empty>", None),
('> "3.6"', '== "3.7.0"', '== "3.7.0"', '> "3.6"'),
('> "3.6"', '== "3.7.1"', '== "3.7.1"', '> "3.6"'),
('> "3.6"', '!= "3.6.2"', '> "3.6"', '!= "3.6.2"'),
('> "3.6"', '!= "3.7.0"', '> "3.7.0"', ""),
('> "3.6"', '!= "3.7.1"', None, ""),
('> "3.6"', '< "3.7.0"', "<empty>", ""),
('> "3.6"', '<= "3.7.0"', '== "3.7.0"', ""),
('> "3.6"', '< "3.7.1"', None, ""),
('> "3.6"', '<= "3.7.1"', None, ""),
# python_version >= 3.6 (equal to python_full_version >= 3.6.0)
('>= "3.6"', '> "3.5.2"', '>= "3.6"', '> "3.5.2"'),
('>= "3.6"', '>= "3.5.2"', '>= "3.6"', '>= "3.5.2"'),
('>= "3.6"', '> "3.6.0"', '> "3.6.0"', '>= "3.6"'),
('>= "3.6"', '>= "3.6.0"', '>= "3.6"', '>= "3.6"'),
('>= "3.6"', '> "3.6.1"', '> "3.6.1"', '>= "3.6"'),
('>= "3.6"', '>= "3.6.1"', '>= "3.6.1"', '>= "3.6"'),
('>= "3.6"', '== "3.5.2"', "<empty>", None),
('>= "3.6"', '== "3.6.0"', '== "3.6.0"', '>= "3.6"'),
('>= "3.6"', '!= "3.5.2"', '>= "3.6"', '!= "3.5.2"'),
('>= "3.6"', '!= "3.6.0"', '> "3.6.0"', ""),
('>= "3.6"', '!= "3.6.1"', None, ""),
('>= "3.6"', '!= "3.7.1"', None, ""),
('>= "3.6"', '< "3.6.0"', "<empty>", ""),
('>= "3.6"', '<= "3.6.0"', '== "3.6.0"', ""),
('>= "3.6"', '< "3.6.1"', None, ""), # '== "3.6.0"'
('>= "3.6"', '<= "3.6.1"', None, ""),
# python_version < 3.6 (equal to python_full_version < 3.6.0)
('< "3.6"', '< "3.5.2"', '< "3.5.2"', '< "3.6"'),
('< "3.6"', '<= "3.5.2"', '<= "3.5.2"', '< "3.6"'),
('< "3.6"', '< "3.6.0"', '< "3.6"', '< "3.6"'),
('< "3.6"', '<= "3.6.0"', '< "3.6"', '<= "3.6.0"'),
('< "3.6"', '< "3.6.1"', '< "3.6"', '< "3.6.1"'),
('< "3.6"', '<= "3.6.1"', '< "3.6"', '<= "3.6.1"'),
('< "3.6"', '== "3.5.2"', '== "3.5.2"', '< "3.6"'),
('< "3.6"', '== "3.6.0"', "<empty>", '<= "3.6.0"'),
('< "3.6"', '!= "3.5.2"', None, ""),
('< "3.6"', '!= "3.6.0"', '< "3.6"', '!= "3.6.0"'),
('< "3.6"', '> "3.6.0"', "<empty>", '!= "3.6.0"'),
('< "3.6"', '>= "3.6.0"', "<empty>", ""),
('< "3.6"', '> "3.5.2"', None, ""),
('< "3.6"', '>= "3.5.2"', None, ""),
# python_version <= 3.6 (equal to python_full_version < 3.7.0)
('<= "3.6"', '< "3.6.1"', '< "3.6.1"', '<= "3.6"'),
('<= "3.6"', '<= "3.6.1"', '<= "3.6.1"', '<= "3.6"'),
('<= "3.6"', '< "3.7.0"', '<= "3.6"', '<= "3.6"'),
('<= "3.6"', '<= "3.7.0"', '<= "3.6"', '<= "3.7.0"'),
('<= "3.6"', '== "3.6.1"', '== "3.6.1"', '<= "3.6"'),
('<= "3.6"', '== "3.7.0"', "<empty>", '<= "3.7.0"'),
('<= "3.6"', '!= "3.6.1"', None, ""),
('<= "3.6"', '!= "3.7.0"', '<= "3.6"', '!= "3.7.0"'),
('<= "3.6"', '> "3.7.0"', "<empty>", '!= "3.7.0"'),
('<= "3.6"', '>= "3.7.0"', "<empty>", ""),
('<= "3.6"', '> "3.6.2"', None, ""),
('<= "3.6"', '>= "3.6.2"', None, ""),
# python_version == 3.6 # noqa: E800
# (equal to python_full_version >= 3.6.0 and python_full_version < 3.7.0)
('== "3.6"', '< "3.5.2"', "<empty>", None),
('== "3.6"', '<= "3.5.2"', "<empty>", None),
('== "3.6"', '> "3.5.2"', '== "3.6"', '> "3.5.2"'),
('== "3.6"', '>= "3.5.2"', '== "3.6"', '>= "3.5.2"'),
('== "3.6"', '!= "3.5.2"', '== "3.6"', '!= "3.5.2"'),
('== "3.6"', '< "3.6.0"', "<empty>", '< "3.7.0"'),
('== "3.6"', '<= "3.6.0"', '== "3.6.0"', '< "3.7.0"'),
('== "3.6"', '> "3.6.0"', None, '>= "3.6.0"'),
('== "3.6"', '>= "3.6.0"', '== "3.6"', '>= "3.6.0"'),
('== "3.6"', '!= "3.6.0"', None, ""),
('== "3.6"', '< "3.6.1"', None, '< "3.7.0"'),
('== "3.6"', '<= "3.6.1"', None, '< "3.7.0"'),
('== "3.6"', '> "3.6.1"', None, '>= "3.6.0"'),
('== "3.6"', '>= "3.6.1"', None, '>= "3.6.0"'),
('== "3.6"', '!= "3.6.1"', None, ""),
('== "3.6"', '< "3.7.0"', '== "3.6"', '< "3.7.0"'),
('== "3.6"', '<= "3.7.0"', '== "3.6"', '<= "3.7.0"'),
('== "3.6"', '> "3.7.0"', "<empty>", None),
('== "3.6"', '>= "3.7.0"', "<empty>", '>= "3.6.0"'),
('== "3.6"', '!= "3.7.0"', '== "3.6"', '!= "3.7.0"'),
('== "3.6"', '<= "3.7.1"', '== "3.6"', '<= "3.7.1"'),
('== "3.6"', '< "3.7.1"', '== "3.6"', '< "3.7.1"'),
('== "3.6"', '> "3.7.1"', "<empty>", None),
('== "3.6"', '>= "3.7.1"', "<empty>", None),
('== "3.6"', '!= "3.7.1"', '== "3.6"', '!= "3.7.1"'),
# python_version != 3.6 # noqa: E800
# (equal to python_full_version < 3.6.0 or python_full_version >= 3.7.0)
('!= "3.6"', '< "3.5.2"', '< "3.5.2"', '!= "3.6"'),
('!= "3.6"', '<= "3.5.2"', '<= "3.5.2"', '!= "3.6"'),
('!= "3.6"', '> "3.5.2"', None, ""),
('!= "3.6"', '>= "3.5.2"', None, ""),
('!= "3.6"', '!= "3.5.2"', None, ""),
('!= "3.6"', '< "3.6.0"', '< "3.6.0"', '!= "3.6"'),
('!= "3.6"', '<= "3.6.0"', '< "3.6.0"', None),
('!= "3.6"', '> "3.6.0"', '>= "3.7.0"', '!= "3.6.0"'),
('!= "3.6"', '>= "3.6.0"', '>= "3.7.0"', ""),
('!= "3.6"', '!= "3.6.0"', '!= "3.6"', '!= "3.6.0"'),
('!= "3.6"', '< "3.6.1"', '< "3.6.0"', None),
('!= "3.6"', '<= "3.6.1"', '< "3.6.0"', None),
('!= "3.6"', '> "3.6.1"', '>= "3.7.0"', None),
('!= "3.6"', '>= "3.6.1"', '>= "3.7.0"', None),
('!= "3.6"', '!= "3.6.1"', '!= "3.6"', '!= "3.6.1"'),
('!= "3.6"', '< "3.7.0"', '< "3.6.0"', ""),
('!= "3.6"', '<= "3.7.0"', None, ""),
('!= "3.6"', '> "3.7.0"', '> "3.7.0"', '!= "3.6"'),
('!= "3.6"', '>= "3.7.0"', '>= "3.7.0"', '!= "3.6"'),
('!= "3.6"', '!= "3.7.0"', None, ""),
('!= "3.6"', '<= "3.7.1"', None, ""),
('!= "3.6"', '< "3.7.1"', None, ""),
('!= "3.6"', '> "3.7.1"', '> "3.7.1"', '!= "3.6"'),
('!= "3.6"', '>= "3.7.1"', '>= "3.7.1"', '!= "3.6"'),
('!= "3.6"', '!= "3.7.1"', None, ""),
],
)
def test_merging_python_version_and_python_full_version(
python_version: str,
python_full_version: str,
expected_intersection_version: str,
expected_union_version: str,
) -> None:
m = f"python_version {python_version}"
m2 = f"python_full_version {python_full_version}"

def get_expected_marker(expected_version: str, op: str) -> str:
if expected_version is None:
expected = f"{m} {op} {m2}"
elif expected_version in ("", "<empty>"):
expected = expected_version
else:
expected_marker_name = (
"python_version"
if expected_version.count(".") == 1
else "python_full_version"
)
expected = f"{expected_marker_name} {expected_version}"
return expected

expected_intersection = get_expected_marker(expected_intersection_version, "and")
expected_union = get_expected_marker(expected_union_version, "or")

intersection = parse_marker(m).intersect(parse_marker(m2))
assert str(intersection) == expected_intersection

union = parse_marker(m).union(parse_marker(m2))
assert str(union) == expected_union

0 comments on commit 69a8b76

Please sign in to comment.