Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Namespaces for embedding #1140

Merged
merged 7 commits into from

3 participants

@takluyver
Owner

This should fix the embedding problem in #1136.

One additional complexity: if you specify only one of user_ns or user_module when instantiating the embedded shell, they are hooked up so that you have a single namespace (i.e. ip.user_ns is ip.user_global_ns). So, the logic at activation time to auto-detect the module and local namespace will only kick in if neither were specified at instantiation.

You should still be able to pass local_ns or module at activation to override them separately.

@g2p

Thank you. Both my simple example and the scrapy shell are working well.

@minrk minrk commented on the diff
IPython/frontend/terminal/embed.py
@@ -170,9 +171,17 @@ def mainloop(self, local_ns=None, module=None, stack_depth=0,
IPython itself (via %run), but some funny things will happen (a few
globals get overwritten). In the future this will be cleaned up, as
there is no fundamental reason why it can't work perfectly."""
+
+ if (global_ns is not None) and (module is None):
+ class DummyMod(object):
+ """A dummy module object for embedded IPython."""
+ pass
+ warnings.warn("global_ns is deprecated, use module instead.", DeprecationWarning)
+ module = DummyMod()
+ module.__dict__ = global_ns
@minrk Owner
minrk added a note

Thanks, I think this is the main thing we need to preserve.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@minrk
Owner

Terrific, thanks for taking care of it. Do we / can we have simple tests for some of the basic cases?

@takluyver
Owner

I'm not quite sure how to automate tests for embedding, but I can tidy up a couple of test scripts and add them in docs/examples to run manually if that's sufficient.

@takluyver
Owner

Added some manual test scripts.

@minrk
Owner

Nice. Manual tests look good, and I checked embedded shells in currently released sympy and django, both of which appear to work happily (at least they don't raise) with your changes. If you think this is good to go, go ahead and merge.

@takluyver takluyver merged commit feb3b24 into ipython:master
@takluyver
Owner

Thanks, merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
3  IPython/core/interactiveshell.py
@@ -881,6 +881,7 @@ def debugger(self,force=False):
#-------------------------------------------------------------------------
# Things related to IPython's various namespaces
#-------------------------------------------------------------------------
+ default_user_namespaces = True
def init_create_namespaces(self, user_module=None, user_ns=None):
# Create the namespace where the user will operate. user_ns is
@@ -919,6 +920,8 @@ def init_create_namespaces(self, user_module=None, user_ns=None):
# These routines return a properly built module and dict as needed by
# the rest of the code, and can also be used by extension writers to
# generate properly initialized namespaces.
+ if (user_ns is not None) or (user_module is not None):
+ self.default_user_namespaces = False
self.user_module, self.user_ns = self.prepare_user_module(user_module, user_ns)
# A record of hidden variables we have added to the user namespace, so
View
64 IPython/frontend/terminal/embed.py
@@ -30,6 +30,7 @@
from contextlib import nested
except:
from IPython.utils.nested_context import nested
+import warnings
from IPython.core import ultratb
from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell
@@ -74,7 +75,11 @@ class InteractiveShellEmbed(TerminalInteractiveShell):
def __init__(self, config=None, ipython_dir=None, user_ns=None,
user_module=None, custom_exceptions=((),None),
usage=None, banner1=None, banner2=None,
- display_banner=None, exit_msg=u''):
+ display_banner=None, exit_msg=u'', user_global_ns=None):
+
+ if user_global_ns is not None:
+ warnings.warn("user_global_ns has been replaced by user_module. The\
+ parameter will be ignored.", DeprecationWarning)
super(InteractiveShellEmbed,self).__init__(
config=config, ipython_dir=ipython_dir, user_ns=user_ns,
@@ -96,24 +101,21 @@ def init_sys_modules(self):
pass
def __call__(self, header='', local_ns=None, module=None, dummy=None,
- stack_depth=1):
+ stack_depth=1, global_ns=None):
"""Activate the interactive interpreter.
- __call__(self,header='',local_ns=None,global_ns,dummy=None) -> Start
+ __call__(self,header='',local_ns=None,module=None,dummy=None) -> Start
the interpreter shell with the given local and global namespaces, and
optionally print a header string at startup.
The shell can be globally activated/deactivated using the
- set/get_dummy_mode methods. This allows you to turn off a shell used
+ dummy_mode attribute. This allows you to turn off a shell used
for debugging globally.
However, *each* time you call the shell you can override the current
state of dummy_mode with the optional keyword parameter 'dummy'. For
- example, if you set dummy mode on with IPShell.set_dummy_mode(1), you
- can still have a specific call work by making it as IPShell(dummy=0).
-
- The optional keyword parameter dummy controls whether the call
- actually does anything.
+ example, if you set dummy mode on with IPShell.dummy_mode = True, you
+ can still have a specific call work by making it as IPShell(dummy=False).
"""
# If the user has turned it off, go away
@@ -140,7 +142,7 @@ def __call__(self, header='', local_ns=None, module=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, module, stack_depth=stack_depth)
+ self.mainloop(local_ns, module, stack_depth=stack_depth, global_ns=global_ns)
self.banner2 = self.old_banner2
@@ -148,20 +150,20 @@ def __call__(self, header='', local_ns=None, module=None, dummy=None,
print self.exit_msg
def mainloop(self, local_ns=None, module=None, stack_depth=0,
- display_banner=None):
+ display_banner=None, global_ns=None):
"""Embeds IPython into a running python program.
Input:
- header: An optional header message can be specified.
- - local_ns, global_ns: working namespaces. If given as None, the
- IPython-initialized one is updated with __main__.__dict__, so that
- program variables become visible but user-specific configuration
- remains possible.
+ - local_ns, module: working local namespace (a dict) and module (a
+ module or similar object). If given as None, they are automatically
+ taken from the scope where the shell was called, so that
+ program variables become visible.
- stack_depth: specifies how many levels in the stack to go to
- looking for namespaces (when local_ns and global_ns are None). This
+ looking for namespaces (when local_ns or module is None). This
allows an intermediate caller to make sure that this function gets
the namespace from the intended level in the stack. By default (0)
it will get its locals and globals from the immediate caller.
@@ -170,9 +172,17 @@ def mainloop(self, local_ns=None, module=None, stack_depth=0,
IPython itself (via %run), but some funny things will happen (a few
globals get overwritten). In the future this will be cleaned up, as
there is no fundamental reason why it can't work perfectly."""
+
+ if (global_ns is not None) and (module is None):
+ class DummyMod(object):
+ """A dummy module object for embedded IPython."""
+ pass
+ warnings.warn("global_ns is deprecated, use module instead.", DeprecationWarning)
+ module = DummyMod()
+ module.__dict__ = global_ns
@minrk Owner
minrk added a note

Thanks, I think this is the main thing we need to preserve.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
# Get locals and globals from caller
- if local_ns is None or module is None:
+ if (local_ns is None or module is None) and self.default_user_namespaces:
call_frame = sys._getframe(stack_depth).f_back
if local_ns is None:
@@ -189,21 +199,26 @@ def mainloop(self, local_ns=None, module=None, stack_depth=0,
# Update namespaces and fire up interpreter
# The global one is easy, we can just throw it in
- self.user_module = module
+ if module is not None:
+ 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 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()
+ if local_ns is not None:
+ 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)
# 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__)
+ # And now, since it wasn't called in the previous version, I'm
+ # commenting out these lines so they can't be called with my new changes
+ # --TK, 2011-12-10
+ #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
# actually completes using the frame's locals/globals
@@ -213,8 +228,9 @@ def mainloop(self, local_ns=None, module=None, stack_depth=0,
self.interact(display_banner=display_banner)
# now, purge out the local namespace of IPython's hidden variables.
- for name in self.user_ns_hidden:
- local_ns.pop(name, None)
+ if local_ns is not None:
+ 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
View
10 docs/examples/test_embed/embed1.py
@@ -0,0 +1,10 @@
+"""This tests standard embedding, automatically detecting the module and
+local namespaces."""
+
+f = set([1,2,3,4,5])
+
+def bar(foo):
+ import IPython
+ IPython.embed(banner1='check f in globals, foo in locals')
+
+bar(f)
View
5 docs/examples/test_embed/embed2.py
@@ -0,0 +1,5 @@
+"""This tests passing a dict for the user_ns at shell instantiation."""
+from IPython import embed
+
+user_ns = dict(cookie='monster')
+embed(user_ns=user_ns, banner1="check 'cookie' present, locals and globals equivalent")
View
7 docs/examples/test_embed/embed3.py
@@ -0,0 +1,7 @@
+"""This tests passing local_ns and global_ns (for backwards compatibility only)
+at activation of an embedded shell."""
+from IPython.frontend.terminal.embed import InteractiveShellEmbed
+
+user_ns = dict(cookie='monster')
+ISE = InteractiveShellEmbed(banner1='check cookie in locals, and globals empty')
+ISE(local_ns=user_ns, global_ns={})
View
10 docs/source/whatsnew/development.txt
@@ -145,10 +145,12 @@ 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
+* For embedding a shell, note that the parameters ``user_global_ns`` and ``global_ns``
+ have been deprectated in favour of ``user_module`` and ``module`` respsectively.
+ The new parameters expect a module-like object, rather than a namespace dict.
+ The old parameters remain for backwards compatibility, although ``user_global_ns``
+ is now ignored. 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.
+ works as before.
.. * use bullet list
Something went wrong with that request. Please try again.