forked from pandas-dev/pandas
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathvalidate_exception_location.py
123 lines (105 loc) · 2.91 KB
/
validate_exception_location.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
"""
Validate that the exceptions and warnings are in approrirate places.
Checks for classes that inherit a python exception and warning and
flags them, unless they are exempted from checking.
Print the exception/warning that do not follow convention.
Usage::
As pre-commit hook (recommended):
pre-commit run validate-errors-locations --all-files
"""
from __future__ import annotations
import argparse
import ast
import sys
from typing import Sequence
ERROR_MESSAGE = (
"{path}:{lineno}:{col_offset}: {exception_name}: "
"Please don't place exceptions or warnings outside of pandas/errors/__init__.py or "
"pandas/_libs\n"
)
exception_warning_list = {
"ArithmeticError",
"AssertionError",
"AttributeError",
"EOFError",
"Exception",
"FloatingPointError",
"GeneratorExit",
"ImportError",
"IndentationError",
"IndexError",
"KeyboardInterrupt",
"KeyError",
"LookupError",
"MemoryError",
"NameError",
"NotImplementedError",
"OSError",
"OverflowError",
"ReferenceError",
"RuntimeError",
"StopIteration",
"SyntaxError",
"SystemError",
"SystemExit",
"TabError",
"TypeError",
"UnboundLocalError",
"UnicodeDecodeError",
"UnicodeEncodeError",
"UnicodeError",
"UnicodeTranslateError",
"ValueError",
"ZeroDivisionError",
"BytesWarning",
"DeprecationWarning",
"FutureWarning",
"ImportWarning",
"PendingDeprecationWarning",
"ResourceWarning",
"RuntimeWarning",
"SyntaxWarning",
"UnicodeWarning",
"UserWarning",
"Warning",
}
permisable_exception_warning_list = [
"LossySetitemError",
"NoBufferPresent",
"InvalidComparison",
"NotThisMethod",
"OptionError",
"InvalidVersion",
]
class Visitor(ast.NodeVisitor):
def __init__(self, path: str) -> None:
self.path = path
def visit_ClassDef(self, node):
classes = {getattr(n, "id", None) for n in node.bases}
if (
classes
and classes.issubset(exception_warning_list)
and node.name not in permisable_exception_warning_list
):
msg = ERROR_MESSAGE.format(
path=self.path,
lineno=node.lineno,
col_offset=node.col_offset,
exception_name=node.name,
)
sys.stdout.write(msg)
sys.exit(1)
def validate_exception_and_warning_placement(file_path: str, file_content: str):
tree = ast.parse(file_content)
visitor = Visitor(file_path)
visitor.visit(tree)
def main(argv: Sequence[str] | None = None) -> None:
parser = argparse.ArgumentParser()
parser.add_argument("paths", nargs="*")
args = parser.parse_args(argv)
for path in args.paths:
with open(path, encoding="utf-8") as fd:
content = fd.read()
validate_exception_and_warning_placement(path, content)
if __name__ == "__main__":
main()