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

simplify python_version markers #826

Merged
merged 1 commit into from
Jan 25, 2025
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
49 changes: 46 additions & 3 deletions src/poetry/core/version/markers.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,13 @@ def __init__(
glue = " || "

constraint_string = glue.join(versions)
elif name == "python_full_version" and not swapped_name_value:
# fix precision of python_full_version marker
precision = self._value.count(".") + 1
if precision < 3:
suffix = ".0" * (3 - precision)
self._value += suffix
constraint_string += suffix
else:
# if we have a in/not in operator we split the constraint
# into a union/multi-constraint of single constraint
Expand Down Expand Up @@ -1170,6 +1177,25 @@ def _merge_single_markers(
for c in result_constraint.constraints
):
result_marker = AtomicMultiMarker(marker1.name, result_constraint)
elif marker1.name == "python_version":
from poetry.core.packages.utils.utils import get_python_constraint_from_marker

if isinstance(result_constraint, VersionRange) and result_constraint.min:
# Convert 'python_version >= "3.8" and python_version < "3.9"'
# to 'python_version == "3.8"'
candidate = parse_marker(f'{marker1.name} == "{result_constraint.min}"')
if get_python_constraint_from_marker(candidate) == result_constraint:
result_marker = candidate

elif isinstance(result_constraint, VersionUnion) and merge_class == MarkerUnion:
# Convert 'python_version == "3.8" or python_version >= "3.9"'
# to 'python_version >= "3.8"'
result_constraint = get_python_constraint_from_marker(marker1).union(
get_python_constraint_from_marker(marker2)
)
if result_constraint.is_simple():
result_marker = SingleMarker(marker1.name, result_constraint)

return result_marker


Expand Down Expand Up @@ -1205,7 +1231,24 @@ def _merge_python_version_single_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)
target_precision = 3
if precision < target_precision:
if merged_marker.operator in {"<", ">="}:
target_precision = 2
marker_string = marker_string.replace(
"python_full_version", "python_version"
)
marker_string = (
marker_string[:-1] + ".0" * (target_precision - precision) + '"'
)
elif (
precision == target_precision
and merged_marker.operator in {"<", ">="}
and marker_string[:-1].endswith(".0")
):
marker_string = marker_string.replace(
"python_full_version", "python_version"
)
marker_string = marker_string[:-3] + '"' # drop trailing ".0"
merged_marker = parse_marker(marker_string)
return merged_marker
5 changes: 2 additions & 3 deletions tests/masonry/builders/test_sdist.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,10 @@ def test_convert_dependencies() -> None:
main = ["B>=1.0,<1.1"]

extra_python = (
':python_version >= "2.7" and python_version < "2.8" '
'or python_version >= "3.6" and python_version < "4.0"'
':python_version == "2.7" or python_version >= "3.6" and python_version < "4.0"'
)
extra_d_dependency = (
'baz:python_version >= "2.7" and python_version < "2.8" '
'baz:python_version == "2.7" '
'or python_version >= "3.4" and python_version < "4.0"'
)
extras = {extra_python: ["C==1.2.3"], extra_d_dependency: ["D==3.4.5"]}
Expand Down
6 changes: 3 additions & 3 deletions tests/packages/test_dependency.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def test_to_pep_508() -> None:
result = dependency.to_pep_508()
assert (
result == "Django (>=1.23,<2.0) ; "
'python_version >= "2.7" and python_version < "2.8" '
'python_version == "2.7" '
'or python_version >= "3.6" and python_version < "4.0"'
)

Expand Down Expand Up @@ -88,7 +88,7 @@ def test_to_pep_508_in_extras() -> None:
assert (
result == "Django (>=1.23,<2.0) ; "
"("
'python_version >= "2.7" and python_version < "2.8" '
'python_version == "2.7" '
'or python_version >= "3.6" and python_version < "4.0"'
") "
'and (extra == "foo" or extra == "bar")'
Expand All @@ -97,7 +97,7 @@ def test_to_pep_508_in_extras() -> None:
result = dependency.to_pep_508(with_extras=False)
assert (
result == "Django (>=1.23,<2.0) ; "
'python_version >= "2.7" and python_version < "2.8" '
'python_version == "2.7" '
'or python_version >= "3.6" and python_version < "4.0"'
)

Expand Down
11 changes: 4 additions & 7 deletions tests/packages/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,11 +176,8 @@ def test_dependency_from_pep_508_with_python_version_union_of_multi() -> None:
assert dep.name == "requests"
assert str(dep.constraint) == "2.18.0"
assert dep.extras == frozenset()
assert dep.python_versions == ">=2.7 <2.8 || >=3.4 <3.5"
assert (
str(dep.marker) == 'python_version >= "2.7" and python_version < "2.8" '
'or python_version >= "3.4" and python_version < "3.5"'
)
assert dep.python_versions == "~2.7 || ~3.4"
assert str(dep.marker) == 'python_version == "2.7" or python_version == "3.4"'


def test_dependency_from_pep_508_with_not_in_op_marker() -> None:
Expand Down Expand Up @@ -294,9 +291,9 @@ def test_dependency_from_pep_508_with_python_full_version() -> None:
assert dep.name == "requests"
assert str(dep.constraint) == "2.18.0"
assert dep.extras == frozenset()
assert dep.python_versions == ">=2.7 <2.8 || >=3.4.0 <3.5.4"
assert dep.python_versions == "~2.7 || >=3.4.0 <3.5.4"
assert (
str(dep.marker) == 'python_version >= "2.7" and python_version < "2.8" '
str(dep.marker) == 'python_version == "2.7" '
'or python_full_version >= "3.4.0" and python_full_version < "3.5.4"'
)

Expand Down
126 changes: 85 additions & 41 deletions tests/version/test_markers.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,24 @@ def test_single_marker_intersect() -> None:
assert str(intersection) == 'python_version >= "3.4" and python_version < "3.6"'


def test_single_marker_intersect_compacts_constraints() -> None:
m = parse_marker('python_version < "3.6"')
@pytest.mark.parametrize(
("marker1", "marker2", "expected"),
[
('python_version < "3.6"', 'python_version < "3.4"', 'python_version < "3.4"'),
(
'python_version >= "3.6"',
'python_version < "3.7"',
'python_version == "3.6"',
),
],
)
def test_single_marker_intersect_is_single_marker(
marker1: str, marker2: str, expected: str
) -> None:
m = parse_marker(marker1)

intersection = m.intersect(parse_marker('python_version < "3.4"'))
assert str(intersection) == 'python_version < "3.4"'
intersection = m.intersect(parse_marker(marker2))
assert str(intersection) == expected


def test_single_marker_intersect_with_multi() -> None:
Expand Down Expand Up @@ -394,6 +407,11 @@ def test_single_marker_union_is_any() -> None:
'python_version > "3.6"',
'python_version != "3.6"',
),
(
'python_version == "3.6"',
'python_version >= "3.7"',
'python_version >= "3.6"',
),
],
)
def test_single_marker_union_is_single_marker(
Expand Down Expand Up @@ -802,35 +820,65 @@ def test_multi_marker_union_multi_is_multi(
(
'python_version >= "3.6" and python_full_version < "3.6.2"',
'python_version >= "3.6" and python_version < "3.7"',
'python_version >= "3.6" and python_version < "3.7"',
'python_version == "3.6"',
),
(
'python_version >= "3.6" and python_full_version < "3.7.2"',
'python_version >= "3.6" and python_version < "3.8"',
'python_version >= "3.6" and python_version < "3.8"',
),
(
'python_version > "3.6" and python_full_version < "3.6.2"',
'python_version > "3.6" and python_version < "3.7"',
'python_version > "3.6" and python_version < "3.7"',
),
(
'python_version > "3.6" and python_full_version < "3.7.2"',
'python_version > "3.6" and python_version < "3.8"',
'python_version > "3.6" and python_version < "3.8"',
),
# Ranges meet exactly
(
'python_version >= "3.6" and python_full_version < "3.6.2"',
'python_full_version >= "3.6.2" and python_version < "3.7"',
'python_version >= "3.6" and python_version < "3.7"',
'python_version == "3.6"',
),
(
'python_version >= "3.6" and python_full_version < "3.7.2"',
'python_full_version >= "3.6.2" and python_version < "3.8"',
'python_version >= "3.6" and python_version < "3.8"',
),
(
'python_version >= "3.6" and python_full_version <= "3.6.2"',
'python_full_version > "3.6.2" and python_version < "3.7"',
'python_version >= "3.6" and python_version < "3.7"',
'python_version == "3.6"',
),
(
'python_version >= "3.6" and python_full_version <= "3.7.2"',
'python_full_version > "3.6.2" and python_version < "3.8"',
'python_version >= "3.6" and python_version < "3.8"',
),
# Ranges overlap
(
'python_version >= "3.6" and python_full_version <= "3.6.8"',
'python_full_version >= "3.6.2" and python_version < "3.7"',
'python_version >= "3.6" and python_version < "3.7"',
'python_version == "3.6"',
),
(
'python_version >= "3.6" and python_full_version <= "3.7.8"',
'python_full_version >= "3.6.2" and python_version < "3.8"',
'python_version >= "3.6" and python_version < "3.8"',
),
# Ranges with same end.
(
'python_version >= "3.6" and python_version < "3.7"',
'python_full_version >= "3.6.2" and python_version < "3.7"',
'python_version >= "3.6" and python_version < "3.7"',
'python_version == "3.6"',
),
(
'python_version >= "3.6" and python_version < "3.8"',
'python_full_version >= "3.6.2" and python_version < "3.8"',
'python_version >= "3.6" and python_version < "3.8"',
),
(
'python_version >= "3.6" and python_version <= "3.7"',
Expand Down Expand Up @@ -984,8 +1032,7 @@ def test_marker_union_intersect_marker_union_drops_unnecessary_markers() -> None

intersection = m.intersect(m2)
expected = (
'python_version >= "2.7" and python_version < "2.8" '
'or python_version >= "3.4" and python_version < "4.0"'
'python_version == "2.7" or python_version >= "3.4" and python_version < "4.0"'
)
assert str(intersection) == expected

Expand Down Expand Up @@ -1358,10 +1405,7 @@ def test_without_extras(marker: str, expected: str) -> None:
' and extra == "foo"'
),
"extra",
(
'python_version >= "2.7" and python_version < "2.8"'
' or python_version >= "3.7" and python_version < "3.8"'
),
('python_version == "2.7" or python_version == "3.7"'),
),
],
)
Expand Down Expand Up @@ -2052,8 +2096,8 @@ def test_complex_union() -> None:
]
assert (
str(union(*markers))
== 'platform_system == "Darwin" and platform_machine == "arm64"'
' and python_version >= "3.6" or python_version >= "3.10"'
== 'python_version >= "3.6" and platform_system == "Darwin"'
' and platform_machine == "arm64" or python_version >= "3.10"'
)


Expand Down Expand Up @@ -2085,8 +2129,8 @@ def test_complex_intersection() -> None:
]
assert (
str(dnf(intersection(*markers).invert()))
== 'platform_system == "Darwin" and platform_machine == "arm64"'
' and python_version >= "3.6" or python_version >= "3.10"'
== 'python_version >= "3.6" and platform_system == "Darwin"'
' and platform_machine == "arm64" or python_version >= "3.10"'
)


Expand Down Expand Up @@ -2132,14 +2176,14 @@ def test_intersection_avoids_combinatorial_explosion() -> None:
)
assert (
str(m1.intersect(m2))
== 'python_full_version >= "3.11.0" and python_full_version < "4.0.0"'
== 'python_version >= "3.11" and python_full_version < "4.0.0"'
' and (platform_machine == "aarch64" or platform_machine == "ppc64le"'
' or platform_machine == "x86_64" or platform_machine == "amd64"'
' or platform_machine == "AMD64" or platform_machine == "win32"'
' or platform_machine == "WIN32")'
)
assert (
str(m2.intersect(m1)) == 'python_full_version >= "3.11.0"'
str(m2.intersect(m1)) == 'python_version >= "3.11"'
' and (platform_machine == "aarch64" or platform_machine == "ppc64le"'
' or platform_machine == "x86_64" or platform_machine == "amd64"'
' or platform_machine == "AMD64" or platform_machine == "win32"'
Expand Down Expand Up @@ -2222,20 +2266,20 @@ def test_intersection_avoids_combinatorial_explosion() -> 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"', EMPTY, '< "3.7"'),
('== "3.6"', '<= "3.6.0"', '== "3.6.0"', '< "3.7"'),
('== "3.6"', '> "3.6.0"', None, '>= "3.6"'),
('== "3.6"', '>= "3.6.0"', '== "3.6"', '>= "3.6"'),
('== "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.7"'),
('== "3.6"', '<= "3.6.1"', None, '< "3.7"'),
('== "3.6"', '> "3.6.1"', None, '>= "3.6"'),
('== "3.6"', '>= "3.6.1"', None, '>= "3.6"'),
('== "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"'),
('== "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"', EMPTY, '>= "3.6"'),
('== "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"'),
Expand All @@ -2249,20 +2293,20 @@ def test_intersection_avoids_combinatorial_explosion() -> None:
('!= "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"'),
('!= "3.6"', '<= "3.6.0"', '< "3.6"', None),
('!= "3.6"', '> "3.6.0"', '>= "3.7"', '!= "3.6.0"'),
('!= "3.6"', '>= "3.6.0"', '>= "3.7"', ""),
('!= "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"', None),
('!= "3.6"', '<= "3.6.1"', '< "3.6"', None),
('!= "3.6"', '> "3.6.1"', '>= "3.7"', None),
('!= "3.6"', '>= "3.6.1"', '>= "3.7"', None),
('!= "3.6"', '!= "3.6.1"', '!= "3.6"', '!= "3.6.1"'),
('!= "3.6"', '< "3.7.0"', '< "3.6.0"', ""),
('!= "3.6"', '< "3.7.0"', '< "3.6"', ""),
('!= "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"', '>= "3.7"', '!= "3.6"'),
('!= "3.6"', '!= "3.7.0"', None, ""),
('!= "3.6"', '<= "3.7.1"', None, ""),
('!= "3.6"', '< "3.7.1"', None, ""),
Expand Down
5 changes: 1 addition & 4 deletions tests/version/test_requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,7 @@ def assert_requirement(
{
"name": "foo",
"constraint": ">=1.2.3",
"marker": (
'python_version >= "2.7" and python_version < "2.8" or'
' python_version >= "3.4" and python_version < "3.5"'
),
"marker": ('python_version == "2.7" or python_version == "3.4"'),
},
),
(
Expand Down
Loading