4848from contextlib import (
4949 redirect_stdout ,
5050)
51- from pathlib import (
52- Path ,
53- )
5451from types import (
5552 ModuleType ,
5653)
168165try :
169166 # noinspection PyUnresolvedReferences,PyPackageRequirements
170167 from IPython import ( # type: ignore[import]
171- embed ,
168+ start_ipython ,
172169 )
173170except ImportError : # pragma: no cover
174171 ipython_available = False
@@ -223,9 +220,9 @@ def __init__(
223220 stdin : Optional [TextIO ] = None ,
224221 stdout : Optional [TextIO ] = None ,
225222 * ,
226- persistent_history_file : Path = '' ,
223+ persistent_history_file : str = '' ,
227224 persistent_history_length : int = 1000 ,
228- startup_script : Path = '' ,
225+ startup_script : str = '' ,
229226 silent_startup_script : bool = False ,
230227 use_ipython : bool = False ,
231228 allow_cli_args : bool = True ,
@@ -4059,13 +4056,13 @@ def py_quit():
40594056 # This is to prevent pyscripts from editing it. (e.g. locals().clear()). It also ensures a pyscript's
40604057 # environment won't be filled with data from a previously run pyscript. Only make a shallow copy since
40614058 # it's OK for py_locals to contain objects which are editable in a pyscript.
4062- localvars = dict ( self .py_locals )
4063- localvars [self .py_bridge_name ] = py_bridge
4064- localvars ['quit' ] = py_quit
4065- localvars ['exit' ] = py_quit
4059+ local_vars = self .py_locals . copy ( )
4060+ local_vars [self .py_bridge_name ] = py_bridge
4061+ local_vars ['quit' ] = py_quit
4062+ local_vars ['exit' ] = py_quit
40664063
40674064 if self .self_in_py :
4068- localvars ['self' ] = self
4065+ local_vars ['self' ] = self
40694066
40704067 # Handle case where we were called by run_pyscript
40714068 if pyscript is not None :
@@ -4079,16 +4076,16 @@ def py_quit():
40794076 self .pexcept ("Error reading script file '{}': {}" .format (expanded_filename , ex ))
40804077 return
40814078
4082- localvars ['__name__' ] = '__main__'
4083- localvars ['__file__' ] = expanded_filename
4079+ local_vars ['__name__' ] = '__main__'
4080+ local_vars ['__file__' ] = expanded_filename
40844081
40854082 # Place the script's directory at sys.path[0] just as Python does when executing a script
40864083 saved_sys_path = list (sys .path )
40874084 sys .path .insert (0 , os .path .dirname (os .path .abspath (expanded_filename )))
40884085
40894086 else :
40904087 # This is the default name chosen by InteractiveConsole when no locals are passed in
4091- localvars ['__name__' ] = '__console__'
4088+ local_vars ['__name__' ] = '__console__'
40924089
40934090 if args .command :
40944091 py_code_to_run = args .command
@@ -4100,7 +4097,7 @@ def py_quit():
41004097 py_bridge .cmd_echo = True
41014098
41024099 # Create the Python interpreter
4103- interp = InteractiveConsole (locals = localvars )
4100+ interp = InteractiveConsole (locals = local_vars )
41044101
41054102 # Check if we are running Python code
41064103 if py_code_to_run :
@@ -4197,47 +4194,55 @@ def do_ipy(self, _: argparse.Namespace) -> Optional[bool]:
41974194
41984195 :return: True if running of commands should stop
41994196 """
4197+ # noinspection PyPackageRequirements
4198+ from IPython .terminal .interactiveshell import (
4199+ TerminalInteractiveShell ,
4200+ )
4201+ from IPython .terminal .ipapp import (
4202+ TerminalIPythonApp ,
4203+ )
4204+ from traitlets .config .loader import (
4205+ Config as TraitletsConfig ,
4206+ )
4207+
42004208 from .py_bridge import (
42014209 PyBridge ,
42024210 )
42034211
4204- # noinspection PyUnusedLocal
4205- def load_ipy (cmd2_app : Cmd , py_bridge : PyBridge ):
4206- """
4207- Embed an IPython shell in an environment that is restricted to only the variables in this function
4208-
4209- :param cmd2_app: instance of the cmd2 app
4210- :param py_bridge: a PyBridge
4211- """
4212- # Create a variable pointing to py_bridge and name it using the value of py_bridge_name
4213- exec ("{} = py_bridge" .format (cmd2_app .py_bridge_name ))
4214-
4215- # Add self variable pointing to cmd2_app, if allowed
4216- if cmd2_app .self_in_py :
4217- exec ("self = cmd2_app" )
4218-
4219- # Delete these names from the environment so IPython can't use them
4220- del cmd2_app
4221- del py_bridge
4222-
4223- # Start ipy shell
4224- embed (
4225- banner1 = (
4226- 'Entering an embedded IPython shell. Type quit or <Ctrl>-d to exit.\n '
4227- 'Run Python code from external files with: run filename.py\n '
4228- ),
4229- exit_msg = 'Leaving IPython, back to {}' .format (sys .argv [0 ]),
4230- )
4231-
42324212 if self .in_pyscript ():
42334213 self .perror ("Recursively entering interactive Python shells is not allowed" )
42344214 return
42354215
42364216 try :
42374217 self ._in_py = True
4238- new_py_bridge = PyBridge (self )
4239- load_ipy (self , new_py_bridge )
4240- return new_py_bridge .stop
4218+ py_bridge = PyBridge (self )
4219+
4220+ # Make a copy of self.py_locals for the locals dictionary in the IPython environment we are creating.
4221+ # This is to prevent ipy from editing it. (e.g. locals().clear()). Only make a shallow copy since
4222+ # it's OK for py_locals to contain objects which are editable in ipy.
4223+ local_vars = self .py_locals .copy ()
4224+ local_vars [self .py_bridge_name ] = py_bridge
4225+ if self .self_in_py :
4226+ local_vars ['self' ] = self
4227+
4228+ # Configure IPython
4229+ config = TraitletsConfig ()
4230+ config .InteractiveShell .banner2 = (
4231+ 'Entering an embedded IPython shell. Type quit or <Ctrl>-d to exit.\n '
4232+ 'Run Python code from external files with: run filename.py\n '
4233+ )
4234+
4235+ # Start IPython
4236+ start_ipython (config = config , argv = [], user_ns = local_vars )
4237+
4238+ # The IPython application is a singleton and won't be recreated next time
4239+ # this function runs. That's a problem since the contents of local_vars
4240+ # may need to be changed. Therefore we must destroy all instances of the
4241+ # relevant classes.
4242+ TerminalIPythonApp .clear_instance ()
4243+ TerminalInteractiveShell .clear_instance ()
4244+
4245+ return py_bridge .stop
42414246 finally :
42424247 self ._in_py = False
42434248
0 commit comments