Skip to content
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

Usermod #648

Merged
merged 13 commits into from
Nov 27, 2011
Merged
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
8 changes: 4 additions & 4 deletions IPython/core/displayhook.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,16 +265,16 @@ def update_user_ns(self, result):
self.___ = self.__
self.__ = self._
self._ = result
self.shell.user_ns.update({'_':self._,
'__':self.__,
'___':self.___})
self.shell.push({'_':self._,
'__':self.__,
'___':self.___}, interactive=False)

# hackish access to top-level namespace to create _1,_2... dynamically
to_main = {}
if self.do_full_cache:
new_result = '_'+`self.prompt_count`
to_main[new_result] = result
self.shell.user_ns.update(to_main)
self.shell.push(to_main, interactive=False)
self.shell.user_ns['_oh'][self.prompt_count] = result

def log_output(self, format_dict):
Expand Down
3 changes: 2 additions & 1 deletion IPython/core/history.py
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,8 @@ def store_inputs(self, line_num, source, source_raw=None):
'_ii': self._ii,
'_iii': self._iii,
new_i : self._i00 }
self.shell.user_ns.update(to_main)

self.shell.push(to_main, interactive=False)

def store_output(self, line_num):
"""If database output logging is enabled, this saves all the
Expand Down
209 changes: 89 additions & 120 deletions IPython/core/interactiveshell.py

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions IPython/core/magic.py
Original file line number Diff line number Diff line change
Expand Up @@ -748,11 +748,10 @@ def magic_who_ls(self, parameter_s=''):
"""

user_ns = self.shell.user_ns
internal_ns = self.shell.internal_ns
user_ns_hidden = self.shell.user_ns_hidden
out = [ i for i in user_ns
if not i.startswith('_') \
and not (i in internal_ns or i in user_ns_hidden) ]
and not i in user_ns_hidden ]

typelist = parameter_s.split()
if typelist:
Expand Down
2 changes: 1 addition & 1 deletion IPython/core/prefilter.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def is_shadowed(identifier, ip):
than ifun, because it can not contain a '.' character."""
# This is much safer than calling ofind, which can change state
return (identifier in ip.user_ns \
or identifier in ip.internal_ns \
or identifier in ip.user_global_ns \
or identifier in ip.ns_table['builtin'])


Expand Down
33 changes: 33 additions & 0 deletions IPython/core/tests/test_interactiveshell.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import tempfile
import unittest
from os.path import join
import sys
from StringIO import StringIO

from IPython.testing import decorators as dec
Expand Down Expand Up @@ -128,6 +129,7 @@ def __repr__(self):
f = IPython.core.formatters.PlainTextFormatter()
f([Spam(),Spam()])


def test_future_flags(self):
"""Check that future flags are used for parsing code (gh-777)"""
ip = get_ipython()
Expand All @@ -151,6 +153,37 @@ def test_future_unicode(self):
finally:
# Reset compiler flags so we don't mess up other tests.
ip.compile.reset_compiler_flags()

def test_can_pickle(self):
"Can we pickle objects defined interactively (GH-29)"
ip = get_ipython()
ip.reset()
ip.run_cell(("class Mylist(list):\n"
" def __init__(self,x=[]):\n"
" list.__init__(self,x)"))
ip.run_cell("w=Mylist([1,2,3])")

from cPickle import dumps

# We need to swap in our main module - this is only necessary
# inside the test framework, because IPython puts the interactive module
# in place (but the test framework undoes this).
_main = sys.modules['__main__']
sys.modules['__main__'] = ip.user_module
try:
res = dumps(ip.user_ns["w"])
finally:
sys.modules['__main__'] = _main
self.assertTrue(isinstance(res, bytes))

def test_global_ns(self):
"Code in functions must be able to access variables outside them."
ip = get_ipython()
ip.run_cell("a = 10")
ip.run_cell(("def f(x):\n"
" return x + a"))
ip.run_cell("b = f(12)")
self.assertEqual(ip.user_ns["b"], 22)

def test_bad_custom_tb(self):
"""Check that InteractiveShell is protected from bad custom exception handlers"""
Expand Down
16 changes: 3 additions & 13 deletions IPython/core/tests/test_iplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,16 @@
# Test functions
#-----------------------------------------------------------------------------

@dec.parametric
def test_reset():
"""reset must clear most namespaces."""
# The number of variables in the private user_ns_hidden is not zero, but it
# should be constant regardless of what we do
nvars_config_ns = len(ip.user_ns_hidden)

# Check that reset runs without error
ip.reset()

# Once we've reset it (to clear of any junk that might have been there from
# other tests, we can count how many variables are in the user's namespace
nvars_user_ns = len(ip.user_ns)
nvars_hidden = len(ip.user_ns_hidden)

# Now add a few variables to user_ns, and check that reset clears them
ip.user_ns['x'] = 1
Expand All @@ -49,15 +46,8 @@ def test_reset():

# Finally, check that all namespaces have only as many variables as we
# expect to find in them:
for ns in ip.ns_refs_table:
if ns is ip.user_ns:
nvars_expected = nvars_user_ns
elif ns is ip.user_ns_hidden:
nvars_expected = nvars_config_ns
else:
nvars_expected = 0

yield nt.assert_equals(len(ns), nvars_expected)
nt.assert_equals(len(ip.user_ns), nvars_user_ns)
nt.assert_equals(len(ip.user_ns_hidden), nvars_hidden)


# Tests for reporting of exceptions in various modes, handling of SystemExit,
Expand Down
12 changes: 12 additions & 0 deletions IPython/core/tests/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,3 +228,15 @@ def test_tclass(self):
else:
err = None
tt.ipexec_validate(self.fname, out, err)

def test_run_i_after_reset(self):
"""Check that %run -i still works after %reset (gh-693)"""
src = "yy = zz\n"
self.mktmp(src)
_ip.run_cell("zz = 23")
_ip.magic('run -i %s' % self.fname)
tt.assert_equals(_ip.user_ns['yy'], 23)
_ip.magic('reset -f')
_ip.run_cell("zz = 23")
_ip.magic('run -i %s' % self.fname)
tt.assert_equals(_ip.user_ns['yy'], 23)
58 changes: 32 additions & 26 deletions IPython/frontend/terminal/embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,13 @@ class InteractiveShellEmbed(TerminalInteractiveShell):
display_banner = CBool(True)

def __init__(self, config=None, ipython_dir=None, user_ns=None,
user_global_ns=None, custom_exceptions=((),None),
user_module=None, custom_exceptions=((),None),
usage=None, banner1=None, banner2=None,
display_banner=None, exit_msg=u''):

super(InteractiveShellEmbed,self).__init__(
config=config, ipython_dir=ipython_dir, user_ns=user_ns,
user_global_ns=user_global_ns, custom_exceptions=custom_exceptions,
user_module=user_module, custom_exceptions=custom_exceptions,
usage=usage, banner1=banner1, banner2=banner2,
display_banner=display_banner
)
Expand All @@ -95,7 +95,7 @@ def __init__(self, config=None, ipython_dir=None, user_ns=None,
def init_sys_modules(self):
pass

def __call__(self, header='', local_ns=None, global_ns=None, dummy=None,
def __call__(self, header='', local_ns=None, module=None, dummy=None,
stack_depth=1):
"""Activate the interactive interpreter.

Expand Down Expand Up @@ -140,14 +140,14 @@ def __call__(self, header='', local_ns=None, global_ns=None, dummy=None,

# Call the embedding code with a stack depth of 1 so it can skip over
# our call and get the original caller's namespaces.
self.mainloop(local_ns, global_ns, stack_depth=stack_depth)
self.mainloop(local_ns, module, stack_depth=stack_depth)

self.banner2 = self.old_banner2

if self.exit_msg is not None:
print self.exit_msg

def mainloop(self, local_ns=None, global_ns=None, stack_depth=0,
def mainloop(self, local_ns=None, module=None, stack_depth=0,
display_banner=None):
"""Embeds IPython into a running python program.

Expand All @@ -172,32 +172,37 @@ def mainloop(self, local_ns=None, global_ns=None, stack_depth=0,
there is no fundamental reason why it can't work perfectly."""

# Get locals and globals from caller
if local_ns is None or global_ns is None:
if local_ns is None or module is None:
call_frame = sys._getframe(stack_depth).f_back

if local_ns is None:
local_ns = call_frame.f_locals
if global_ns is None:
if module is None:
global_ns = call_frame.f_globals

module = sys.modules[global_ns['__name__']]

# Save original namespace and module so we can restore them after
# embedding; otherwise the shell doesn't shut down correctly.
orig_user_module = self.user_module
orig_user_ns = self.user_ns

# Update namespaces and fire up interpreter

# The global one is easy, we can just throw it in
self.user_global_ns = global_ns
self.user_module = module

# but the user/local one is tricky: ipython needs it to store internal
# data, but we also need the locals. We'll copy locals in the user
# one, but will track what got copied so we can delete them at exit.
# This is so that a later embedded call doesn't see locals from a
# previous call (which most likely existed in a separate scope).
local_varnames = local_ns.keys()
self.user_ns.update(local_ns)
#self.user_ns['local_ns'] = local_ns # dbg
# But the user/local one is tricky: ipython needs it to store internal
# data, but we also need the locals. We'll throw our hidden variables
# like _ih and get_ipython() into the local namespace, but delete them
# later.
self.user_ns = local_ns
self.init_user_ns()

# Patch for global embedding to make sure that things don't overwrite
# user globals accidentally. Thanks to Richard <rxe@renre-europe.com>
# FIXME. Test this a bit more carefully (the if.. is new)
if local_ns is None and global_ns is None:
# N.B. This can't now ever be called. Not sure what it was for.
if local_ns is None and module is None:
self.user_global_ns.update(__main__.__dict__)

# make sure the tab-completer has the correct frame information, so it
Expand All @@ -206,13 +211,14 @@ def mainloop(self, local_ns=None, global_ns=None, stack_depth=0,

with nested(self.builtin_trap, self.display_trap):
self.interact(display_banner=display_banner)

# now, purge out the user namespace from anything we might have added
# from the caller's local namespace
delvar = self.user_ns.pop
for var in local_varnames:
delvar(var,None)


# now, purge out the local namespace of IPython's hidden variables.
for name in self.user_ns_hidden:
local_ns.pop(name, None)

# Restore original namespace so shell can shut down when we exit.
self.user_module = orig_user_module
self.user_ns = orig_user_ns

_embedded_shell = None

Expand Down
4 changes: 2 additions & 2 deletions IPython/frontend/terminal/interactiveshell.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,13 +172,13 @@ class TerminalInteractiveShell(InteractiveShell):
)

def __init__(self, config=None, ipython_dir=None, profile_dir=None, user_ns=None,
user_global_ns=None, custom_exceptions=((),None),
user_module=None, custom_exceptions=((),None),
usage=None, banner1=None, banner2=None,
display_banner=None):

super(TerminalInteractiveShell, self).__init__(
config=config, profile_dir=profile_dir, user_ns=user_ns,
user_global_ns=user_global_ns, custom_exceptions=custom_exceptions
user_module=user_module, custom_exceptions=custom_exceptions
)
# use os.system instead of utils.process.system by default,
# because piped system doesn't make sense in the Terminal:
Expand Down
4 changes: 1 addition & 3 deletions IPython/testing/globalipapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,6 @@ def start_ipython():
# Create and initialize our test-friendly IPython instance.
shell = TerminalInteractiveShell.instance(config=config,
user_ns=ipnsdict(),
user_global_ns={}
)

# A few more tweaks needed for playing nicely with doctests...
Expand All @@ -206,8 +205,7 @@ def start_ipython():
# can capture subcommands and print them to Python's stdout, otherwise the
# doctest machinery would miss them.
shell.system = py3compat.MethodType(xsys, shell)



shell._showtraceback = py3compat.MethodType(_showtraceback, shell)

# IPython is ready, now clean up some global state...
Expand Down
4 changes: 4 additions & 0 deletions IPython/testing/plugin/ipdoctest.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,8 @@ def setUp(self):
# for IPython examples *only*, we swap the globals with the ipython
# namespace, after updating it with the globals (which doctest
# fills with the necessary info from the module being tested).
self.user_ns_orig = {}
self.user_ns_orig.update(_ip.user_ns)
_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)
Expand All @@ -286,6 +288,8 @@ def tearDown(self):
# teardown doesn't destroy the ipython namespace
if isinstance(self._dt_test.examples[0],IPExample):
self._dt_test.globs = self._dt_test_globs_ori
_ip.user_ns.clear()
_ip.user_ns.update(self.user_ns_orig)
# 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
Expand Down
10 changes: 10 additions & 0 deletions docs/source/whatsnew/development.txt
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ Major Bugs fixed
* IPython no longer crashes when started on recent versions of Python 3 in
Windows (:ghissue:`737`).

* Instances of classes defined interactively can now be pickled (:ghissue:`29`;
:ghpull:`648`). Note that pickling saves a reference to the class definition,
so unpickling the instances will only work where the class has been defined.

.. * use bullet list

Backwards incompatible changes
Expand Down Expand Up @@ -132,4 +136,10 @@ Backwards incompatible changes
The full path will still work, and is necessary for using custom launchers not in
IPython's launcher module.

* For embedding a shell, note that the parameter ``user_global_ns`` has been
replaced by ``user_module``, and expects a module-like object, rather than
a namespace dict. The ``user_ns`` parameter works the same way as before, and
calling :func:`~IPython.frontend.terminal.embed.embed` with no arguments still
works the same way.

.. * use bullet list