8
8
from ._compat import tomllib
9
9
10
10
11
+ def get_parent (node : ast .AST | None , depth : int = 1 ) -> ast .AST | None :
12
+ for _ in range (depth ):
13
+ node = getattr (node , "parent" , None )
14
+ return node
15
+
16
+
17
+ def is_main (parent : ast .AST | None ) -> bool :
18
+ if parent is None :
19
+ return False
20
+
21
+ # This would be much nicer with 3.10's pattern matching!
22
+ if not isinstance (parent , ast .If ):
23
+ return False
24
+ if not isinstance (parent .test , ast .Compare ):
25
+ return False
26
+
27
+ try :
28
+ (op ,) = parent .test .ops
29
+ (comp ,) = parent .test .comparators
30
+ except ValueError :
31
+ return False
32
+
33
+ if not isinstance (op , ast .Eq ):
34
+ return False
35
+
36
+ values = {comp , parent .test .left }
37
+
38
+ mains = {x for x in values if isinstance (x , ast .Constant ) and x .value == "__main__" }
39
+ if len (mains ) != 1 :
40
+ return False
41
+ consts = {x for x in values if isinstance (x , ast .Name ) and x .id == "__name__" }
42
+ if len (consts ) != 1 :
43
+ return False
44
+
45
+ return True
46
+
47
+
11
48
class Analyzer (ast .NodeVisitor ):
12
49
def __init__ (self ) -> None :
13
50
self .requires_python : str | None = None
@@ -19,13 +56,22 @@ def visit(self, node: ast.AST) -> None:
19
56
super ().visit (node )
20
57
21
58
def visit_keyword (self , node : ast .keyword ) -> None :
59
+ # Must not be nested except for if __name__ == "__main__"
60
+
22
61
self .generic_visit (node )
23
- # Must not be nested in an if or other structure
24
62
# This will be Module -> Expr -> Call -> keyword
63
+ parent = get_parent (node , 4 )
64
+ unnested = parent is None
65
+
66
+ # This will be Module -> If -> Expr -> Call -> keyword
67
+ name_main_unnested = (
68
+ parent is not None and get_parent (parent ) is None and is_main (get_parent (node , 3 ))
69
+ )
70
+
25
71
if (
26
72
node .arg == "python_requires"
27
- and not hasattr (node .parent .parent .parent , "parent" ) # type: ignore[attr-defined]
28
73
and isinstance (node .value , ast .Constant )
74
+ and (unnested or name_main_unnested )
29
75
):
30
76
self .requires_python = node .value .value
31
77
0 commit comments