Skip to content

Commit e13f91c

Browse files
committed
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
1 parent cff4d7d commit e13f91c

File tree

2 files changed

+69
-55
lines changed

2 files changed

+69
-55
lines changed

src/poetry/utils/setup_reader.py

+66-49
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
from pathlib import Path
1515

1616

17+
class SetupReaderError(Exception):
18+
pass
19+
20+
1721
class SetupReader:
1822
"""
1923
Class that reads a setup.py file without executing it.
@@ -192,77 +196,77 @@ def _find_sub_setup_call(
192196
return None
193197

194198
def _find_install_requires(self, call: ast.Call, body: list[ast.stmt]) -> list[str]:
195-
install_requires: list[str] = []
196199
value = self._find_in_call(call, "install_requires")
197200
if value is None:
198201
# Trying to find in kwargs
199202
kwargs = self._find_call_kwargs(call)
200203

201204
if kwargs is None or not isinstance(kwargs, ast.Name):
202-
return install_requires
205+
return []
203206

204207
variable = self._find_variable_in_body(body, kwargs.id)
205-
if not isinstance(variable, (ast.Dict, ast.Call)):
206-
return install_requires
207-
208-
if isinstance(variable, ast.Call):
209-
if not isinstance(variable.func, ast.Name):
210-
return install_requires
211208

212-
if variable.func.id != "dict":
213-
return install_requires
209+
if isinstance(variable, ast.Dict):
210+
value = self._find_in_dict(variable, "install_requires")
214211

212+
elif (
213+
isinstance(variable, ast.Call)
214+
and isinstance(variable.func, ast.Name)
215+
and variable.func.id == "dict"
216+
):
215217
value = self._find_in_call(variable, "install_requires")
218+
216219
else:
217-
value = self._find_in_dict(variable, "install_requires")
220+
raise SetupReaderError(f"Cannot handle variable {variable}")
218221

219222
if value is None:
220-
return install_requires
223+
return []
224+
225+
if isinstance(value, ast.Constant) and value.value is None:
226+
return []
221227

222228
if isinstance(value, ast.List):
223-
for el in value.elts:
224-
if isinstance(el, ast.Constant) and isinstance(el.value, str):
225-
install_requires.append(el.value)
226-
elif isinstance(value, ast.Name):
227-
variable = self._find_variable_in_body(body, value.id)
229+
return string_list_values(value)
228230

231+
if isinstance(value, ast.Name):
232+
variable = self._find_variable_in_body(body, value.id)
229233
if variable is not None and isinstance(variable, ast.List):
230-
for el in variable.elts:
231-
if isinstance(el, ast.Constant) and isinstance(el.value, str):
232-
install_requires.append(el.value)
234+
return string_list_values(variable)
233235

234-
return install_requires
236+
raise SetupReaderError(f"Cannot handle value of type {type(value)}")
235237

236238
def _find_extras_require(
237239
self, call: ast.Call, body: list[ast.stmt]
238240
) -> dict[str, list[str]]:
239-
extras_require: dict[str, list[str]] = {}
240241
value = self._find_in_call(call, "extras_require")
241242
if value is None:
242243
# Trying to find in kwargs
243244
kwargs = self._find_call_kwargs(call)
244245

245246
if kwargs is None or not isinstance(kwargs, ast.Name):
246-
return extras_require
247+
return {}
247248

248249
variable = self._find_variable_in_body(body, kwargs.id)
249-
if not isinstance(variable, (ast.Dict, ast.Call)):
250-
return extras_require
251-
252-
if isinstance(variable, ast.Call):
253-
if not isinstance(variable.func, ast.Name):
254-
return extras_require
255-
256-
if variable.func.id != "dict":
257-
return extras_require
250+
if isinstance(variable, ast.Dict):
251+
value = self._find_in_dict(variable, "extras_require")
258252

253+
elif (
254+
isinstance(variable, ast.Call)
255+
and isinstance(variable.func, ast.Name)
256+
and variable.func.id == "dict"
257+
):
259258
value = self._find_in_call(variable, "extras_require")
259+
260260
else:
261-
value = self._find_in_dict(variable, "extras_require")
261+
raise SetupReaderError(f"Cannot handle variable {variable}")
262262

263263
if value is None:
264-
return extras_require
264+
return {}
265+
266+
if isinstance(value, ast.Constant) and value.value is None:
267+
return {}
265268

269+
extras_require: dict[str, list[str]] = {}
266270
if isinstance(value, ast.Dict):
267271
val: ast.expr | None
268272
for key, val in zip(value.keys, value.values):
@@ -272,17 +276,18 @@ def _find_extras_require(
272276
if isinstance(val, ast.Name):
273277
val = self._find_variable_in_body(body, val.id)
274278

275-
if isinstance(val, ast.List):
276-
extras_require[key.value] = [
277-
e.value
278-
for e in val.elts
279-
if isinstance(e, ast.Constant) and isinstance(e.value, str)
280-
]
281-
elif isinstance(value, ast.Name):
279+
if not isinstance(val, ast.List):
280+
raise SetupReaderError(f"Cannot handle value of type {type(val)}")
281+
282+
extras_require[key.value] = string_list_values(val)
283+
284+
return extras_require
285+
286+
if isinstance(value, ast.Name):
282287
variable = self._find_variable_in_body(body, value.id)
283288

284289
if variable is None or not isinstance(variable, ast.Dict):
285-
return extras_require
290+
return {}
286291

287292
for key, val in zip(variable.keys, variable.values):
288293
if not isinstance(key, ast.Constant) or not isinstance(key.value, str):
@@ -291,14 +296,14 @@ def _find_extras_require(
291296
if isinstance(val, ast.Name):
292297
val = self._find_variable_in_body(body, val.id)
293298

294-
if isinstance(val, ast.List):
295-
extras_require[key.value] = [
296-
e.value
297-
for e in val.elts
298-
if isinstance(e, ast.Constant) and isinstance(e.value, str)
299-
]
299+
if not isinstance(val, ast.List):
300+
raise SetupReaderError(f"Cannot handle value of type {type(val)}")
301+
302+
extras_require[key.value] = string_list_values(val)
303+
304+
return extras_require
300305

301-
return extras_require
306+
raise SetupReaderError(f"Cannot handle value of type {type(value)}")
302307

303308
def _find_single_string(
304309
self, call: ast.Call, body: list[ast.stmt], name: str
@@ -383,3 +388,15 @@ def _find_in_dict(self, dict_: ast.Dict, name: str) -> ast.expr | None:
383388
return val
384389

385390
return None
391+
392+
393+
def string_list_values(value: ast.List) -> list[str]:
394+
strings = []
395+
for element in value.elts:
396+
if isinstance(element, ast.Constant) and isinstance(element.value, str):
397+
strings.append(element.value)
398+
399+
else:
400+
raise SetupReaderError("Found non-string element in list")
401+
402+
return strings

tests/inspection/test_info.py

+3-6
Original file line numberDiff line numberDiff line change
@@ -303,12 +303,9 @@ def test_info_setup_complex_pep517_legacy(
303303
def test_info_setup_complex_disable_build(
304304
mocker: MockerFixture, demo_setup_complex: Path
305305
) -> None:
306-
spy = mocker.spy(VirtualEnv, "run")
307-
info = PackageInfo.from_directory(demo_setup_complex, disable_build=True)
308-
assert spy.call_count == 0
309-
assert info.name == "demo"
310-
assert info.version == "0.1.0"
311-
assert info.requires_dist is None
306+
# Cannot extract install_requires from list comprehension.
307+
with pytest.raises(PackageInfoError):
308+
PackageInfo.from_directory(demo_setup_complex, disable_build=True)
312309

313310

314311
@pytest.mark.network

0 commit comments

Comments
 (0)