Open a REPL from an embedded python installation

Viewed 328

I am using a piece of software that comes with an embedded (I hope that is the right word) version of IronPython. The IP is there to allow the user to write .py scripts that interact with the program API.

There is no ipy.exe or python.exe file available inside of the program files installation directory. The only thing in there are the various IronPython DLL files. The standard library is fully available.

There is no interactive command line available. I really need to debug my code with something more powerful than output to a text only popup widow, but the program just hangs when I trying to use the debugger:

import pdb;pdb.set_trace()

It would be great if there were a piece of code I could just drop in one of these .py modules that will launch a command line REPL for the current python session. I've tried solutions from SO using the code module, but it doesn't work.

Can it be done?

2 Answers

For some reason using FIFO did not work properly so instead I implemented this strangeness. It uses files to communicate between a controller instance (just running it from terminal) and the pdb in a different code called from wherever. Also uses monkey patching to get our new set_trace into pdb.

import time
import pdb


IN_FILE = "in.pdb"
OUT_FILE = "out.pdb"


class _Receiver(object):
    def __init__(self, in_file):
        self.in_file = in_file
        with open(in_file, "w") as file:
            file.write("")

    def readline(self):
        x = ""
        while x == "":
            time.sleep(0.1)
            with open(self.in_file, "r") as file:
                x = file.readline()

        with open(self.in_file, "w") as file:
            file.write("")

        return x


class _Sender(object):
    def __init__(self, out_file):
        self.out_file = out_file
        with open(out_file, "w") as file:
            file.write("")

    def write(self, x):
        with open(self.out_file, "a") as file:
            file.write(x)

    def flush(self):
        pass


mypdb = pdb.Pdb(stdin=_Receiver(in_file=IN_FILE),
                stdout=_Sender(out_file=OUT_FILE))

pdb.set_trace = mypdb.set_trace

To use a breakpoint in some other code:

from youcannameit import pdb

pdb.set_trace()

To connect to pdb run the main batch script in a cmd. It works fairly well. Not as good as the bash version so I'll leave it there in case you choose to use that. You can't use CRT-C type q instead. Once you quit I suggest opening a new cmd because running again in the same one is buggy as hell. Maybe someone with more batch knowledge can fix it.

Main batch script:

@echo off

set IN_FILE="in.pdb"
set OUT_FILE="out.pdb"
set LOCK="lock.lock"

echo running > %LOCK%
start /b read.cmd
timeout 1 /NOBREAK >NUL
echo PDB:
goto :write_pdb


:print_file
type %OUT_FILE%
timeout 1 /NOBREAK >NUL
NUL > %OUT_FILE% 2>NUL
goto :read_pdb


:read_pdb
for /f %%i in (%OUT_FILE%) do set size=%%~zi
if %size% gtr 0 goto :print_file
timeout 1 /NOBREAK >NUL
goto :read_pdb


:write_pdb
set /P INPUT=%=%
echo %INPUT% > %IN_FILE%
If /I "%Input%"=="q" goto :end
If /I "%Input%"=="quit" goto :end
If /I "%Input%"=="exit" goto :end
goto :write_pdb


:end
del %LOCK%
echo Exiting..
EXIT /B 0

Secondary batch script so we can print and input at the same time (name it read.cmd and put it in the same folder as the main script):

@echo off

set IN_FILE="in.pdb"
set OUT_FILE="out.pdb"
set LOCK="lock.lock"

goto :read_pdb


:print_file
type %OUT_FILE%
timeout 1 /NOBREAK >NUL
NUL > %OUT_FILE% 2>NUL
goto :read_pdb


:read_pdb
if not exist %LOCK% goto :end
for /f %%i in (%OUT_FILE%) do set size=%%~zi
if %size% gtr 0 goto :print_file
timeout 1 /NOBREAK >NUL
goto :read_pdb

:end

In case you stick with the bash version::

#!/bin/bash

IN_FILE="in.pdb"
OUT_FILE="out.pdb"

echo PDB:

# trap ctrl-c and call ctrl_c()
trap ctrl_c INT

function ctrl_c() {
    kill $READER >/dev/null 2>&1
    exit 1
}

read_pdb(){
    while true
    do
        if [ -s $OUT_FILE ]
        then
            cat $OUT_FILE
            sleep 0.05
            > $OUT_FILE
        fi
    done
}

write_pdb(){
    while true
    do
        sleep 0.1
        read user_inp
        echo $user_inp > $IN_FILE

        if [ "$user_inp" == "q" ];
        then
            kill $READER >/dev/null 2>&1
            exit 1
        fi
    done
}

read_pdb &
READER=$!
write_pdb

To connect to a breakpoint in a terminal just execute the bash script. You do have to make sure that the paths (IN_FILE, OUT_FILE, LOCK) are actual paths for your system.

Try this and pls report if it worked:

import code
code.InteractiveConsole(locals=globals()).interact()

It runs a usual Python console:

$ python3 ./temp.py
Python 3.6.9 (default, Jul 17 2020, 12:50:27)
[GCC 8.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>
Related