如何在响应式调用上执行PyQt5应用程序

埃里尔·马里蒙

内容:

我有一个Flask应用程序提供资源POST /start要执行的逻辑包括PyQt5 QWebEnginePage加载URL并返回有关它的某些数据。

问题:

当执行QApplication(调用app.exec_())时,我得到警告:

WARNING: QApplication was not created in the main() thread.

然后是错误:

2019-07-17 13:06:19.461 Python[56513:5183122] *** Assertion failure in +[NSUndoManager _endTopLevelGroupings], /BuildRoot/Library/Caches/com.apple.xbs/Sources/Foundation/Foundation-1562/Foundation/Misc.subproj/NSUndoManager.m:361
2019-07-17 13:06:19.464 Python[56513:5183122] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '+[NSUndoManager(NSInternal) _endTopLevelGroupings] is only safe to invoke on the main thread.'
*** First throw call stack:
(
   0   CoreFoundation                      0x00007fff4e1abded __exceptionPreprocess + 256
   1   libobjc.A.dylib                     0x00007fff7a273720 objc_exception_throw + 48
   ...
   ...
   122 libsystem_pthread.dylib             0x00007fff7b53826f _pthread_start + 70
   123 libsystem_pthread.dylib             0x00007fff7b534415 thread_start + 13
)
libc++abi.dylib: terminating with uncaught exception of type NSException
Received signal 6
[0x00010a766de6]
[0x7fff7b52cb3d]
...
...
[0x000105a0de27]
[end of stack trace]

似乎QApplication总是需要在主线程上运行,而事实并非如此,因为flask在后台线程上运行资源。我考虑过的可能解决方案是将QApplication作为os子进程运行,但并不理想。

题:

是否可以将其保留在Flask应用程序中?

示例PyQt类:

import sys

from PyQt5.QtWebEngineWidgets import QWebEnginePage
from PyQt5.QtWebEngineWidgets import QWebEngineProfile
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import QUrl


class PyQtWebClient(QWebEnginePage):
    def __init__(self, url):

        # Pointless variable for showcase purposes
        self.total_runtime = None

        self.app = QApplication(sys.argv)

        self.profile = QWebEngineProfile()

        # This is a sample to show the constructor I am actually using, my 'profile' is more complex than this
        super().__init__(self.profile, None)

        # Register callback to run when the page loads
        self.loadFinished.connect(self._on_load_finished)
        self.load(QUrl(url))
        self.app.exec_()

    def _on_load_finished(self):
        self.total_runtime = 10


if __name__ == '__main__':
    url = "https://www.example.com"
    page = PyQtWebClient(url)

Flask示例app.py

from flask import Flask
from flask_restful import Resource, Api
from lenomi import PyQtWebClient

app = Flask(__name__)
api = Api(app)


class TestPyqt5(Resource):
    def post(self):
        web = PyQtWebClient("http://www.example.com")
        # At this point PyQtWebClient should have finished loading the url, and the process is done
        print(web.total_runtime)


api.add_resource(TestPyqt5, "/pyqt")

if __name__ == '__main__':
    app.run(debug=True)
永乐

资源在辅助线程中执行post,get等方法,以避免执行执行flask的线程不会阻塞,因此QApplication在该辅助线程中运行,而Qt禁止生成该错误。

在这种情况下,解决方案是。

  • 创建一个类,该类通过在主线程上运行的QWebEnginePage处理请求。

  • 使烧瓶在辅助线程上运行,以免阻塞Qt事件循环。

  • 通过post方法和处理请求的类之间的信号发送信息。

考虑到这一点,我实现了一个示例,在该示例中,您可以通过API向页面进行请求,获取该页面的HTML

lenomi.py

from functools import partial

from PyQt5 import QtCore, QtWebEngineWidgets


class Signaller(QtCore.QObject):
    emitted = QtCore.pyqtSignal(object)


class PyQtWebClient(QtCore.QObject):
    @QtCore.pyqtSlot(Signaller, str)
    def get(self, signaller, url):
        self.total_runtime = None
        profile = QtWebEngineWidgets.QWebEngineProfile(self)
        page = QtWebEngineWidgets.QWebEnginePage(profile, self)
        wrapper = partial(self._on_load_finished, signaller)
        page.loadFinished.connect(wrapper)
        page.load(QtCore.QUrl(url))

    @QtCore.pyqtSlot(Signaller, bool)
    def _on_load_finished(self, signaller, ok):
        page = self.sender()
        if not isinstance(page, QtWebEngineWidgets.QWebEnginePage) or not ok:
            signaller.emitted.emit(None)
            return

        self.total_runtime = 10
        html = PyQtWebClient.download_html(page)
        args = self.total_runtime, html
        signaller.emitted.emit(args)

        profile = page.profile()
        page.deleteLater()
        profile.deleteLater()

    @staticmethod
    def download_html(page):
        html = ""
        loop = QtCore.QEventLoop()

        def callback(r):
            nonlocal html
            html = r
            loop.quit()

        page.toHtml(callback)
        loop.exec_()
        return html

app.py

import sys
import threading
from functools import partial

from flask import Flask
from flask_restful import Resource, Api, reqparse

from PyQt5 import QtCore, QtWidgets

from lenomi import PyQtWebClient, Signaller


app = Flask(__name__)
api = Api(app)
parser = reqparse.RequestParser()


class TestPyqt5(Resource):
    def __init__(self, client):
        self.m_client = client

    def post(self):
        parser.add_argument("url", type=str)
        args = parser.parse_args()
        url = args["url"]
        if url:
            total_runtime, html, error = 0, "", "not error"

            def callback(loop, results=None):
                if results is None:
                    nonlocal error
                    error = "Not load"
                else:
                    nonlocal total_runtime, html
                    total_runtime, html = results
                loop.quit()

            signaller = Signaller()
            loop = QtCore.QEventLoop()
            signaller.emitted.connect(partial(callback, loop))
            wrapper = partial(self.m_client.get, signaller, url)
            QtCore.QTimer.singleShot(0, wrapper)
            loop.exec_()

            return {
                "html": html,
                "total_runtime": total_runtime,
                "error": error,
            }


qt_app = None


def main():

    global qt_app
    qt_app = QtWidgets.QApplication(sys.argv)

    client = PyQtWebClient()
    api.add_resource(
        TestPyqt5, "/pyqt", resource_class_kwargs={"client": client}
    )

    threading.Thread(
        target=app.run,
        kwargs=dict(debug=False, use_reloader=False),
        daemon=True,
    ).start()

    return qt_app.exec_()


if __name__ == "__main__":
    sys.exit(main())
curl http://localhost:5000/pyqt -d "url=https://www.example.com" -X POST

输出:

{"html": "<!DOCTYPE html><html><head>\n    <title>Example Domain</title>\n\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <style type=\"text/css\">\n    body {\n        background-color: #f0f0f2;\n        margin: 0;\n        padding: 0;\n        font-family: \"Open Sans\", \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n        \n    }\n    div {\n        width: 600px;\n        margin: 5em auto;\n        padding: 50px;\n        background-color: #fff;\n        border-radius: 1em;\n    }\n    a:link, a:visited {\n        color: #38488f;\n        text-decoration: none;\n    }\n    @media (max-width: 700px) {\n        body {\n            background-color: #fff;\n        }\n        div {\n            width: auto;\n            margin: 0 auto;\n            border-radius: 0;\n            padding: 1em;\n        }\n    }\n    </style>    \n</head>\n\n<body>\n<div>\n    <h1>Example Domain</h1>\n    <p>This domain is established to be used for illustrative examples in documents. You may use this\n    domain in examples without prior coordination or asking for permission.</p>\n    <p><a href=\"http://www.iana.org/domains/example\">More information...</a></p>\n</div>\n\n\n</body></html>", "total_runtime": 10, "error": "not error"}

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

如何在 Windows 上为 pyqt5 应用程序制作包(pypi)?

如何在PyQt5中制作多页应用程序?

如何在PyQt5应用程序中使用Mutagen显示专辑封面?

如何在PyQt5应用程序中从Matplotlib NavigationToolbar更改默认文件名?

如何重启PyQt5应用程序

如何正确运行 pyqt5 应用程序?

如何从另一个 pyqt5 应用程序打开 pyqt5 应用程序?

无法使用嵌入式iPython Qtconsole退出PyQt5应用程序

无法为 pyqt5 gui 应用程序执行脚本 fbs_pyinstaller_hook

带有QML的PyQt5可执行应用程序

如何在没有QProcess的情况下将终端嵌入PyQt5应用程序?

如何在不停止的情况下在 PyQt5 应用程序中运行生成器循环?

如何在 Ionic 应用程序中调用上下文菜单以保存图像?

如何使用 PyQT5 附加和分离外部应用程序或停靠外部应用程序?

PyQt5 终止来自工人的应用程序

UI 应用程序未加载 PyQt5

PyQt5 QFileDialog停止关闭应用程序

调用gnuplot时如何保持PyQt5响应?

如何从 gnome 面板 QML PyQt5 中隐藏正在运行的应用程序图标

如何将叶子地图包含到PyQt5应用程序窗口中?

如何使应用程序窗口在pyqt5中居于首位?

如何让我的 PyQt5 应用程序成为一个实例?

如何实现 .ui 文件以使用 fbs 打包 PyQt5 应用程序?

如何在Python中自动执行交互式控制台应用程序?

如何使用python PyQt5确定我的应用程序(窗口)的活动屏幕(监视器)?

如何让我的pyqt5无框应用程序根据不同的分辨率最大化到Max

如何在Spring Webflux / Reactor Netty Web应用程序中执行阻止调用

如何在Ionic4应用程序的ngInit中执行多个嵌套的Firebase调用

如何在UI5应用程序中设置背景图像并使之响应?