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