Skip to content

Support current IPython, add %%nose cell magic #11

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
0.2.0
=====

- added support for newer IPython versions
- dropped support for IPython < 4.0
19 changes: 8 additions & 11 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ Usage
``-v`` is handled specially, but other arguments are passed to nosetests as
if they were passed at the command-line.

* Only run test-like things in the current cell using the ``%%nose`` cell magic,
e.g.::

%%nose

def test_just_this():
assert True


Caveats
-------
Expand All @@ -74,17 +82,6 @@ Caveats
not specifically to line 2.


TODO
----

* Have a cell magic to only run test-like things in the current cell, e.g.::

%%nose

def test_just_this():
assert True


Authors
-------

Expand Down
134 changes: 89 additions & 45 deletions ipython_nose.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import re
import shlex
import string
import sys
import types
import uuid

Expand All @@ -14,7 +13,9 @@
from nose.plugins.base import Plugin
from nose.plugins.skip import SkipTest
from nose.plugins.manager import DefaultPluginManager
from IPython.core import displaypub, magic
from nose.selector import defaultSelector
from IPython.core import magic
from IPython.display import display


class Template(string.Formatter):
Expand Down Expand Up @@ -45,35 +46,32 @@ def flush(self, *arg):
class NotebookLiveOutput(object):
def __init__(self):
self.output_id = 'ipython_nose_%s' % uuid.uuid4().hex
displaypub.publish_display_data(
u'IPython.core.displaypub.publish_html',
{'text/html':'<div id="%s"></div>' % self.output_id})
displaypub.publish_display_data(
u'IPython.core.displaypub.publish_javascript',
{'application/javascript':
'document.%s = $("#%s");' % (self.output_id, self.output_id)})
display({'text/html': ('<div id="{self.output_id}"></div>'
.format(self=self))},
raw=True)
display({'application/javascript': ('document.{self.output_id} = '
'$("#{self.output_id}");'
.format(self=self))},
raw=True)

def finalize(self):
displaypub.publish_display_data(
u'IPython.core.displaypub.publish_javascript',
{'application/javascript':
'delete document.%s;' % self.output_id})

display({'application/javascript': ('delete document.{self.output_id};'
.format(self=self))},
raw=True)

def write_chars(self, chars):
displaypub.publish_display_data(
u'IPython.core.displaypub.publish_javascript',
{'application/javascript':
'document.%s.append($("<span>%s</span>"));' % (
self.output_id, cgi.escape(chars))})
display({'application/javascript': ('document.{self.output_id}.append('
'$("<span>{chars}</span>"));'
.format(self=self,
chars=cgi.escape(chars)))},
raw=True)

def write_line(self, line):
displaypub.publish_display_data(
u'IPython.core.displaypub.publish_javascript',
{'application/javascript':
'document.%s.append($("<div>%s</div>"));' % (
self.output_id, cgi.escape(line))})

display({'application/javascript': ('document.{self.output_id}.append('
'$("<div>{line}</div>"));')
.format(self=self,
line=cgi.escape(line))},
raw=True)


class ConsoleLiveOutput(object):
Expand Down Expand Up @@ -101,9 +99,10 @@ class IPythonDisplay(Plugin):
enabled = True
score = 2

def __init__(self, verbose=False):
def __init__(self, verbose=False, expand_tracebacks=False):
super(IPythonDisplay, self).__init__()
self.verbose = verbose
self.expand_tracebacks = expand_tracebacks
self.html = []
self.num_tests = 0
self.failures = []
Expand Down Expand Up @@ -184,7 +183,6 @@ def __init__(self, verbose=False):
padding: 1em;
margin-left: 0px;
margin-top: 0px;
display: none;
}
</style>
'''
Expand Down Expand Up @@ -254,7 +252,7 @@ def _summary(self, numtests, numfailed, numskipped, template):
failed: <span class="nosefailedfunc">{name!e}</span>
[<a class="nosefailtoggle" href="#">toggle traceback</a>]
</div>
<pre class="nosetraceback">{formatted_traceback!e}</pre>
<pre class="nosetraceback" style="display:{hide_traceback_style}">{formatted_traceback!e}</pre>
</div>
''')

Expand All @@ -267,28 +265,34 @@ def _tracebacks(self, failures, template):
name = test.shortDescription() or str(test)
formatted_traceback = ''.join(traceback.format_exception(*exc))
output.append(template.format(
name=name, formatted_traceback=formatted_traceback
name=name, formatted_traceback=formatted_traceback,
hide_traceback_style=('block' if self.expand_tracebacks
else 'none')
))
return ''.join(output)

def _write_test_line(self, test, status):
self.live_output.write_line(
"{} ... {}".format(test.shortDescription() or str(test), status))

def addSuccess(self, test):
if self.verbose:
self.live_output.write_line(str(test) + " ... pass")
self._write_test_line(test, 'pass')
else:
self.live_output.write_chars('.')

def addError(self, test, err):
if issubclass(err[0], SkipTest):
return self.addSkip(test)
if self.verbose:
self.live_output.write_line(str(test) + " ... error")
self._write_test_line(test, 'error')
else:
self.live_output.write_chars('E')
self.failures.append((test, err))

def addFailure(self, test, err):
if self.verbose:
self.live_output.write_line(str(test) + " ... fail")
self._write_test_line(test, 'fail')
else:
self.live_output.write_chars('F')
self.failures.append((test, err))
Expand All @@ -303,15 +307,7 @@ def addSkip(self, test):
self.skipped += 1

def begin(self):
# This feels really hacky
try: # >= ipython 1.0
from IPython.kernel.zmq.displayhook import ZMQShellDisplayHook
except ImportError:
from IPython.zmq.displayhook import ZMQShellDisplayHook
if isinstance(sys.displayhook, ZMQShellDisplayHook):
self.live_output = NotebookLiveOutput()
else:
self.live_output = ConsoleLiveOutput(self)
self.live_output = NotebookLiveOutput()

def finalize(self, result):
self.result = result
Expand Down Expand Up @@ -389,16 +385,64 @@ def makeNoseConfig(env):
return Config(env=env, files=cfg_files, plugins=manager)


def nose(line, test_module=get_ipython_user_ns_as_a_module):
class ExcludingTestSelector(defaultSelector):
def __init__(self, config, excluded_objects):
super(ExcludingTestSelector, self).__init__(config)
self.excluded_objects = list(excluded_objects)

def _in_excluded_objects(self, obj):
for excluded_object in self.excluded_objects:
try:
if obj == excluded_object:
return True
except Exception:
return False
return False

def wantClass(self, cls):
if self._in_excluded_objects(cls):
return False
else:
return super(ExcludingTestSelector, self).wantClass(cls)

def wantFunction(self, function):
if self._in_excluded_objects(function):
return False
else:
return super(ExcludingTestSelector, self).wantFunction(function)

def wantMethod(self, method):
if self._in_excluded_objects(type(method.__self__)):
return False
else:
return super(ExcludingTestSelector, self).wantMethod(method)


def nose(line, cell=None, test_module=get_ipython_user_ns_as_a_module):
if callable(test_module):
test_module = test_module()
config = makeNoseConfig(os.environ)
loader = nose_loader.TestLoader(config=config)
if cell is None:
# Called as the %nose line magic.
# All objects in the notebook namespace should be considered for the
# test suite.
selector = None
else:
# Called as the %%nose cell magic.
# Classes and functions defined outside the cell should be excluded from
# the test run.
selector = ExcludingTestSelector(config, test_module.__dict__.values())
# Evaluate the cell and add objects it defined into the test module.
exec(cell, test_module.__dict__)
loader = nose_loader.TestLoader(config=config, selector=selector)
tests = loader.loadTestsFromModule(test_module)
extra_args = shlex.split(str(line))
expand_tracebacks = '--expand-tracebacks' in extra_args
if expand_tracebacks:
extra_args.remove('--expand-tracebacks')
argv = ['ipython-nose', '--with-ipython-html', '--no-skip'] + extra_args
verbose = '-v' in extra_args
plug = IPythonDisplay(verbose=verbose)
plug = IPythonDisplay(verbose=verbose, expand_tracebacks=expand_tracebacks)

nose_core.TestProgram(
argv=argv, suite=tests, addplugins=[plug], exit=False, config=config)
Expand All @@ -407,4 +451,4 @@ def nose(line, test_module=get_ipython_user_ns_as_a_module):


def load_ipython_extension(ipython):
magic.register_line_magic(nose)
magic.register_line_cell_magic(nose)
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
ipython>=0.13
nose==1.2.1
ipython>=4.0
nose>=1.2.1,<2
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

setup(
name='ipython_nose',
version='0.1.0',
version='0.2.0.dev',
author='Taavi Burns <taavi at taaviburns dot ca>, Greg Ward <greg at gerg dot ca>',
py_modules=['ipython_nose'],
url='https://github.com/taavi/ipython_nose',
Expand Down