##// END OF EJS Templates
Backport PR #3013: py3 workaround for reload in cythonmagic...
MinRK -
Show More
@@ -1,200 +1,205 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 Cython related magics.
3 Cython related magics.
4
4
5 Author:
5 Author:
6 * Brian Granger
6 * Brian Granger
7
7
8 Parts of this code were taken from Cython.inline.
8 Parts of this code were taken from Cython.inline.
9 """
9 """
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Copyright (C) 2010-2011, IPython Development Team.
11 # Copyright (C) 2010-2011, IPython Development Team.
12 #
12 #
13 # Distributed under the terms of the Modified BSD License.
13 # Distributed under the terms of the Modified BSD License.
14 #
14 #
15 # The full license is in the file COPYING.txt, distributed with this software.
15 # The full license is in the file COPYING.txt, distributed with this software.
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 import io
18 import io
19 import os, sys
19 import os, sys
20 import imp
20 import imp
21
21
22 try:
22 try:
23 reload
24 except NameError: # Python 3
25 from imp import reload
26
27 try:
23 import hashlib
28 import hashlib
24 except ImportError:
29 except ImportError:
25 import md5 as hashlib
30 import md5 as hashlib
26
31
27 from distutils.core import Distribution, Extension
32 from distutils.core import Distribution, Extension
28 from distutils.command.build_ext import build_ext
33 from distutils.command.build_ext import build_ext
29
34
30 from IPython.core.magic import Magics, magics_class, cell_magic
35 from IPython.core.magic import Magics, magics_class, cell_magic
31 from IPython.testing.skipdoctest import skip_doctest
36 from IPython.testing.skipdoctest import skip_doctest
32 from IPython.core.magic_arguments import (
37 from IPython.core.magic_arguments import (
33 argument, magic_arguments, parse_argstring
38 argument, magic_arguments, parse_argstring
34 )
39 )
35 from IPython.utils import py3compat
40 from IPython.utils import py3compat
36
41
37 import Cython
42 import Cython
38 from Cython.Compiler.Errors import CompileError
43 from Cython.Compiler.Errors import CompileError
39 from Cython.Compiler.Main import Context, default_options
44 from Cython.Compiler.Main import Context, default_options
40 from Cython.Build.Dependencies import cythonize
45 from Cython.Build.Dependencies import cythonize
41
46
42
47
43 @magics_class
48 @magics_class
44 class CythonMagics(Magics):
49 class CythonMagics(Magics):
45
50
46 def __init__(self, shell):
51 def __init__(self, shell):
47 super(CythonMagics,self).__init__(shell)
52 super(CythonMagics,self).__init__(shell)
48 self._reloads = {}
53 self._reloads = {}
49 self._code_cache = {}
54 self._code_cache = {}
50
55
51 def _import_all(self, module):
56 def _import_all(self, module):
52 for k,v in module.__dict__.items():
57 for k,v in module.__dict__.items():
53 if not k.startswith('__'):
58 if not k.startswith('__'):
54 self.shell.push({k:v})
59 self.shell.push({k:v})
55
60
56 @cell_magic
61 @cell_magic
57 def cython_inline(self, line, cell):
62 def cython_inline(self, line, cell):
58 """Compile and run a Cython code cell using Cython.inline.
63 """Compile and run a Cython code cell using Cython.inline.
59
64
60 This magic simply passes the body of the cell to Cython.inline
65 This magic simply passes the body of the cell to Cython.inline
61 and returns the result. If the variables `a` and `b` are defined
66 and returns the result. If the variables `a` and `b` are defined
62 in the user's namespace, here is a simple example that returns
67 in the user's namespace, here is a simple example that returns
63 their sum::
68 their sum::
64
69
65 %%cython_inline
70 %%cython_inline
66 return a+b
71 return a+b
67
72
68 For most purposes, we recommend the usage of the `%%cython` magic.
73 For most purposes, we recommend the usage of the `%%cython` magic.
69 """
74 """
70 locs = self.shell.user_global_ns
75 locs = self.shell.user_global_ns
71 globs = self.shell.user_ns
76 globs = self.shell.user_ns
72 return Cython.inline(cell, locals=locs, globals=globs)
77 return Cython.inline(cell, locals=locs, globals=globs)
73
78
74 @cell_magic
79 @cell_magic
75 def cython_pyximport(self, line, cell):
80 def cython_pyximport(self, line, cell):
76 """Compile and import a Cython code cell using pyximport.
81 """Compile and import a Cython code cell using pyximport.
77
82
78 The contents of the cell are written to a `.pyx` file in the current
83 The contents of the cell are written to a `.pyx` file in the current
79 working directory, which is then imported using `pyximport`. This
84 working directory, which is then imported using `pyximport`. This
80 magic requires a module name to be passed::
85 magic requires a module name to be passed::
81
86
82 %%cython_pyximport modulename
87 %%cython_pyximport modulename
83 def f(x):
88 def f(x):
84 return 2.0*x
89 return 2.0*x
85
90
86 The compiled module is then imported and all of its symbols are injected into
91 The compiled module is then imported and all of its symbols are injected into
87 the user's namespace. For most purposes, we recommend the usage of the
92 the user's namespace. For most purposes, we recommend the usage of the
88 `%%cython` magic.
93 `%%cython` magic.
89 """
94 """
90 module_name = line.strip()
95 module_name = line.strip()
91 if not module_name:
96 if not module_name:
92 raise ValueError('module name must be given')
97 raise ValueError('module name must be given')
93 fname = module_name + '.pyx'
98 fname = module_name + '.pyx'
94 with io.open(fname, 'w', encoding='utf-8') as f:
99 with io.open(fname, 'w', encoding='utf-8') as f:
95 f.write(cell)
100 f.write(cell)
96 if 'pyximport' not in sys.modules:
101 if 'pyximport' not in sys.modules:
97 import pyximport
102 import pyximport
98 pyximport.install(reload_support=True)
103 pyximport.install(reload_support=True)
99 if module_name in self._reloads:
104 if module_name in self._reloads:
100 module = self._reloads[module_name]
105 module = self._reloads[module_name]
101 reload(module)
106 reload(module)
102 else:
107 else:
103 __import__(module_name)
108 __import__(module_name)
104 module = sys.modules[module_name]
109 module = sys.modules[module_name]
105 self._reloads[module_name] = module
110 self._reloads[module_name] = module
106 self._import_all(module)
111 self._import_all(module)
107
112
108 @magic_arguments()
113 @magic_arguments()
109 @argument(
114 @argument(
110 '-c', '--compile-args', action='append', default=[],
115 '-c', '--compile-args', action='append', default=[],
111 help="Extra flags to pass to compiler via the `extra_compile_args` Extension flag (can be specified multiple times)."
116 help="Extra flags to pass to compiler via the `extra_compile_args` Extension flag (can be specified multiple times)."
112 )
117 )
113 @argument(
118 @argument(
114 '-l', '--lib', action='append', default=[],
119 '-l', '--lib', action='append', default=[],
115 help="Add a library to link the extension against (can be specified multiple times)."
120 help="Add a library to link the extension against (can be specified multiple times)."
116 )
121 )
117 @argument(
122 @argument(
118 '-I', '--include', action='append', default=[],
123 '-I', '--include', action='append', default=[],
119 help="Add a path to the list of include directories (can be specified multiple times)."
124 help="Add a path to the list of include directories (can be specified multiple times)."
120 )
125 )
121 @argument(
126 @argument(
122 '-f', '--force', action='store_true', default=False,
127 '-f', '--force', action='store_true', default=False,
123 help="Force the compilation of the pyx module even if it hasn't changed"
128 help="Force the compilation of the pyx module even if it hasn't changed"
124 )
129 )
125 @cell_magic
130 @cell_magic
126 def cython(self, line, cell):
131 def cython(self, line, cell):
127 """Compile and import everything from a Cython code cell.
132 """Compile and import everything from a Cython code cell.
128
133
129 The contents of the cell are written to a `.pyx` file in the
134 The contents of the cell are written to a `.pyx` file in the
130 directory `IPYTHONDIR/cython` using a filename with the hash of the code.
135 directory `IPYTHONDIR/cython` using a filename with the hash of the code.
131 This file is then cythonized and compiled. The resulting module
136 This file is then cythonized and compiled. The resulting module
132 is imported and all of its symbols are injected into the user's
137 is imported and all of its symbols are injected into the user's
133 namespace. The usage is similar to that of `%%cython_pyximport` but
138 namespace. The usage is similar to that of `%%cython_pyximport` but
134 you don't have to pass a module name::
139 you don't have to pass a module name::
135
140
136 %%cython
141 %%cython
137 def f(x):
142 def f(x):
138 return 2.0*x
143 return 2.0*x
139 """
144 """
140 args = parse_argstring(self.cython, line)
145 args = parse_argstring(self.cython, line)
141 code = cell if cell.endswith('\n') else cell+'\n'
146 code = cell if cell.endswith('\n') else cell+'\n'
142 lib_dir = os.path.join(self.shell.ipython_dir, 'cython')
147 lib_dir = os.path.join(self.shell.ipython_dir, 'cython')
143 cython_include_dirs = ['.']
148 cython_include_dirs = ['.']
144 force = args.force
149 force = args.force
145 quiet = True
150 quiet = True
146 ctx = Context(cython_include_dirs, default_options)
151 ctx = Context(cython_include_dirs, default_options)
147 key = code, sys.version_info, sys.executable, Cython.__version__
152 key = code, sys.version_info, sys.executable, Cython.__version__
148 module_name = "_cython_magic_" + hashlib.md5(str(key).encode('utf-8')).hexdigest()
153 module_name = "_cython_magic_" + hashlib.md5(str(key).encode('utf-8')).hexdigest()
149 so_ext = [ ext for ext,_,mod_type in imp.get_suffixes() if mod_type == imp.C_EXTENSION ][0]
154 so_ext = [ ext for ext,_,mod_type in imp.get_suffixes() if mod_type == imp.C_EXTENSION ][0]
150 module_path = os.path.join(lib_dir, module_name+so_ext)
155 module_path = os.path.join(lib_dir, module_name+so_ext)
151
156
152 if not os.path.exists(lib_dir):
157 if not os.path.exists(lib_dir):
153 os.makedirs(lib_dir)
158 os.makedirs(lib_dir)
154
159
155 if force or not os.path.isfile(module_path):
160 if force or not os.path.isfile(module_path):
156 c_include_dirs = args.include
161 c_include_dirs = args.include
157 if 'numpy' in code:
162 if 'numpy' in code:
158 import numpy
163 import numpy
159 c_include_dirs.append(numpy.get_include())
164 c_include_dirs.append(numpy.get_include())
160 pyx_file = os.path.join(lib_dir, module_name + '.pyx')
165 pyx_file = os.path.join(lib_dir, module_name + '.pyx')
161 pyx_file = py3compat.cast_bytes_py2(pyx_file, encoding=sys.getfilesystemencoding())
166 pyx_file = py3compat.cast_bytes_py2(pyx_file, encoding=sys.getfilesystemencoding())
162 with io.open(pyx_file, 'w', encoding='utf-8') as f:
167 with io.open(pyx_file, 'w', encoding='utf-8') as f:
163 f.write(code)
168 f.write(code)
164 extension = Extension(
169 extension = Extension(
165 name = module_name,
170 name = module_name,
166 sources = [pyx_file],
171 sources = [pyx_file],
167 include_dirs = c_include_dirs,
172 include_dirs = c_include_dirs,
168 extra_compile_args = args.compile_args,
173 extra_compile_args = args.compile_args,
169 libraries = args.lib,
174 libraries = args.lib,
170 )
175 )
171 dist = Distribution()
176 dist = Distribution()
172 config_files = dist.find_config_files()
177 config_files = dist.find_config_files()
173 try:
178 try:
174 config_files.remove('setup.cfg')
179 config_files.remove('setup.cfg')
175 except ValueError:
180 except ValueError:
176 pass
181 pass
177 dist.parse_config_files(config_files)
182 dist.parse_config_files(config_files)
178 build_extension = build_ext(dist)
183 build_extension = build_ext(dist)
179 build_extension.finalize_options()
184 build_extension.finalize_options()
180 try:
185 try:
181 build_extension.extensions = cythonize([extension], ctx=ctx, quiet=quiet)
186 build_extension.extensions = cythonize([extension], ctx=ctx, quiet=quiet)
182 except CompileError:
187 except CompileError:
183 return
188 return
184 build_extension.build_temp = os.path.dirname(pyx_file)
189 build_extension.build_temp = os.path.dirname(pyx_file)
185 build_extension.build_lib = lib_dir
190 build_extension.build_lib = lib_dir
186 build_extension.run()
191 build_extension.run()
187 self._code_cache[key] = module_name
192 self._code_cache[key] = module_name
188
193
189 module = imp.load_dynamic(module_name, module_path)
194 module = imp.load_dynamic(module_name, module_path)
190 self._import_all(module)
195 self._import_all(module)
191
196
192
197
193 _loaded = False
198 _loaded = False
194
199
195 def load_ipython_extension(ip):
200 def load_ipython_extension(ip):
196 """Load the extension in IPython."""
201 """Load the extension in IPython."""
197 global _loaded
202 global _loaded
198 if not _loaded:
203 if not _loaded:
199 ip.register_magics(CythonMagics)
204 ip.register_magics(CythonMagics)
200 _loaded = True
205 _loaded = True
General Comments 0
You need to be logged in to leave comments. Login now