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