<#2249 Unable to run a script in interactive mode ...
# github-notifications
c
#2249 Unable to run a script in interactive mode `-i` Issue created by gauthamnair Issue description and reproduction steps A pex with no pre-defined entry point or console script will run a script or module (
-m
) that it is passed as a CLI arg similarly to how the python command line does
Copy code
$ cat << EOF > foo.py
> bar = 42
> print(f'hello {bar}')
> EOF
$ python foo.py
hello 42
Copy code
~/code/pantsbuild/pex (main) $ python -m pex -o testpex.pex
~/code/pantsbuild/pex (main) $ ./testpex.pex foo.py
hello 42
the python command line allows you to run the target in interactive mode by passing the `-i` flag. It executes the target but then hands you interactive control.
Copy code
$ python -i foo.py
hello 42
>>> bar
42
>>> quit()
Trying something similar with a
.pex
does not work. The target executes successfully but you get an error stack trace after it. Then you end up in a REPL but it doesn't have the expected names loaded.
Copy code
~/code/pantsbuild/pex (main) $ ./testpex.pex -i foo.py
hello 42
Traceback (most recent call last):
  File "/Users/gauthamnair/.pex/unzipped_pexes/53e3acded6c4b7940375dd07ace6edae8800941b/__main__.py", line 108, in <module>
    bootstrap_pex(__entry_point__, execute=__execute__, venv_dir=__venv_dir__)
  File "/Users/gauthamnair/.pex/unzipped_pexes/53e3acded6c4b7940375dd07ace6edae8800941b/.bootstrap/pex/pex_bootstrapper.py", line 627, in bootstrap_pex
    pex.PEX(entry_point).execute()
  File "/Users/gauthamnair/.pex/unzipped_pexes/53e3acded6c4b7940375dd07ace6edae8800941b/.bootstrap/pex/pex.py", line 560, in execute
    sys.exit(self._wrap_coverage(self._wrap_profiling, self._execute))
SystemExit
>>> bar
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'bar' is not defined
Motivation Similar to #2248 this was discovered while trying out support in pants for supplying args to a
pants repl
command. Running
-i some_script.py
is one of the more common modifiers to boot an interactive session. Diagnosis
PEX.execute_interpreter
assumes that the user is either trying to run a script/module/etc. or trying to jump into an interactive session. The structure is more or less like this:
Copy code
def execute_interpreter(self):
    args = sys.argv[1:]
    python_options = []
    for index, arg in enumerate(args):
        # ...
        # partition python_options from args

    # The first time through we are effectively running with a cmdline like:
    # python testpex.pex -i foo.py
    # => sys.argv[1:] = [-i, foo.py]
    # => python_options, args = [-i], [foo.py] 
    #
    # So we reexecute, giving the [-i] to the interpreter
    # python -i testpex.pex foo.py
    # => sys.argv[1:] = [foo.py]
    # => python_options, args = [], [foo.py]
    #
    # The second time through, this code does not see the interpreter options.
    if python_options:
        return self.execute_with_options(python_options, args)

    if args:
        # This is the execute some code branch
        arg = args[0]
        if arg == "-c":
            # ...
            return self.execute_content("-c <cmd>", content, argv0="-c")
        elif arg == "-m":
            # ...
            return self.execute_module(module)
        else:
            content = '...' # grab content from sys.stdin or from the supplied script or directory in arg
            sys.argv = args
            return self.execute_content(arg, content)
    else:
        # This is the open-a-REPL branch
        # We don't get here because there was an arg [foo.py]

        import code
        code.interact()  # opens an interactive REPL within the current python process
        return None
When we come around the second time we execute the script with
self.execute_content
, which loads its names into a copy of
globals()
rather than into
globals()
itself:
Copy code
# PEX.execute_ast(...)
        globals_map = globals().copy()
        globals_map["__name__"] = "__main__"
        globals_map["__file__"] = name
        exec_function(program, globals_map)
        return None
and we then then system exit from the calling code in `PEX.execute`:
sys.exit(self._wrap_coverage(self._wrap_profiling, self._execute))
Because the interpreter is in
-i
mode it stops the system exit and shows us the exit stack trace and drops us in an interpreter, which won't have the loaded names. It is not very clear how this could be fixed or if there is a much simpler workflow to support #2248 which has no problem running for ipython shells presumably because ipython runs with an entry point
default_main = ConsoleScript("ipython")
pantsbuild/pex