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