##// END OF EJS Templates
Using py3compat.unicode_to_str instead of str.
Cyrille Rossant -
Show More
@@ -1,341 +1,341 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 =====================
3 =====================
4 Cython related magics
4 Cython related magics
5 =====================
5 =====================
6
6
7 Magic command interface for interactive work with Cython
7 Magic command interface for interactive work with Cython
8
8
9 .. note::
9 .. note::
10
10
11 The ``Cython`` package needs to be installed separately. It
11 The ``Cython`` package needs to be installed separately. It
12 can be obtained using ``easy_install`` or ``pip``.
12 can be obtained using ``easy_install`` or ``pip``.
13
13
14 Usage
14 Usage
15 =====
15 =====
16
16
17 To enable the magics below, execute ``%load_ext cythonmagic``.
17 To enable the magics below, execute ``%load_ext cythonmagic``.
18
18
19 ``%%cython``
19 ``%%cython``
20
20
21 {CYTHON_DOC}
21 {CYTHON_DOC}
22
22
23 ``%%cython_inline``
23 ``%%cython_inline``
24
24
25 {CYTHON_INLINE_DOC}
25 {CYTHON_INLINE_DOC}
26
26
27 ``%%cython_pyximport``
27 ``%%cython_pyximport``
28
28
29 {CYTHON_PYXIMPORT_DOC}
29 {CYTHON_PYXIMPORT_DOC}
30
30
31 Author:
31 Author:
32 * Brian Granger
32 * Brian Granger
33
33
34 Parts of this code were taken from Cython.inline.
34 Parts of this code were taken from Cython.inline.
35 """
35 """
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37 # Copyright (C) 2010-2011, IPython Development Team.
37 # Copyright (C) 2010-2011, IPython Development Team.
38 #
38 #
39 # Distributed under the terms of the Modified BSD License.
39 # Distributed under the terms of the Modified BSD License.
40 #
40 #
41 # The full license is in the file COPYING.txt, distributed with this software.
41 # The full license is in the file COPYING.txt, distributed with this software.
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43
43
44 from __future__ import print_function
44 from __future__ import print_function
45
45
46 import imp
46 import imp
47 import io
47 import io
48 import os
48 import os
49 import re
49 import re
50 import sys
50 import sys
51 import time
51 import time
52
52
53 try:
53 try:
54 reload
54 reload
55 except NameError: # Python 3
55 except NameError: # Python 3
56 from imp import reload
56 from imp import reload
57
57
58 try:
58 try:
59 import hashlib
59 import hashlib
60 except ImportError:
60 except ImportError:
61 import md5 as hashlib
61 import md5 as hashlib
62
62
63 from distutils.core import Distribution, Extension
63 from distutils.core import Distribution, Extension
64 from distutils.command.build_ext import build_ext
64 from distutils.command.build_ext import build_ext
65
65
66 from IPython.core import display
66 from IPython.core import display
67 from IPython.core import magic_arguments
67 from IPython.core import magic_arguments
68 from IPython.core.magic import Magics, magics_class, cell_magic
68 from IPython.core.magic import Magics, magics_class, cell_magic
69 from IPython.utils import py3compat
69 from IPython.utils import py3compat
70 from IPython.utils.path import get_ipython_cache_dir
70 from IPython.utils.path import get_ipython_cache_dir
71
71
72 import Cython
72 import Cython
73 from Cython.Compiler.Errors import CompileError
73 from Cython.Compiler.Errors import CompileError
74 from Cython.Build.Dependencies import cythonize
74 from Cython.Build.Dependencies import cythonize
75
75
76
76
77 @magics_class
77 @magics_class
78 class CythonMagics(Magics):
78 class CythonMagics(Magics):
79
79
80 def __init__(self, shell):
80 def __init__(self, shell):
81 super(CythonMagics,self).__init__(shell)
81 super(CythonMagics,self).__init__(shell)
82 self._reloads = {}
82 self._reloads = {}
83 self._code_cache = {}
83 self._code_cache = {}
84
84
85 def _import_all(self, module):
85 def _import_all(self, module):
86 for k,v in module.__dict__.items():
86 for k,v in module.__dict__.items():
87 if not k.startswith('__'):
87 if not k.startswith('__'):
88 self.shell.push({k:v})
88 self.shell.push({k:v})
89
89
90 @cell_magic
90 @cell_magic
91 def cython_inline(self, line, cell):
91 def cython_inline(self, line, cell):
92 """Compile and run a Cython code cell using Cython.inline.
92 """Compile and run a Cython code cell using Cython.inline.
93
93
94 This magic simply passes the body of the cell to Cython.inline
94 This magic simply passes the body of the cell to Cython.inline
95 and returns the result. If the variables `a` and `b` are defined
95 and returns the result. If the variables `a` and `b` are defined
96 in the user's namespace, here is a simple example that returns
96 in the user's namespace, here is a simple example that returns
97 their sum::
97 their sum::
98
98
99 %%cython_inline
99 %%cython_inline
100 return a+b
100 return a+b
101
101
102 For most purposes, we recommend the usage of the `%%cython` magic.
102 For most purposes, we recommend the usage of the `%%cython` magic.
103 """
103 """
104 locs = self.shell.user_global_ns
104 locs = self.shell.user_global_ns
105 globs = self.shell.user_ns
105 globs = self.shell.user_ns
106 return Cython.inline(cell, locals=locs, globals=globs)
106 return Cython.inline(cell, locals=locs, globals=globs)
107
107
108 @cell_magic
108 @cell_magic
109 def cython_pyximport(self, line, cell):
109 def cython_pyximport(self, line, cell):
110 """Compile and import a Cython code cell using pyximport.
110 """Compile and import a Cython code cell using pyximport.
111
111
112 The contents of the cell are written to a `.pyx` file in the current
112 The contents of the cell are written to a `.pyx` file in the current
113 working directory, which is then imported using `pyximport`. This
113 working directory, which is then imported using `pyximport`. This
114 magic requires a module name to be passed::
114 magic requires a module name to be passed::
115
115
116 %%cython_pyximport modulename
116 %%cython_pyximport modulename
117 def f(x):
117 def f(x):
118 return 2.0*x
118 return 2.0*x
119
119
120 The compiled module is then imported and all of its symbols are
120 The compiled module is then imported and all of its symbols are
121 injected into the user's namespace. For most purposes, we recommend
121 injected into the user's namespace. For most purposes, we recommend
122 the usage of the `%%cython` magic.
122 the usage of the `%%cython` magic.
123 """
123 """
124 module_name = line.strip()
124 module_name = line.strip()
125 if not module_name:
125 if not module_name:
126 raise ValueError('module name must be given')
126 raise ValueError('module name must be given')
127 fname = module_name + '.pyx'
127 fname = module_name + '.pyx'
128 with io.open(fname, 'w', encoding='utf-8') as f:
128 with io.open(fname, 'w', encoding='utf-8') as f:
129 f.write(cell)
129 f.write(cell)
130 if 'pyximport' not in sys.modules:
130 if 'pyximport' not in sys.modules:
131 import pyximport
131 import pyximport
132 pyximport.install(reload_support=True)
132 pyximport.install(reload_support=True)
133 if module_name in self._reloads:
133 if module_name in self._reloads:
134 module = self._reloads[module_name]
134 module = self._reloads[module_name]
135 reload(module)
135 reload(module)
136 else:
136 else:
137 __import__(module_name)
137 __import__(module_name)
138 module = sys.modules[module_name]
138 module = sys.modules[module_name]
139 self._reloads[module_name] = module
139 self._reloads[module_name] = module
140 self._import_all(module)
140 self._import_all(module)
141
141
142 @magic_arguments.magic_arguments()
142 @magic_arguments.magic_arguments()
143 @magic_arguments.argument(
143 @magic_arguments.argument(
144 '-c', '--compile-args', action='append', default=[],
144 '-c', '--compile-args', action='append', default=[],
145 help="Extra flags to pass to compiler via the `extra_compile_args` "
145 help="Extra flags to pass to compiler via the `extra_compile_args` "
146 "Extension flag (can be specified multiple times)."
146 "Extension flag (can be specified multiple times)."
147 )
147 )
148 @magic_arguments.argument(
148 @magic_arguments.argument(
149 '--link-args', action='append', default=[],
149 '--link-args', action='append', default=[],
150 help="Extra flags to pass to linker via the `extra_link_args` "
150 help="Extra flags to pass to linker via the `extra_link_args` "
151 "Extension flag (can be specified multiple times)."
151 "Extension flag (can be specified multiple times)."
152 )
152 )
153 @magic_arguments.argument(
153 @magic_arguments.argument(
154 '-l', '--lib', action='append', default=[],
154 '-l', '--lib', action='append', default=[],
155 help="Add a library to link the extension against (can be specified "
155 help="Add a library to link the extension against (can be specified "
156 "multiple times)."
156 "multiple times)."
157 )
157 )
158 @magic_arguments.argument(
158 @magic_arguments.argument(
159 '-n', '--name',
159 '-n', '--name',
160 help="Specify a name for the Cython module."
160 help="Specify a name for the Cython module."
161 )
161 )
162 @magic_arguments.argument(
162 @magic_arguments.argument(
163 '-L', dest='library_dirs', metavar='dir', action='append', default=[],
163 '-L', dest='library_dirs', metavar='dir', action='append', default=[],
164 help="Add a path to the list of libary directories (can be specified "
164 help="Add a path to the list of libary directories (can be specified "
165 "multiple times)."
165 "multiple times)."
166 )
166 )
167 @magic_arguments.argument(
167 @magic_arguments.argument(
168 '-I', '--include', action='append', default=[],
168 '-I', '--include', action='append', default=[],
169 help="Add a path to the list of include directories (can be specified "
169 help="Add a path to the list of include directories (can be specified "
170 "multiple times)."
170 "multiple times)."
171 )
171 )
172 @magic_arguments.argument(
172 @magic_arguments.argument(
173 '-+', '--cplus', action='store_true', default=False,
173 '-+', '--cplus', action='store_true', default=False,
174 help="Output a C++ rather than C file."
174 help="Output a C++ rather than C file."
175 )
175 )
176 @magic_arguments.argument(
176 @magic_arguments.argument(
177 '-f', '--force', action='store_true', default=False,
177 '-f', '--force', action='store_true', default=False,
178 help="Force the compilation of a new module, even if the source has been "
178 help="Force the compilation of a new module, even if the source has been "
179 "previously compiled."
179 "previously compiled."
180 )
180 )
181 @magic_arguments.argument(
181 @magic_arguments.argument(
182 '-a', '--annotate', action='store_true', default=False,
182 '-a', '--annotate', action='store_true', default=False,
183 help="Produce a colorized HTML version of the source."
183 help="Produce a colorized HTML version of the source."
184 )
184 )
185 @cell_magic
185 @cell_magic
186 def cython(self, line, cell):
186 def cython(self, line, cell):
187 """Compile and import everything from a Cython code cell.
187 """Compile and import everything from a Cython code cell.
188
188
189 The contents of the cell are written to a `.pyx` file in the
189 The contents of the cell are written to a `.pyx` file in the
190 directory `IPYTHONDIR/cython` using a filename with the hash of the
190 directory `IPYTHONDIR/cython` using a filename with the hash of the
191 code. This file is then cythonized and compiled. The resulting module
191 code. This file is then cythonized and compiled. The resulting module
192 is imported and all of its symbols are injected into the user's
192 is imported and all of its symbols are injected into the user's
193 namespace. The usage is similar to that of `%%cython_pyximport` but
193 namespace. The usage is similar to that of `%%cython_pyximport` but
194 you don't have to pass a module name::
194 you don't have to pass a module name::
195
195
196 %%cython
196 %%cython
197 def f(x):
197 def f(x):
198 return 2.0*x
198 return 2.0*x
199
199
200 To compile OpenMP codes, pass the required `--compile-args`
200 To compile OpenMP codes, pass the required `--compile-args`
201 and `--link-args`. For example with gcc::
201 and `--link-args`. For example with gcc::
202
202
203 %%cython --compile-args=-fopenmp --link-args=-fopenmp
203 %%cython --compile-args=-fopenmp --link-args=-fopenmp
204 ...
204 ...
205 """
205 """
206 args = magic_arguments.parse_argstring(self.cython, line)
206 args = magic_arguments.parse_argstring(self.cython, line)
207 code = cell if cell.endswith('\n') else cell+'\n'
207 code = cell if cell.endswith('\n') else cell+'\n'
208 lib_dir = os.path.join(get_ipython_cache_dir(), 'cython')
208 lib_dir = os.path.join(get_ipython_cache_dir(), 'cython')
209 quiet = True
209 quiet = True
210 key = code, sys.version_info, sys.executable, Cython.__version__
210 key = code, sys.version_info, sys.executable, Cython.__version__
211
211
212 if not os.path.exists(lib_dir):
212 if not os.path.exists(lib_dir):
213 os.makedirs(lib_dir)
213 os.makedirs(lib_dir)
214
214
215 if args.force:
215 if args.force:
216 # Force a new module name by adding the current time to the
216 # Force a new module name by adding the current time to the
217 # key which is hashed to determine the module name.
217 # key which is hashed to determine the module name.
218 key += time.time(),
218 key += time.time(),
219
219
220 if args.name:
220 if args.name:
221 module_name = str(args.name)
221 module_name = py3compat.unicode_to_str(args.name)
222 else:
222 else:
223 module_name = "_cython_magic_" + hashlib.md5(str(key).encode('utf-8')).hexdigest()
223 module_name = "_cython_magic_" + hashlib.md5(str(key).encode('utf-8')).hexdigest()
224 module_path = os.path.join(lib_dir, module_name + self.so_ext)
224 module_path = os.path.join(lib_dir, module_name + self.so_ext)
225
225
226 have_module = os.path.isfile(module_path)
226 have_module = os.path.isfile(module_path)
227 need_cythonize = not have_module
227 need_cythonize = not have_module
228
228
229 if args.annotate:
229 if args.annotate:
230 html_file = os.path.join(lib_dir, module_name + '.html')
230 html_file = os.path.join(lib_dir, module_name + '.html')
231 if not os.path.isfile(html_file):
231 if not os.path.isfile(html_file):
232 need_cythonize = True
232 need_cythonize = True
233
233
234 if need_cythonize:
234 if need_cythonize:
235 c_include_dirs = args.include
235 c_include_dirs = args.include
236 if 'numpy' in code:
236 if 'numpy' in code:
237 import numpy
237 import numpy
238 c_include_dirs.append(numpy.get_include())
238 c_include_dirs.append(numpy.get_include())
239 pyx_file = os.path.join(lib_dir, module_name + '.pyx')
239 pyx_file = os.path.join(lib_dir, module_name + '.pyx')
240 pyx_file = py3compat.cast_bytes_py2(pyx_file, encoding=sys.getfilesystemencoding())
240 pyx_file = py3compat.cast_bytes_py2(pyx_file, encoding=sys.getfilesystemencoding())
241 with io.open(pyx_file, 'w', encoding='utf-8') as f:
241 with io.open(pyx_file, 'w', encoding='utf-8') as f:
242 f.write(code)
242 f.write(code)
243 extension = Extension(
243 extension = Extension(
244 name = module_name,
244 name = module_name,
245 sources = [pyx_file],
245 sources = [pyx_file],
246 include_dirs = c_include_dirs,
246 include_dirs = c_include_dirs,
247 library_dirs = args.library_dirs,
247 library_dirs = args.library_dirs,
248 extra_compile_args = args.compile_args,
248 extra_compile_args = args.compile_args,
249 extra_link_args = args.link_args,
249 extra_link_args = args.link_args,
250 libraries = args.lib,
250 libraries = args.lib,
251 language = 'c++' if args.cplus else 'c',
251 language = 'c++' if args.cplus else 'c',
252 )
252 )
253 build_extension = self._get_build_extension()
253 build_extension = self._get_build_extension()
254 try:
254 try:
255 opts = dict(
255 opts = dict(
256 quiet=quiet,
256 quiet=quiet,
257 annotate = args.annotate,
257 annotate = args.annotate,
258 force = True,
258 force = True,
259 )
259 )
260 build_extension.extensions = cythonize([extension], **opts)
260 build_extension.extensions = cythonize([extension], **opts)
261 except CompileError:
261 except CompileError:
262 return
262 return
263
263
264 if not have_module:
264 if not have_module:
265 build_extension.build_temp = os.path.dirname(pyx_file)
265 build_extension.build_temp = os.path.dirname(pyx_file)
266 build_extension.build_lib = lib_dir
266 build_extension.build_lib = lib_dir
267 build_extension.run()
267 build_extension.run()
268 self._code_cache[key] = module_name
268 self._code_cache[key] = module_name
269
269
270 module = imp.load_dynamic(module_name, module_path)
270 module = imp.load_dynamic(module_name, module_path)
271 self._import_all(module)
271 self._import_all(module)
272
272
273 if args.annotate:
273 if args.annotate:
274 try:
274 try:
275 with io.open(html_file, encoding='utf-8') as f:
275 with io.open(html_file, encoding='utf-8') as f:
276 annotated_html = f.read()
276 annotated_html = f.read()
277 except IOError as e:
277 except IOError as e:
278 # File could not be opened. Most likely the user has a version
278 # File could not be opened. Most likely the user has a version
279 # of Cython before 0.15.1 (when `cythonize` learned the
279 # of Cython before 0.15.1 (when `cythonize` learned the
280 # `force` keyword argument) and has already compiled this
280 # `force` keyword argument) and has already compiled this
281 # exact source without annotation.
281 # exact source without annotation.
282 print('Cython completed successfully but the annotated '
282 print('Cython completed successfully but the annotated '
283 'source could not be read.', file=sys.stderr)
283 'source could not be read.', file=sys.stderr)
284 print(e, file=sys.stderr)
284 print(e, file=sys.stderr)
285 else:
285 else:
286 return display.HTML(self.clean_annotated_html(annotated_html))
286 return display.HTML(self.clean_annotated_html(annotated_html))
287
287
288 @property
288 @property
289 def so_ext(self):
289 def so_ext(self):
290 """The extension suffix for compiled modules."""
290 """The extension suffix for compiled modules."""
291 try:
291 try:
292 return self._so_ext
292 return self._so_ext
293 except AttributeError:
293 except AttributeError:
294 self._so_ext = self._get_build_extension().get_ext_filename('')
294 self._so_ext = self._get_build_extension().get_ext_filename('')
295 return self._so_ext
295 return self._so_ext
296
296
297 def _clear_distutils_mkpath_cache(self):
297 def _clear_distutils_mkpath_cache(self):
298 """clear distutils mkpath cache
298 """clear distutils mkpath cache
299
299
300 prevents distutils from skipping re-creation of dirs that have been removed
300 prevents distutils from skipping re-creation of dirs that have been removed
301 """
301 """
302 try:
302 try:
303 from distutils.dir_util import _path_created
303 from distutils.dir_util import _path_created
304 except ImportError:
304 except ImportError:
305 pass
305 pass
306 else:
306 else:
307 _path_created.clear()
307 _path_created.clear()
308
308
309 def _get_build_extension(self):
309 def _get_build_extension(self):
310 self._clear_distutils_mkpath_cache()
310 self._clear_distutils_mkpath_cache()
311 dist = Distribution()
311 dist = Distribution()
312 config_files = dist.find_config_files()
312 config_files = dist.find_config_files()
313 try:
313 try:
314 config_files.remove('setup.cfg')
314 config_files.remove('setup.cfg')
315 except ValueError:
315 except ValueError:
316 pass
316 pass
317 dist.parse_config_files(config_files)
317 dist.parse_config_files(config_files)
318 build_extension = build_ext(dist)
318 build_extension = build_ext(dist)
319 build_extension.finalize_options()
319 build_extension.finalize_options()
320 return build_extension
320 return build_extension
321
321
322 @staticmethod
322 @staticmethod
323 def clean_annotated_html(html):
323 def clean_annotated_html(html):
324 """Clean up the annotated HTML source.
324 """Clean up the annotated HTML source.
325
325
326 Strips the link to the generated C or C++ file, which we do not
326 Strips the link to the generated C or C++ file, which we do not
327 present to the user.
327 present to the user.
328 """
328 """
329 r = re.compile('<p>Raw output: <a href="(.*)">(.*)</a>')
329 r = re.compile('<p>Raw output: <a href="(.*)">(.*)</a>')
330 html = '\n'.join(l for l in html.splitlines() if not r.match(l))
330 html = '\n'.join(l for l in html.splitlines() if not r.match(l))
331 return html
331 return html
332
332
333 __doc__ = __doc__.format(
333 __doc__ = __doc__.format(
334 CYTHON_DOC = ' '*8 + CythonMagics.cython.__doc__,
334 CYTHON_DOC = ' '*8 + CythonMagics.cython.__doc__,
335 CYTHON_INLINE_DOC = ' '*8 + CythonMagics.cython_inline.__doc__,
335 CYTHON_INLINE_DOC = ' '*8 + CythonMagics.cython_inline.__doc__,
336 CYTHON_PYXIMPORT_DOC = ' '*8 + CythonMagics.cython_pyximport.__doc__,
336 CYTHON_PYXIMPORT_DOC = ' '*8 + CythonMagics.cython_pyximport.__doc__,
337 )
337 )
338
338
339 def load_ipython_extension(ip):
339 def load_ipython_extension(ip):
340 """Load the extension in IPython."""
340 """Load the extension in IPython."""
341 ip.register_magics(CythonMagics)
341 ip.register_magics(CythonMagics)
General Comments 0
You need to be logged in to leave comments. Login now