1

I have a Python 2.7 script that starts a Kivy GUI app and runs a Flask server in a parallel process. Flask listens and fetches incoming JSON http requests and passes them to Kivy. Then Kivy does the parsing.

While parsing I want to open a popup in the Kivy main screen and wait for the user response, each time when a condition is met. Basically I would like to pause and resume the parsing with a popup. See picture below.

enter image description here

So far I managed only to open another Kivy window instead of a popup in the main screen. No matter in which class I put the parsing method always I end up with a window. I do not know if it is a matter of communication between two processes or wrong popup calling or both. I have checked out few resources (sync, pymotw, inter-process, popup triggering) but I am still lost. I would really appreciate some help, thank you.

eg_api.py

#!/usr/bin/python2.7 python2.7
# -*- coding: utf-8 -*-

# kivy modules first, if not Kivy may cause problems
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.label import Label
from kivy.uix.popup import Popup
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import StringProperty
kivy.require('1.10.0')

# common modules
import sys
import signal
from multiprocessing import Process, Queue

# Flask & similar modules
from flask import Flask
from flask import request
from flask_restful import reqparse, abort, Api, Resource
from flask_httpauth import HTTPBasicAuth
import json, ast
import eventlet
from eventlet import wsgi


# imports from other files
from eg_wrapper import Actions

# async server setup
eventlet.monkey_patch()
app = Flask(__name__)
api = Api(app)
auth = HTTPBasicAuth()

# user access setup
USER_DATA = {
    "admin": "SuperSecretPwd"
}

# global variable
data_json = None

@auth.verify_password
def verify(username, password):
    if not (username and password):
        return False
    return USER_DATA.get(username) == password

def start_Flask():
    print("Starting Flask...")
    wsgi.server(eventlet.listen(('', 5000)), app)     # deploy as an eventlet WSGI server


def signal_handler(signal, frame):
    print " CTRL + C detected, exiting ... "
    exit(0)


#######################   flask classes   #########################
class LoadBag(Resource):
    @auth.login_required
    def post(self):
        print "**********  class Load(Resource): *****************"
        data = request.json
        print data
        parsed_data = self.Parse_LoadBag_Request(data)
        #print parsed_data
        #lw = Actions()
        #data_json = lw.Parse_Load_Request(data)
        res = {'data': data_json}
        return res, 201

    def Parse_LoadBag_Request(self, request):     # parse the JSON data
        res={}
        list_of_tasks = []
        data = request
        print "********** Parse_LoadBag_Request(self, request) **********"
        data = ast.literal_eval(json.dumps(data)) # Removing uni-code chars

        # get the value of the key "position", write it to csv and log
        p = data.get('position')
        print "position", p

        # get the value of the key "weight", write it to csv and log
        w = data.get('weight')
        print "weight", w

        # print all elements of the list "tasks" 
        print "number of tasks", (len(data["tasks"]))   # print the number of tasks

        popup = MessageBox10(self)
        popup.open()




#######################   resource calls   #######################
api.add_resource(LoadBag, '/loadbag')


################### kivy classes ###############################

# main screen        
class MainScreen(Screen):
    def __init__(self, **kwargs):
        self.name="MAIN SCREEN"
        super(Screen, self).__init__(**kwargs)

# popup        
class MessageBox10(Popup):
    def __init__(self, obj, **kwargs):
        super(MessageBox10, self).__init__(**kwargs)
        self.obj = obj

# popup        
class MessageBox20(Popup):
    def __init__(self, obj, **kwargs):
        super(MessageBox20, self).__init__(**kwargs)
        self.obj = obj

class Touch(App):
    MainScreenTitle = "MainScreen title"
    MainScreenLabel = "MainScreen label"

    MessageBox10Title = "MessageBox10 title"
    MessageBox10Label = "MessageBox10 label"

    MessageBox20Title = "MessageBox20 title"
    MessageBox20Label = "MessageBox20 label"

    MessageBox30Title = "MessageBox30 title"
    MessageBox30Label = "MessageBox30 label"

    MessageButtonConfirm = "CONFERMA"
    MessageButtonCancel = "CANCELLA"

    def do(self):
        print "do something"

    def cancel(self):
        print "load cancelled by user"

    def exit(self):
        print "exiting..."
        p1.terminate()
        exit(1)

    def enter(self):
        # open the init file and write the parameters
        print "do something"
        #popup = MessageBox10(self)
        #popup.open()

    def popup10(self):
        popup = MessageBox10(self)
        popup.open()

    def build(self):
        sm = Builder.load_string("""

ScreenManager
    MainScreen:
        size_hint: 1, .7
        auto_dismiss: False
        title: app.MainScreenTitle       
        title_align: "center"

        BoxLayout:
            orientation: "vertical"
            Label:
                text: app.MainScreenLabel
            BoxLayout:
                orientation: "horizontal"
                spacing: 10
                size_hint: 1, .5
                Button:
                    text: app.MessageButtonConfirm  # CONFIRM
                    on_press:
                        app.enter()
                Button:
                    text: app.MessageButtonCancel  # CANCEL
                    on_press:
                        app.exit()

<MessageBox20>:
    size_hint: 1, .7
    auto_dismiss: False
    title: app.MessageBox20Title       
    title_align: "center"

    BoxLayout:
        orientation: "vertical"
        Label:
            text: app.MessageBox20Label
        BoxLayout:
            orientation: "horizontal"
            spacing: 10
            size_hint: 1, .5
            Button:
                text: app.MessageButtonConfirm  # "CONFIRM"
                on_press:
                    app.do()
                    root.dismiss()
            Button:
                text: app.MessageButtonCancel  # "CANCEL"
                on_press:
                    app.cancel()
                    root.dismiss()  

<MessageBox10>:
    size_hint: 1, .7
    auto_dismiss: False
    title: app.MessageBox10Title       
    title_align: "center"

    BoxLayout:
        orientation: "vertical"
        Label:
            text: app.MessageBox10Label
        BoxLayout:
            orientation: "horizontal"
            spacing: 10
            size_hint: 1, .5
            Button:
                text: app.MessageButtonConfirm  # "CONFIRM"
                on_press:
                    app.do()
                    root.dismiss()
            Button:
                text: app.MessageButtonCancel  # "CANCEL"
                on_press:
                    app.cancel()
                    root.dismiss() 

        """)

        return sm


if __name__ == '__main__':

    #CTRL+C signal handler
    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)

    global p1
    p1 = Process(target=start_Flask)    # assign Flask to a process
    p1.start()                          # run Flask as process
    Touch().run())                      # run the Kivy UI

UPDATE I have moved the parsing method from the Flask process to the Kivy process. This way I have skipped the use of semaphores and an extra inter-process communication queue. The updated script is below. The parsing now works but the popup blocking the background running script does not. I have checked these two questions: modal view and multithreading kivy but I can not make a popup pause the running script. Any ideas?

CODE

#!/usr/bin/python2.7 python2.7
# -*- coding: utf-8 -*-

# kivy modules first, if not Kivy may cause problems
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.label import Label
from kivy.uix.popup import Popup
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import StringProperty
kivy.require('1.10.0')

# common modules
import sys
import os
import time
import signal
from multiprocessing import Process, Manager, Pool 
from multiprocessing import Queue

# Flask modules
from flask import Flask
from flask import request
from flask_restful import reqparse, abort, Api, Resource

# wsgi (Web Server Gateway Interface) modules
import eventlet
from eventlet import wsgi

# json modules
import json, ast

# async server setup
app = Flask(__name__)
api = Api(app)

# global variables
data_json = None

def start_Flask(q):
    print("Starting Flask...")
    q.put([42, None, 'hello'])
    wsgi.server(eventlet.listen(('', 5000)), app)     # deploy as an eventlet WSGI server

def signal_handler(signal, frame):
    print " CTRL + C detected, exiting ... "
    exit(0)

# api resource classes ###############################################
class LoadBag(Resource):
    @auth.login_required
    def post(self):
        print("# class Load(Resource) => putting request in queue ")
        data = request.json
        package = ("loadbag", data) # create tuple
        q.put(package)  # put tuple into queue

# setup the Api resource routing here ##################################
api.add_resource(Load, '/load')

# kivy gui classes ######################################################
# main screen        
class MainScreen(Screen):
    def __init__(self, **kwargs):
        self.name="MAIN SCREEN"
        super(Screen, self).__init__(**kwargs)

# popup        
class MessageBox(Popup):
    def __init__(self, obj, **kwargs):
        super(MessageBox, self).__init__(**kwargs)
        self.obj = obj

class Touch(App):
    MainScreenTitle = "MainScreen title"
    MainScreenLabel = "MainScreen label"

    MessageBoxTitle = "MessageBoxTitle"
    MessageBoxLabel = "MessageBoxLabel"

    MessageButtonConfirm = "OK"
    MessageButtonCancel = "CANCEL"

    MessageButtonEnter = "GO"
    MessageButtonExit = "EXIT"


    def do(self):
        print "do something"

    def cancel(self):
        print "load cancelled by user"

    def exit(self):
        print "exiting..."
        p1.terminate()
        exit(1)

    def enter(self):
        print "queue element"
        try:
            if q.empty() == False:
                package = q.get()   # retrieve package from queue
                print(package)
                data = package[1]   # assign second tuple element to data
                #print(item)
                if package[0] == "load":                
                    self.Parsing_Request(data)  # begin parsing the data
        except:
            self.MainScreenLabel = "queue is empty"
            print "end of queue"


    def popup(self, text):
        message = text
        #set the messages of the popup
        self.MessageButtonConfirm = "ok"
        self.MessageButtonCancel = "exit"
        self.MessageBoxTitle = "some title"
        self.MessageBoxLabel = message
        popup = MessageBox(self)    #call popup
        popup.open()



    def Parsing_Request(self, data):     # parse the JSON data
        data = ast.literal_eval(json.dumps(data)) # Removing uni-code chars
        print("number of tasks", (len(data["tasks"])))   # print the number of tasks
        for i in range(len(data["tasks"])):
            ad = data["tasks"][i].get("actionDescription")
            print("begin task n.", i, ad)
            list_of_tasks.append(data["tasks"][i]["actionDescription"])

            title = "Parsing request"
            msg = str(i)
            self.popup(msg)


    def build(self):
        sm = Builder.load_string("""

ScreenManager
    MainScreen:
        size_hint: 1, .7
        auto_dismiss: False
        title: app.MainScreenTitle       
        title_align: "center"

        BoxLayout:
            orientation: "vertical"
            Label:
                text: app.MainScreenLabel
            BoxLayout:
                orientation: "horizontal"
                spacing: 10
                size_hint: 1, .5
                Button:
                    text: app.MessageButtonEnter  # start app
                    on_press:
                        app.enter()
                Button:
                    text: app.MessageButtonExit  # exit app
                    on_press:
                        app.exit()

<MessageBox>:
    size_hint: 1, .7
    auto_dismiss: False
    title: app.MessageBoxTitle       
    title_align: "center"

    BoxLayout:
        orientation: "vertical"
        Label:
            text: app.MessageBoxLabel
        BoxLayout:
            orientation: "horizontal"
            spacing: 10
            size_hint: 1, .5
            Button:
                text: app.MessageButtonConfirm  # "CONFIRM"
                on_press:
                    app.do()
                    root.dismiss()
            Button:
                text: app.MessageButtonCancel  # "CANCEL"
                on_press:
                    app.cancel()
                    root.dismiss()

        """)

        return sm


# main #################################################################
if __name__ == '__main__':

    #CTRL+C signal handler
    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)

    q = Queue()

    global p1
    p1 = Process(target=start_Flask, args=(q,)) # assign Flask to a process
    p1.daemon = True
    p1.start()  #launch Flask as separate process
    Touch().run()   # run Kivy app
parovelb
  • 353
  • 4
  • 19
  • At first glance, I would suggest eliminating `p2`, and just call `Kivy().run()` (by the way, bad choice of `App` class name). The GUI expects to run in the main thread, and you will encounter problems if that is not the case. – John Anderson Jan 03 '20 at 15:56
  • Thank you for the advice @JohnAnderson. I updated the code accordingly. – parovelb Jan 04 '20 at 12:17
  • I added the method `def popup10(self): MessageBox10(self).open()` to the `class Touch(App):`. Then I called the method with `Touch().get_running_app().popup10()` from the method `def Parse_LoadBag_Request(self, request):` of the `class LoadBag(Resource):`. Again the result is a new window instead of the popup. – parovelb Jan 04 '20 at 19:35
  • Are you sure you're not just misinterpreting what you see? a `Popup` is just another window. Try changing the `size_hint` in your `:` rule to `size_hint: .7, .7` and see if that looks more like a popup. – John Anderson Jan 04 '20 at 19:47
  • When you call `Touch().get_running_app().popup10()`, you are creating a new instance of the `Touch` app, which is probably creating the window that you see. But since you are not calling the `run()` method of `Touch`, no event loop is being started, so no GUI elements will work correctly. You could start a different app in your `p1` process, and use that to do the `Popup`. But if you just want to show a `Popup`, I think it would be better to use the communication between processes and have the original `Touch` app do the `Popup`. – John Anderson Jan 04 '20 at 20:12
  • I just want to show a Popup in the original Touch. So communication between processes it is the way. Thank you again @JohnAnderson. – parovelb Jan 04 '20 at 20:19
  • I have no experience with `Flask`, but if you can run your flask in a thread instead of a process, the whole issue goes away. – John Anderson Jan 05 '20 at 02:04
  • Regardless if Flask, Tornado or a close relative, running it in a thread is not the way. Flask does not fetch requests while the Kivy GUI is running and vice versa, see [thread](https://stackoverflow.com/questions/59447574/kivy-and-flask-threads-blocking-each-other-in-python2/59544079#59544079). – parovelb Jan 06 '20 at 09:01

0 Answers0