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