1

I'm working on a bot that uses PRAW to scan all new comments in a user defined subreddit and searches for specific phrases to reply to. I've used PySimpleGUI for the user interface, as you can see here:

Image of GUI with text inputs for Reddit API authorization

Because of an issue where it freezes during long operations, I've had to implement threading. My code runs well as long as I'm running it in my IDLE (PyCharm), but after converting it to an executable, running it, entering my credentials along with the relevant terms, and clicking "RUN", I get a threading error:

ERROR  Unable to complete operation on element with key 0

File "PySimpleGUI\PySimpleGUI.py"   
line 20892
in_error_popup_with_traceback

You cannot perform operations (such as calling update) on an Element until:
window.read() is called or finalize=True when Window created.
Adding a "finalize=True" parameter to your Window creation will likely fix this.
The PySimpleGUI internal reporting function is _widget_was_created
The error originated from:
File "threading.py"
line 1319
in invoke_excepthook

Screenshot of this error message.

I've been working on this for two days now with no further progress, although I am brand new to python so I'm sure I'm missing something. Here is my full code:

#importing the necessary libraries
from threading import Thread
import praw
import subprocess
import sys
import time
from datetime import datetime
import PySimpleGUI as psg

#worker function
def bot(id, secret, key, agent, user, sub, keywords, body):
    
    #initializes PRAW with user's credentials
    reddit = praw.Reddit(
        client_id=id,
        client_secret=secret,
        password=key,
        user_agent=agent,
        username=user)
    
    #specifies which subreddit to perform the operation on
    subreddit = reddit.subreddit(sub)
    
    #initiates a live feed of new comments and checks for the user defined search term using for loops
    for comment in subreddit.stream.comments():
        if comment:
            normalized_comment = comment.body.lower()
            for phrase in keywords:
                if phrase in normalized_comment:
                    
                    #if the phrase is found, prints a timestamp and the comment to the console
                    current_time = datetime.now()
                    print(str(current_time) + " - Replying to: " + '"' + comment.body + '"')
                    
                    #tries to reply to the comment found
                    try:                                                
                        comment.reply(body=body)
                        # A reply has been made so do not attempt to match other phrases.
                        break
                        
                    #if the bot is flagged for a cooldown, sleep for two minutes
                    except:
                        current_time = datetime.now()
                        print(str(current_time) + " - Rate limited, retrying in two minutes.")
                        time.sleep(120)

#this function creates the GUI
def gui():
    psg.theme("Dark")

    #layout for input window
    layout = [[psg.Text("Personal Use Script", size=(15, 1), font='Lucida', justification='right'), psg.Input()],
              [psg.Text("Secret", size=(15, 1), font='Lucida', justification='right'), psg.Input()],
              [psg.Text("Bot Password", size=(15, 1), font='Lucida', justification='right'), psg.Input()],
              [psg.Text("Bot Call Sign", size=(15, 1), font='Lucida', justification='right'), psg.Input()],
              [psg.Text("Bot Username", size=(15, 1), font='Lucida', justification='right'), psg.Input()],
              [psg.Text("Subreddit to Search", size=(15, 1), font='Lucida', justification='right'), psg.Input()],
              [psg.Text("Trigger Phrase", size=(15, 1), font='Lucida', justification='right'), psg.Input()],
              [psg.Text("Comment to Send", size=(15, 1), font='Lucida', justification='right'), psg.Input()],
              [psg.Button("RUN", font=("Times New Roman", 12))]]
    
    #this line initiates the GUI window
    win = psg.Window("Find & Reply v 0.1", layout, finalize=True, element_justification='c')
    
    #this line listens for any interactions with the elements on the window
    e, v = win.read()
    
    while True:
        
        #closes the program if the window is closed
        if e in (psg.WIN_CLOSED, 'Exit'):
            break
            
        #listens for the "RUN" button to be clicked and executes the bot function thread    
        if e == 'RUN':
            
            #this line closes the initial input window
            win.close()
            bot_thread = Thread(target=bot, args=(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]))
            bot_thread.start()

            # layout for a secondary output window so the user can read the console
            layout1 = [[psg.Output(size=(60, 15))]]
            
            #initiates the secondary window
            win1 = psg.Window("Scanning all new comments...", layout1, finalize=True, element_justification='c')
            
            #listens for any interactions with the elements on the window
            event, values = win1.Read()
            
            #closes the program in the event the window is close (doesn't work)
            if event in (psg.WIN_CLOSED, 'Exit'):
                break

            if event == 'Run':  # the two lines of code needed to get button and run command
                runCommand(cmd=values['_IN_'], window=win1)
            win1.close()

#this function sends the console text to the secondary output window for the user to read
def runCommand(cmd, timeout=None, window=None):
    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)                         # also return the output just for fun


if __name__ == '__main__':
    
    #this is where I initiate the GUI thread
    gui_thread = Thread(target=gui)
    gui_thread.start()
  • GUI should be in the main thread, no `gui_thread = Thread(target=gui); gui_thread.start()`. It looks like that the code for event loop in `gui` looks strange, I didn't check it. Where the event `Run` come from ? the win1 is only an Output element. – Jason Yang May 14 '22 at 04:29
  • Thank you @JasonYang, you're correct about the event loop being strange, I hadn't caught that before. That came from some code I found on how to print the console to a new window and to be honest I don't understand fully what is happening in the runCommand function, but it does print so I'm happy lol. I did remove that strange `if event == 'Run'` line though. When you say GUI should be in main thread, are you referring to `def bot():`? Thanks for the help and I appreciate your input. – Justin Deffendall May 14 '22 at 17:27
  • Where did you get the code from? – Mike from PSG May 14 '22 at 17:56
  • @MikefromPSG I actually found it [here](https://stackoverflow.com/questions/64680384/pysimplegui-displaying-console-output-in-gui) on stack overflow after searching how to display console output to GUI. Clicking through, I see you're actually the one who wrote the original code so I apologize for butchering so thoroughly lol. Also just realized you and Jason are the devs behind PySimpleGUI so just wanted to say thank you for the work and making GUI creation so simple! – Justin Deffendall May 14 '22 at 19:40
  • Thank you very much for the many great comments you made in your comment @JustinDeffendall! Really appreciate the help. I wanted to understand the source because I've been going back through my old answers and "fixing" them, providing more instruction about the dangers of getting code here.... even my old code is wrong or no longer the best way of doing things. It's a problem I hadn't thought of when I wrote answers.... nothing here dies and "popular" answers don't mean they're the best, especially years later. Thank you so much for the kind words. They fuel the project! – Mike from PSG May 15 '22 at 12:35
  • @JasonYang is the real hero here on SO with providing PySimpleGUI support! I'm a mere mortal. – Mike from PSG May 15 '22 at 12:36

0 Answers0