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