Search code examples
pythonmultithreadingbottle

threading a bottle app


I have a simple bottle script that forwards button processes on a web page. Within the same script I was looking to have a continuous loop that among other tasks listened out for these button presses. I attempted to run the bottle script in a separate thread but it doesn't work as I expected.

Is there a better (or should I say correct) way to do this?

from bottle import get, post, request, run, redirect
import threading

@get('/button')
def button():
    return '''
        <form action="/button" method="post">
            <input type="submit" value="Push" />
        </form>
    '''

@post('/button')
def action():
    print "button pushed"
    pushed = True
    redirect("/button")

#run(host='localhost', port=8080)
threading.Thread(target=run, kwargs=dict(host='localhost', port=8080)).start()


def method():
    pushed = False
    print "started"
    while 1:
        # do other stuff
        if pushed:
            print "push recieved"
            pushed = False

method()

Solution

  • It doesn't work because it's only seeing the local variable pushed defined inside of method as opposed to a globally visible and modifiable pushed variable.

    What you need instead is the following (but scroll down for a correct solution):

    pushed = False
    
    @post('/button')
    def action():
        global pushed  # needed because otherwise assigning to `pushed` will
                       # just create a local variable shadowing the global one
        pushed = True
        redirect("/button")
    
    def method():
        # pushed = False   <----- this line is not needed, and if enabled, will, again, 
        #                         create a local variable shadowing the global one
        global pushed  # again, otherwise the `pushed = False` statement will create a local variable
        while True:  # use real booleans, i.e. True/False not 1/0
            if pushed:
                print "push recieved"
                pushed = False
    
    method()
    

    NOTE: pay attention to the comments I added inside of the snippet.

    BUT, it is bad practice to communicate with threads via global variables (or normal variables) because multiple other threads might be accessing (reading or writing) the same variable simultaneously. Instead, to signal events between threads, use queues:

    from Queue import Queue, Empty
    
    button_pressed = Queue()
    
    @post('/button')
    def action():
        button_pressed.put(1)  # can be any value really
        redirect("/button")
    
    def method():
        while True:
            try:
                button_pressed.get_nowait()
            except Empty:
                # NOTE: if you don't do anything here, this thread
                # will consume a single CPU core
                pass
            else:
                print "push recieved"
    

    get_nowait() checks if something has been put into the queue and if yes, returns it; otherwise it immediately raises Empty. You can also pass a non-zero timeout to it, or call it without a timeout, in which case it will wait until something is available in the queue.

    It's likely better to use .get() with a timeout rather than the nowait version to not make the thread consume CPU uselessly.

    Furthermore, code such as the line where you start the thread as well as where you call method() should not be put directly into the module top-level scope; instead, call them conditionally like this:

    if __name__ == '__main__':
        threading.Thread(target=run, kwargs=dict(host='localhost', port=8080)).start()
        method()
    

    this way that code will only execute if the .py file is executed directly and not when it's imported as a module. Also, consider calling run() normally and instead putting method inside of a thread.