Skip to content

Commit

Permalink
be more pessimistic about reading setup.py
Browse files Browse the repository at this point in the history
if encountering something we don't understand, best assume that we can't
get the right answer

fixes #8774
  • Loading branch information
dimbleby committed Feb 21, 2024
1 parent cff4d7d commit e13f91c
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 55 deletions.
115 changes: 66 additions & 49 deletions src/poetry/utils/setup_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
from pathlib import Path


class SetupReaderError(Exception):
pass


class SetupReader:
"""
Class that reads a setup.py file without executing it.
Expand Down Expand Up @@ -192,77 +196,77 @@ 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.Constant) and value.value is None:
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)
return string_list_values(value)

if isinstance(value, ast.Name):
variable = 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)
return string_list_values(variable)

return install_requires
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.Constant) and value.value is None:
return {}

extras_require: dict[str, list[str]] = {}
if isinstance(value, ast.Dict):
val: ast.expr | None
for key, val in zip(value.keys, value.values):
Expand All @@ -272,17 +276,18 @@ def _find_extras_require(
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):
if not isinstance(val, ast.List):
raise SetupReaderError(f"Cannot handle value of type {type(val)}")

extras_require[key.value] = string_list_values(val)

return extras_require

if 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
return {}

for key, val in zip(variable.keys, variable.values):
if not isinstance(key, ast.Constant) or not isinstance(key.value, str):
Expand All @@ -291,14 +296,14 @@ def _find_extras_require(
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)
]
if not isinstance(val, ast.List):
raise SetupReaderError(f"Cannot handle value of type {type(val)}")

extras_require[key.value] = string_list_values(val)

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
Expand Down Expand Up @@ -383,3 +388,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
9 changes: 3 additions & 6 deletions tests/inspection/test_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit e13f91c

Please sign in to comment.