12
12
13
13
14
14
mypy_argv = []
15
- nodeid_name = ' mypy'
15
+ nodeid_name = " mypy"
16
16
17
17
18
18
def default_file_error_formatter (item , results , errors ):
19
19
"""Create a string to be displayed when mypy finds errors in a file."""
20
- return ' \n ' .join (errors )
20
+ return " \n " .join (errors )
21
21
22
22
23
23
file_error_formatter = default_file_error_formatter
24
24
25
25
26
26
def pytest_addoption (parser ):
27
27
"""Add options for enabling and running mypy."""
28
- group = parser .getgroup ('mypy' )
28
+ group = parser .getgroup ("mypy" )
29
+ group .addoption ("--mypy" , action = "store_true" , help = "run mypy on .py files" )
29
30
group .addoption (
30
- '--mypy' , action = 'store_true' ,
31
- help = 'run mypy on .py files' )
32
- group .addoption (
33
- '--mypy-ignore-missing-imports' , action = 'store_true' ,
34
- help = "suppresses error messages about imports that cannot be resolved" )
31
+ "--mypy-ignore-missing-imports" ,
32
+ action = "store_true" ,
33
+ help = "suppresses error messages about imports that cannot be resolved" ,
34
+ )
35
35
36
36
37
37
XDIST_WORKERINPUT_ATTRIBUTE_NAMES = (
38
- ' workerinput' ,
38
+ " workerinput" ,
39
39
# xdist < 2.0.0:
40
- ' slaveinput' ,
40
+ " slaveinput" ,
41
41
)
42
42
43
43
@@ -76,38 +76,38 @@ def pytest_configure(config):
76
76
77
77
# If xdist is enabled, then the results path should be exposed to
78
78
# the workers so that they know where to read parsed results from.
79
- if config .pluginmanager .getplugin ('xdist' ):
79
+ if config .pluginmanager .getplugin ("xdist" ):
80
+
80
81
class _MypyXdistPlugin :
81
82
def pytest_configure_node (self , node ): # xdist hook
82
83
"""Pass config._mypy_results_path to workers."""
83
- _get_xdist_workerinput (node )['_mypy_results_path' ] = \
84
- node .config ._mypy_results_path
84
+ _get_xdist_workerinput (node )[
85
+ "_mypy_results_path"
86
+ ] = node .config ._mypy_results_path
87
+
85
88
config .pluginmanager .register (_MypyXdistPlugin ())
86
89
87
90
# pytest_terminal_summary cannot accept config before pytest 4.2.
88
91
global _pytest_terminal_summary_config
89
92
_pytest_terminal_summary_config = config
90
93
91
94
config .addinivalue_line (
92
- 'markers' ,
93
- '{marker}: mark tests to be checked by mypy.' .format (
94
- marker = MypyItem .MARKER ,
95
- ),
95
+ "markers" ,
96
+ "{marker}: mark tests to be checked by mypy." .format (marker = MypyItem .MARKER ),
96
97
)
97
- if config .getoption (' --mypy-ignore-missing-imports' ):
98
- mypy_argv .append (' --ignore-missing-imports' )
98
+ if config .getoption (" --mypy-ignore-missing-imports" ):
99
+ mypy_argv .append (" --ignore-missing-imports" )
99
100
100
101
101
102
def pytest_collect_file (path , parent ):
102
103
"""Create a MypyFileItem for every file mypy should run on."""
103
- if path .ext in {'.py' , '.pyi' } and any ([
104
- parent .config .option .mypy ,
105
- parent .config .option .mypy_ignore_missing_imports ,
106
- ]):
104
+ if path .ext in {".py" , ".pyi" } and any (
105
+ [parent .config .option .mypy , parent .config .option .mypy_ignore_missing_imports ],
106
+ ):
107
107
# Do not create MypyFile instance for a .py file if a
108
108
# .pyi file with the same name already exists;
109
109
# pytest will complain about duplicate modules otherwise
110
- if path .ext == ' .pyi' or not path .new (ext = ' .pyi' ).isfile ():
110
+ if path .ext == " .pyi" or not path .new (ext = " .pyi" ).isfile ():
111
111
return MypyFile .from_parent (parent = parent , fspath = path )
112
112
return None
113
113
@@ -120,17 +120,15 @@ class MypyFile(pytest.File):
120
120
def from_parent (cls , * args , ** kwargs ):
121
121
"""Override from_parent for compatibility."""
122
122
# pytest.File.from_parent did not exist before pytest 5.4.
123
- return getattr (super (), ' from_parent' , cls )(* args , ** kwargs )
123
+ return getattr (super (), " from_parent" , cls )(* args , ** kwargs )
124
124
125
125
def collect (self ):
126
126
"""Create a MypyFileItem for the File."""
127
127
yield MypyFileItem .from_parent (parent = self , name = nodeid_name )
128
128
# Since mypy might check files that were not collected,
129
129
# pytest could pass even though mypy failed!
130
130
# To prevent that, add an explicit check for the mypy exit status.
131
- if not any (
132
- isinstance (item , MypyStatusItem ) for item in self .session .items
133
- ):
131
+ if not any (isinstance (item , MypyStatusItem ) for item in self .session .items ):
134
132
yield MypyStatusItem .from_parent (
135
133
parent = self ,
136
134
name = nodeid_name + "-status" ,
@@ -141,7 +139,7 @@ class MypyItem(pytest.Item):
141
139
142
140
"""A Mypy-related test Item."""
143
141
144
- MARKER = ' mypy'
142
+ MARKER = " mypy"
145
143
146
144
def __init__ (self , * args , ** kwargs ):
147
145
super ().__init__ (* args , ** kwargs )
@@ -151,7 +149,7 @@ def __init__(self, *args, **kwargs):
151
149
def from_parent (cls , * args , ** kwargs ):
152
150
"""Override from_parent for compatibility."""
153
151
# pytest.Item.from_parent did not exist before pytest 5.4.
154
- return getattr (super (), ' from_parent' , cls )(* args , ** kwargs )
152
+ return getattr (super (), " from_parent" , cls )(* args , ** kwargs )
155
153
156
154
def repr_failure (self , excinfo ):
157
155
"""
@@ -193,7 +191,7 @@ def runtest(self):
193
191
results = MypyResults .from_session (self .session )
194
192
if results .status :
195
193
raise MypyError (
196
- ' mypy exited with status {status}.' .format (
194
+ " mypy exited with status {status}." .format (
197
195
status = results .status ,
198
196
),
199
197
)
@@ -218,33 +216,32 @@ def dump(self, results_f: TextIO) -> None:
218
216
return json .dump (vars (self ), results_f )
219
217
220
218
@classmethod
221
- def load (cls , results_f : TextIO ) -> ' MypyResults' :
219
+ def load (cls , results_f : TextIO ) -> " MypyResults" :
222
220
"""Get results cached by dump()."""
223
221
return cls (** json .load (results_f ))
224
222
225
223
@classmethod
226
224
def from_mypy (
227
- cls ,
228
- items : List [MypyFileItem ],
229
- * ,
230
- opts : Optional [List [str ]] = None
231
- ) -> ' MypyResults' :
225
+ cls ,
226
+ items : List [MypyFileItem ],
227
+ * ,
228
+ opts : Optional [List [str ]] = None # noqa: C816
229
+ ) -> " MypyResults" :
232
230
"""Generate results from mypy."""
233
231
234
232
if opts is None :
235
233
opts = mypy_argv [:]
236
234
abspath_errors = {
237
- os .path .abspath (str (item .fspath )): []
238
- for item in items
235
+ os .path .abspath (str (item .fspath )): [] for item in items
239
236
} # type: MypyResults._abspath_errors_type
240
237
241
238
stdout , stderr , status = mypy .api .run (opts + list (abspath_errors ))
242
239
243
240
unmatched_lines = []
244
- for line in stdout .split (' \n ' ):
241
+ for line in stdout .split (" \n " ):
245
242
if not line :
246
243
continue
247
- path , _ , error = line .partition (':' )
244
+ path , _ , error = line .partition (":" )
248
245
abspath = os .path .abspath (path )
249
246
try :
250
247
abspath_errors [abspath ].append (error )
@@ -257,27 +254,26 @@ def from_mypy(
257
254
stderr = stderr ,
258
255
status = status ,
259
256
abspath_errors = abspath_errors ,
260
- unmatched_stdout = ' \n ' .join (unmatched_lines ),
257
+ unmatched_stdout = " \n " .join (unmatched_lines ),
261
258
)
262
259
263
260
@classmethod
264
- def from_session (cls , session ) -> ' MypyResults' :
261
+ def from_session (cls , session ) -> " MypyResults" :
265
262
"""Load (or generate) cached mypy results for a pytest session."""
266
263
results_path = (
267
264
session .config ._mypy_results_path
268
- if _is_master (session .config ) else
269
- _get_xdist_workerinput (session .config )[' _mypy_results_path' ]
265
+ if _is_master (session .config )
266
+ else _get_xdist_workerinput (session .config )[" _mypy_results_path" ]
270
267
)
271
- with FileLock (results_path + ' .lock' ):
268
+ with FileLock (results_path + " .lock" ):
272
269
try :
273
- with open (results_path , mode = 'r' ) as results_f :
270
+ with open (results_path , mode = "r" ) as results_f :
274
271
results = cls .load (results_f )
275
272
except FileNotFoundError :
276
- results = cls .from_mypy ([
277
- item for item in session .items
278
- if isinstance (item , MypyFileItem )
279
- ])
280
- with open (results_path , mode = 'w' ) as results_f :
273
+ results = cls .from_mypy (
274
+ [item for item in session .items if isinstance (item , MypyFileItem )],
275
+ )
276
+ with open (results_path , mode = "w" ) as results_f :
281
277
results .dump (results_f )
282
278
return results
283
279
@@ -293,15 +289,15 @@ def pytest_terminal_summary(terminalreporter):
293
289
"""Report stderr and unrecognized lines from stdout."""
294
290
config = _pytest_terminal_summary_config
295
291
try :
296
- with open (config ._mypy_results_path , mode = 'r' ) as results_f :
292
+ with open (config ._mypy_results_path , mode = "r" ) as results_f :
297
293
results = MypyResults .load (results_f )
298
294
except FileNotFoundError :
299
295
# No MypyItems executed.
300
296
return
301
297
if results .unmatched_stdout or results .stderr :
302
- terminalreporter .section (' mypy' )
298
+ terminalreporter .section (" mypy" )
303
299
if results .unmatched_stdout :
304
- color = {' red' : True } if results .status else {' green' : True }
300
+ color = {" red" : True } if results .status else {" green" : True }
305
301
terminalreporter .write_line (results .unmatched_stdout , ** color )
306
302
if results .stderr :
307
303
terminalreporter .write_line (results .stderr , yellow = True )
0 commit comments