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