88from ._compat import tomllib
99
1010
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+
1148class Analyzer (ast .NodeVisitor ):
1249 def __init__ (self ) -> None :
1350 self .requires_python : str | None = None
@@ -19,13 +56,22 @@ def visit(self, node: ast.AST) -> None:
1956 super ().visit (node )
2057
2158 def visit_keyword (self , node : ast .keyword ) -> None :
59+ # Must not be nested except for if __name__ == "__main__"
60+
2261 self .generic_visit (node )
23- # Must not be nested in an if or other structure
2462 # 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+
2571 if (
2672 node .arg == "python_requires"
27- and not hasattr (node .parent .parent .parent , "parent" ) # type: ignore[attr-defined]
2873 and isinstance (node .value , ast .Constant )
74+ and (unnested or name_main_unnested )
2975 ):
3076 self .requires_python = node .value .value
3177
0 commit comments