diff --git a/IPython/extensions/cythonmagic.py b/IPython/extensions/cythonmagic.py index 2773afd..f1ea6a4 100644 --- a/IPython/extensions/cythonmagic.py +++ b/IPython/extensions/cythonmagic.py @@ -1,37 +1,10 @@ # -*- coding: utf-8 -*- """ -===================== -Cython related magics -===================== +The cython magic has been integrated into Cython itself, +which is now released in version 0.21. -Magic command interface for interactive work with Cython - -.. note:: - - The ``Cython`` package needs to be installed separately. It - can be obtained using ``easy_install`` or ``pip``. - -Usage -===== - -To enable the magics below, execute ``%load_ext cythonmagic``. - -``%%cython`` - -{CYTHON_DOC} - -``%%cython_inline`` - -{CYTHON_INLINE_DOC} - -``%%cython_pyximport`` - -{CYTHON_PYXIMPORT_DOC} - -Author: -* Brian Granger - -Parts of this code were taken from Cython.inline. +cf github `Cython` organisation, `Cython` repo, under the +file `Cython/Build/IpythonMagic.py` """ #----------------------------------------------------------------------------- # Copyright (C) 2010-2011, IPython Development Team. @@ -43,303 +16,28 @@ Parts of this code were taken from Cython.inline. from __future__ import print_function -import imp -import io -import os -import re -import sys -import time +import IPython.utils.version as version try: - reload -except NameError: # Python 3 - from imp import reload + import Cython +except: + Cython = None try: - import hashlib -except ImportError: - import md5 as hashlib - -from distutils.core import Distribution, Extension -from distutils.command.build_ext import build_ext - -from IPython.core import display -from IPython.core import magic_arguments -from IPython.core.magic import Magics, magics_class, cell_magic -from IPython.utils import py3compat -from IPython.utils.path import get_ipython_cache_dir -from IPython.utils.text import dedent - -import Cython -from Cython.Compiler.Errors import CompileError -from Cython.Build.Dependencies import cythonize - - -@magics_class -class CythonMagics(Magics): - - def __init__(self, shell): - super(CythonMagics,self).__init__(shell) - self._reloads = {} - self._code_cache = {} - - def _import_all(self, module): - for k,v in module.__dict__.items(): - if not k.startswith('__'): - self.shell.push({k:v}) - - @cell_magic - def cython_inline(self, line, cell): - """Compile and run a Cython code cell using Cython.inline. - - This magic simply passes the body of the cell to Cython.inline - and returns the result. If the variables `a` and `b` are defined - in the user's namespace, here is a simple example that returns - their sum:: - - %%cython_inline - return a+b - - For most purposes, we recommend the usage of the `%%cython` magic. - """ - locs = self.shell.user_global_ns - globs = self.shell.user_ns - return Cython.inline(cell, locals=locs, globals=globs) - - @cell_magic - def cython_pyximport(self, line, cell): - """Compile and import a Cython code cell using pyximport. + from Cython.Build.IpythonMagic import CythonMagics +except : + pass - The contents of the cell are written to a `.pyx` file in the current - working directory, which is then imported using `pyximport`. This - magic requires a module name to be passed:: - - %%cython_pyximport modulename - def f(x): - return 2.0*x - - The compiled module is then imported and all of its symbols are - injected into the user's namespace. For most purposes, we recommend - the usage of the `%%cython` magic. - """ - module_name = line.strip() - if not module_name: - raise ValueError('module name must be given') - fname = module_name + '.pyx' - with io.open(fname, 'w', encoding='utf-8') as f: - f.write(cell) - if 'pyximport' not in sys.modules: - import pyximport - pyximport.install(reload_support=True) - if module_name in self._reloads: - module = self._reloads[module_name] - reload(module) - else: - __import__(module_name) - module = sys.modules[module_name] - self._reloads[module_name] = module - self._import_all(module) - - @magic_arguments.magic_arguments() - @magic_arguments.argument( - '-c', '--compile-args', action='append', default=[], - help="Extra flags to pass to compiler via the `extra_compile_args` " - "Extension flag (can be specified multiple times)." - ) - @magic_arguments.argument( - '--link-args', action='append', default=[], - help="Extra flags to pass to linker via the `extra_link_args` " - "Extension flag (can be specified multiple times)." - ) - @magic_arguments.argument( - '-l', '--lib', action='append', default=[], - help="Add a library to link the extension against (can be specified " - "multiple times)." - ) - @magic_arguments.argument( - '-n', '--name', - help="Specify a name for the Cython module." - ) - @magic_arguments.argument( - '-L', dest='library_dirs', metavar='dir', action='append', default=[], - help="Add a path to the list of libary directories (can be specified " - "multiple times)." - ) - @magic_arguments.argument( - '-I', '--include', action='append', default=[], - help="Add a path to the list of include directories (can be specified " - "multiple times)." - ) - @magic_arguments.argument( - '-+', '--cplus', action='store_true', default=False, - help="Output a C++ rather than C file." - ) - @magic_arguments.argument( - '-f', '--force', action='store_true', default=False, - help="Force the compilation of a new module, even if the source has been " - "previously compiled." - ) - @magic_arguments.argument( - '-a', '--annotate', action='store_true', default=False, - help="Produce a colorized HTML version of the source." - ) - @cell_magic - def cython(self, line, cell): - """Compile and import everything from a Cython code cell. - - The contents of the cell are written to a `.pyx` file in the - directory `IPYTHONDIR/cython` using a filename with the hash of the - code. This file is then cythonized and compiled. The resulting module - is imported and all of its symbols are injected into the user's - namespace. The usage is similar to that of `%%cython_pyximport` but - you don't have to pass a module name:: - - %%cython - def f(x): - return 2.0*x - - To compile OpenMP codes, pass the required `--compile-args` - and `--link-args`. For example with gcc:: - - %%cython --compile-args=-fopenmp --link-args=-fopenmp - ... - """ - args = magic_arguments.parse_argstring(self.cython, line) - code = cell if cell.endswith('\n') else cell+'\n' - lib_dir = os.path.join(get_ipython_cache_dir(), 'cython') - quiet = True - key = code, sys.version_info, sys.executable, Cython.__version__ - - if not os.path.exists(lib_dir): - os.makedirs(lib_dir) - - if args.force: - # Force a new module name by adding the current time to the - # key which is hashed to determine the module name. - key += time.time(), - - if args.name: - module_name = py3compat.unicode_to_str(args.name) - else: - module_name = "_cython_magic_" + hashlib.md5(str(key).encode('utf-8')).hexdigest() - module_path = os.path.join(lib_dir, module_name + self.so_ext) - - have_module = os.path.isfile(module_path) - need_cythonize = not have_module - - if args.annotate: - html_file = os.path.join(lib_dir, module_name + '.html') - if not os.path.isfile(html_file): - need_cythonize = True - - if need_cythonize: - c_include_dirs = args.include - if 'numpy' in code: - import numpy - c_include_dirs.append(numpy.get_include()) - pyx_file = os.path.join(lib_dir, module_name + '.pyx') - pyx_file = py3compat.cast_bytes_py2(pyx_file, encoding=sys.getfilesystemencoding()) - with io.open(pyx_file, 'w', encoding='utf-8') as f: - f.write(code) - extension = Extension( - name = module_name, - sources = [pyx_file], - include_dirs = c_include_dirs, - library_dirs = args.library_dirs, - extra_compile_args = args.compile_args, - extra_link_args = args.link_args, - libraries = args.lib, - language = 'c++' if args.cplus else 'c', - ) - build_extension = self._get_build_extension() - try: - opts = dict( - quiet=quiet, - annotate = args.annotate, - force = True, - ) - build_extension.extensions = cythonize([extension], **opts) - except CompileError: - return - - if not have_module: - build_extension.build_temp = os.path.dirname(pyx_file) - build_extension.build_lib = lib_dir - build_extension.run() - self._code_cache[key] = module_name - - module = imp.load_dynamic(module_name, module_path) - self._import_all(module) - - if args.annotate: - try: - with io.open(html_file, encoding='utf-8') as f: - annotated_html = f.read() - except IOError as e: - # File could not be opened. Most likely the user has a version - # of Cython before 0.15.1 (when `cythonize` learned the - # `force` keyword argument) and has already compiled this - # exact source without annotation. - print('Cython completed successfully but the annotated ' - 'source could not be read.', file=sys.stderr) - print(e, file=sys.stderr) - else: - return display.HTML(self.clean_annotated_html(annotated_html)) - - @property - def so_ext(self): - """The extension suffix for compiled modules.""" - try: - return self._so_ext - except AttributeError: - self._so_ext = self._get_build_extension().get_ext_filename('') - return self._so_ext - - def _clear_distutils_mkpath_cache(self): - """clear distutils mkpath cache - - prevents distutils from skipping re-creation of dirs that have been removed - """ - try: - from distutils.dir_util import _path_created - except ImportError: - pass - else: - _path_created.clear() - - def _get_build_extension(self): - self._clear_distutils_mkpath_cache() - dist = Distribution() - config_files = dist.find_config_files() - try: - config_files.remove('setup.cfg') - except ValueError: - pass - dist.parse_config_files(config_files) - build_extension = build_ext(dist) - build_extension.finalize_options() - return build_extension - - @staticmethod - def clean_annotated_html(html): - """Clean up the annotated HTML source. - - Strips the link to the generated C or C++ file, which we do not - present to the user. - """ - r = re.compile('
Raw output: (.*)')
- html = '\n'.join(l for l in html.splitlines() if not r.match(l))
- return html
-
-__doc__ = __doc__.format(
- # rST doesn't see the -+ flag as part of an option list, so we
- # hide it from the module-level docstring.
- CYTHON_DOC = dedent(CythonMagics.cython.__doc__\
- .replace('-+, --cplus','--cplus ')),
- CYTHON_INLINE_DOC = dedent(CythonMagics.cython_inline.__doc__),
- CYTHON_PYXIMPORT_DOC = dedent(CythonMagics.cython_pyximport.__doc__),
-)
+## still load the magic in IPython 3.x, remove completely in future versions.
def load_ipython_extension(ip):
"""Load the extension in IPython."""
- ip.register_magics(CythonMagics)
+
+ print("""The Cython magic has been move to the Cython package, hence """)
+ print("""`%load_ext cythonmagic` is deprecated; Please use `%load_ext Cython` instead.""")
+
+ if Cython is None or not version.check_version(Cython.__version__, "0.21"):
+ print("You need Cython version >=0.21 to use the Cython magic")
+ return
+ print("""\nThough, because I am nice, I'll still try to load it for you this time.""")
+ Cython.load_ipython_extension(ip)
diff --git a/IPython/extensions/tests/test_cythonmagic.py b/IPython/extensions/tests/test_cythonmagic.py
deleted file mode 100644
index 41dea38..0000000
--- a/IPython/extensions/tests/test_cythonmagic.py
+++ /dev/null
@@ -1,70 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Tests for the Cython magics extension."""
-
-import os
-import nose.tools as nt
-
-from IPython.testing import decorators as dec
-from IPython.utils import py3compat
-
-code = py3compat.str_to_unicode("""def f(x):
- return 2*x
-""")
-
-try:
- import Cython
-except:
- __test__ = False
-
-ip = get_ipython()
-
-
-def setup():
- ip.extension_manager.load_extension('cythonmagic')
-
-
-def test_cython_inline():
- ip.ex('a=10; b=20')
- result = ip.run_cell_magic('cython_inline','','return a+b')
- nt.assert_equal(result, 30)
-
-
-@dec.skip_win32
-def test_cython_pyximport():
- module_name = '_test_cython_pyximport'
- ip.run_cell_magic('cython_pyximport', module_name, code)
- ip.ex('g = f(10)')
- nt.assert_equal(ip.user_ns['g'], 20.0)
- ip.run_cell_magic('cython_pyximport', module_name, code)
- ip.ex('h = f(-10)')
- nt.assert_equal(ip.user_ns['h'], -20.0)
- try:
- os.remove(module_name+'.pyx')
- except OSError:
- pass
-
-
-def test_cython():
- ip.run_cell_magic('cython', '', code)
- ip.ex('g = f(10)')
- nt.assert_equal(ip.user_ns['g'], 20.0)
-
-
-def test_cython_name():
- # The Cython module named 'mymodule' defines the function f.
- ip.run_cell_magic('cython', '--name=mymodule', code)
- # This module can now be imported in the interactive namespace.
- ip.ex('import mymodule; g = mymodule.f(10)')
- nt.assert_equal(ip.user_ns['g'], 20.0)
-
-
-@dec.skip_win32
-def test_extlibs():
- code = py3compat.str_to_unicode("""
-from libc.math cimport sin
-x = sin(0.0)
- """)
- ip.user_ns['x'] = 1
- ip.run_cell_magic('cython', '-l m', code)
- nt.assert_equal(ip.user_ns['x'], 0)
-
diff --git a/IPython/testing/iptest.py b/IPython/testing/iptest.py
index cb6f608..1389dd5 100644
--- a/IPython/testing/iptest.py
+++ b/IPython/testing/iptest.py
@@ -131,7 +131,6 @@ have['pymongo'] = test_for('pymongo')
have['pygments'] = test_for('pygments')
have['qt'] = test_for('IPython.external.qt')
have['sqlite3'] = test_for('sqlite3')
-have['cython'] = test_for('Cython')
have['tornado'] = test_for('tornado.version_info', (3,1,0), callback=None)
have['jinja2'] = test_for('jinja2')
have['mistune'] = test_for('mistune')
@@ -240,9 +239,6 @@ test_sections['kernel.inprocess'].requires('zmq')
# extensions:
sec = test_sections['extensions']
-if not have['cython']:
- sec.exclude('cythonmagic')
- sec.exclude('tests.test_cythonmagic')
# This is deprecated in favour of rpy2
sec.exclude('rmagic')
# autoreload does some strange stuff, so move it to its own test section
diff --git a/docs/source/config/extensions/cythonmagic.rst b/docs/source/config/extensions/cythonmagic.rst
index f6b76fb..fffecef 100644
--- a/docs/source/config/extensions/cythonmagic.rst
+++ b/docs/source/config/extensions/cythonmagic.rst
@@ -4,4 +4,4 @@
cythonmagic
===========
-.. automodule:: IPython.extensions.cythonmagic
+The `cython` magic has been moved in the `Cython` package.
diff --git a/docs/source/config/extensions/index.rst b/docs/source/config/extensions/index.rst
index 9d70c5c..a940a8d 100644
--- a/docs/source/config/extensions/index.rst
+++ b/docs/source/config/extensions/index.rst
@@ -100,3 +100,5 @@ Extensions bundled with IPython
* ``rmagic`` is now part of `rpy2