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