Skip to content

Instantly share code, notes, and snippets.

@code-yeongyu
Last active January 24, 2024 11:17
Show Gist options
  • Save code-yeongyu/6cc9a0ac60c2d2212c0c4b690f27f82b to your computer and use it in GitHub Desktop.
Save code-yeongyu/6cc9a0ac60c2d2212c0c4b690f27f82b to your computer and use it in GitHub Desktop.
import ast
from concurrent.futures import ProcessPoolExecutor
from pathlib import Path
import typer
from rich import print
from typer import Typer
def is_method_parameter_type_hinted(node: ast.FunctionDef | ast.AsyncFunctionDef) -> bool:
"""
Check if all parameters in a method have type hints, except 'self' and 'cls'.
"""
for arg in node.args.args:
if arg.arg not in ("self", "cls") and getattr(arg, "annotation", None) is None:
return False
return True
def is_given_node_type_hinted(node: ast.AST, file_path: Path) -> bool:
"""
Recursively check all methods in the given node.
Returns True if all methods are type hinted, otherwise False.
"""
all_type_hinted: bool = True
for child in ast.iter_child_nodes(node):
if isinstance(child, ast.FunctionDef | ast.AsyncFunctionDef):
method_name: str = child.name
if not is_method_parameter_type_hinted(child):
line_no: int = child.lineno
col_offset: int = child.col_offset
print(
f"[bold red]{str(file_path)}:{line_no}:{col_offset}[/bold red] "
f"Method '[bold]{method_name}[/bold]' is missing type hint.",
)
all_type_hinted = False
if isinstance(child, ast.ClassDef | ast.FunctionDef | ast.AsyncFunctionDef):
if not is_given_node_type_hinted(child, file_path):
all_type_hinted = False
return all_type_hinted
def is_this_code_annotated(file_path: Path) -> bool:
with open(file_path) as file:
source = file.read()
tree = ast.parse(source)
return is_given_node_type_hinted(tree, file_path)
app = Typer()
@app.command()
def main(file_paths_list: list[Path]) -> None:
file_paths: set[Path] = set()
for p in file_paths_list:
resolved_path: Path = p.expanduser().resolve()
if resolved_path.is_dir():
for py_file in resolved_path.rglob("*.py"):
file_paths.add(py_file)
else:
file_paths.add(resolved_path)
del file_paths_list
single_file: bool = len(file_paths) == 1
if single_file:
annotated: bool = is_this_code_annotated(file_paths.pop())
if not annotated:
raise typer.Exit(code=1)
return
with ProcessPoolExecutor() as executor:
results: list[bool] = list(executor.map(is_this_code_annotated, file_paths))
if not all(results):
raise typer.Exit(code=1)
if __name__ == "__main__":
app()
@code-yeongyu
Copy link
Author

LICENSE: MIT

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment