2
2
import sys
3
3
from configparser import ConfigParser
4
4
from pathlib import Path
5
- from typing import Any , Dict , Optional
5
+ from typing import Any , Optional
6
6
7
7
import toml
8
8
11
11
12
12
def get_constant (x : ast .Str ) -> str :
13
13
return x .s
14
+
15
+
14
16
else :
15
17
Constant = ast .Constant
16
18
@@ -21,24 +23,23 @@ def get_constant(x: ast.Constant) -> Any:
21
23
class Analyzer (ast .NodeVisitor ):
22
24
def __init__ (self ) -> None :
23
25
self .requires_python : Optional [str ] = None
24
- self .constants : Dict [str , str ] = {}
25
26
26
- def visit_Assign (self , node : ast .Assign ) -> None :
27
- for target in node .targets :
28
- if (
29
- isinstance (target , ast .Name )
30
- and isinstance (node .value , Constant )
31
- and isinstance (get_constant (node .value ), str )
32
- ):
33
- self .constants [target .id ] = get_constant (node .value )
27
+ def visit (self , content : ast .AST ) -> None :
28
+ for node in ast .walk (content ):
29
+ for child in ast .iter_child_nodes (node ):
30
+ child .parent = node # type: ignore
31
+ super ().visit (content )
34
32
35
33
def visit_keyword (self , node : ast .keyword ) -> None :
36
34
self .generic_visit (node )
37
35
if node .arg == "python_requires" :
38
- if isinstance (node .value , Constant ):
36
+ # Must not be nested in an if or other structure
37
+ # This will be Module -> Expr -> Call -> keyword
38
+ if (
39
+ not hasattr (node .parent .parent .parent , "parent" ) # type: ignore
40
+ and isinstance (node .value , Constant )
41
+ ):
39
42
self .requires_python = get_constant (node .value )
40
- elif isinstance (node .value , ast .Name ):
41
- self .requires_python = self .constants .get (node .value .id )
42
43
43
44
44
45
def setup_py_python_requires (content : str ) -> Optional [str ]:
@@ -54,27 +55,23 @@ def setup_py_python_requires(content: str) -> Optional[str]:
54
55
def get_requires_python_str (package_dir : Path ) -> Optional [str ]:
55
56
"Return the python requires string from the most canonical source available, or None"
56
57
57
- setup_py = package_dir / 'setup.py'
58
- setup_cfg = package_dir / 'setup.cfg'
59
- pyproject_toml = package_dir / 'pyproject.toml'
60
-
61
58
# Read in from pyproject.toml:project.requires-python
62
59
try :
63
- info = toml .load (pyproject_toml )
60
+ info = toml .load (package_dir / 'pyproject.toml' )
64
61
return str (info ['project' ]['requires-python' ])
65
62
except (FileNotFoundError , KeyError , IndexError , TypeError ):
66
63
pass
67
64
68
65
# Read in from setup.cfg:options.python_requires
69
66
try :
70
67
config = ConfigParser ()
71
- config .read (setup_cfg )
68
+ config .read (package_dir / 'setup.cfg' )
72
69
return str (config ['options' ]['python_requires' ])
73
70
except (FileNotFoundError , KeyError , IndexError , TypeError ):
74
71
pass
75
72
76
73
try :
77
- with open (setup_py ) as f :
74
+ with open (package_dir / 'setup.py' ) as f :
78
75
return setup_py_python_requires (f .read ())
79
76
except FileNotFoundError :
80
77
pass
0 commit comments