Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

be more pessimistic about reading setup.py #9000

Merged
merged 1 commit into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading