diff --git a/src/poetry/core/packages/dependency.py b/src/poetry/core/packages/dependency.py index d69735355..7033c42aa 100644 --- a/src/poetry/core/packages/dependency.py +++ b/src/poetry/core/packages/dependency.py @@ -14,6 +14,7 @@ ) from poetry.core.packages.dependency_group import MAIN_GROUP from poetry.core.packages.specification import PackageSpecification +from poetry.core.packages.utils.utils import contains_group_without_marker from poetry.core.semver.helpers import parse_constraint from poetry.core.semver.version_range_constraint import VersionRangeConstraint from poetry.core.version.markers import parse_marker @@ -180,7 +181,7 @@ def marker(self, marker: str | BaseMarker) -> None: # Recalculate python versions. self._python_versions = "*" - if "python_version" in markers: + if not contains_group_without_marker(markers, "python_version"): ors = [] for or_ in markers["python_version"]: ands = [] diff --git a/src/poetry/core/packages/utils/utils.py b/src/poetry/core/packages/utils/utils.py index a53909493..1ae92b88f 100644 --- a/src/poetry/core/packages/utils/utils.py +++ b/src/poetry/core/packages/utils/utils.py @@ -21,6 +21,8 @@ from poetry.core.semver.version_union import VersionUnion from poetry.core.version.markers import BaseMarker + ConvertedMarkers = dict[str, list[list[tuple[str, str]]]] + BZ2_EXTENSIONS = (".tar.bz2", ".tbz") XZ_EXTENSIONS = (".tar.xz", ".txz", ".tlz", ".tar.lz", ".tar.lzma") @@ -138,67 +140,34 @@ def splitext(path: str) -> tuple[str, str]: return base, ext -def group_markers( - markers: list[BaseMarker], or_: bool = False -) -> list[tuple[str, str, str] | list[tuple[str, str, str]]]: +def convert_markers(marker: BaseMarker) -> ConvertedMarkers: from poetry.core.version.markers import MarkerUnion from poetry.core.version.markers import MultiMarker from poetry.core.version.markers import SingleMarker - groups = [[]] - - for marker in markers: - if or_: - groups.append([]) - - if isinstance(marker, (MultiMarker, MarkerUnion)): - groups[-1].append( - group_markers(marker.markers, isinstance(marker, MarkerUnion)) - ) - elif isinstance(marker, SingleMarker): - lhs, op, rhs = marker.name, marker.operator, marker.value - - groups[-1].append((lhs, op, rhs)) - - return groups - - -def convert_markers(marker: BaseMarker) -> dict[str, list[list[tuple[str, str]]]]: - groups = group_markers([dnf(marker)]) - requirements = {} + marker = dnf(marker) + conjunctions = marker.markers if isinstance(marker, MarkerUnion) else [marker] + group_count = len(conjunctions) - def _group( - _groups: list[tuple[str, str, str] | list[tuple[str, str, str]]], - or_: bool = False, + def add_constraint( + marker_name: str, constraint: tuple[str, str], group_index: int ) -> None: - ors = {} - for group in _groups: - if isinstance(group, list): - _group(group, or_=True) - else: - variable, op, value = group - group_name = str(variable) - - # python_full_version is equivalent to python_version - # for Poetry so we merge them - if group_name == "python_full_version": - group_name = "python_version" - - if group_name not in requirements: - requirements[group_name] = [] - - if group_name not in ors: - ors[group_name] = or_ - - if ors[group_name] or not requirements[group_name]: - requirements[group_name].append([]) - - requirements[group_name][-1].append((str(op), str(value))) - - ors[group_name] = False - - _group(groups, or_=True) + # python_full_version is equivalent to python_version + # for Poetry so we merge them + if marker_name == "python_full_version": + marker_name = "python_version" + if marker_name not in requirements: + requirements[marker_name] = [[] for _ in range(group_count)] + requirements[marker_name][group_index].append(constraint) + + for i, sub_marker in enumerate(conjunctions): + if isinstance(sub_marker, MultiMarker): + for m in sub_marker.markers: + if isinstance(m, SingleMarker): + add_constraint(m.name, (m.operator, m.value), i) + elif isinstance(sub_marker, SingleMarker): + add_constraint(sub_marker.name, (sub_marker.operator, sub_marker.value), i) for group_name in requirements: # remove duplicates @@ -210,6 +179,10 @@ def _group( return requirements +def contains_group_without_marker(markers: ConvertedMarkers, marker_name: str) -> bool: + return marker_name not in markers or [] in markers[marker_name] + + def create_nested_marker( name: str, constraint: BaseConstraint | VersionUnion | Version | VersionConstraint, @@ -315,6 +288,11 @@ def get_python_constraint_from_marker( return EmptyConstraint() markers = convert_markers(marker) + if contains_group_without_marker(markers, "python_version"): + # groups are in disjunctive normal form (DNF), + # an empty group means that python_version does not appear in this group, + # which means that python_version is arbitrary for this group + return VersionRange() ors = [] for or_ in markers["python_version"]: diff --git a/tests/packages/test_dependency.py b/tests/packages/test_dependency.py index 6d3e7544f..63a8900f6 100644 --- a/tests/packages/test_dependency.py +++ b/tests/packages/test_dependency.py @@ -324,12 +324,19 @@ def test_with_constraint() -> None: assert new.transitive_python_constraint == dependency.transitive_python_constraint -def test_marker_properly_sets_python_constraint() -> None: +@pytest.mark.parametrize( + "marker, expected", + [ + ('python_version >= "3.6" and python_version < "4.0"', ">=3.6,<4.0"), + ('sys_platform == "linux"', "*"), + ('python_version >= "3.9" or sys_platform == "linux"', "*"), + ('python_version >= "3.9" and sys_platform == "linux"', ">=3.9"), + ], +) +def test_marker_properly_sets_python_constraint(marker: str, expected: str) -> None: dependency = Dependency("foo", "^1.2.3") - - dependency.marker = 'python_version >= "3.6" and python_version < "4.0"' # type: ignore[assignment] - - assert str(dependency.python_constraint) == ">=3.6,<4.0" + dependency.marker = marker # type: ignore[assignment] + assert str(dependency.python_constraint) == expected def test_dependency_markers_are_the_same_as_markers() -> None: diff --git a/tests/packages/utils/test_utils.py b/tests/packages/utils/test_utils.py index ea61b0c61..d8bfcbf0c 100644 --- a/tests/packages/utils/test_utils.py +++ b/tests/packages/utils/test_utils.py @@ -51,6 +51,20 @@ "python_version": [[("<", "2.7")], [(">=", "3.0.0")]], }, ), + ( + 'python_version >= "3.9" or sys_platform == "linux"', + { + "python_version": [[(">=", "3.9")], []], + "sys_platform": [[], [("==", "linux")]], + }, + ), + ( + 'python_version >= "3.9" and sys_platform == "linux"', + { + "python_version": [[(">=", "3.9")]], + "sys_platform": [[("==", "linux")]], + }, + ), ], ) def test_convert_markers( @@ -108,6 +122,10 @@ def test_convert_markers( ), # no python_version ('sys_platform == "linux"', "*"), + # no relevant python_version + ('python_version >= "3.9" or sys_platform == "linux"', "*"), + # relevant python_version + ('python_version >= "3.9" and sys_platform == "linux"', ">=3.9"), ], ) def test_get_python_constraint_from_marker(marker: str, constraint: str) -> None: