@@ -3093,8 +3093,7 @@ def _restore_cmd2_env(self, cmd2_env: _SavedCmd2Env) -> None:
30933093
30943094 # This is a hidden flag for telling do_py to run a pyscript. It is intended only to be used by run_pyscript
30953095 # after it sets up sys.argv for the script being run. When this flag is present, it takes precedence over all
3096- # other arguments. run_pyscript uses this method instead of "py run('file')" because file names with
3097- # 2 or more consecutive spaces cause issues with our parser, which isn't meant to parse Python statements.
3096+ # other arguments.
30983097 py_parser .add_argument ('--pyscript' , help = argparse .SUPPRESS )
30993098
31003099 # Preserve quotes since we are passing these strings to Python
@@ -3104,65 +3103,68 @@ def do_py(self, args: argparse.Namespace) -> Optional[bool]:
31043103 Enter an interactive Python shell
31053104 :return: True if running of commands should stop
31063105 """
3106+ def py_quit ():
3107+ """Function callable from the interactive Python console to exit that environment"""
3108+ raise EmbeddedConsoleExit
3109+
31073110 from .py_bridge import PyBridge
3111+ py_bridge = PyBridge (self )
3112+ saved_sys_path = None
3113+
31083114 if self .in_pyscript ():
31093115 err = "Recursively entering interactive Python consoles is not allowed."
31103116 self .perror (err )
31113117 return
31123118
3113- py_bridge = PyBridge (self )
3114- py_code_to_run = ''
3115-
3116- # Handle case where we were called by run_pyscript
3117- if args .pyscript :
3118- args .pyscript = utils .strip_quotes (args .pyscript )
3119-
3120- # Run the script - use repr formatting to escape things which
3121- # need to be escaped to prevent issues on Windows
3122- py_code_to_run = 'run({!r})' .format (args .pyscript )
3123-
3124- elif args .command :
3125- py_code_to_run = args .command
3126- if args .remainder :
3127- py_code_to_run += ' ' + ' ' .join (args .remainder )
3128-
3129- # Set cmd_echo to True so PyBridge statements like: py app('help')
3130- # run at the command line will print their output.
3131- py_bridge .cmd_echo = True
3132-
31333119 try :
31343120 self ._in_py = True
3121+ py_code_to_run = ''
31353122
3136- def py_run (filename : str ):
3137- """Run a Python script file in the interactive console.
3138- :param filename: filename of script file to run
3139- """
3140- expanded_filename = os .path .expanduser (filename )
3123+ # Use self.py_locals as the locals() dictionary in the Python environment we are creating, but make
3124+ # a copy to prevent pyscripts from editing it. (e.g. locals().clear()). Only make a shallow copy since
3125+ # it's OK for py_locals to contain objects which are editable in a pyscript.
3126+ localvars = dict (self .py_locals )
3127+ localvars [self .py_bridge_name ] = py_bridge
3128+ localvars ['quit' ] = py_quit
3129+ localvars ['exit' ] = py_quit
3130+
3131+ if self .self_in_py :
3132+ localvars ['self' ] = self
3133+
3134+ # Handle case where we were called by run_pyscript
3135+ if args .pyscript :
3136+ # Read the script file
3137+ expanded_filename = os .path .expanduser (utils .strip_quotes (args .pyscript ))
31413138
31423139 try :
31433140 with open (expanded_filename ) as f :
3144- interp . runcode ( f .read () )
3141+ py_code_to_run = f .read ()
31453142 except OSError as ex :
31463143 self .pexcept ("Error reading script file '{}': {}" .format (expanded_filename , ex ))
3144+ return
31473145
3148- def py_quit ():
3149- """Function callable from the interactive Python console to exit that environment"""
3150- raise EmbeddedConsoleExit
3146+ localvars ['__name__' ] = '__main__'
3147+ localvars ['__file__' ] = expanded_filename
31513148
3152- # Set up Python environment
3153- self .py_locals [self .py_bridge_name ] = py_bridge
3154- self .py_locals ['run' ] = py_run
3155- self .py_locals ['quit' ] = py_quit
3156- self .py_locals ['exit' ] = py_quit
3149+ # Place the script's directory at sys.path[0] just as Python does when executing a script
3150+ saved_sys_path = list (sys .path )
3151+ sys .path .insert (0 , os .path .dirname (os .path .abspath (expanded_filename )))
31573152
3158- if self .self_in_py :
3159- self .py_locals ['self' ] = self
3160- elif 'self' in self .py_locals :
3161- del self .py_locals ['self' ]
3153+ else :
3154+ # This is the default name chosen by InteractiveConsole when no locals are passed in
3155+ localvars ['__name__' ] = '__console__'
3156+
3157+ if args .command :
3158+ py_code_to_run = args .command
3159+ if args .remainder :
3160+ py_code_to_run += ' ' + ' ' .join (args .remainder )
31623161
3163- localvars = self .py_locals
3162+ # Set cmd_echo to True so PyBridge statements like: py app('help')
3163+ # run at the command line will print their output.
3164+ py_bridge .cmd_echo = True
3165+
3166+ # Create the Python interpreter
31643167 interp = InteractiveConsole (locals = localvars )
3165- interp .runcode ('import sys, os;sys.path.insert(0, os.getcwd())' )
31663168
31673169 # Check if we are running Python code
31683170 if py_code_to_run :
@@ -3177,8 +3179,7 @@ def py_quit():
31773179 else :
31783180 cprt = 'Type "help", "copyright", "credits" or "license" for more information.'
31793181 instructions = ('End with `Ctrl-D` (Unix) / `Ctrl-Z` (Windows), `quit()`, `exit()`.\n '
3180- 'Non-Python commands can be issued with: {}("your command")\n '
3181- 'Run Python code from external script files with: run("script.py")'
3182+ 'Non-Python commands can be issued with: {}("your command")'
31823183 .format (self .py_bridge_name ))
31833184
31843185 saved_cmd2_env = None
@@ -3205,7 +3206,10 @@ def py_quit():
32053206 pass
32063207
32073208 finally :
3208- self ._in_py = False
3209+ with self .sigint_protection :
3210+ if saved_sys_path is not None :
3211+ sys .path = saved_sys_path
3212+ self ._in_py = False
32093213
32103214 return py_bridge .stop
32113215
0 commit comments