From e84d27b51e57e0a1243266ebf5b272857636d50e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Brunner?= Date: Thu, 17 Oct 2024 15:33:28 +0200 Subject: [PATCH] Fix multiple constraints --- requirements_detector/detect.py | 40 ++++++++++++++++++++------- tests/test_poetry_semver/test_main.py | 14 ++++++++++ 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/requirements_detector/detect.py b/requirements_detector/detect.py index a684817..75201ce 100644 --- a/requirements_detector/detect.py +++ b/requirements_detector/detect.py @@ -1,12 +1,13 @@ import re from pathlib import Path -from typing import List, Union +from typing import List, Optional, Union import toml from .exceptions import CouldNotParseRequirements, RequirementsNotFound from .handle_setup import from_setup_py from .poetry_semver import parse_constraint +from .poetry_semver.version_constraint import VersionConstraint from .requirement import DetectedRequirement __all__ = [ @@ -102,6 +103,25 @@ def find_requirements(path: P) -> List[DetectedRequirement]: raise RequirementsNotFound +def _version_from_spec(spec: Union[list, dict, str]) -> Optional[VersionConstraint]: + if isinstance(spec, list): + constraint = None + for new_constraint in [_version_from_spec(s) for s in spec]: + if constraint is None: + constraint = new_constraint + elif new_constraint is not None: + constraint = constraint.union(new_constraint) + return constraint + + if isinstance(spec, dict): + if "version" in spec: + spec = spec["version"] + else: + return None + + return parse_constraint(spec) + + def from_pyproject_toml(toml_file: P) -> List[DetectedRequirement]: requirements = [] @@ -116,15 +136,15 @@ def from_pyproject_toml(toml_file: P) -> List[DetectedRequirement]: for name, spec in dependencies.items(): if name.lower() == "python": continue - if isinstance(spec, dict): - if "version" in spec: - spec = spec["version"] - else: - req = DetectedRequirement.parse(f"{name}", toml_file) - if req is not None: - requirements.append(req) - continue - parsed_spec = str(parse_constraint(spec)) + + parsed_spec_obj = _version_from_spec(spec) + if parsed_spec_obj is None and isinstance(spec, dict) and "version" not in spec: + req = DetectedRequirement.parse(f"{name}", toml_file) + if req is not None: + requirements.append(req) + continue + assert parsed_spec_obj is not None + parsed_spec = str(parsed_spec_obj) if "," not in parsed_spec and "<" not in parsed_spec and ">" not in parsed_spec and "=" not in parsed_spec: parsed_spec = f"=={parsed_spec}" diff --git a/tests/test_poetry_semver/test_main.py b/tests/test_poetry_semver/test_main.py index 6f8a4fa..3323498 100644 --- a/tests/test_poetry_semver/test_main.py +++ b/tests/test_poetry_semver/test_main.py @@ -196,3 +196,17 @@ def test_versions_are_sortable(unsorted, sorted_): sorted_ = [parse_constraint(s) for s in sorted_] assert sorted(unsorted) == sorted_ + + +@pytest.mark.parametrize( + "constraint, expected", + [ + ([ + {'version': "<=1.9", 'python': ">=3.6,<3.8"}, + {'version': "^2.0", 'python': ">=3.8"} +], VersionUnion(VersionRange(max=Version(1, 9), include_max=True), VersionRange(Version(2), Version(3), True))) + +]) +def test_versions_list(constraint, expected): + from requirements_detector.detect import _version_from_spec + assert _version_from_spec(constraint) == expected