diff --git a/src/poetry/core/semver/empty_constraint.py b/src/poetry/core/semver/empty_constraint.py index ba5083481..ebdbc04c3 100644 --- a/src/poetry/core/semver/empty_constraint.py +++ b/src/poetry/core/semver/empty_constraint.py @@ -14,6 +14,9 @@ def is_empty(self) -> bool: def is_any(self) -> bool: return False + def is_simple(self) -> bool: + return True + def allows(self, version: "Version") -> bool: return False diff --git a/src/poetry/core/semver/version.py b/src/poetry/core/semver/version.py index e617ddc62..e2851fdad 100644 --- a/src/poetry/core/semver/version.py +++ b/src/poetry/core/semver/version.py @@ -78,6 +78,9 @@ def is_any(self) -> bool: def is_empty(self) -> bool: return False + def is_simple(self) -> bool: + return True + def allows(self, version: "Version") -> bool: if version is None: return False diff --git a/src/poetry/core/semver/version_constraint.py b/src/poetry/core/semver/version_constraint.py index 4dcf7f1dd..69c40fb1b 100644 --- a/src/poetry/core/semver/version_constraint.py +++ b/src/poetry/core/semver/version_constraint.py @@ -15,6 +15,10 @@ def is_empty(self) -> bool: def is_any(self) -> bool: raise NotImplementedError() + @abstractmethod + def is_simple(self) -> bool: + raise NotImplementedError() + @abstractmethod def allows(self, version: "Version") -> bool: raise NotImplementedError() diff --git a/src/poetry/core/semver/version_range.py b/src/poetry/core/semver/version_range.py index 8874dc756..a104af9d7 100644 --- a/src/poetry/core/semver/version_range.py +++ b/src/poetry/core/semver/version_range.py @@ -65,6 +65,9 @@ def is_empty(self) -> bool: def is_any(self) -> bool: return self._min is None and self._max is None + def is_simple(self) -> bool: + return self._min is None or self._max is None + def allows(self, other: "Version") -> bool: if self._min is not None: if other < self._min: diff --git a/src/poetry/core/semver/version_union.py b/src/poetry/core/semver/version_union.py index baf715661..449a832a4 100644 --- a/src/poetry/core/semver/version_union.py +++ b/src/poetry/core/semver/version_union.py @@ -83,6 +83,9 @@ def is_empty(self) -> bool: def is_any(self) -> bool: return False + def is_simple(self) -> bool: + return self.excludes_single_version() + def allows(self, version: "Version") -> bool: return any([constraint.allows(version) for constraint in self._ranges]) diff --git a/src/poetry/core/version/markers.py b/src/poetry/core/version/markers.py index 528640bc7..a5ce07c3c 100644 --- a/src/poetry/core/version/markers.py +++ b/src/poetry/core/version/markers.py @@ -7,6 +7,7 @@ from typing import List from typing import Union +from poetry.core.semver.version_constraint import VersionConstraint from poetry.core.version.grammars import GRAMMAR_PEP_508_MARKERS from poetry.core.version.parser import Parser @@ -14,7 +15,7 @@ if TYPE_CHECKING: from lark import Tree - from poetry.core.semver.helpers import VersionTypes + from poetry.core.packages.constraints import BaseConstraint MarkerTypes = Union[ "AnyMarker", "EmptyMarker", "SingleMarker", "MultiMarker", "MarkerUnion" @@ -184,7 +185,9 @@ class SingleMarker(BaseMarker): "platform_release", } - def __init__(self, name: str, constraint: Union[str, "VersionTypes"]) -> None: + def __init__( + self, name: str, constraint: Union[str, "BaseConstraint", VersionConstraint] + ) -> None: from poetry.core.packages.constraints import ( parse_constraint as parse_generic_constraint, ) @@ -247,7 +250,7 @@ def constraint_string(self) -> str: return self._constraint_string @property - def constraint(self) -> "VersionTypes": + def constraint(self) -> Union["BaseConstraint", VersionConstraint]: return self._constraint @property @@ -551,28 +554,30 @@ def of(cls, *markers: BaseMarker) -> MarkerTypes: if marker in markers: continue - if ( - isinstance(marker, SingleMarker) - and marker.name in PYTHON_VERSION_MARKERS - ): + if isinstance(marker, SingleMarker): included = False for i, mark in enumerate(markers): - if ( - not isinstance(mark, SingleMarker) - or mark.name not in PYTHON_VERSION_MARKERS + if isinstance(mark, SingleMarker) and ( + mark.name == marker.name + or ( + mark.name in PYTHON_VERSION_MARKERS + and marker.name in PYTHON_VERSION_MARKERS + ) ): - continue - - union = mark.constraint.union(marker.constraint) - if union == mark.constraint: - included = True - break - elif union == marker.constraint: - markers[i] = marker - included = True - break - elif union.is_any(): - return AnyMarker() + union = mark.constraint.union(marker.constraint) + if union == mark.constraint: + included = True + break + elif union == marker.constraint: + markers[i] = marker + included = True + break + elif union.is_any(): + return AnyMarker() + elif isinstance(union, VersionConstraint) and union.is_simple(): + markers[i] = SingleMarker(mark.name, union) + included = True + break if included: continue diff --git a/tests/version/test_markers.py b/tests/version/test_markers.py index be1ef3c2b..b5737a0ef 100644 --- a/tests/version/test_markers.py +++ b/tests/version/test_markers.py @@ -173,17 +173,51 @@ def test_single_marker_union(): union = m.union(parse_marker('implementation_name == "cpython"')) assert str(union) == 'sys_platform == "darwin" or implementation_name == "cpython"' + +def test_single_marker_union_is_any(): m = parse_marker('python_version >= "3.4"') union = m.union(parse_marker('python_version < "3.6"')) assert union.is_any() -def test_single_marker_union_compacts_constraints(): - m = parse_marker('python_version < "3.6"') +@pytest.mark.parametrize( + ("marker1", "marker2", "expected"), + [ + ( + 'python_version < "3.6"', + 'python_version < "3.4"', + 'python_version < "3.6"', + ), + ( + 'sys_platform == "linux"', + 'sys_platform != "win32"', + 'sys_platform != "win32"', + ), + ( + 'python_version == "3.6"', + 'python_version > "3.6"', + 'python_version >= "3.6"', + ), + ( + 'python_version == "3.6"', + 'python_version < "3.6"', + 'python_version <= "3.6"', + ), + ( + 'python_version < "3.6"', + 'python_version > "3.6"', + 'python_version != "3.6"', + ), + ], +) +def test_single_marker_union_is_single_marker( + marker1: str, marker2: str, expected: str +): + m = parse_marker(marker1) - union = m.union(parse_marker('python_version < "3.4"')) - assert str(union) == 'python_version < "3.6"' + union = m.union(parse_marker(marker2)) + assert str(union) == expected def test_single_marker_union_with_multi():