1313
1414import argparse
1515import importlib
16+ import io
1617import os
1718import pathlib
1819import subprocess
1920import sys
2021
21- from collections .abc import Iterator
22-
23- import pandas
22+ import pandas as pd
2423
2524DP_ROOT = pathlib .Path (__file__ ).absolute ().parent .parent
2625FAILING = """
@@ -59,55 +58,25 @@ def enforce_pep561(module_name):
5958 return
6059
6160
62- def mypy_to_pandas (input_lines : Iterator [ str ] ) -> pandas .DataFrame :
61+ def mypy_to_pandas (mypy_result : str ) -> pd .DataFrame :
6362 """Reformats mypy output with error codes to a DataFrame.
6463
6564 Adapted from: https://gist.github.com/michaelosthege/24d0703e5f37850c9e5679f69598930a
6665 """
67- current_section = None
68- data = {
69- "file" : [],
70- "line" : [],
71- "type" : [],
72- "errorcode" : [],
73- "message" : [],
74- }
75- for line in input_lines :
76- line = line .strip ()
77- elems = line .split (":" )
78- if len (elems ) < 3 :
79- continue
80- try :
81- file , lineno , message_type , * _ = elems [0 :3 ]
82- message_type = message_type .strip ()
83- if message_type == "error" :
84- current_section = line .split (" [" )[- 1 ][:- 1 ]
85- message = line .replace (f"{ file } :{ lineno } : { message_type } : " , "" ).replace (
86- f" [{ current_section } ]" , ""
87- )
88- data ["file" ].append (file )
89- data ["line" ].append (lineno )
90- data ["type" ].append (message_type )
91- data ["errorcode" ].append (current_section )
92- data ["message" ].append (message )
93- except Exception as ex :
94- print (elems )
95- print (ex )
96- return pandas .DataFrame (data = data ).set_index (["file" , "line" ])
97-
98-
99- def check_no_unexpected_results (mypy_lines : Iterator [str ]):
66+ return pd .read_json (io .StringIO (mypy_result ), lines = True )
67+
68+
69+ def check_no_unexpected_results (mypy_df : pd .DataFrame , show_expected : bool ):
10070 """Compare mypy results with list of known FAILING files.
10171
10272 Exits the process with non-zero exit code upon unexpected results.
10373 """
104- df = mypy_to_pandas (mypy_lines )
10574 all_files = {
10675 str (fp ).replace (str (DP_ROOT ), "" ).strip (os .sep ).replace (os .sep , "/" )
10776 for fp in DP_ROOT .glob ("pymc/**/*.py" )
10877 if "tests" not in str (fp )
10978 }
110- failing = set (df . reset_index () .file .str .replace (os .sep , "/" , regex = False ))
79+ failing = set (mypy_df .file .str .replace (os .sep , "/" , regex = False ))
11180 if not failing .issubset (all_files ):
11281 raise Exception (
11382 "Mypy should have ignored these files:\n "
@@ -122,13 +91,24 @@ def check_no_unexpected_results(mypy_lines: Iterator[str]):
12291 print (f"{ len (passing )} /{ len (all_files )} files pass as expected." )
12392 else :
12493 print ("!!!!!!!!!" )
125- print (f"{ len (unexpected_failing )} files unexpectedly failed. " )
94+ print (f"{ len (unexpected_failing )} files unexpectedly failed: " )
12695 print ("\n " .join (sorted (map (str , unexpected_failing ))))
127- print (
128- "These files did not fail before, so please check the above output"
129- f" for errors in { unexpected_failing } and fix them."
130- )
131- print ("You can run `python scripts/run_mypy.py --verbose` to reproduce this test locally." )
96+
97+ if show_expected :
98+ print (
99+ "\n These files did not fail before, so please check the above output"
100+ f" for errors in { unexpected_failing } and fix them."
101+ )
102+ else :
103+ print ("\n These files did not fail before. Fix all errors reported in the output above." )
104+ print (
105+ f"\n Note: In addition to these errors, { len (failing .intersection (expected_failing ))} errors in files "
106+ f'marked as "expected failures" were also found. To see these failures, run: '
107+ f"`python scripts/run_mypy.py --show-expected`"
108+ )
109+
110+ print ("You can run `python scripts/run_mypy.py` to reproduce this test locally." )
111+
132112 sys .exit (1 )
133113
134114 if unexpected_passing :
@@ -149,7 +129,12 @@ def check_no_unexpected_results(mypy_lines: Iterator[str]):
149129if __name__ == "__main__" :
150130 parser = argparse .ArgumentParser (description = "Run mypy type checks on PyMC codebase." )
151131 parser .add_argument (
152- "--verbose" , action = "count" , default = 0 , help = "Pass this to print mypy output."
132+ "--verbose" , action = "count" , default = 1 , help = "Pass this to print mypy output."
133+ )
134+ parser .add_argument (
135+ "--show-expected" ,
136+ action = "store_true" ,
137+ help = "Also show expected failures in verbose output." ,
153138 )
154139 parser .add_argument (
155140 "--groupby" ,
@@ -159,16 +144,24 @@ def check_no_unexpected_results(mypy_lines: Iterator[str]):
159144 args , _ = parser .parse_known_args ()
160145
161146 cp = subprocess .run (
162- ["mypy" , "--show-error-codes" , "--exclude" , "tests" , "pymc" ],
147+ ["mypy" , "--output" , "json" , "-- show-error-codes" , "--exclude" , "tests" , "pymc" ],
163148 capture_output = True ,
164149 )
165- output = cp .stdout .decode ()
150+
151+ output = cp .stdout .decode ("utf-8" )
152+ df = mypy_to_pandas (output )
153+
166154 if args .verbose :
167- df = mypy_to_pandas (output .split ("\n " ))
168- for section , sdf in df .reset_index ().groupby (args .groupby ):
155+ if not args .show_expected :
156+ expected_failing = set (FAILING .strip ().split ("\n " )) - {"" }
157+ filtered_df = df .query ("file not in @expected_failing" )
158+ else :
159+ filtered_df = df
160+
161+ for section , sdf in filtered_df .groupby (args .groupby ):
169162 print (f"\n \n [{ section } ]" )
170- for row in sdf .itertuples ():
171- print (f"{ row .file } :{ row .line } : { row .type } [{ row .errorcode } ]: { row .message } " )
163+ for idx , row in sdf .iterrows ():
164+ print (f"{ row .file } :{ row .line } : { row .code } [{ row .severity } ]: { row .message } " )
172165 print ()
173166 else :
174167 print (
@@ -177,5 +170,6 @@ def check_no_unexpected_results(mypy_lines: Iterator[str]):
177170 " or `python run_mypy.py --help` for other options."
178171 )
179172
180- check_no_unexpected_results (output .split ("\n " ))
173+ check_no_unexpected_results (df , show_expected = args .show_expected )
174+
181175 sys .exit (0 )
0 commit comments