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>)
andqt_manager.view: (None)
- When run locally these are printed as:
qt_manager: (<__main__.Manager object at 0x7fda0adb9160>)
andqt_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'