0

I am attempting to create a Dash app hosted by Heroku where the Dash app launches a PyQT5 element. For this to work, Dash app needs to be running on a separate thread, however, I believe this might be an issue within Heroku that the dash app not running on the main thread?

Dash app runs fine locally but get the error QEventLoop: Cannot be used without QApplication when run through Heroku.

I have attached a minimal example below (taken from Show PyQt5 elements from inside Dash callback) which I am trying to host.

Python Code

import functools
import os
import threading

from PyQt5 import QtCore, QtWidgets

import dash
import dash_html_components as html
from dash.dependencies import Input, Output


class MainWindow(QtWidgets.QMainWindow):
    closed = QtCore.pyqtSignal()

    def closeEvent(self, event):
        self.closed.emit()
        super().closeEvent(event)


class Manager(QtCore.QObject):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._view = None

    @property
    def view(self):
        return self._view

    def init_gui(self):
        self._view = MainWindow()

    @QtCore.pyqtSlot()
    def show_popup(self):
        if self.view is not None:
            self.view.show()


qt_manager = Manager()

app = dash.Dash()
server = app.server

app.layout = html.Div(
    children=[
        html.H1(children="Hello Dash"),
        html.Button("show pop up", id="button"),
        html.H2(children="", id="result"),
    ]
)


@app.callback(
    Output(component_id="result", component_property="children"),
    [Input(component_id="button", component_property="n_clicks")],
)
def popUp(n_clicks):
    if not n_clicks:
        raise dash.exceptions.PreventUpdate

    loop = QtCore.QEventLoop()

    print("qt_manager")
    print(qt_manager)
    print("qt_manager.view")
    print(qt_manager.view)

    qt_manager.view.closed.connect(loop.quit)
    QtCore.QMetaObject.invokeMethod(
        qt_manager, "show_popup", QtCore.Qt.QueuedConnection
    )
    loop.exec_()

    return "You saw a pop-up"


def main():
    qt_app = QtWidgets.QApplication.instance()
    if qt_app is None:
        qt_app = QtWidgets.QApplication([os.getcwd()])
    qt_app.setQuitOnLastWindowClosed(False)
    qt_manager.init_gui()
    threading.Thread(
        target=app.run_server, kwargs=dict(debug=False), daemon=True,
    ).start()

    return qt_app.exec_()


if __name__ == "__main__":
    main()

Heroku Output

  • Notice the two print statements showing the elements
    • qt_manager: (<app.Manager object at 0x7f24e2d49040>) and
    • qt_manager.view: (None)
  • When run locally these are printed as:
    • qt_manager: (<__main__.Manager object at 0x7fda0adb9160>) and
    • qt_manager.view: (<__main__.MainWindow object at 0x7fda1141f040>)
2021-10-28T08:50:02.880367+00:00 app[web.1]: QEventLoop: Cannot be used without QApplication
2021-10-28T08:50:02.880380+00:00 app[web.1]: qt_manager
2021-10-28T08:50:02.880380+00:00 app[web.1]: <app.Manager object at 0x7f24e2d49040>
2021-10-28T08:50:02.880380+00:00 app[web.1]: qt_manager.view
2021-10-28T08:50:02.880398+00:00 app[web.1]: None
2021-10-28T08:50:02.881701+00:00 app[web.1]: [2021-10-28 08:50:02,880] ERROR in app: Exception on /_dash-update-component [POST]
2021-10-28T08:50:02.881702+00:00 app[web.1]: Traceback (most recent call last):
2021-10-28T08:50:02.881702+00:00 app[web.1]: File "/app/.heroku/python/lib/python3.9/site-packages/flask/app.py", line 2073, in wsgi_app
2021-10-28T08:50:02.881702+00:00 app[web.1]: response = self.full_dispatch_request()
2021-10-28T08:50:02.881705+00:00 app[web.1]: File "/app/.heroku/python/lib/python3.9/site-packages/flask/app.py", line 1518, in full_dispatch_request
2021-10-28T08:50:02.881705+00:00 app[web.1]: rv = self.handle_user_exception(e)
2021-10-28T08:50:02.881705+00:00 app[web.1]: File "/app/.heroku/python/lib/python3.9/site-packages/flask/app.py", line 1516, in full_dispatch_request
2021-10-28T08:50:02.881705+00:00 app[web.1]: rv = self.dispatch_request()
2021-10-28T08:50:02.881706+00:00 app[web.1]: File "/app/.heroku/python/lib/python3.9/site-packages/flask/app.py", line 1502, in dispatch_request
2021-10-28T08:50:02.881706+00:00 app[web.1]: return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
2021-10-28T08:50:02.881706+00:00 app[web.1]: File "/app/.heroku/python/lib/python3.9/site-packages/dash/dash.py", line 1336, in dispatch
2021-10-28T08:50:02.881707+00:00 app[web.1]: response.set_data(func(*args, outputs_list=outputs_list))
2021-10-28T08:50:02.881707+00:00 app[web.1]: File "/app/.heroku/python/lib/python3.9/site-packages/dash/_callback.py", line 151, in add_context
2021-10-28T08:50:02.881708+00:00 app[web.1]: output_value = func(*func_args, **func_kwargs)  # %% callback invoked %%
2021-10-28T08:50:02.881708+00:00 app[web.1]: File "/app/app.py", line 67, in popUp
2021-10-28T08:50:02.881708+00:00 app[web.1]: qt_manager.view.closed.connect(loop.quit)
2021-10-28T08:50:02.881709+00:00 app[web.1]: AttributeError: 'NoneType' object has no attribute 'closed'
  • In the linked answer, the application and UI "init" are initialized in the `popUp`, but here you're doing that in the `if __name__` block. If you're running the code from Heroku, that code is not in the `__main__` anymore, so that block won't be executed. – musicamante Oct 28 '21 at 09:20
  • Thanks @musicmante. So the `main()` inside `if __name__ == "__main__":` is not being run? How do I get that code to execute in Heroku? – Kevin Sweeney Oct 28 '21 at 10:43
  • Yes: that block is only executed when the script is being run as the *main* script (as the name says), otherwise it's not. Unfortunately I don't know about Heroku, and maybe there's some way to check if it's being run in the Heroku app in the `dash` module; if that's the case, then you should put that checking along with the `if __name__ ...`. – musicamante Oct 28 '21 at 11:00

0 Answers0