397 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			397 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # encoding: utf-8
 | |
| """
 | |
| An embedded yap_ipython shell.
 | |
| """
 | |
| # Copyright (c) yap_ipython Development Team.
 | |
| # Distributed under the terms of the Modified BSD License.
 | |
| 
 | |
| 
 | |
| import sys
 | |
| import warnings
 | |
| 
 | |
| from yap_ipython.core import ultratb, compilerop
 | |
| from yap_ipython.core import magic_arguments
 | |
| from yap_ipython.core.magic import Magics, magics_class, line_magic
 | |
| from yap_ipython.core.interactiveshell import DummyMod, InteractiveShell
 | |
| from yap_ipython.terminal.interactiveshell import TerminalInteractiveShell
 | |
| from yap_ipython.terminal.ipapp import load_default_config
 | |
| 
 | |
| from traitlets import Bool, CBool, Unicode
 | |
| from yap_ipython.utils.io import ask_yes_no
 | |
| 
 | |
| class KillEmbedded(Exception):pass
 | |
| 
 | |
| # kept for backward compatibility as yap_ipython 6 was released with
 | |
| # the typo. See https://github.com/ipython/ipython/pull/10706
 | |
| KillEmbeded = KillEmbedded
 | |
| 
 | |
| # This is an additional magic that is exposed in embedded shells.
 | |
| @magics_class
 | |
| 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 yap_ipython
 | |
| 
 | |
|         This function (after asking for confirmation) sets an internal flag so
 | |
|         that an embedded yap_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 yap_ipython 5.2
 | |
| 
 | |
|         """
 | |
| 
 | |
|         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 yap_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 yap_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
 | |
|     def exit_raise(self, parameter_s=''):
 | |
|         """%exit_raise Make the current embedded kernel exit and raise and exception.
 | |
| 
 | |
|         This function sets an internal flag so that an embedded yap_ipython will
 | |
|         raise a `yap_ipython.terminal.embed.KillEmbedded` Exception on exit, and then exit the current I. This is
 | |
|         useful to permanently exit a loop that create yap_ipython embed instance.
 | |
|         """
 | |
| 
 | |
|         self.shell.should_raise = True
 | |
|         self.shell.ask_exit()
 | |
| 
 | |
| 
 | |
| 
 | |
| class InteractiveShellEmbed(TerminalInteractiveShell):
 | |
| 
 | |
|     dummy_mode = Bool(False)
 | |
|     exit_msg = Unicode('')
 | |
|     embedded = CBool(True)
 | |
|     should_raise = CBool(False)
 | |
|     # Like the base class display_banner is not configurable, but here it
 | |
|     # is True by default.
 | |
|     display_banner = CBool(True)
 | |
|     exit_msg = Unicode()
 | |
| 
 | |
|     # When embedding, by default we don't change the terminal title
 | |
|     term_title = Bool(False,
 | |
|         help="Automatically set the terminal title"
 | |
|     ).tag(config=True)
 | |
| 
 | |
|     _inactive_locations = set()
 | |
| 
 | |
|     @property
 | |
|     def embedded_active(self):
 | |
|         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:
 | |
|             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)
 | |
| 
 | |
|     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 yap_ipython 4.0.")
 | |
| 
 | |
|         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)
 | |
| 
 | |
|         # don't use the ipython crash handler so that user exceptions aren't
 | |
|         # trapped
 | |
|         sys.excepthook = ultratb.FormattedTB(color_scheme=self.colors,
 | |
|                                              mode=self.xmode,
 | |
|                                              call_pdb=self.pdb)
 | |
| 
 | |
|     def init_sys_modules(self):
 | |
|         """
 | |
|         Explicitly overwrite :mod:`yap_ipython.core.interactiveshell` to do nothing.
 | |
|         """
 | |
|         pass
 | |
| 
 | |
|     def init_magics(self):
 | |
|         super(InteractiveShellEmbed, self).init_magics()
 | |
|         self.register_magics(EmbeddedMagics)
 | |
| 
 | |
|     def __call__(self, header='', local_ns=None, module=None, dummy=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
 | |
|         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
 | |
|         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.dummy_mode = True, you
 | |
|         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
 | |
| 
 | |
|         # Normal exits from interactive mode set this flag, so the shell can't
 | |
|         # re-enter (it checks this variable at the start of interactive mode).
 | |
|         self.exit_now = False
 | |
| 
 | |
|         # Allow the dummy parameter to override the global __dummy_mode
 | |
|         if dummy or (dummy != 0 and self.dummy_mode):
 | |
|             return
 | |
| 
 | |
|         # self.banner is auto computed
 | |
|         if header:
 | |
|             self.old_banner2 = self.banner2
 | |
|             self.banner2 = self.banner2 + '\n' + header + '\n'
 | |
|         else:
 | |
|             self.old_banner2 = ''
 | |
| 
 | |
|         if self.display_banner:
 | |
|             self.show_banner()
 | |
| 
 | |
|         # 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,
 | |
|                       global_ns=global_ns, compile_flags=compile_flags)
 | |
| 
 | |
|         self.banner2 = self.old_banner2
 | |
| 
 | |
|         if self.exit_msg is not None:
 | |
|             print(self.exit_msg)
 | |
| 
 | |
|         if self.should_raise:
 | |
|             raise KillEmbedded('Embedded yap_ipython raising error, as user requested.')
 | |
| 
 | |
| 
 | |
|     def mainloop(self, local_ns=None, module=None, stack_depth=0,
 | |
|                  display_banner=None, global_ns=None, compile_flags=None):
 | |
|         """Embeds yap_ipython into a running python program.
 | |
| 
 | |
|         Parameters
 | |
|         ----------
 | |
| 
 | |
|         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 : int
 | |
|           How many levels in the stack to go to 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.
 | |
| 
 | |
|         compile_flags
 | |
|           A bit field identifying the __future__ features
 | |
|           that are enabled, as passed to the builtin :func:`compile` function.
 | |
|           If given as None, they are automatically taken from the scope where
 | |
|           the shell was called.
 | |
| 
 | |
|         """
 | |
|         
 | |
|         if (global_ns is not None) and (module is None):
 | |
|             raise DeprecationWarning("'global_ns' keyword argument is deprecated, and has been removed in yap_ipython 5.0 use `module` keyword argument instead.")
 | |
| 
 | |
|         if (display_banner is not None):
 | |
|             warnings.warn("The display_banner parameter is deprecated since yap_ipython 4.0", DeprecationWarning)
 | |
| 
 | |
|         # Get locals and globals from caller
 | |
|         if ((local_ns is None or module is None or compile_flags is None)
 | |
|             and self.default_user_namespaces):
 | |
|             call_frame = sys._getframe(stack_depth).f_back
 | |
| 
 | |
|             if local_ns is None:
 | |
|                 local_ns = call_frame.f_locals
 | |
|             if module is None:
 | |
|                 global_ns = call_frame.f_globals
 | |
|                 try:
 | |
|                     module = sys.modules[global_ns['__name__']]
 | |
|                 except KeyError:
 | |
|                     warnings.warn("Failed to get module %s" % \
 | |
|                         global_ns.get('__name__', 'unknown module')
 | |
|                     )
 | |
|                     module = DummyMod()
 | |
|                     module.__dict__ = global_ns
 | |
|             if compile_flags is None:
 | |
|                 compile_flags = (call_frame.f_code.co_flags &
 | |
|                                  compilerop.PyCF_MASK)
 | |
|         
 | |
|         # 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
 | |
|         orig_compile_flags = self.compile.flags
 | |
|         
 | |
|         # Update namespaces and fire up interpreter
 | |
|         
 | |
|         # The global one is easy, we can just throw it in
 | |
|         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.
 | |
|         if local_ns is not None:
 | |
|             reentrant_local_ns = {k: v for (k, v) in local_ns.items() if k not in self.user_ns_hidden.keys()}
 | |
|             self.user_ns = reentrant_local_ns
 | |
|             self.init_user_ns()
 | |
| 
 | |
|         # Compiler flags
 | |
|         if compile_flags is not None:
 | |
|             self.compile.flags = compile_flags
 | |
| 
 | |
|         # make sure the tab-completer has the correct frame information, so it
 | |
|         # actually completes using the frame's locals/globals
 | |
|         self.set_completer_frame()
 | |
| 
 | |
|         with self.builtin_trap, self.display_trap:
 | |
|             self.interact()
 | |
|         
 | |
|         # now, purge out the local namespace of yap_ipython's hidden variables.
 | |
|         if local_ns is not None:
 | |
|             local_ns.update({k: v for (k, v) in self.user_ns.items() if k not in self.user_ns_hidden.keys()})
 | |
| 
 | |
|         
 | |
|         # Restore original namespace so shell can shut down when we exit.
 | |
|         self.user_module = orig_user_module
 | |
|         self.user_ns = orig_user_ns
 | |
|         self.compile.flags = orig_compile_flags
 | |
| 
 | |
| 
 | |
| def embed(**kwargs):
 | |
|     """Call this to embed yap_ipython at the current point in your program.
 | |
| 
 | |
|     The first invocation of this will create an :class:`InteractiveShellEmbed`
 | |
|     instance and then call it.  Consecutive calls just call the already
 | |
|     created instance.
 | |
| 
 | |
|     If you don't want the kernel to initialize the namespace
 | |
|     from the scope of the surrounding function,
 | |
|     and/or you want to load full yap_ipython configuration,
 | |
|     you probably want `yap_ipython.start_ipython()` instead.
 | |
| 
 | |
|     Here is a simple example::
 | |
| 
 | |
|         from yap_ipython import embed
 | |
|         a = 10
 | |
|         b = 20
 | |
|         embed(header='First time')
 | |
|         c = 30
 | |
|         d = 40
 | |
|         embed()
 | |
| 
 | |
|     Full customization can be done by passing a :class:`Config` in as the
 | |
|     config argument.
 | |
|     """
 | |
|     config = kwargs.get('config')
 | |
|     header = kwargs.pop('header', u'')
 | |
|     compile_flags = kwargs.pop('compile_flags', None)
 | |
|     if config is None:
 | |
|         config = load_default_config()
 | |
|         config.InteractiveShellEmbed = config.TerminalInteractiveShell
 | |
|         kwargs['config'] = config
 | |
|     #save ps1/ps2 if defined
 | |
|     ps1 = None
 | |
|     ps2 = None
 | |
|     try:
 | |
|         ps1 = sys.ps1
 | |
|         ps2 = sys.ps2
 | |
|     except AttributeError:
 | |
|         pass
 | |
|     #save previous instance
 | |
|     saved_shell_instance = InteractiveShell._instance
 | |
|     if saved_shell_instance is not None:
 | |
|         cls = type(saved_shell_instance)
 | |
|         cls.clear_instance()
 | |
|     frame = sys._getframe(1)
 | |
|     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:
 | |
|         cls = type(saved_shell_instance)
 | |
|         cls.clear_instance()
 | |
|         for subclass in cls._walk_mro():
 | |
|             subclass._instance = saved_shell_instance
 | |
|     if ps1 is not None:
 | |
|         sys.ps1 = ps1
 | |
|         sys.ps2 = ps2
 |