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