From b882a145abb99a083e2f132964d3128583987f2d 2017-01-29 21:34:54 From: Matthias Bussonnier Date: 2017-01-29 21:34:54 Subject: [PATCH] Fix deactivation of embedded instance. While deactivation of full instances make some sens, the most common behavior user expect when creating an instance and using ``kill_embedded`` is deactivation of the current call location. Implement the expected on by default, add options to get previous behavior, and add flag for direct exit and no-confirm Fix #9761 --- diff --git a/IPython/terminal/embed.py b/IPython/terminal/embed.py index a194f0e..3a0df87 100644 --- a/IPython/terminal/embed.py +++ b/IPython/terminal/embed.py @@ -10,6 +10,7 @@ import sys import warnings from IPython.core import ultratb, compilerop +from IPython.core import magic_arguments from IPython.core.magic import Magics, magics_class, line_magic from IPython.core.interactiveshell import DummyMod, InteractiveShell from IPython.terminal.interactiveshell import TerminalInteractiveShell @@ -25,22 +26,68 @@ class KillEmbeded(Exception):pass class EmbeddedMagics(Magics): @line_magic + @magic_arguments.magic_arguments() + @magic_arguments.argument('-i', '--instance', action='store_true', + help='Kill instance instead of call location') + @magic_arguments.argument('-x', '--exit', action='store_true', + help='Also exit the current session') + @magic_arguments.argument('-y', '--yes', action='store_true', + help='Do not ask confirmation') def kill_embedded(self, parameter_s=''): - """%kill_embedded : deactivate for good the current embedded IPython. + """%kill_embedded : deactivate for good the current embedded IPython This function (after asking for confirmation) sets an internal flag so - that an embedded IPython will never activate again. This is useful to - permanently disable a shell that is being called inside a loop: once - you've figured out what you needed from it, you may then kill it and - the program will then continue to run without the interactive shell - interfering again. + that an embedded IPython will never activate again for the given call + location. This is useful to permanently disable a shell that is being + called inside a loop: once you've figured out what you needed from it, + you may then kill it and the program will then continue to run without + the interactive shell interfering again. + + + Kill Instance Option + -------------------- + + If for some reasons you need to kill the location where the instance is + created and not called, for example if you create a single instance in + one place and debug in many locations, you can use the ``--instance`` + option to kill this specific instance. Like for the ``call location`` + killing an "instance" should work even if it is recreated within a + loop. + + .. note:: + + This was the default behavior before IPython 5.2 + """ - kill = ask_yes_no("Are you sure you want to kill this embedded instance? [y/N] ",'n') - if kill: - self.shell.embedded_active = False - print ("This embedded IPython will not reactivate anymore " - "once you exit.") + args = magic_arguments.parse_argstring(self.kill_embedded, parameter_s) + print(args) + if args.instance: + # let no ask + if not args.yes: + kill = ask_yes_no( + "Are you sure you want to kill this embedded instance? [y/N] ", 'n') + else: + kill = True + if kill: + self.shell._disable_init_location() + print("This embedded IPython instance will not reactivate anymore " + "once you exit.") + else: + if not args.yes: + kill = ask_yes_no( + "Are you sure you want to kill this embedded call_location? [y/N] ", 'n') + else: + kill = True + if kill: + self.shell.embedded_active = False + print("This embedded IPython call location will not reactivate anymore " + "once you exit.") + + if args.exit: + # Ask-exit does not really ask, it just set internals flags to exit + # on next loop. + self.shell.ask_exit() @line_magic @@ -77,29 +124,37 @@ class InteractiveShellEmbed(TerminalInteractiveShell): @property def embedded_active(self): - return self._call_location_id not in InteractiveShellEmbed._inactive_locations + return (self._call_location_id not in InteractiveShellEmbed._inactive_locations)\ + and (self._init_location_id not in InteractiveShellEmbed._inactive_locations) + + def _disable_init_location(self): + """Disable the current Instance creation location""" + InteractiveShellEmbed._inactive_locations.add(self._init_location_id) @embedded_active.setter def embedded_active(self, value): - if value : - if self._call_location_id in InteractiveShellEmbed._inactive_locations: - InteractiveShellEmbed._inactive_locations.remove(self._call_location_id) + if value: + InteractiveShellEmbed._inactive_locations.discard( + self._call_location_id) + InteractiveShellEmbed._inactive_locations.discard( + self._init_location_id) else: - InteractiveShellEmbed._inactive_locations.add(self._call_location_id) + InteractiveShellEmbed._inactive_locations.add( + self._call_location_id) def __init__(self, **kw): - - if kw.get('user_global_ns', None) is not None: - raise DeprecationWarning("Key word argument `user_global_ns` has been replaced by `user_module` since IPython 4.0.") + raise DeprecationWarning( + "Key word argument `user_global_ns` has been replaced by `user_module` since IPython 4.0.") - self._call_location_id = kw.pop('_call_location_id', None) + clid = kw.pop('_init_location_id', None) + if not clid: + frame = sys._getframe(1) + clid = '%s:%s' % (frame.f_code.co_filename, frame.f_lineno) + self._init_location_id = clid super(InteractiveShellEmbed,self).__init__(**kw) - if not self._call_location_id: - frame = sys._getframe(1) - self._call_location_id = '%s:%s' % (frame.f_code.co_filename, frame.f_lineno) # don't use the ipython crash handler so that user exceptions aren't # trapped sys.excepthook = ultratb.FormattedTB(color_scheme=self.colors, @@ -107,6 +162,9 @@ class InteractiveShellEmbed(TerminalInteractiveShell): call_pdb=self.pdb) def init_sys_modules(self): + """ + Explicitly overwrite :any:`IPython.core.interactiveshell` to do nothing. + """ pass def init_magics(self): @@ -114,7 +172,7 @@ class InteractiveShellEmbed(TerminalInteractiveShell): self.register_magics(EmbeddedMagics) def __call__(self, header='', local_ns=None, module=None, dummy=None, - stack_depth=1, global_ns=None, compile_flags=None): + stack_depth=1, global_ns=None, compile_flags=None, **kw): """Activate the interactive interpreter. __call__(self,header='',local_ns=None,module=None,dummy=None) -> Start @@ -131,7 +189,16 @@ class InteractiveShellEmbed(TerminalInteractiveShell): can still have a specific call work by making it as IPShell(dummy=False). """ + # we are called, set the underlying interactiveshell not to exit. + self.keep_running = True + # If the user has turned it off, go away + clid = kw.pop('_call_location_id', None) + if not clid: + frame = sys._getframe(1) + clid = '%s:%s' % (frame.f_code.co_filename, frame.f_lineno) + self._call_location_id = clid + if not self.embedded_active: return @@ -310,8 +377,10 @@ def embed(**kwargs): cls = type(saved_shell_instance) cls.clear_instance() frame = sys._getframe(1) - shell = InteractiveShellEmbed.instance(_call_location_id='%s:%s' % (frame.f_code.co_filename, frame.f_lineno), **kwargs) - shell(header=header, stack_depth=2, compile_flags=compile_flags) + shell = InteractiveShellEmbed.instance(_init_location_id='%s:%s' % ( + frame.f_code.co_filename, frame.f_lineno), **kwargs) + shell(header=header, stack_depth=2, compile_flags=compile_flags, + _call_location_id='%s:%s' % (frame.f_code.co_filename, frame.f_lineno)) InteractiveShellEmbed.clear_instance() #restore previous instance if saved_shell_instance is not None: diff --git a/docs/source/whatsnew/version5.rst b/docs/source/whatsnew/version5.rst index f5cf9bf..ff2f6a7 100644 --- a/docs/source/whatsnew/version5.rst +++ b/docs/source/whatsnew/version5.rst @@ -31,6 +31,23 @@ Remarkable changes and fixes: ``limit_to_all`` option of the completer. :ghpull:`10198` +Changes of behavior to :any:`InteractiveShellEmbed`. + +:any:`InteractiveShellEmbed` interactive behavior have changed a bit in between +5.1 and 5.2. By default ``%kill_embedded`` magic will prevent further invocation +of the current ``call location`` instead of preventing further invocation of +the current instance creation location. For most use case this will not change +much for you, though previous behavior was confusing and less consistent with +previous IPython versions. + +You can now deactivate instances by using ``%kill_embedded --instance`` flag, +(or ``-i`` in short). The ``%kill_embedded`` magic also gained a +``--yes``/``-y`` option which skip confirmation step, and ``-x``/``--exit`` +which also exit the current embedded call without asking for confirmation. + +See :ghpull:`10207`. + + IPython 5.1 ===========