Skip to content

Commit bc0d670

Browse files
Update run_mypy.py (#1733)
* Update `run_mypy.py` * add `annotation-unchecked` * remove test exclusion
1 parent 8996811 commit bc0d670

File tree

1 file changed

+61
-61
lines changed

1 file changed

+61
-61
lines changed

scripts/run_mypy.py

Lines changed: 61 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
#!/usr/bin/env python
12
"""
2-
Invokes mypy and compare the reults with files in /pytensor
3-
and a list of files that are known to fail.
3+
Invoke mypy and compare the reults with files in /pymc.
4+
5+
Excludes tests and a list of files that are known to fail.
46
57
Exit code 0 indicates that there are no unexpected results.
68
@@ -11,17 +13,18 @@
1113

1214
import argparse
1315
import importlib
16+
import io
17+
import os
1418
import subprocess
1519
import sys
16-
from collections.abc import Iterable
1720
from pathlib import Path
1821

1922
import pandas as pd
2023

2124

2225
DP_ROOT = Path(__file__).absolute().parent.parent
2326
FAILING = [
24-
Path(line.strip()).absolute()
27+
line.strip()
2528
for line in (DP_ROOT / "scripts" / "mypy-failing.txt").read_text().splitlines()
2629
]
2730

@@ -37,52 +40,25 @@ def enforce_pep561(module_name):
3740
return
3841

3942

40-
def mypy_to_pandas(input_lines: Iterable[str]) -> pd.DataFrame:
43+
def mypy_to_pandas(mypy_result: str) -> pd.DataFrame:
4144
"""Reformats mypy output with error codes to a DataFrame.
4245
4346
Adapted from: https://gist.github.com/michaelosthege/24d0703e5f37850c9e5679f69598930a
4447
"""
45-
current_section = None
46-
data: dict[str, list] = {
47-
"file": [],
48-
"line": [],
49-
"type": [],
50-
"errorcode": [],
51-
"message": [],
52-
}
53-
for line in input_lines:
54-
line = line.strip()
55-
elems = line.split(":")
56-
if len(elems) < 3:
57-
continue
58-
try:
59-
file, lineno, message_type, *_ = elems[0:3]
60-
message_type = message_type.strip()
61-
if message_type == "error":
62-
current_section = line.split(" [")[-1][:-1]
63-
message = line.replace(f"{file}:{lineno}: {message_type}: ", "").replace(
64-
f" [{current_section}]", ""
65-
)
66-
data["file"].append(Path(file))
67-
data["line"].append(lineno)
68-
data["type"].append(message_type)
69-
data["errorcode"].append(current_section)
70-
data["message"].append(message)
71-
except Exception as ex:
72-
print(elems)
73-
print(ex)
74-
return pd.DataFrame(data=data).set_index(["file", "line"])
48+
return pd.read_json(io.StringIO(mypy_result), lines=True)
7549

7650

77-
def check_no_unexpected_results(mypy_lines: Iterable[str]):
78-
"""Compares mypy results with list of known FAILING files.
51+
def check_no_unexpected_results(mypy_df: pd.DataFrame, show_expected: bool):
52+
"""Compare mypy results with list of known FAILING files.
7953
8054
Exits the process with non-zero exit code upon unexpected results.
8155
"""
82-
df = mypy_to_pandas(mypy_lines)
83-
84-
all_files = {fp.absolute() for fp in DP_ROOT.glob("pytensor/**/*.py")}
85-
failing = {f.absolute() for f in df.reset_index().file}
56+
all_files = {
57+
str(fp).replace(str(DP_ROOT), "").strip(os.sep).replace(os.sep, "/")
58+
for fp in DP_ROOT.glob("pytensor/**/*.py")
59+
if "tests" not in str(fp)
60+
}
61+
failing = set(mypy_df.file.str.replace(os.sep, "/", regex=False))
8662
if not failing.issubset(all_files):
8763
raise Exception(
8864
"Mypy should have ignored these files:\n"
@@ -97,15 +73,28 @@ def check_no_unexpected_results(mypy_lines: Iterable[str]):
9773
print(f"{len(passing)}/{len(all_files)} files pass as expected.")
9874
else:
9975
print("!!!!!!!!!")
100-
print(f"{len(unexpected_failing)} files unexpectedly failed.")
76+
print(f"{len(unexpected_failing)} files unexpectedly failed:")
10177
print("\n".join(sorted(map(str, unexpected_failing))))
78+
79+
if show_expected:
80+
print(
81+
"\nThese files did not fail before, so please check the above output"
82+
f" for errors in {unexpected_failing} and fix them."
83+
)
84+
else:
85+
print(
86+
"\nThese files did not fail before. Fix all errors reported in the output above."
87+
)
88+
print(
89+
f"\nNote: In addition to these errors, {len(failing.intersection(expected_failing))} errors in files "
90+
f'marked as "expected failures" were also found. To see these failures, run: '
91+
f"`python scripts/run_mypy.py --show-expected`"
92+
)
93+
10294
print(
103-
"These files did not fail before, so please check the above output"
104-
f" for errors in {unexpected_failing} and fix them."
105-
)
106-
print(
107-
"You can run `python scripts/run_mypy.py --verbose` to reproduce this test locally."
95+
"You can run `python scripts/run_mypy.py` to reproduce this test locally."
10896
)
97+
10998
sys.exit(1)
11099

111100
if unexpected_passing:
@@ -125,40 +114,50 @@ def check_no_unexpected_results(mypy_lines: Iterable[str]):
125114

126115
if __name__ == "__main__":
127116
parser = argparse.ArgumentParser(
128-
description="Run mypy type checks on PyTensor codebase."
117+
description="Run mypy type checks on PyMC codebase."
129118
)
130119
parser.add_argument(
131-
"--verbose", action="count", default=0, help="Pass this to print mypy output."
120+
"--verbose", action="count", default=1, help="Pass this to print mypy output."
121+
)
122+
parser.add_argument(
123+
"--show-expected",
124+
action="store_true",
125+
help="Also show expected failures in verbose output.",
132126
)
133127
parser.add_argument(
134128
"--groupby",
135129
default="file",
136130
help="How to group verbose output. One of {file|errorcode|message}.",
137131
)
138132
args, _ = parser.parse_known_args()
139-
missing = [path for path in FAILING if not path.exists()]
140-
if missing:
141-
print("These files are missing but still kept in FAILING")
142-
print(*missing, sep="\n")
143-
sys.exit(1)
133+
144134
cp = subprocess.run(
145135
[
146136
"mypy",
147-
"--show-error-codes",
137+
"--output",
138+
"json",
148139
"--disable-error-code",
149140
"annotation-unchecked",
150141
"pytensor",
151142
],
152143
capture_output=True,
153144
)
154-
output = cp.stdout.decode()
145+
146+
output = cp.stdout.decode("utf-8")
147+
df = mypy_to_pandas(output)
148+
155149
if args.verbose:
156-
df = mypy_to_pandas(output.split("\n"))
157-
for section, sdf in df.reset_index().groupby(args.groupby):
150+
if not args.show_expected:
151+
expected_failing = set(FAILING)
152+
filtered_df = df.query("file not in @expected_failing")
153+
else:
154+
filtered_df = df
155+
156+
for section, sdf in filtered_df.groupby(args.groupby):
158157
print(f"\n\n[{section}]")
159-
for row in sdf.itertuples():
158+
for idx, row in sdf.iterrows():
160159
print(
161-
f"{row.file}:{row.line}: {row.type} [{row.errorcode}]: {row.message}"
160+
f"{row.file}:{row.line}: {row.code} [{row.severity}]: {row.message}"
162161
)
163162
print()
164163
else:
@@ -168,5 +167,6 @@ def check_no_unexpected_results(mypy_lines: Iterable[str]):
168167
" or `python run_mypy.py --help` for other options."
169168
)
170169

171-
check_no_unexpected_results(output.split("\n"))
170+
check_no_unexpected_results(df, show_expected=args.show_expected)
171+
172172
sys.exit(0)

0 commit comments

Comments
 (0)