Caffe2 - Python API
A deep learning, cross platform ML framework
test_type_hints.py
1 from __future__ import print_function
2 import unittest
3 from common_utils import TestCase, run_tests, download_file
4 import tempfile
5 import torch
6 import re
7 import os
8 import sys
9 import subprocess
10 import inspect
11 
12 try:
13  import mypy
14  HAVE_MYPY = True
15 except ImportError:
16  HAVE_MYPY = False
17 
18 
19 def get_examples_from_docstring(docstr):
20  """
21  Extracts all runnable python code from the examples
22  in docstrings; returns a list of lines.
23  """
24  # TODO: Figure out if there's a way to use doctest directly to
25  # implement this
26  example_file_lines = []
27  # the detection is a bit hacky because there isn't a nice way of detecting
28  # where multiline commands end. Thus we keep track of how far we got in beginning
29  # and continue to add lines until we have a compileable Python statement.
30  exampleline_re = re.compile(r"^\s+(?:>>>|\.\.\.) (.*)$")
31  beginning = ""
32  for l in docstr.split('\n'):
33  if beginning:
34  m = exampleline_re.match(l)
35  if m:
36  beginning += m.group(1)
37  else:
38  beginning += l
39  else:
40  m = exampleline_re.match(l)
41  if m:
42  beginning += m.group(1)
43  if beginning:
44  complete = True
45  try:
46  compile(beginning, "", "exec")
47  except SyntaxError:
48  complete = False
49  if complete:
50  # found one
51  example_file_lines += beginning.split('\n')
52  beginning = ""
53  else:
54  beginning += "\n"
55  return [' ' + l for l in example_file_lines]
56 
57 
58 def get_all_examples():
59  """get_all_examples() -> str
60 
61  This function grabs (hopefully all) examples from the torch documentation
62  strings and puts them in one nonsensical module returned as a string.
63  """
64  blacklist = {"_np"}
65  allexamples = ""
66 
67  example_file_lines = [
68  "import torch",
69  "import torch.nn.functional as F",
70  "import math # type: ignore", # mypy complains about floats where SupportFloat is expected
71  "import numpy # type: ignore",
72  "import io # type: ignore",
73  "import itertools # type: ignore",
74  "",
75  # for requires_grad_ example
76  # NB: We are parsing this file as Python 2, so we must use
77  # Python 2 type annotation syntax
78  "def preprocess(inp):",
79  " # type: (torch.Tensor) -> torch.Tensor",
80  " return inp",
81  ]
82 
83  for fname in dir(torch):
84  fn = getattr(torch, fname)
85  docstr = inspect.getdoc(fn)
86  if docstr and fname not in blacklist:
87  e = get_examples_from_docstring(docstr)
88  if e:
89  example_file_lines.append("\n\ndef example_torch_{}():".format(fname))
90  example_file_lines += e
91 
92  for fname in dir(torch.Tensor):
93  fn = getattr(torch.Tensor, fname)
94  docstr = inspect.getdoc(fn)
95  if docstr and fname not in blacklist:
96  e = get_examples_from_docstring(docstr)
97  if e:
98  example_file_lines.append("\n\ndef example_torch_tensor_{}():".format(fname))
99  example_file_lines += e
100 
101  return "\n".join(example_file_lines)
102 
103 
105  @unittest.skipIf(sys.version_info[0] == 2, "no type hints for Python 2")
106  @unittest.skipIf(not HAVE_MYPY, "need mypy")
107  def test_doc_examples(self):
108  """
109  Run documentation examples through mypy.
110  """
111  fn = os.path.join(os.path.dirname(__file__), 'generated_type_hints_smoketest.py')
112  with open(fn, "w") as f:
113  print(get_all_examples(), file=f)
114 
115  # OK, so here's the deal. mypy treats installed packages
116  # and local modules differently: if a package is installed,
117  # mypy will refuse to use modules from that package for type
118  # checking unless the module explicitly says that it supports
119  # type checking. (Reference:
120  # https://mypy.readthedocs.io/en/latest/running_mypy.html#missing-imports
121  # )
122  #
123  # Now, PyTorch doesn't support typechecking, and we shouldn't
124  # claim that it supports typechecking (it doesn't.) However, not
125  # claiming we support typechecking is bad for this test, which
126  # wants to use the partial information we get from the bits of
127  # PyTorch which are typed to check if it typechecks. And
128  # although mypy will work directly if you are working in source,
129  # some of our tests involve installing PyTorch and then running
130  # its tests.
131  #
132  # The guidance we got from Michael Sullivan and Joshua Oreman,
133  # and also independently developed by Thomas Viehmann,
134  # is that we should create a fake directory and add symlinks for
135  # the packages that should typecheck. So that is what we do
136  # here.
137  #
138  # If you want to run mypy by hand, and you run from PyTorch
139  # root directory, it should work fine to skip this step (since
140  # mypy will preferentially pick up the local files first). The
141  # temporary directory here is purely needed for CI. For this
142  # reason, we also still drop the generated file in the test
143  # source folder, for ease of inspection when there are failures.
144  with tempfile.TemporaryDirectory() as tmp_dir:
145  try:
146  os.symlink(
147  os.path.dirname(torch.__file__),
148  os.path.join(tmp_dir, 'torch'),
149  target_is_directory=True
150  )
151  except OSError:
152  raise unittest.SkipTest('cannot symlink')
153  try:
154  subprocess.run([
155  sys.executable,
156  '-mmypy',
157  '--follow-imports', 'silent',
158  '--check-untyped-defs',
159  os.path.abspath(fn)],
160  cwd=tmp_dir,
161  check=True)
162  except subprocess.CalledProcessError as e:
163  raise AssertionError("mypy failed. Look above this error for mypy's output.")
164 
165 
166 if __name__ == '__main__':
167  run_tests()