Skip to content

Commit

Permalink
be more pessimistic about reading setup.py (#9000)
Browse files Browse the repository at this point in the history
  • Loading branch information
dimbleby authored Feb 23, 2024
1 parent cff4d7d commit d354f83
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 d354f83

Please sign in to comment.