From 43fac7c1d6964cc8d324973ff59aaff543f3a39b 2011-02-09 02:10:12 From: Fernando Perez Date: 2011-02-09 02:10:12 Subject: [PATCH] Merge branch 'fix_string_input2' into trunk --- diff --git a/IPython/core/displayhook.py b/IPython/core/displayhook.py index c5d3116..409ff14 100644 --- a/IPython/core/displayhook.py +++ b/IPython/core/displayhook.py @@ -260,11 +260,14 @@ class DisplayHook(Configurable): self.flush() # Don't overwrite '_' and friends if '_' is in __builtin__ (otherwise # we cause buggy behavior for things like gettext). + if '_' not in __builtin__.__dict__: self.___ = self.__ self.__ = self._ self._ = result - self.shell.user_ns.update({'_':self._,'__':self.__,'___':self.___}) + self.shell.user_ns.update({'_':self._, + '__':self.__, + '___':self.___}) # hackish access to top-level namespace to create _1,_2... dynamically to_main = {} diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 16cf17e..f9f1ca2 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -4,7 +4,7 @@ #----------------------------------------------------------------------------- # Copyright (C) 2001 Janko Hauser # Copyright (C) 2001-2007 Fernando Perez. -# Copyright (C) 2008-2010 The IPython Development Team +# Copyright (C) 2008-2011 The IPython Development Team # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. @@ -2113,19 +2113,6 @@ class InteractiveShell(Configurable, Magic): self.history_manager.store_inputs(ipy_cell, cell) self.logger.log(ipy_cell, cell) - # dbg code!!! - if 0: - def myapp(self, val): # dbg - import traceback as tb - stack = ''.join(tb.format_stack()) - print 'Value:', val - print 'Stack:\n', stack - list.append(self, val) - - import new - self.history_manager.input_hist_parsed.append = types.MethodType(myapp, - self.history_manager.input_hist_parsed) - # End dbg # All user code execution must happen with our context managers active with nested(self.builtin_trap, self.display_trap): @@ -2167,15 +2154,31 @@ class InteractiveShell(Configurable, Magic): self.execution_count += 1 def run_one_block(self, block): - """Run a single interactive block. + """Run a single interactive block of source code. If the block is single-line, dynamic transformations are applied to it (like automagics, autocall and alias recognition). + + If the block is multi-line, it must consist of valid Python code only. + + Parameters + ---------- + block : string + A (possibly multiline) string of code to be executed. + + Returns + ------- + The output of the underlying execution method used, be it + :meth:`run_source` or :meth:`run_single_line`. """ if len(block.splitlines()) <= 1: out = self.run_single_line(block) else: - out = self.run_code(block) + # Call run_source, which correctly compiles the input cell. + # run_code must only be called when we know we have a code object, + # as it does a naked exec and the compilation mode may not be what + # we wanted. + out = self.run_source(block) return out def run_single_line(self, line): @@ -2329,7 +2332,7 @@ class InteractiveShell(Configurable, Magic): try: try: self.hooks.pre_run_code_hook() - #rprint('Running code') # dbg + #rprint('Running code', repr(code_obj)) # dbg exec code_obj in self.user_global_ns, self.user_ns finally: # Reset our crash handler in place diff --git a/IPython/core/tests/test_inputsplitter.py b/IPython/core/tests/test_inputsplitter.py index 9ebc831..5b2cb9c 100644 --- a/IPython/core/tests/test_inputsplitter.py +++ b/IPython/core/tests/test_inputsplitter.py @@ -1,5 +1,10 @@ # -*- coding: utf-8 -*- """Tests for the inputsplitter module. + +Authors +------- +* Fernando Perez +* Robert Kern """ #----------------------------------------------------------------------------- # Copyright (C) 2010 The IPython Development Team diff --git a/IPython/core/tests/test_interactiveshell.py b/IPython/core/tests/test_interactiveshell.py new file mode 100644 index 0000000..3a081cf --- /dev/null +++ b/IPython/core/tests/test_interactiveshell.py @@ -0,0 +1,37 @@ +"""Tests for the key interactiveshell module. + +Historically the main classes in interactiveshell have been under-tested. This +module should grow as many single-method tests as possible to trap many of the +recurring bugs we seem to encounter with high-level interaction. + +Authors +------- +* Fernando Perez +""" +#----------------------------------------------------------------------------- +# Copyright (C) 2011 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- +# stdlib +import unittest + +#----------------------------------------------------------------------------- +# Tests +#----------------------------------------------------------------------------- + +class InteractiveShellTestCase(unittest.TestCase): + def test_naked_string_cells(self): + """Test that cells with only naked strings are fully executed""" + ip = get_ipython() + # First, single-line inputs + ip.run_cell('"a"\n') + self.assertEquals(ip.user_ns['_'], 'a') + # And also multi-line cells + ip.run_cell('"""a\nb"""\n') + self.assertEquals(ip.user_ns['_'], 'a\nb') diff --git a/IPython/testing/_doctest26.py b/IPython/testing/_doctest26.py deleted file mode 100644 index e30c024..0000000 --- a/IPython/testing/_doctest26.py +++ /dev/null @@ -1,121 +0,0 @@ -"""Code taken from the Python2.6 standard library for backwards compatibility. - -This is just so we can use 2.6 features when running in 2.5, the code below is -copied verbatim from the stdlib's collections and doctest modules. -""" - -#----------------------------------------------------------------------------- -# Copyright (C) 2009 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -from keyword import iskeyword as _iskeyword -from operator import itemgetter as _itemgetter -import sys as _sys - -def namedtuple(typename, field_names, verbose=False): - """Returns a new subclass of tuple with named fields. - - >>> Point = namedtuple('Point', 'x y') - >>> Point.__doc__ # docstring for the new class - 'Point(x, y)' - >>> p = Point(11, y=22) # instantiate with positional args or keywords - >>> p[0] + p[1] # indexable like a plain tuple - 33 - >>> x, y = p # unpack like a regular tuple - >>> x, y - (11, 22) - >>> p.x + p.y # fields also accessable by name - 33 - >>> d = p._asdict() # convert to a dictionary - >>> d['x'] - 11 - >>> Point(**d) # convert from a dictionary - Point(x=11, y=22) - >>> p._replace(x=100) # _replace() is like str.replace() but targets named fields - Point(x=100, y=22) - - """ - - # Parse and validate the field names. Validation serves two purposes, - # generating informative error messages and preventing template injection attacks. - if isinstance(field_names, basestring): - field_names = field_names.replace(',', ' ').split() # names separated by whitespace and/or commas - field_names = tuple(map(str, field_names)) - for name in (typename,) + field_names: - if not all(c.isalnum() or c=='_' for c in name): - raise ValueError('Type names and field names can only contain alphanumeric characters and underscores: %r' % name) - if _iskeyword(name): - raise ValueError('Type names and field names cannot be a keyword: %r' % name) - if name[0].isdigit(): - raise ValueError('Type names and field names cannot start with a number: %r' % name) - seen_names = set() - for name in field_names: - if name.startswith('_'): - raise ValueError('Field names cannot start with an underscore: %r' % name) - if name in seen_names: - raise ValueError('Encountered duplicate field name: %r' % name) - seen_names.add(name) - - # Create and fill-in the class template - numfields = len(field_names) - argtxt = repr(field_names).replace("'", "")[1:-1] # tuple repr without parens or quotes - reprtxt = ', '.join('%s=%%r' % name for name in field_names) - dicttxt = ', '.join('%r: t[%d]' % (name, pos) for pos, name in enumerate(field_names)) - template = '''class %(typename)s(tuple): - '%(typename)s(%(argtxt)s)' \n - __slots__ = () \n - _fields = %(field_names)r \n - def __new__(_cls, %(argtxt)s): - return _tuple.__new__(_cls, (%(argtxt)s)) \n - @classmethod - def _make(cls, iterable, new=tuple.__new__, len=len): - 'Make a new %(typename)s object from a sequence or iterable' - result = new(cls, iterable) - if len(result) != %(numfields)d: - raise TypeError('Expected %(numfields)d arguments, got %%d' %% len(result)) - return result \n - def __repr__(self): - return '%(typename)s(%(reprtxt)s)' %% self \n - def _asdict(t): - 'Return a new dict which maps field names to their values' - return {%(dicttxt)s} \n - def _replace(_self, **kwds): - 'Return a new %(typename)s object replacing specified fields with new values' - result = _self._make(map(kwds.pop, %(field_names)r, _self)) - if kwds: - raise ValueError('Got unexpected field names: %%r' %% kwds.keys()) - return result \n - def __getnewargs__(self): - return tuple(self) \n\n''' % locals() - for i, name in enumerate(field_names): - template += ' %s = _property(_itemgetter(%d))\n' % (name, i) - if verbose: - print template - - # Execute the template string in a temporary namespace and - # support tracing utilities by setting a value for frame.f_globals['__name__'] - namespace = dict(_itemgetter=_itemgetter, __name__='namedtuple_%s' % typename, - _property=property, _tuple=tuple) - try: - exec template in namespace - except SyntaxError, e: - raise SyntaxError(e.message + ':\n' + template) - result = namespace[typename] - - # For pickling to work, the __module__ variable needs to be set to the frame - # where the named tuple is created. Bypass this step in enviroments where - # sys._getframe is not defined (Jython for example). - if hasattr(_sys, '_getframe'): - result.__module__ = _sys._getframe(1).f_globals.get('__name__', '__main__') - - return result - - -TestResults = namedtuple('TestResults', 'failed attempted') diff --git a/IPython/testing/globalipapp.py b/IPython/testing/globalipapp.py index 7c0f202..1c3de6a 100644 --- a/IPython/testing/globalipapp.py +++ b/IPython/testing/globalipapp.py @@ -68,11 +68,19 @@ class ipnsdict(dict): This subclass adds a simple checkpointing capability so that when testing machinery clears it (we use it as the test execution context), it doesn't get completely destroyed. + + In addition, it can handle the presence of the '_' key in a special manner, + which is needed because of how Python's doctest machinery operates with + '_'. See constructor and :meth:`update` for details. """ def __init__(self,*a): dict.__init__(self,*a) self._savedict = {} + # If this flag is True, the .update() method will unconditionally + # remove a key named '_'. This is so that such a dict can be used as a + # namespace in doctests that call '_'. + self.protect_underscore = False def clear(self): dict.clear(self) @@ -86,10 +94,15 @@ class ipnsdict(dict): self._checkpoint() dict.update(self,other) - # If '_' is in the namespace, python won't set it when executing code, - # and we have examples that test it. So we ensure that the namespace - # is always 'clean' of it before it's used for test code execution. - self.pop('_',None) + if self.protect_underscore: + # If '_' is in the namespace, python won't set it when executing + # code *in doctests*, and we have multiple doctests that use '_'. + # So we ensure that the namespace is always 'clean' of it before + # it's used for test code execution. + # This flag is only turned on by the doctest machinery, so that + # normal test code can assume the _ key is updated like any other + # key and can test for its presence after cell executions. + self.pop('_', None) # The builtins namespace must *always* be the real __builtin__ module, # else weird stuff happens. The main ipython code does have provisions diff --git a/IPython/testing/ipunittest.py b/IPython/testing/ipunittest.py index e593c74..952c5e6 100644 --- a/IPython/testing/ipunittest.py +++ b/IPython/testing/ipunittest.py @@ -39,11 +39,7 @@ from __future__ import absolute_import import re import sys import unittest -from doctest import DocTestFinder, DocTestRunner -try: - from doctest import TestResults -except: - from ._doctest26 import TestResults +from doctest import DocTestFinder, DocTestRunner, TestResults # We already have python3-compliant code for parametric tests if sys.version[0]=='2': diff --git a/IPython/testing/plugin/ipdoctest.py b/IPython/testing/plugin/ipdoctest.py index 2f2dc85..f2b2c75 100644 --- a/IPython/testing/plugin/ipdoctest.py +++ b/IPython/testing/plugin/ipdoctest.py @@ -273,6 +273,10 @@ class DocTestCase(doctests.DocTestCase): # fills with the necessary info from the module being tested). _ip.user_ns.update(self._dt_test.globs) self._dt_test.globs = _ip.user_ns + # IPython must protect the _ key in the namespace (it can't exist) + # so that Python's doctest code sets it naturally, so we enable + # this feature of our testing namespace. + _ip.user_ns.protect_underscore = True super(DocTestCase, self).setUp() @@ -282,6 +286,9 @@ class DocTestCase(doctests.DocTestCase): # teardown doesn't destroy the ipython namespace if isinstance(self._dt_test.examples[0],IPExample): self._dt_test.globs = self._dt_test_globs_ori + # Restore the behavior of the '_' key in the user namespace to + # normal after each doctest, so that unittests behave normally + _ip.user_ns.protect_underscore = False # XXX - fperez: I am not sure if this is truly a bug in nose 0.11, but # it does look like one to me: its tearDown method tries to run