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