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