From a571fa5706bda1a3e0424a1809d637d8c94049f6 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Wed, 1 Jun 2022 13:41:21 +0100 Subject: [PATCH 1/4] fix and make common python version normalization --- src/poetry/core/packages/dependency.py | 38 ++++--------------------- src/poetry/core/packages/utils/utils.py | 26 ++++++++--------- tests/packages/test_main.py | 4 +-- tests/packages/utils/test_utils.py | 4 +-- 4 files changed, 21 insertions(+), 51 deletions(-) diff --git a/src/poetry/core/packages/dependency.py b/src/poetry/core/packages/dependency.py index 1767d6a60..5d75cd2e6 100644 --- a/src/poetry/core/packages/dependency.py +++ b/src/poetry/core/packages/dependency.py @@ -16,6 +16,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.packages.utils.utils import normalize_python_version_markers 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 @@ -192,39 +193,10 @@ def marker(self, marker: str | BaseMarker) -> None: # Recalculate python versions. self._python_versions = "*" if not contains_group_without_marker(markers, "python_version"): - ors = [] - for or_ in markers["python_version"]: - ands = [] - for op, version in or_: - # Expand python version - if op == "==" and "*" not in version: - version = "~" + version - op = "" - elif op == "!=": - version += ".*" - elif op in ("in", "not in"): - versions = [] - for v in re.split("[ ,]+", version): - split = v.split(".") - if len(split) in [1, 2]: - split.append("*") - op_ = "" if op == "in" else "!=" - else: - op_ = "==" if op == "in" else "!=" - - versions.append(op_ + ".".join(split)) - - glue = " || " if op == "in" else ", " - if versions: - ands.append(glue.join(versions)) - - continue - - ands.append(f"{op}{version}") - - ors.append(" ".join(ands)) - - self._python_versions = " || ".join(ors) + python_version_markers = markers["python_version"] + self._python_versions = normalize_python_version_markers( + python_version_markers + ) self._python_constraint = parse_constraint(self._python_versions) diff --git a/src/poetry/core/packages/utils/utils.py b/src/poetry/core/packages/utils/utils.py index 701cc1b5f..4f7e4e2d2 100644 --- a/src/poetry/core/packages/utils/utils.py +++ b/src/poetry/core/packages/utils/utils.py @@ -15,13 +15,14 @@ from urllib.request import url2pathname from poetry.core.pyproject.toml import PyProjectTOML +from poetry.core.semver.helpers import parse_constraint +from poetry.core.semver.version import Version from poetry.core.semver.version_range import VersionRange from poetry.core.version.markers import dnf if TYPE_CHECKING: from poetry.core.packages.constraints import BaseConstraint - from poetry.core.semver.version import Version from poetry.core.semver.version_constraint import VersionConstraint from poetry.core.semver.version_union import VersionUnion from poetry.core.version.markers import BaseMarker @@ -206,7 +207,6 @@ def create_nested_marker( from poetry.core.packages.constraints.constraint import Constraint from poetry.core.packages.constraints.multi_constraint import MultiConstraint from poetry.core.packages.constraints.union_constraint import UnionConstraint - from poetry.core.semver.version import Version from poetry.core.semver.version_union import VersionUnion if constraint.is_any(): @@ -286,8 +286,6 @@ def get_python_constraint_from_marker( marker: BaseMarker, ) -> VersionConstraint: from poetry.core.semver.empty_constraint import EmptyConstraint - from poetry.core.semver.helpers import parse_constraint - from poetry.core.semver.version import Version from poetry.core.semver.version_range import VersionRange python_marker = marker.only("python_version", "python_full_version") @@ -304,8 +302,15 @@ def get_python_constraint_from_marker( # which means that python_version is arbitrary for this group return VersionRange() + python_version_markers = markers["python_version"] + normalized = normalize_python_version_markers(python_version_markers) + constraint = parse_constraint(normalized) + return constraint + + +def normalize_python_version_markers(disjunction: list[list[tuple[str, str]]]) -> str: ors = [] - for or_ in markers["python_version"]: + for or_ in disjunction: ands = [] for op, version in or_: # Expand python version @@ -318,14 +323,7 @@ def get_python_constraint_from_marker( version += ".*" elif op in ("<=", ">"): parsed_version = Version.parse(version) - if parsed_version.precision == 1: - if op == "<=": - op = "<" - version = parsed_version.next_major().text - elif op == ">": - op = ">=" - version = parsed_version.next_major().text - elif parsed_version.precision == 2: + if parsed_version.precision == 2: if op == "<=": op = "<" version = parsed_version.next_minor().text @@ -354,4 +352,4 @@ def get_python_constraint_from_marker( ors.append(" ".join(ands)) - return parse_constraint(" || ".join(ors)) + return " || ".join(ors) diff --git a/tests/packages/test_main.py b/tests/packages/test_main.py index d6693dd93..81c3cfbf2 100644 --- a/tests/packages/test_main.py +++ b/tests/packages/test_main.py @@ -294,9 +294,9 @@ def test_dependency_from_pep_508_should_not_produce_empty_constraints_for_correc assert dep.name == "pytest-mypy" assert str(dep.constraint) == "*" - assert dep.python_versions == "<=3.10 >3" + assert dep.python_versions == "<3.11 >3" assert dep.python_constraint.allows(Version.parse("3.6")) - assert dep.python_constraint.allows(Version.parse("3.10")) + assert dep.python_constraint.allows(Version.parse("3.10.4")) assert not dep.python_constraint.allows(Version.parse("3")) assert dep.python_constraint.allows(Version.parse("3.0.1")) assert ( diff --git a/tests/packages/utils/test_utils.py b/tests/packages/utils/test_utils.py index 455f8dde7..764adf3cf 100644 --- a/tests/packages/utils/test_utils.py +++ b/tests/packages/utils/test_utils.py @@ -96,8 +96,8 @@ def test_convert_markers( ('python_version != "3.6.* "', "!=3.6.*"), # <, <=, >, >= precision 1 ('python_version < "3"', "<3"), - ('python_version <= "3"', "<4"), - ('python_version > "3"', ">=4"), + ('python_version <= "3"', "<=3"), + ('python_version > "3"', ">3"), ('python_version >= "3"', ">=3"), # <, <=, >, >= precision 2 ('python_version < "3.6"', "<3.6"), From 664bd51419cbe9017546e3d791eb3fdba8f6952a Mon Sep 17 00:00:00 2001 From: David Hotham Date: Wed, 1 Jun 2022 22:04:28 +0100 Subject: [PATCH 2/4] commenting Also handle a single digit of precision more carefully at the boundary. --- src/poetry/core/packages/utils/utils.py | 27 ++++++++++++++++++++++++- tests/packages/test_main.py | 4 ++-- tests/packages/utils/test_utils.py | 4 ++-- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/poetry/core/packages/utils/utils.py b/src/poetry/core/packages/utils/utils.py index 4f7e4e2d2..12643df24 100644 --- a/src/poetry/core/packages/utils/utils.py +++ b/src/poetry/core/packages/utils/utils.py @@ -322,8 +322,33 @@ def normalize_python_version_markers(disjunction: list[list[tuple[str, str]]]) - if "*" not in version: version += ".*" elif op in ("<=", ">"): + # Make adjustments on encountering versions with less than full + # precision. + # + # Per PEP-508: + # python_version <-> '.'.join(platform.python_version_tuple()[:2]) + # + # So for two digits of precision we make the following adjustments: + # - `python_version > "x.y"` requires version >= x.(y+1).anything + # - `python_version <= "x.y"` requires version < x.(y+1).anything + # + # Treatment when we see a single digit of precision is less clear: is + # that even a legitimate marker? + # + # Experiment suggests that pip behaviour is essentially to make a + # lexicographical comparison, for example `python_version > "3"` is + # satisfied by version 3.anything, whereas `python_version <= "3"` is + # satisfied only by version 2.anything. + # + # We achieve the above by fiddling with the operator and version in the + # marker. parsed_version = Version.parse(version) - if parsed_version.precision == 2: + if parsed_version.precision == 1: + if op == "<=": + op = "<" + elif op == ">": + op = ">=" + elif parsed_version.precision == 2: if op == "<=": op = "<" version = parsed_version.next_minor().text diff --git a/tests/packages/test_main.py b/tests/packages/test_main.py index 81c3cfbf2..e7e599f69 100644 --- a/tests/packages/test_main.py +++ b/tests/packages/test_main.py @@ -294,10 +294,10 @@ def test_dependency_from_pep_508_should_not_produce_empty_constraints_for_correc assert dep.name == "pytest-mypy" assert str(dep.constraint) == "*" - assert dep.python_versions == "<3.11 >3" + assert dep.python_versions == "<3.11 >=3" assert dep.python_constraint.allows(Version.parse("3.6")) assert dep.python_constraint.allows(Version.parse("3.10.4")) - assert not dep.python_constraint.allows(Version.parse("3")) + assert dep.python_constraint.allows(Version.parse("3")) assert dep.python_constraint.allows(Version.parse("3.0.1")) assert ( str(dep.marker) diff --git a/tests/packages/utils/test_utils.py b/tests/packages/utils/test_utils.py index 764adf3cf..e4517b082 100644 --- a/tests/packages/utils/test_utils.py +++ b/tests/packages/utils/test_utils.py @@ -96,8 +96,8 @@ def test_convert_markers( ('python_version != "3.6.* "', "!=3.6.*"), # <, <=, >, >= precision 1 ('python_version < "3"', "<3"), - ('python_version <= "3"', "<=3"), - ('python_version > "3"', ">3"), + ('python_version <= "3"', "<3"), + ('python_version > "3"', ">=3"), ('python_version >= "3"', ">=3"), # <, <=, >, >= precision 2 ('python_version < "3.6"', "<3.6"), From 8a670b9dd78715f77df926bf93a333f6414f7f77 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Wed, 1 Jun 2022 22:50:11 +0100 Subject: [PATCH 3/4] appeasing sonarcloud --- src/poetry/core/packages/utils/utils.py | 27 ++++++++++--------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/poetry/core/packages/utils/utils.py b/src/poetry/core/packages/utils/utils.py index 12643df24..5080c8368 100644 --- a/src/poetry/core/packages/utils/utils.py +++ b/src/poetry/core/packages/utils/utils.py @@ -314,13 +314,11 @@ def normalize_python_version_markers(disjunction: list[list[tuple[str, str]]]) - ands = [] for op, version in or_: # Expand python version - if op == "==": - if "*" not in version: - version = "~" + version - op = "" - elif op == "!=": - if "*" not in version: - version += ".*" + if op == "==" and "*" not in version: + version = "~" + version + op = "" + elif op == "!=" and "*" not in version: + version += ".*" elif op in ("<=", ">"): # Make adjustments on encountering versions with less than full # precision. @@ -343,18 +341,15 @@ def normalize_python_version_markers(disjunction: list[list[tuple[str, str]]]) - # We achieve the above by fiddling with the operator and version in the # marker. parsed_version = Version.parse(version) - if parsed_version.precision == 1: + if parsed_version.precision < 3: if op == "<=": op = "<" elif op == ">": op = ">=" - elif parsed_version.precision == 2: - if op == "<=": - op = "<" - version = parsed_version.next_minor().text - elif op == ">": - op = ">=" - version = parsed_version.next_minor().text + + if parsed_version.precision == 2: + version = parsed_version.next_minor().text + elif op in ("in", "not in"): versions = [] for v in re.split("[ ,]+", version): @@ -367,8 +362,8 @@ def normalize_python_version_markers(disjunction: list[list[tuple[str, str]]]) - versions.append(op_ + ".".join(split)) - glue = " || " if op == "in" else ", " if versions: + glue = " || " if op == "in" else ", " ands.append(glue.join(versions)) continue From 8207439e71fbe9a9cfe869495691ec080dfdebfc Mon Sep 17 00:00:00 2001 From: David Hotham Date: Wed, 1 Jun 2022 23:00:36 +0100 Subject: [PATCH 4/4] suppress sonarcloud? --- src/poetry/core/packages/utils/utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/poetry/core/packages/utils/utils.py b/src/poetry/core/packages/utils/utils.py index 5080c8368..5aed5b0c9 100644 --- a/src/poetry/core/packages/utils/utils.py +++ b/src/poetry/core/packages/utils/utils.py @@ -308,7 +308,9 @@ def get_python_constraint_from_marker( return constraint -def normalize_python_version_markers(disjunction: list[list[tuple[str, str]]]) -> str: +def normalize_python_version_markers( # NOSONAR + disjunction: list[list[tuple[str, str]]], +) -> str: ors = [] for or_ in disjunction: ands = [] @@ -317,8 +319,10 @@ def normalize_python_version_markers(disjunction: list[list[tuple[str, str]]]) - if op == "==" and "*" not in version: version = "~" + version op = "" + elif op == "!=" and "*" not in version: version += ".*" + elif op in ("<=", ">"): # Make adjustments on encountering versions with less than full # precision.