Repository: numpy/numpy

Issue:
`test_warning_calls` error on Python 3.6
As of today, the daily numpy wheel builds show the following error on master for all Python 3.6 builds:

```
======================================================================
ERROR: test_warnings.test_warning_calls
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/venv/lib/python3.6/site-packages/nose/case.py", line 198, in runTest
    self.test(*self.arg)
  File "/venv/lib/python3.6/site-packages/numpy/tests/test_warnings.py", line 81, in test_warning_calls
    tree = ast.parse(file.read())
  File "/opt/cp36m/lib/python3.6/ast.py", line 35, in parse
    return compile(source, filename, mode, PyCF_ONLY_AST)
  File "<unknown>", line 1675
SyntaxError: invalid escape sequence \m
```

https://travis-ci.org/MacPython/numpy-wheels/builds/204888912

This is testing commit 5f5ccecbf .  I see that the usual travis-ci builds tested the same commit without error : https://travis-ci.org/numpy/numpy/builds/204805650 .  Any thoughts?


Discussion:

Resolving patch:
diff --git a/numpy/core/code_generators/genapi.py b/numpy/core/code_generators/genapi.py
--- a/numpy/core/code_generators/genapi.py
+++ b/numpy/core/code_generators/genapi.py
@@ -469,7 +469,7 @@ def fullapi_hash(api_dicts):
 
 # To parse strings like 'hex = checksum' where hex is e.g. 0x1234567F and
 # checksum a 128 bits md5 checksum (hex format as well)
-VERRE = re.compile('(^0x[\da-f]{8})\s*=\s*([\da-f]{32})')
+VERRE = re.compile(r'(^0x[\da-f]{8})\s*=\s*([\da-f]{32})')
 
 def get_versions_hash():
     d = []
diff --git a/numpy/lib/function_base.py b/numpy/lib/function_base.py
--- a/numpy/lib/function_base.py
+++ b/numpy/lib/function_base.py
@@ -1644,7 +1644,7 @@ def gradient(f, *varargs, **kwargs):
                 + \\left(h_{d}^{2} - h_{s}^{2}\\right)f\\left(x_{i}\\right) 
                 - h_{d}^{2}f\\left(x_{i}-h_{s}\\right)}
                 { h_{s}h_{d}\\left(h_{d} + h_{s}\\right)}
-            + \mathcal{O}\\left(\\frac{h_{d}h_{s}^{2} 
+            + \\mathcal{O}\\left(\\frac{h_{d}h_{s}^{2} 
                                 + h_{s}h_{d}^{2}}{h_{d} 
                                 + h_{s}}\\right)
 
@@ -1656,7 +1656,7 @@ def gradient(f, *varargs, **kwargs):
         
         \\hat f_{i}^{(1)}=
             \\frac{f\\left(x_{i+1}\\right) - f\\left(x_{i-1}\\right)}{2h} 
-            + \mathcal{O}\\left(h^{2}\\right)
+            + \\mathcal{O}\\left(h^{2}\\right)
 
     With a similar procedure the forward/backward approximations used for 
     boundaries can be derived.
diff --git a/tools/find_deprecated_escaped_characters.py b/tools/find_deprecated_escaped_characters.py
new file mode 100644
--- /dev/null
+++ b/tools/find_deprecated_escaped_characters.py
@@ -0,0 +1,69 @@
+#! /usr/bin/env python
+"""
+Look for escape sequences deprecated in Python 3.6.
+
+Python 3.6 deprecates a number of non-escape sequences starting with `\` that
+were accepted before. For instance, '\(' was previously accepted but must now
+be written as '\\(' or r'\('.
+
+"""
+from __future__ import division, absolute_import, print_function
+
+import sys
+
+def main(root):
+    """Find deprecated escape sequences.
+
+    Checks for deprecated escape sequences in ``*.py files``. If `root` is a
+    file, that file is checked, if `root` is a directory all ``*.py`` files
+    found in a recursive descent are checked.
+
+    If a deprecated escape sequence is found, the file and line where found is
+    printed. Note that for multiline strings the line where the string ends is
+    printed and the error(s) are somewhere in the body of the string.
+
+    Parameters
+    ----------
+    root : str
+        File or directory to check.
+    Returns
+    -------
+    None
+
+    """
+    count = 0
+
+    if sys.version_info[:2] >= (3, 6):
+        import ast
+        import tokenize
+        import warnings
+        from pathlib import Path
+
+        base = Path(root)
+        paths = base.rglob("*.py") if base.is_dir() else [base]
+        for path in paths:
+            # use tokenize to auto-detect encoding on systems where no
+            # default encoding is defined (e.g. LANG='C')
+            with tokenize.open(str(path)) as f:
+                with warnings.catch_warnings(record=True) as w:
+                    warnings.simplefilter('always')
+                    tree = ast.parse(f.read())
+                if w:
+                    print("file: ", str(path))
+                    for e in w:
+                        print('line: ', e.lineno, ': ', e.message)
+                    print()
+                    count += len(w)
+    else:
+        raise RuntimeError("Python version must be >= 3.6")
+
+    print("Errors Found", count)
+
+
+if __name__ == "__main__":
+    from argparse import ArgumentParser
+
+    parser = ArgumentParser(description="Find deprecated escaped characters")
+    parser.add_argument('root', help='directory or file to be checked')
+    args = parser.parse_args()
+    main(args.root)


Test patch: