Repository: cython/cython

Issue:
Fix IpythonMagic for IPython >= 7.11
In IPython 7.11.0, [a number of function in the py3compat have been removed](https://github.com/ipython/ipython/blob/master/docs/source/whatsnew/version7.rst#ipython-711).

Fixes https://github.com/ipython/ipython/issues/12068

```
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-2-84ee719fa1da> in <module>
----> 1 get_ipython().run_cell_magic('cython', '', '\ndef tmp():\n    return 0\n')

X:\Python37\lib\site-packages\IPython\core\interactiveshell.py in run_cell_magic(self, magic_name, line, cell)
   2350             with self.builtin_trap:
   2351                 args = (magic_arg_s, cell)
-> 2352                 result = fn(*args, **kwargs)
   2353             return result
   2354 

<X:\Python37\lib\site-packages\decorator.py:decorator-gen-159> in cython(self, line, cell)

X:\Python37\lib\site-packages\IPython\core\magic.py in <lambda>(f, *a, **k)
    185     # but it's overkill for just that one bit of state.
    186     def magic_deco(arg):
--> 187         call = lambda f, *a, **k: f(*a, **k)
    188 
    189         if callable(arg):

X:\Python37\lib\site-packages\Cython\Build\IpythonMagic.py in cython(self, line, cell)
    322         extension = None
    323         if need_cythonize:
--> 324             extensions = self._cythonize(module_name, code, lib_dir, args, quiet=args.quiet)
    325             if extensions is None:
    326                 # Compilation failed and printed error message

X:\Python37\lib\site-packages\Cython\Build\IpythonMagic.py in _cythonize(self, module_name, code, lib_dir, args, quiet)
    407     def _cythonize(self, module_name, code, lib_dir, args, quiet=True):
    408         pyx_file = os.path.join(lib_dir, module_name + '.pyx')
--> 409         pyx_file = py3compat.cast_bytes_py2(pyx_file, encoding=sys.getfilesystemencoding())
    410 
    411         c_include_dirs = args.include

AttributeError: module 'IPython.utils.py3compat' has no attribute 'cast_bytes_py2'
```


Discussion:
I'll reintroduce them in a 7.11.1 as I know Cython release is not fast. 

What I can also suggest is to be explicit as to when you use `... = no_code` and when  not; 
like use the if `(sys.version_info < 3)`. This will make it way easier to cleanup the code later. 

If you have `try... except` then in a few year you will have to hunt down which versions of Python/IPython have the import and it will take you hours. 
See ipython/ipython#12069
I'm not going to release a 7.11.1 for now to let you test this PR. Once your happy with that let me know on ipython/ipython#12069 and I can merge and do a release.
Thanks. Apparently, `str_to_unicode()` is also missing now, which is used in `TestIpythonMagic.py`.

@Carreau, is `py3compat` considered an internal module? My guess is that it might have been used in the implementation because "it was there". Should we get rid of the usage all-together, and only have a couple of dedicated fallbacks for the Py2 case then?
> My guess is that it might have been used in the implementation because "it was there". Should we get rid of the usage all-together, and only have a couple of dedicated fallbacks for the Py2 case then?

It was probably used because the Cython magic was originally in IPython and was transplanted in Cython. It is kinda internal, as it would be better to rely on `six` for both Py2 and Py3 compact, but out utils predates six.

Yes I would suggest to be explicit about the Py2/Py3 when possible, especially since you only have a couple of usage and that both of the one I see are no-op functions in Python 3. 

Resolving patch:
diff --git a/Cython/Build/IpythonMagic.py b/Cython/Build/IpythonMagic.py
--- a/Cython/Build/IpythonMagic.py
+++ b/Cython/Build/IpythonMagic.py
@@ -56,6 +56,8 @@
 import distutils.log
 import textwrap
 
+IO_ENCODING = sys.getfilesystemencoding()
+IS_PY2 = sys.version_info[0] < 3
 
 try:
     reload
@@ -73,7 +75,6 @@
 from IPython.core import display
 from IPython.core import magic_arguments
 from IPython.core.magic import Magics, magics_class, cell_magic
-from IPython.utils import py3compat
 try:
     from IPython.paths import get_ipython_cache_dir
 except ImportError:
@@ -101,6 +102,14 @@
 PGO_CONFIG['mingw32'] = PGO_CONFIG['gcc']
 
 
+if IS_PY2:
+    def encode_fs(name):
+        return name if isinstance(name, bytes) else name.encode(IO_ENCODING)
+else:
+    def encode_fs(name):
+        return name
+
+
 @magics_class
 class CythonMagics(Magics):
 
@@ -306,7 +315,7 @@ def critical_function(data):
             key += (time.time(),)
 
         if args.name:
-            module_name = py3compat.unicode_to_str(args.name)
+            module_name = str(args.name)  # no-op in Py3
         else:
             module_name = "_cython_magic_" + hashlib.md5(str(key).encode('utf-8')).hexdigest()
         html_file = os.path.join(lib_dir, module_name + '.html')
@@ -406,7 +415,7 @@ def _profile_pgo_wrapper(self, extension, lib_dir):
 
     def _cythonize(self, module_name, code, lib_dir, args, quiet=True):
         pyx_file = os.path.join(lib_dir, module_name + '.pyx')
-        pyx_file = py3compat.cast_bytes_py2(pyx_file, encoding=sys.getfilesystemencoding())
+        pyx_file = encode_fs(pyx_file)
 
         c_include_dirs = args.include
         c_src_files = list(map(str, args.src))
@@ -526,10 +535,10 @@ def build_extensions(self):
         build_extension = _build_ext(dist)
         build_extension.finalize_options()
         if temp_dir:
-            temp_dir = py3compat.cast_bytes_py2(temp_dir, encoding=sys.getfilesystemencoding())
+            temp_dir = encode_fs(temp_dir)
             build_extension.build_temp = temp_dir
         if lib_dir:
-            lib_dir = py3compat.cast_bytes_py2(lib_dir, encoding=sys.getfilesystemencoding())
+            lib_dir = encode_fs(lib_dir)
             build_extension.build_lib = lib_dir
         if extension is not None:
             build_extension.extensions = [extension]


Test patch:
diff --git a/Cython/Build/Tests/TestIpythonMagic.py b/Cython/Build/Tests/TestIpythonMagic.py
--- a/Cython/Build/Tests/TestIpythonMagic.py
+++ b/Cython/Build/Tests/TestIpythonMagic.py
@@ -13,15 +13,8 @@
 
 try:
     import IPython.testing.globalipapp
-    from IPython.utils import py3compat
 except ImportError:
     # Disable tests and fake helpers for initialisation below.
-    class _py3compat(object):
-        def str_to_unicode(self, s):
-            return s
-
-    py3compat = _py3compat()
-
     def skip_if_not_installed(_):
         return None
 else:
@@ -35,24 +28,24 @@ def skip_if_not_installed(c):
 except ImportError:
     pass
 
-code = py3compat.str_to_unicode("""\
+code = u"""\
 def f(x):
     return 2*x
-""")
+"""
 
-cython3_code = py3compat.str_to_unicode("""\
+cython3_code = u"""\
 def f(int x):
     return 2 / x
 
 def call(x):
     return f(*(x,))
-""")
+"""
 
-pgo_cython3_code = cython3_code + py3compat.str_to_unicode("""\
+pgo_cython3_code = cython3_code + u"""\
 def main():
     for _ in range(100): call(5)
 main()
-""")
+"""
 
 
 if sys.platform == 'win32':
@@ -161,10 +154,10 @@ def test_cython3_pgo(self):
     @skip_win32('Skip on Windows')
     def test_extlibs(self):
         ip = self._ip
-        code = py3compat.str_to_unicode("""
+        code = u"""
 from libc.math cimport sin
 x = sin(0.0)
-        """)
+        """
         ip.user_ns['x'] = 1
         ip.run_cell_magic('cython', '-l m', code)
         self.assertEqual(ip.user_ns['x'], 0)