Skip to content

Commit d9402ec

Browse files
author
hauntsaninja
committed
Check for PEP 604 usage in CI
Since this is a common review issue and our stubs have all been converted
1 parent 32a6257 commit d9402ec

File tree

2 files changed

+71
-0
lines changed

2 files changed

+71
-0
lines changed

.github/workflows/tests.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ jobs:
1717
- run: pip install toml
1818
- run: ./tests/check_consistent.py
1919

20+
pep-604:
21+
name: Check for PEP 604 usage
22+
runs-on: ubuntu-latest
23+
steps:
24+
- uses: actions/checkout@v2
25+
- uses: actions/setup-python@v2
26+
- run: ./tests/check_pep_604.py
27+
2028
flake8:
2129
name: Lint with flake8
2230
runs-on: ubuntu-latest

tests/check_pep_604.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#!/usr/bin/env python3
2+
3+
import ast
4+
import sys
5+
from pathlib import Path
6+
7+
8+
def check_pep_604(tree: ast.AST, path: Path) -> list[str]:
9+
errors = []
10+
11+
class UnionFinder(ast.NodeVisitor):
12+
def visit_Subscript(self, node: ast.Subscript) -> None:
13+
if (
14+
isinstance(node.value, ast.Name)
15+
and node.value.id == "Union"
16+
and isinstance(node.slice, ast.Tuple)
17+
):
18+
new_syntax = " | ".join(ast.unparse(x) for x in node.slice.elts)
19+
errors.append(
20+
(f"{path}:{node.lineno}: Use PEP 604 syntax for Union, e.g. `{new_syntax}`")
21+
)
22+
if (
23+
isinstance(node.value, ast.Name)
24+
and node.value.id == "Optional"
25+
):
26+
new_syntax = f"{ast.unparse(node.slice)} | None"
27+
errors.append(
28+
(f"{path}:{node.lineno}: Use PEP 604 syntax for Optional, e.g. `{new_syntax}`")
29+
)
30+
31+
# This doesn't check type aliases (or type var bounds, etc), since those are not
32+
# currently supported
33+
class AnnotationFinder(ast.NodeVisitor):
34+
def visit_AnnAssign(self, node: ast.AnnAssign) -> None:
35+
UnionFinder().visit(node.annotation)
36+
37+
def visit_arg(self, node: ast.arg) -> None:
38+
if node.annotation is not None:
39+
UnionFinder().visit(node.annotation)
40+
41+
AnnotationFinder().visit(tree)
42+
return errors
43+
44+
45+
def main() -> None:
46+
errors = []
47+
for path in Path(".").glob("**/*.pyi"):
48+
if "@python2" in path.parts:
49+
continue
50+
if "stubs/protobuf/google/protobuf" in str(path): # TODO: fix protobuf stubs
51+
continue
52+
53+
with open(path) as f:
54+
tree = ast.parse(f.read())
55+
errors.extend(check_pep_604(tree, path))
56+
57+
if errors:
58+
print("\n".join(errors))
59+
sys.exit(1)
60+
61+
62+
if __name__ == "__main__":
63+
main()

0 commit comments

Comments
 (0)