From 494bb9d988b2afef1797acf74729cfb8a45b2f65 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Sun, 18 Feb 2024 19:36:46 +0000 Subject: [PATCH] be more pessimistic about reading setup.py if encountering something we don't understand, best assume that we can't get the right answer fixes #8774 --- src/poetry/utils/setup_reader.py | 120 +++++++++++++++---------------- tests/inspection/test_info.py | 9 +-- 2 files changed, 62 insertions(+), 67 deletions(-) diff --git a/src/poetry/utils/setup_reader.py b/src/poetry/utils/setup_reader.py index ffc10a13a87..3c233cd92c7 100644 --- a/src/poetry/utils/setup_reader.py +++ b/src/poetry/utils/setup_reader.py @@ -14,6 +14,10 @@ from pathlib import Path +class SetupReaderError(Exception): + pass + + class SetupReader: """ Class that reads a setup.py file without executing it. @@ -192,113 +196,95 @@ def _find_sub_setup_call( return None def _find_install_requires(self, call: ast.Call, body: list[ast.stmt]) -> list[str]: - install_requires: list[str] = [] value = self._find_in_call(call, "install_requires") if value is None: # Trying to find in kwargs kwargs = self._find_call_kwargs(call) if kwargs is None or not isinstance(kwargs, ast.Name): - return install_requires + return [] variable = self._find_variable_in_body(body, kwargs.id) - if not isinstance(variable, (ast.Dict, ast.Call)): - return install_requires - - if isinstance(variable, ast.Call): - if not isinstance(variable.func, ast.Name): - return install_requires - if variable.func.id != "dict": - return install_requires + if isinstance(variable, ast.Dict): + value = self._find_in_dict(variable, "install_requires") + elif ( + isinstance(variable, ast.Call) + and isinstance(variable.func, ast.Name) + and variable.func.id == "dict" + ): value = self._find_in_call(variable, "install_requires") + else: - value = self._find_in_dict(variable, "install_requires") + raise SetupReaderError(f"Cannot handle variable {variable}") if value is None: - return install_requires + return [] - if isinstance(value, ast.List): - for el in value.elts: - if isinstance(el, ast.Constant) and isinstance(el.value, str): - install_requires.append(el.value) - elif isinstance(value, ast.Name): - variable = self._find_variable_in_body(body, value.id) + if isinstance(value, ast.Name): + value = self._find_variable_in_body(body, value.id) - if variable is not None and isinstance(variable, ast.List): - for el in variable.elts: - if isinstance(el, ast.Constant) and isinstance(el.value, str): - install_requires.append(el.value) + if isinstance(value, ast.Constant) and value.value is None: + return [] - return install_requires + if isinstance(value, ast.List): + return string_list_values(value) + + raise SetupReaderError(f"Cannot handle value of type {type(value)}") def _find_extras_require( self, call: ast.Call, body: list[ast.stmt] ) -> dict[str, list[str]]: - extras_require: dict[str, list[str]] = {} value = self._find_in_call(call, "extras_require") if value is None: # Trying to find in kwargs kwargs = self._find_call_kwargs(call) if kwargs is None or not isinstance(kwargs, ast.Name): - return extras_require + return {} variable = self._find_variable_in_body(body, kwargs.id) - if not isinstance(variable, (ast.Dict, ast.Call)): - return extras_require - - if isinstance(variable, ast.Call): - if not isinstance(variable.func, ast.Name): - return extras_require - - if variable.func.id != "dict": - return extras_require + if isinstance(variable, ast.Dict): + value = self._find_in_dict(variable, "extras_require") + elif ( + isinstance(variable, ast.Call) + and isinstance(variable.func, ast.Name) + and variable.func.id == "dict" + ): value = self._find_in_call(variable, "extras_require") + else: - value = self._find_in_dict(variable, "extras_require") + raise SetupReaderError(f"Cannot handle variable {variable}") if value is None: - return extras_require + return {} + + if isinstance(value, ast.Name): + value = self._find_variable_in_body(body, value.id) + + if isinstance(value, ast.Constant) and value.value is None: + return {} if isinstance(value, ast.Dict): + extras_require: dict[str, list[str]] = {} val: ast.expr | None for key, val in zip(value.keys, value.values): if not isinstance(key, ast.Constant) or not isinstance(key.value, str): - continue + raise SetupReaderError(f"Cannot handle key {key}") if isinstance(val, ast.Name): val = self._find_variable_in_body(body, val.id) - if isinstance(val, ast.List): - extras_require[key.value] = [ - e.value - for e in val.elts - if isinstance(e, ast.Constant) and isinstance(e.value, str) - ] - elif isinstance(value, ast.Name): - variable = self._find_variable_in_body(body, value.id) - - if variable is None or not isinstance(variable, ast.Dict): - return extras_require - - for key, val in zip(variable.keys, variable.values): - if not isinstance(key, ast.Constant) or not isinstance(key.value, str): - continue + if not isinstance(val, ast.List): + raise SetupReaderError(f"Cannot handle value of type {type(val)}") - if isinstance(val, ast.Name): - val = self._find_variable_in_body(body, val.id) + extras_require[key.value] = string_list_values(val) - if isinstance(val, ast.List): - extras_require[key.value] = [ - e.value - for e in val.elts - if isinstance(e, ast.Constant) and isinstance(e.value, str) - ] + return extras_require - return extras_require + raise SetupReaderError(f"Cannot handle value of type {type(value)}") def _find_single_string( self, call: ast.Call, body: list[ast.stmt], name: str @@ -383,3 +369,15 @@ def _find_in_dict(self, dict_: ast.Dict, name: str) -> ast.expr | None: return val return None + + +def string_list_values(value: ast.List) -> list[str]: + strings = [] + for element in value.elts: + if isinstance(element, ast.Constant) and isinstance(element.value, str): + strings.append(element.value) + + else: + raise SetupReaderError("Found non-string element in list") + + return strings diff --git a/tests/inspection/test_info.py b/tests/inspection/test_info.py index 1e84c37417a..ae2022424c7 100644 --- a/tests/inspection/test_info.py +++ b/tests/inspection/test_info.py @@ -303,12 +303,9 @@ def test_info_setup_complex_pep517_legacy( def test_info_setup_complex_disable_build( mocker: MockerFixture, demo_setup_complex: Path ) -> None: - spy = mocker.spy(VirtualEnv, "run") - info = PackageInfo.from_directory(demo_setup_complex, disable_build=True) - assert spy.call_count == 0 - assert info.name == "demo" - assert info.version == "0.1.0" - assert info.requires_dist is None + # Cannot extract install_requires from list comprehension. + with pytest.raises(PackageInfoError): + PackageInfo.from_directory(demo_setup_complex, disable_build=True) @pytest.mark.network