From bf762e12b95c6d588a27de399677ba1f0581ed0f Mon Sep 17 00:00:00 2001 From: MikeTheWatchGuy Date: Fri, 24 May 2019 08:51:17 -0400 Subject: [PATCH] PySimpleGUI Debugger! Initial checkin --- DemoPrograms/Demo_Debugger_Integration.py | 38 +++++ ...emo_Python_REPL_Variable_Watches_In_GUI.py | 131 ------------------ PSGdebugger.py | 125 +++++++++++++++++ 3 files changed, 163 insertions(+), 131 deletions(-) create mode 100644 DemoPrograms/Demo_Debugger_Integration.py delete mode 100644 DemoPrograms/Demo_Python_REPL_Variable_Watches_In_GUI.py create mode 100644 PSGdebugger.py diff --git a/DemoPrograms/Demo_Debugger_Integration.py b/DemoPrograms/Demo_Debugger_Integration.py new file mode 100644 index 00000000..eb677137 --- /dev/null +++ b/DemoPrograms/Demo_Debugger_Integration.py @@ -0,0 +1,38 @@ +import PySimpleGUI as sg +import PSGdebugger + +""" + Demo program that shows you how to integrate the PySimpleGUI Debugger + into your program. + There are TWO steps, and they are copy and pastes. + 1. At the top of your app to debug add + import PSGdebugger + 2. At the end of your app's event loop add + PSGdebugger.refresh(locals(), globals()) +""" + + +layout = [ + [sg.T('A typical PSG application')], + [sg.In(key='_IN_')], + [sg.T(' ', key='_OUT_')], + [sg.Radio('a',1, key='_R1_'), sg.Radio('b',1, key='_R2_'), sg.Radio('c',1, key='_R3_')], + [sg.Combo(['c1', 'c2', 'c3'], size=(6,3), key='_COMBO_')], + [sg.Output(size=(40,6))], + [sg.Ok(), sg.Exit()], + ] + + +window = sg.Window('This is your Application Window', layout) + +# Variables that we'll use to demonstrate the debugger's features +counter = 0 +timeout = 100 + +while True: # Event Loop + event, values = window.Read(timeout=timeout) + if event in (None, 'Exit'): + break + counter += 1 + window.Element('_OUT_').Update(values['_IN_']) + PSGdebugger.refresh(locals(), globals()) diff --git a/DemoPrograms/Demo_Python_REPL_Variable_Watches_In_GUI.py b/DemoPrograms/Demo_Python_REPL_Variable_Watches_In_GUI.py deleted file mode 100644 index c1eb9d7c..00000000 --- a/DemoPrograms/Demo_Python_REPL_Variable_Watches_In_GUI.py +++ /dev/null @@ -1,131 +0,0 @@ -import subprocess -import sys -import ast -import copy -import PySimpleGUI as sg - -""" - Python repl with a "watch list" - This Demo Program was created as an answer to a Reddit question. - The idea is to build a Python interpreter >>> prompt, accept command and execute them... and - to also inslude a watcher feature. The "Watched" variables or expressions are constantly updated - as the proram runs. - At the moment, the event loop runs once a second. It could easily be shortened if that's too slow -""" - -WIDTH_VARIABLES = 12 -WIDTH_RESULTS = 16 - -def convertExpr2Expression(Expr): - Expr.lineno = 0 - Expr.col_offset = 0 - result = ast.Expression(Expr.value, lineno=0, col_offset = 0) - - return result - -def exec_with_return(code): - code_ast = ast.parse(code) - - init_ast = copy.deepcopy(code_ast) - init_ast.body = code_ast.body[:-1] - - last_ast = copy.deepcopy(code_ast) - last_ast.body = code_ast.body[-1:] - - exec(compile(init_ast, "", "exec"), globals()) - if type(last_ast.body[0]) == ast.Expr: - return eval(compile(convertExpr2Expression(last_ast.body[0]), "", "eval"),globals()) - else: - exec(compile(last_ast, "", "exec"),globals()) - - -def func(x=''): - return f'return value from func()={x}' - - -def init(): - def InVar(key1, key2): - row1 = [sg.T(' '), - sg.I(key=key1, size=(WIDTH_VARIABLES,1)), - sg.I(key=key1+'CHANGED_', size=(WIDTH_RESULTS,1)),sg.B('Detail', key=key1+'DETAIL_'), sg.T(' '), - sg.T(' '), sg.I(key=key2, size=(WIDTH_VARIABLES, 1)), sg.I(key=key2 + 'CHANGED_', size=(WIDTH_RESULTS, 1)), sg.B('Detail', key=key2+'DETAIL_'),] - return row1 - - variables_frame = [ InVar('_VAR1_', '_VAR2_'), - InVar('_VAR3_', '_VAR4_'), - InVar('_VAR5_', '_VAR6_'),] - - interactive_frame = [[sg.T('>>> '), sg.In(size=(70,1), key='_INTERACTIVE_'), sg.B('Go', bind_return_key=True, visible=False)], - [sg.Output(size=(70,8))],] - - layout = [ [sg.Frame('Variables or Expressions to Watch', variables_frame)], - [sg.Frame('Interactive REPL', interactive_frame)], - [sg.Button('Exit')]] - - window = sg.Window('Realtime REPL Command Output + Watches', layout).Finalize() - window.Element('_INTERACTIVE_').SetFocus() - return window - # event_loop(window) - -event_once = lambda window, var=0 : exec(""" - -# while True: # Event Loop -event, values = window.Read(timeout=100) -print(event, values) if event != sg.TIMEOUT_KEY else None -if event in (None, 'Exit'): - False -cmd = values['_INTERACTIVE_'] -if event == 'Run': - runCommand(cmd=cmd, window=window) -elif event == 'Go': - window.Element('_INTERACTIVE_').Update('') - out='' - print(">>> ", cmd) - try: - print(exec_with_return(cmd)) - except Exception as e: - print(f'Exception on output {e}') -elif event.endswith('_DETAIL_'): - try: sg.PopupScrolled(eval(values[f'_VAR{event[4]}_'])) - except: pass -# -------------------- Process the "watch list" ------------------ -for i in range(1, 6): - key = f'_VAR{i}_' - out_key = f'_VAR{i}_CHANGED_' - if window.Element(key): - if values[key]: - try: - window.Element(out_key).Update(eval(values[key])) - except: - window.Element(out_key).Update('') -var += 1 -True -""") - -def runCommand(cmd, timeout=None, window=None): - """ run shell command - @param cmd: command to execute - @param timeout: timeout for command execution - @param window: the PySimpleGUI window that the output is going to (needed to do refresh on) - @return: (return code from command, command output) - """ - p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - output = '' - for line in p.stdout: - line = line.decode(errors='replace' if (sys.version_info) < (3, 5) else 'backslashreplace').rstrip() - output += line - print(line) - window.Refresh() if window else None # yes, a 1-line if, so shoot me - - retval = p.wait(timeout) - return (retval, output) - -if __name__ == '__main__': - window = init() - my_variable=1000 - while True: - event_once(window) - # if not event_once(window): - # break - window.Close() - diff --git a/PSGdebugger.py b/PSGdebugger.py new file mode 100644 index 00000000..02cdf88e --- /dev/null +++ b/PSGdebugger.py @@ -0,0 +1,125 @@ +import subprocess +import sys +import PySimpleGUI as sg + + +""" + The Offiicial Unofficiall official PySimpleGUI debug tool + Not calling it a debugger, but it is also quite a step up from "print statemements" +""" +PSGDebugLogo = b'R0lGODlhMgAtAPcAAAAAADD/2akK/4yz0pSxyZWyy5u3zZ24zpW30pG52J250J+60aC60KS90aDC3a3E163F2K3F2bPI2bvO3rzP3qvJ4LHN4rnR5P/zuf/zuv/0vP/0vsDS38XZ6cnb6f/xw//zwv/yxf/1w//zyP/1yf/2zP/3z//30wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAP8ALAAAAAAyAC0AAAj/AP8JHEiwoMGDCBMqXMiwoUOFAiJGXBigYoAPDxlK3CigwUGLIAOEyIiQI8cCBUOqJFnQpEkGA1XKZPlPgkuXBATK3JmRws2bB3TuXNmQw8+jQoeCbHj0qIGkSgNobNoUqlKIVJs++BfV4oiEWalaHVpyosCwJidw7Sr1YMQFBDn+y4qSbUW3AiDElXiWqoK1bPEKGLixr1jAXQ9GuGn4sN22Bl02roo4Kla+c8OOJbsQM9rNPJlORlr5asbPpTk/RP2YJGu7rjWnDm2RIQLZrSt3zgp6ZmqwmkHAng3ccWDEMe8Kpnw8JEHlkXnPdh6SxHPILaU/dp60LFUP07dfRq5aYntohAO0m+c+nvT6pVMPZ3jv8AJu8xktyNbw+ATJDtKFBx9NlA20gWU0DVQBYwZhsJMICRrkwEYJJGRCSBtEqGGCAQEAOw==' + +COLOR_SCHEME = 'LightGreen' + +WIDTH_VARIABLES = 12 +WIDTH_RESULTS = 36 + +# done purely for testing / show +def func(x=''): + return f'return value from func()={x}' + + +def _init(): + global watcher_window + sg.ChangeLookAndFeel(COLOR_SCHEME) + def InVar(key1, key2): + row1 = [sg.T(' '), + sg.I(key=key1, size=(WIDTH_VARIABLES,1)), + sg.T('',key=key1+'CHANGED_', size=(WIDTH_RESULTS,1)),sg.B('Detail', key=key1+'DETAIL_'), sg.T(' '), + sg.T(' '), sg.I(key=key2, size=(WIDTH_VARIABLES, 1)), sg.T('',key=key2 + 'CHANGED_', size=(WIDTH_RESULTS, 1)), sg.B('Detail', key=key2+'DETAIL_'),] + return row1 + + variables_frame = [ InVar('_VAR1_', '_VAR2_'), + InVar('_VAR3_', '_VAR4_'), + InVar('_VAR5_', '_VAR6_'),] + + interactive_frame = [[sg.T('>>> '), sg.In(size=(83,1), key='_INTERACTIVE_'), sg.B('Go', bind_return_key=True, visible=False)], + [sg.Multiline(size=(88,12),key='_OUTPUT_',autoscroll=True, do_not_clear=True)],] + + layout = [ [sg.Frame('Variables or Expressions to Watch', variables_frame, )], + [sg.Frame('REPL-Light', interactive_frame,)], + [sg.Button('Exit')]] + + window = sg.Window('PySimpleGUI Debugger', layout, icon=PSGDebugLogo).Finalize() + window.Element('_INTERACTIVE_').SetFocus() + watcher_window = window + sg.ChangeLookAndFeel('SystemDefault') + + return window + +def _event_once(_window, mylocals, myglobals): + _window = watcher_window + global myrc + _event, _values = _window.Read(timeout=100) + if _event in (None, 'Exit'): + return False + cmd = _values['_INTERACTIVE_'] + if _event == 'Run': + _runCommand(cmd=cmd, window=_window) + elif _event == 'Go': + _window.Element('_INTERACTIVE_').Update('') + _window.Element('_OUTPUT_').Update(">>> {}\n".format(cmd), append=True, autoscroll=True) + expression = """ +global myrc +PSGdebugger.myrc = {} """.format(cmd) + try: + exec(expression, myglobals, mylocals) + _window.Element('_OUTPUT_').Update('{}\n'.format(myrc),append=True, autoscroll=True) + + except Exception as e: + _window.Element('_OUTPUT_').Update('Exception {}\n'.format(e),append=True, autoscroll=True) + + elif _event.endswith('_DETAIL_'): + expression = """ +global myrc +PSGdebugger.myrc = {} """.format(_values[f'_VAR{_event[4]}_']) + try: + exec(expression, myglobals, mylocals) + sg.PopupScrolled(myrc) + except: + print('Detail failed') + + # -------------------- Process the "watch list" ------------------ + for i in range(1, 7): + key = f'_VAR{i}_' + out_key = f'_VAR{i}_CHANGED_' + myrc ='' + if _window.Element(key): + if _values[key]: + expression = """ +global myrc +PSGdebugger.myrc = {} """.format(_values[key]) + try: + exec(expression, myglobals, mylocals) + except Exception as e: + pass + _window.Element(out_key).Update(myrc) + else: + _window.Element(out_key).Update('') + + + +def _runCommand(cmd, timeout=None, window=None): + """ run shell command + @param cmd: command to execute + @param timeout: timeout for command execution + @param window: the PySimpleGUI window that the output is going to (needed to do refresh on) + @return: (return code from command, command output) + """ + p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + output = '' + for line in p.stdout: + line = line.decode(errors='replace' if (sys.version_info) < (3, 5) else 'backslashreplace').rstrip() + output += line + print(line) + window.Refresh() if window else None # yes, a 1-line if, so shoot me + + retval = p.wait(timeout) + return (retval, output) + +def refresh(locals, globals): + global watcher_window + _event_once(watcher_window, locals, globals) + +myrc = '' +watcher_window = _init()