Merge pull request #1613 from henryiii/henryiii/fix/mainif
fix(setup.py): look inside if name == main block
This commit is contained in:
@@ -8,6 +8,43 @@ from pathlib import Path
|
|||||||
from ._compat import tomllib
|
from ._compat import tomllib
|
||||||
|
|
||||||
|
|
||||||
|
def get_parent(node: ast.AST | None, depth: int = 1) -> ast.AST | None:
|
||||||
|
for _ in range(depth):
|
||||||
|
node = getattr(node, "parent", None)
|
||||||
|
return node
|
||||||
|
|
||||||
|
|
||||||
|
def is_main(parent: ast.AST | None) -> bool:
|
||||||
|
if parent is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# This would be much nicer with 3.10's pattern matching!
|
||||||
|
if not isinstance(parent, ast.If):
|
||||||
|
return False
|
||||||
|
if not isinstance(parent.test, ast.Compare):
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
(op,) = parent.test.ops
|
||||||
|
(comp,) = parent.test.comparators
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not isinstance(op, ast.Eq):
|
||||||
|
return False
|
||||||
|
|
||||||
|
values = {comp, parent.test.left}
|
||||||
|
|
||||||
|
mains = {x for x in values if isinstance(x, ast.Constant) and x.value == "__main__"}
|
||||||
|
if len(mains) != 1:
|
||||||
|
return False
|
||||||
|
consts = {x for x in values if isinstance(x, ast.Name) and x.id == "__name__"}
|
||||||
|
if len(consts) != 1:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class Analyzer(ast.NodeVisitor):
|
class Analyzer(ast.NodeVisitor):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.requires_python: str | None = None
|
self.requires_python: str | None = None
|
||||||
@@ -19,13 +56,22 @@ class Analyzer(ast.NodeVisitor):
|
|||||||
super().visit(node)
|
super().visit(node)
|
||||||
|
|
||||||
def visit_keyword(self, node: ast.keyword) -> None:
|
def visit_keyword(self, node: ast.keyword) -> None:
|
||||||
|
# Must not be nested except for if __name__ == "__main__"
|
||||||
|
|
||||||
self.generic_visit(node)
|
self.generic_visit(node)
|
||||||
# Must not be nested in an if or other structure
|
|
||||||
# This will be Module -> Expr -> Call -> keyword
|
# This will be Module -> Expr -> Call -> keyword
|
||||||
|
parent = get_parent(node, 4)
|
||||||
|
unnested = parent is None
|
||||||
|
|
||||||
|
# This will be Module -> If -> Expr -> Call -> keyword
|
||||||
|
name_main_unnested = (
|
||||||
|
parent is not None and get_parent(parent) is None and is_main(get_parent(node, 3))
|
||||||
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
node.arg == "python_requires"
|
node.arg == "python_requires"
|
||||||
and not hasattr(node.parent.parent.parent, "parent") # type: ignore[attr-defined]
|
|
||||||
and isinstance(node.value, ast.Constant)
|
and isinstance(node.value, ast.Constant)
|
||||||
|
and (unnested or name_main_unnested)
|
||||||
):
|
):
|
||||||
self.requires_python = node.value.value
|
self.requires_python = node.value.value
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,72 @@ def test_read_setup_py_simple(tmp_path):
|
|||||||
assert get_requires_python_str(tmp_path) == "1.23"
|
assert get_requires_python_str(tmp_path) == "1.23"
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_setup_py_if_main(tmp_path):
|
||||||
|
with open(tmp_path / "setup.py", "w") as f:
|
||||||
|
f.write(
|
||||||
|
dedent(
|
||||||
|
"""
|
||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
setup(
|
||||||
|
name = "hello",
|
||||||
|
other = 23,
|
||||||
|
example = ["item", "other"],
|
||||||
|
python_requires = "1.23",
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert setup_py_python_requires(tmp_path.joinpath("setup.py").read_text()) == "1.23"
|
||||||
|
assert get_requires_python_str(tmp_path) == "1.23"
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_setup_py_if_main_reversed(tmp_path):
|
||||||
|
with open(tmp_path / "setup.py", "w") as f:
|
||||||
|
f.write(
|
||||||
|
dedent(
|
||||||
|
"""
|
||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
if "__main__" == __name__:
|
||||||
|
setup(
|
||||||
|
name = "hello",
|
||||||
|
other = 23,
|
||||||
|
example = ["item", "other"],
|
||||||
|
python_requires = "1.23",
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert setup_py_python_requires(tmp_path.joinpath("setup.py").read_text()) == "1.23"
|
||||||
|
assert get_requires_python_str(tmp_path) == "1.23"
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_setup_py_if_invalid(tmp_path):
|
||||||
|
with open(tmp_path / "setup.py", "w") as f:
|
||||||
|
f.write(
|
||||||
|
dedent(
|
||||||
|
"""
|
||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
if True:
|
||||||
|
setup(
|
||||||
|
name = "hello",
|
||||||
|
other = 23,
|
||||||
|
example = ["item", "other"],
|
||||||
|
python_requires = "1.23",
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert not setup_py_python_requires(tmp_path.joinpath("setup.py").read_text())
|
||||||
|
assert not get_requires_python_str(tmp_path)
|
||||||
|
|
||||||
|
|
||||||
def test_read_setup_py_full(tmp_path):
|
def test_read_setup_py_full(tmp_path):
|
||||||
with open(tmp_path / "setup.py", "w", encoding="utf8") as f:
|
with open(tmp_path / "setup.py", "w", encoding="utf8") as f:
|
||||||
f.write(
|
f.write(
|
||||||
|
|||||||
Reference in New Issue
Block a user