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 494bb9d
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 67 deletions.
120 changes: 59 additions & 61 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,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
Expand Down Expand Up @@ -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
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 494bb9d

Please sign in to comment.