1#!/usr/bin/env python 2"""Find the functions in a module missing type annotations. 3 4To use it run 5 6./functions_missing_types.py <module> 7 8and it will print out a list of functions in the module that don't 9have types. 10 11""" 12import argparse 13import ast 14import importlib 15import os 16 17NUMPY_ROOT = os.path.dirname(os.path.join( 18 os.path.abspath(__file__), "..", 19)) 20 21# Technically "public" functions (they don't start with an underscore) 22# that we don't want to include. 23EXCLUDE_LIST = { 24 "numpy": { 25 # Stdlib modules in the namespace by accident 26 "absolute_import", 27 "division", 28 "print_function", 29 "warnings", 30 "sys", 31 "os", 32 "math", 33 # Accidentally public, deprecated, or shouldn't be used 34 "Tester", 35 "alen", 36 "add_docstring", 37 "add_newdoc", 38 "add_newdoc_ufunc", 39 "core", 40 "compat", 41 "fastCopyAndTranspose", 42 "get_array_wrap", 43 "int_asbuffer", 44 "oldnumeric", 45 "safe_eval", 46 "set_numeric_ops", 47 "test", 48 # Builtins 49 "bool", 50 "complex", 51 "float", 52 "int", 53 "long", 54 "object", 55 "str", 56 "unicode", 57 # More standard names should be preferred 58 "alltrue", # all 59 "sometrue", # any 60 } 61} 62 63 64class FindAttributes(ast.NodeVisitor): 65 """Find top-level attributes/functions/classes in stubs files. 66 67 Do this by walking the stubs ast. See e.g. 68 69 https://greentreesnakes.readthedocs.io/en/latest/index.html 70 71 for more information on working with Python's ast. 72 73 """ 74 75 def __init__(self): 76 self.attributes = set() 77 78 def visit_FunctionDef(self, node): 79 if node.name == "__getattr__": 80 # Not really a module member. 81 return 82 self.attributes.add(node.name) 83 # Do not call self.generic_visit; we are only interested in 84 # top-level functions. 85 return 86 87 def visit_ClassDef(self, node): 88 if not node.name.startswith("_"): 89 self.attributes.add(node.name) 90 return 91 92 def visit_AnnAssign(self, node): 93 self.attributes.add(node.target.id) 94 95 96def find_missing(module_name): 97 module_path = os.path.join( 98 NUMPY_ROOT, 99 module_name.replace(".", os.sep), 100 "__init__.pyi", 101 ) 102 103 module = importlib.import_module(module_name) 104 module_attributes = { 105 attribute for attribute in dir(module) if not attribute.startswith("_") 106 } 107 108 if os.path.isfile(module_path): 109 with open(module_path) as f: 110 tree = ast.parse(f.read()) 111 ast_visitor = FindAttributes() 112 ast_visitor.visit(tree) 113 stubs_attributes = ast_visitor.attributes 114 else: 115 # No stubs for this module yet. 116 stubs_attributes = set() 117 118 exclude_list = EXCLUDE_LIST.get(module_name, set()) 119 120 missing = module_attributes - stubs_attributes - exclude_list 121 print("\n".join(sorted(missing))) 122 123 124def main(): 125 parser = argparse.ArgumentParser() 126 parser.add_argument("module") 127 args = parser.parse_args() 128 129 find_missing(args.module) 130 131 132if __name__ == "__main__": 133 main() 134