summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorJosh Wilson <person142@users.noreply.github.com>2020-06-17 20:52:26 -0700
committerJosh Wilson <person142@users.noreply.github.com>2020-06-17 21:08:23 -0700
commita08586a0a6e86148c85d09ffba167c686d52a8b0 (patch)
tree60125ff90bff229289e0c6f5fcc14370b6e77456 /tools
parent8245b392a344a1ae0db6e569ab68b368ad8883c1 (diff)
downloadnumpy-a08586a0a6e86148c85d09ffba167c686d52a8b0.tar.gz
ENH: add tool to find functions missing types
Closes https://github.com/numpy/numpy/issues/16625. This is ported from the numpy-stubs here: https://github.com/numpy/numpy-stubs/blob/master/runtests.py#L94 You run ``` ./tools/functions_missing_types.py <module> ``` and it will give you a list of things in that module that don't have type annotations.
Diffstat (limited to 'tools')
-rwxr-xr-xtools/functions_missing_types.py129
1 files changed, 129 insertions, 0 deletions
diff --git a/tools/functions_missing_types.py b/tools/functions_missing_types.py
new file mode 100755
index 000000000..a3f57ddec
--- /dev/null
+++ b/tools/functions_missing_types.py
@@ -0,0 +1,129 @@
+#!/usr/bin/env python
+import argparse
+import ast
+import importlib
+import os
+
+NUMPY_ROOT = os.path.dirname(os.path.join(
+ os.path.abspath(__file__), "..",
+))
+
+# Technically "public" functions (they don't start with an underscore)
+# that we don't want to include.
+EXCLUDE_LIST = {
+ "numpy": {
+ # Stdlib modules in the namespace by accident
+ "absolute_import",
+ "division",
+ "print_function",
+ "warnings",
+ # Accidentally public, deprecated, or shouldn't be used
+ "Tester",
+ "add_docstring",
+ "add_newdoc",
+ "add_newdoc_ufunc",
+ "core",
+ "fastCopyAndTranspose",
+ "get_array_wrap",
+ "int_asbuffer",
+ "oldnumeric",
+ "safe_eval",
+ "set_numeric_ops",
+ "test",
+ # Builtins
+ "bool",
+ "complex",
+ "float",
+ "int",
+ "long",
+ "object",
+ "str",
+ "unicode",
+ # Should use numpy_financial instead
+ "fv",
+ "ipmt",
+ "irr",
+ "mirr",
+ "nper",
+ "npv",
+ "pmt",
+ "ppmt",
+ "pv",
+ "rate",
+ # More standard names should be preferred
+ "alltrue", # all
+ "sometrue", # any
+ }
+}
+
+
+class FindAttributes(ast.NodeVisitor):
+ """Find top-level attributes/functions/classes a stubs file.
+
+ Do this by walking the stubs ast. See e.g.
+
+ https://greentreesnakes.readthedocs.io/en/latest/index.html
+
+ for more information on working with Python's ast.
+
+ """
+
+ def __init__(self):
+ self.attributes = set()
+
+ def visit_FunctionDef(self, node):
+ if node.name == "__getattr__":
+ # Not really a module member.
+ return
+ self.attributes.add(node.name)
+ # Do not call self.generic_visit; we are only interested in
+ # top-level functions.
+ return
+
+ def visit_ClassDef(self, node):
+ if not node.name.startswith("_"):
+ self.attributes.add(node.name)
+ return
+
+ def visit_AnnAssign(self, node):
+ self.attributes.add(node.target.id)
+
+
+def find_missing(module_name):
+ module_path = os.path.join(
+ NUMPY_ROOT,
+ module_name.replace(".", os.sep),
+ "__init__.pyi",
+ )
+
+ module = importlib.import_module(module_name)
+ module_attributes = {
+ attribute for attribute in dir(module) if not attribute.startswith("_")
+ }
+
+ if os.path.isfile(module_path):
+ with open(module_path) as f:
+ tree = ast.parse(f.read())
+ ast_visitor = FindAttributes()
+ ast_visitor.visit(tree)
+ stubs_attributes = ast_visitor.attributes
+ else:
+ # No stubs for this module yet.
+ stubs_attributes = set()
+
+ exclude_list = EXCLUDE_LIST.get(module_name, set())
+
+ missing = module_attributes - stubs_attributes - exclude_list
+ print("\n".join(sorted(missing)))
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument("module")
+ args = parser.parse_args()
+
+ find_missing(args.module)
+
+
+if __name__ == "__main__":
+ main()