6
6
import re
7
7
import sys
8
8
9
- from typing import Any , Callable , Dict , List , Mapping , Optional , Tuple , TextIO
9
+ import toml
10
+ from typing import Any , Callable , Dict , List , Mapping , Optional , Tuple , TextIO , MutableMapping
10
11
from typing_extensions import Final
11
12
12
13
from mypy import defaults
@@ -48,11 +49,22 @@ def split_and_match_files(paths: str) -> List[str]:
48
49
49
50
Where a path/glob matches no file, we still include the raw path in the resulting list.
50
51
52
+ Returns a list of file paths
53
+ """
54
+
55
+ return match_files (paths .split (',' ))
56
+
57
+
58
+ def match_files (paths : List [str ]) -> List [str ]:
59
+ """Take list of files/directories (with support for globbing through the glob library).
60
+
61
+ Where a path/glob matches no file, we still include the raw path in the resulting list.
62
+
51
63
Returns a list of file paths
52
64
"""
53
65
expanded_paths = []
54
66
55
- for path in paths . split ( ',' ) :
67
+ for path in paths :
56
68
path = expand_path (path .strip ())
57
69
globbed_files = fileglob .glob (path , recursive = True )
58
70
if globbed_files :
@@ -67,7 +79,7 @@ def split_and_match_files(paths: str) -> List[str]:
67
79
# sufficient, and we don't have to do anything here. This table
68
80
# exists to specify types for values initialized to None or container
69
81
# types.
70
- config_types = {
82
+ ini_type_converters = {
71
83
'python_version' : parse_version ,
72
84
'strict_optional_whitelist' : lambda s : s .split (),
73
85
'custom_typing_module' : str ,
@@ -88,6 +100,16 @@ def split_and_match_files(paths: str) -> List[str]:
88
100
} # type: Final
89
101
90
102
103
+ toml_type_converters = {
104
+ 'python_version' : parse_version ,
105
+ 'custom_typeshed_dir' : expand_path ,
106
+ 'mypy_path' : lambda l : [expand_path (p ) for p in l ],
107
+ 'files' : match_files ,
108
+ 'cache_dir' : expand_path ,
109
+ 'python_executable' : expand_path ,
110
+ } # type: Final
111
+
112
+
91
113
def parse_config_file (options : Options , set_strict_flags : Callable [[], None ],
92
114
filename : Optional [str ],
93
115
stdout : Optional [TextIO ] = None ,
@@ -98,45 +120,130 @@ def parse_config_file(options: Options, set_strict_flags: Callable[[], None],
98
120
99
121
If filename is None, fall back to default config files.
100
122
"""
101
- stdout = stdout or sys .stdout
123
+ if filename is not None :
124
+ filename = os .path .expanduser (filename )
125
+ if os .path .splitext (filename )[1 ] == '.toml' :
126
+ parse_toml_config_file (options , set_strict_flags , filename , stdout , stderr )
127
+ else :
128
+ parse_ini_config_file (options , set_strict_flags , filename , stdout , stderr )
129
+ else :
130
+ for filename in defaults .CONFIG_FILES :
131
+ filename = os .path .expanduser (filename )
132
+ if not os .path .isfile (filename ):
133
+ continue
134
+ if os .path .splitext (filename )[1 ] == '.toml' :
135
+ parsed = parse_toml_config_file (options , set_strict_flags ,
136
+ filename , stdout , stderr )
137
+ else :
138
+ parsed = parse_ini_config_file (options , set_strict_flags ,
139
+ filename , stdout , stderr )
140
+ if parsed :
141
+ break
142
+
143
+
144
+ def parse_toml_config_file (options : Options , set_strict_flags : Callable [[], None ],
145
+ filename : str ,
146
+ stdout : Optional [TextIO ] = None ,
147
+ stderr : Optional [TextIO ] = None ) -> bool :
102
148
stderr = stderr or sys .stderr
103
149
104
- if filename is not None :
105
- config_files = (filename ,) # type: Tuple[str, ...]
150
+ # Load the toml config file.
151
+ try :
152
+ table = toml .load (filename ) # type: MutableMapping[str, Any]
153
+ except (TypeError , toml .TomlDecodeError , IOError ) as err :
154
+ print ("%s: %s" % (filename , err ), file = stderr )
155
+ return False
106
156
else :
107
- config_files = tuple ( map ( os . path . expanduser , defaults . CONFIG_FILES ))
157
+ options . config_file = filename
108
158
109
- parser = configparser .RawConfigParser ()
159
+ if 'mypy' not in table :
160
+ print ("%s: No [mypy] table in config file" % filename , file = stderr )
161
+ return False
110
162
111
- for config_file in config_files :
112
- if not os .path .exists (config_file ):
113
- continue
114
- try :
115
- parser .read (config_file )
116
- except configparser .Error as err :
117
- print ("%s: %s" % (config_file , err ), file = stderr )
163
+ # Handle the mypy table.
164
+ for key , value in table ['mypy' ].items ():
165
+
166
+ # Is an option.
167
+ if key != 'overrides' :
168
+
169
+ # Is a report directory.
170
+ if key .endswith ('_report' ):
171
+ report_type = key [:- 7 ].replace ('_' , '-' )
172
+ if report_type in defaults .REPORTER_NAMES :
173
+ options .report_dirs [report_type ] = table ['mypy' ][key ]
174
+ else :
175
+ print ("%s: Unrecognized report type: %s" %
176
+ (filename , key ),
177
+ file = stderr )
178
+ elif key == 'strict' :
179
+ set_strict_flags ()
180
+ else :
181
+ if key in toml_type_converters :
182
+ value = toml_type_converters [key ](value ) # type: ignore
183
+ setattr (options , key , value )
184
+
185
+ # Read the per-module override sub-tables.
118
186
else :
119
- file_read = config_file
120
- options .config_file = file_read
121
- break
187
+ for glob , override in value .items ():
188
+ if (any (c in glob for c in '?[]!' ) or
189
+ any ('*' in x and x != '*' for x in glob .split ('.' ))):
190
+ print ("%s: Patterns must be fully-qualified module names, optionally "
191
+ "with '*' in some components (e.g spam.*.eggs.*)"
192
+ % filename , file = stderr )
193
+
194
+ values = {}
195
+ for subkey , subvalue in override .items ():
196
+ if subkey .endswith ('_report' ):
197
+ print ("Per-module override [%s] should not specify reports (%s)" %
198
+ (glob , subkey ), file = stderr )
199
+ continue
200
+ elif subkey not in PER_MODULE_OPTIONS :
201
+ print ("Per-module tables [%s] should only specify per-module flags (%s)" %
202
+ (key , subkey ), file = stderr )
203
+ continue
204
+
205
+ if subkey in toml_type_converters :
206
+ subvalue = toml_type_converters [subkey ](subvalue ) # type: ignore
207
+ values [subkey ] = subvalue
208
+
209
+ options .per_module_options [glob ] = values
210
+ return True
211
+
212
+
213
+ def parse_ini_config_file (options : Options , set_strict_flags : Callable [[], None ],
214
+ filename : str ,
215
+ stdout : Optional [TextIO ] = None ,
216
+ stderr : Optional [TextIO ] = None ) -> bool :
217
+ stderr = stderr or sys .stderr
218
+ parser = configparser .RawConfigParser ()
219
+ retv = False
220
+
221
+ try :
222
+ parser .read (filename )
223
+ except configparser .Error as err :
224
+ print ("%s: %s" % (filename , err ), file = stderr )
225
+ return retv
122
226
else :
123
- return
227
+ options . config_file = filename
124
228
125
229
if 'mypy' not in parser :
126
- if filename or file_read not in defaults .SHARED_CONFIG_FILES :
127
- print ("%s: No [mypy] section in config file" % file_read , file = stderr )
230
+ if filename not in defaults .SHARED_CONFIG_FILES :
231
+ print ("%s: No [mypy] section in config file" % filename , file = stderr )
128
232
else :
233
+ retv = True
129
234
section = parser ['mypy' ]
130
- prefix = '%s: [%s]: ' % (file_read , 'mypy' )
131
- updates , report_dirs = parse_section (prefix , options , set_strict_flags , section , stderr )
235
+ prefix = '%s: [%s]: ' % (filename , 'mypy' )
236
+ updates , report_dirs = parse_ini_section (
237
+ prefix , options , set_strict_flags , section , stderr )
132
238
for k , v in updates .items ():
133
239
setattr (options , k , v )
134
240
options .report_dirs .update (report_dirs )
135
241
136
242
for name , section in parser .items ():
137
243
if name .startswith ('mypy-' ):
138
- prefix = '%s: [%s]: ' % (file_read , name )
139
- updates , report_dirs = parse_section (
244
+ retv = True
245
+ prefix = '%s: [%s]: ' % (filename , name )
246
+ updates , report_dirs = parse_ini_section (
140
247
prefix , options , set_strict_flags , section , stderr )
141
248
if report_dirs :
142
249
print ("%sPer-module sections should not specify reports (%s)" %
@@ -162,13 +269,14 @@ def parse_config_file(options: Options, set_strict_flags: Callable[[], None],
162
269
file = stderr )
163
270
else :
164
271
options .per_module_options [glob ] = updates
272
+ return retv
165
273
166
274
167
- def parse_section (prefix : str , template : Options ,
168
- set_strict_flags : Callable [[], None ],
169
- section : Mapping [str , str ],
170
- stderr : TextIO = sys .stderr
171
- ) -> Tuple [Dict [str , object ], Dict [str , str ]]:
275
+ def parse_ini_section (prefix : str , template : Options ,
276
+ set_strict_flags : Callable [[], None ],
277
+ section : Mapping [str , str ],
278
+ stderr : TextIO = sys .stderr
279
+ ) -> Tuple [Dict [str , object ], Dict [str , str ]]:
172
280
"""Parse one section of a config file.
173
281
174
282
Returns a dict of option values encountered, and a dict of report directories.
@@ -178,8 +286,8 @@ def parse_section(prefix: str, template: Options,
178
286
for key in section :
179
287
invert = False
180
288
options_key = key
181
- if key in config_types :
182
- ct = config_types [key ]
289
+ if key in ini_type_converters :
290
+ ct = ini_type_converters [key ]
183
291
else :
184
292
dv = None
185
293
# We have to keep new_semantic_analyzer in Options
@@ -337,7 +445,7 @@ def set_strict_flags() -> None:
337
445
nonlocal strict_found
338
446
strict_found = True
339
447
340
- new_sections , reports = parse_section (
448
+ new_sections , reports = parse_ini_section (
341
449
'' , template , set_strict_flags , parser ['dummy' ], stderr = stderr )
342
450
errors .extend ((lineno , x ) for x in stderr .getvalue ().strip ().split ('\n ' ) if x )
343
451
if reports :
0 commit comments