首页 > 其他分享 >PySide/PyQt中使网络请求更加方便简洁的实践

PySide/PyQt中使网络请求更加方便简洁的实践

时间:2024-07-10 12:59:23浏览次数:12  
标签:return PySide QPromise self PyQt 中使 lastFuncResult Any def

众所周知,在PySide中,想要发送网络请求且不阻塞GUI线程,需要使用QNetworkAccessManager,但是这个东西用起来十分麻烦,需要写很多槽函数,而且必须要绑定在对象上,否则会报空指针。

这种写法非常不优雅,而且让代码变得十分复杂。因此在写项目的实践中,我写了这样一个库,可以简化网络请求,特此分享出来。

声明:其使用方法可能有点像JS中的Promise,不过笔者精力和技术有限,没有深入了解过异步编程和JS Promise,所以下面分享出来的可能不是最好的方法,但至少解决了笔者自己在项目中的问题。

废话少说,show you the code。

这是一个基本示例:

def test1():
    app = QApplication()
    print("不阻塞1")
    (
        QRequestReady(app)
            .get("https://www.example.com")
            .then(lambda content: print("do then 1"))
            .catch(lambda error: print(f"error: {error.name}"))
            .done()
    )
    print("不阻塞2")
    (
        QRequestReady(app)
            .get("https://www.example.com")
            .then(lambda content: print("do then 2"))
            .catch(lambda error: print(f"error: {error.name}"))
            .done()
    )
    app.exec()

输出:

不阻塞1
不阻塞2
do then 2
do then 1

可以看到这里的请求是完全不阻塞主线程的。

同时,你也可以在then中返回任何值,它会作为下一个then函数的参数传入,这让你可以很方便的把一些网络数据的处理逻辑给封装起来。

ps:QRequestReady.get方法会返回一个QPromise对象。

例如函数getResult中,你可能需要对请求api后返回的json进行一些过滤,你可以把这部分逻辑放到then中,然后返回这个QPromise对象,这样上级调用者就可以使用处理好的返回值进行其它操作,例如更新视图。

def test2():
    """测试能否正确传递返回值"""
    app = QApplication()

    def getResult():
        return (
            QRequestReady(app)
                .get("https://www.example.com")
                .then(lambda content: "some value")
            )
    getResult().then(lambda value: print(value)).catch(lambda error: print(error)).done()
    app.exec()

输出:

some value

而在一些复杂的功能中,你可能需要针对请求的数据,再次发送请求。因此QPromise对于返回一个QPromise的then函数,会把之后的所有then函数都放到这个QPromise的then函数列表末尾,然后启动这个QPromise。

简单来说就是QPromise会自动处理串联请求。

阅读下面这个示例代码应该可以帮助你理解我的意思:

def test3():
    # 测试在then中返回QPromise
    app = QApplication()
    request = QRequestReady(app)
    # 创建一个请求,在then中返回访问baidu的promise,所以这里应该会打印出baidu网页
    (
        request.get("https://www.example.com")
        .then(lambda _: request.get("https://www.baidu.com"))
        .then(lambda content: print(content.decode()))
        .done()
    )
    print("不阻塞")
    app.exec()

输出:

不阻塞
<html>
<head>
        <script>
                location.replace(location.href.replace("https://","http://"));
        </script>
</head>
<body>
        <noscript><meta http-equiv="refresh" content="0;url=http://www.baidu.com/"></noscript>
</body>
</html>

由于时间和精力有限,这个库没有做到完善,很多地方可能会有bug,这里面也没有支持像js promise那样的raise和catch链。

这里将源码放出来分享给大家。

这些代码完全为笔者本人原创,采用BSD许可证开源。

如果你需要使用或者基于笔者的代码进行二次开发和修改,请务必留下这篇文章的链接与笔者的名字

from enum import Enum
from typing import Any, Callable, List, Self
from PySide6 import QtNetwork
from PySide6 import QtCore
from PySide6.QtCore import QObject
from PySide6.QtNetwork import QNetworkAccessManager
from PySide6.QtWidgets import QApplication

from features.common.tricks import interfaceMethod


class GlobalQNetworkAccessManager:
    """全局网络访问管理器

    使用单例模式,每次new出来都是同一个实例。
    """

    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = QNetworkAccessManager()
        return cls._instance

class QRequestMode(Enum):
    GET = 1
    POST = 2
    PUT = 3

class QPromise:
    def __init__(self):
        self.thenFuncList: List[Callable[..., Any]] = []
        self.catchFunc: Callable[..., Any] | None = None

    def then(self, func: Callable[..., Any]) -> Self:
        self.thenFuncList.append(func)
        return self

    def catch(self, func: Callable[..., Any]) -> Self:
        self.catchFunc = func
        return self

    @interfaceMethod
    def done(self) -> Self: ...

class QRequestPromise(QObject, QPromise):
    """请求Promise

    封装了Qt网络请求相关的调用,使用方法详见`QRequestReady`
    """

    def __init__(self, parent: QObject, mode: QRequestMode, url: str):
        super().__init__(parent)
        self.url: str = url
        self.mode = mode
        self.lastFuncResult: Any = None

    def then(self, func: Callable[..., Any]) -> Self:
        self.thenFuncList.append(func)
        return self

    def catch(self, func: Callable[..., Any]) -> Self:
        self.catchFunc = func
        return self

    def done(self) -> Self:
        request = QtNetwork.QNetworkRequest(QtCore.QUrl(self.url))
        match self.mode:
            case QRequestMode.GET:
                self.reply = GlobalQNetworkAccessManager().get(request)
            case QRequestMode.POST:
                raise NotImplementedError("QRequest POST not implemented")
            case QRequestMode.PUT:
                raise NotImplementedError("QRequest PUT not implemented")
        self.reply.finished.connect(self.onFinished)
        self.reply.errorOccurred.connect(self.onErrorOccurred)
        return self

    def onFinished(self) -> None:
        if self.reply.error() != QtNetwork.QNetworkReply.NetworkError.NoError:
            return
        content = bytes(self.reply.readAll().data())
        self.lastFuncResult: Any = content
        for index, thenFunc in enumerate(self.thenFuncList):
            self.lastFuncResult = thenFunc(self.lastFuncResult)
            # 如果这个函数返回的也是QPromise,则不再执行后续的thenFunc
            # 而是把后续的thenFunc全部放到这个QPromise中
            # 等这个QPromise执行完毕,再由它去执行后续的thenFunc
            if isinstance(self.lastFuncResult, QPromise):
                self.lastFuncResult.thenFuncList.extend(self.thenFuncList[index + 1:])
                self.lastFuncResult.done()
                break
        self.reply.deleteLater()

    def one rrorOccurred(self, error: QtNetwork.QNetworkReply.NetworkError) -> None:
        if self.catchFunc:
            result = self.catchFunc(error)
        self.reply.deleteLater()

class QDataPromise(QPromise):
    def __init__(self, data: Any):
        self.data = data
        self.thenFuncList: List[Callable[..., Any]] = []
        self.catchFunc: Callable[[QtNetwork.QNetworkReply.NetworkError], Any] | None = None
        self.lastFuncResult = None
    def then(self, func: Callable[..., Any]) -> Self:
        self.thenFuncList.append(func)
        return self
    def catch(self, func: Callable[[QtNetwork.QNetworkReply.NetworkError], Any]) -> Self:
        self.catchFunc = func
        return self
    def done(self) -> Self:
        self.lastFuncResult: Any = self.data
        for index, thenFunc in enumerate(self.thenFuncList):
            self.lastFuncResult = thenFunc(self.lastFuncResult)
            if isinstance(self.lastFuncResult, QPromise):
                self.lastFuncResult.thenFuncList.extend(self.thenFuncList[index + 1:])
                self.lastFuncResult.done()
                break
        return self



class QRequestReady(QObject):
    """Qt异步请求的封装

    在QRequest之上封装了parent和mode参数,使得使用更加方便,且可以复用RequestReady对象,减少重复代码。

    使用示例:
    ```
    (
        RequestReady(app)
        .get("https://www.example.com")
        .then(lambda content: print("do then"))
        .catch(lambda error: print(f"error: {error.name}"))
        .done()
    )
    ```

    其中then可以附加多个,前一个的返回值将作为后一个的参数。
    由于网络请求需要时间,因此done函数调用后再附加then一般也可以正常运行。
    但安全起见,建议封装功能函数中把自己的then附加上即可,catch和done都交给调用者来执行。
    """

    def __init__(self, parent: QObject):
        super().__init__(parent)

    def get(self, url: str) -> QRequestPromise:
        return QRequestPromise(self, QRequestMode.GET, url)

    def post(self, url: str) -> QRequestPromise:
        return QRequestPromise(self, QRequestMode.POST, url)

    def put(self, url: str) -> QRequestPromise:
        return QRequestPromise(self, QRequestMode.PUT, url)

标签:return,PySide,QPromise,self,PyQt,中使,lastFuncResult,Any,def
From: https://www.cnblogs.com/rech/p/18293838

相关文章

  • PySide/PyQt中使网络请求更加方便简洁的实践
    众所周知,在PySide中,想要发送网络请求且不阻塞GUI线程,需要使用QNetworkAccessManager,但是这个东西用起来十分麻烦,需要写很多槽函数,而且不能必须要绑定在对象上,否则会报空指针。这种写法非常不优雅,而且让代码变得十分复杂。因此在写项目的实践中,我写了这样一个库,可以简化网络请求,特......
  • 【PHP】关于fastadmin框架中使用with进行连表查询时setEagerlyType字段的理解
    前言FastAdmin是我第一个接触的后台管理系统框架。FastAdmin是一款开源且免费商用的后台开发框架,它基于ThinkPHP和Bootstrap两大主流技术构建的极速后台开发框架,它有着非常完善且强大的功能和便捷的开发体验,使我逐渐喜欢上了它。什么是setEagerlyType?1.回归正题,什么是setEagerl......
  • 在Windows中使用开源高性能编辑器Zed(持续更新)
    简介“Zedisahigh-performance,multiplayercodeeditorfromthecreatorsofAtomandTree-sitter.It'salsoopensource.”“Zed是一款高性能的支持多人协作的代码编辑器,由Atom和Tree-sitter的创建者开发。它也是开源的。”Zed主打“高性能”,实际体验下来,无论启动编......
  • 2024已过半,还没试过在vue3中使用ioc容器吗?
    Vue3已经非常强大和灵活了,为什么还要引入IOC容器呢?IOC容器离不开Class,那么我们就从Class谈起Class的应用场景一提起Class,大家一定会想到这是Vue官方不再推荐的代码范式。其实,更确切的说,Vue官方是不推荐基于Class来定义Vue组件。如图所示:社区确实有几款基于Clas......
  • 新知识get,vue3是如何实现在style中使用响应式变量?
    前言vue2的时候想必大家有遇到需要在style模块中访问script模块中的响应式变量,为此我们不得不使用css变量去实现。现在vue3已经内置了这个功能啦,可以在style中使用v-bind指令绑定script模块中的响应式变量,这篇文章我们来讲讲vue是如何实现在style中使用script模块中的响应式变量......
  • 如何在 Linux 中使用 ACL、chmod 和 chown 进行文件权限控制
    在Linux系统中,ACL(访问控制列表)、chown和chmod是管理文件和目录权限的主要工具。本文将详细介绍如何在CentOS中使用这些工具进行权限控制,并对它们的区别和具体用法进行说明。1.基本概念ACL(AccessControlList)ACL(AccessControlList)是一种用于控制文件和目录访问......
  • Windows 11 中使用 Win10的文件资源管理器!
    1.在Windows11中恢复旧文件资源管理器,首先打开记事本并粘贴以下文本代码:WindowsRegistryEditorVersion5.00[HKEY_CURRENT_USER\Software\Classes\CLSID\{2aa9162e-c906-4dd9-ad0b-3d24a8eef5a0}]@="CLSID_ItemsViewAdapter"[HKEY_CURRENT_USER\Software\Classes\CLS......
  • python pyqt5学习记录(三)
    一、布局在PyQt5中,可以使用QHBoxLayout来创建水平布局,使用QVBoxLayout来创建垂直布局。以下是一个简单的例子,展示了如何将两个按钮分别放置在水平和垂直布局中。importsysfromPyQt5.QtWidgetsimportQApplication,QWidget,QPushButton,QVBoxLayout,QHBoxLayoutcla......
  • Spring MVC 中使用 RESTFul 编程风格
    1.SpringMVC中使用RESTFul编程风格@目录1.SpringMVC中使用RESTFul编程风格2.RESTFul编程风格2.1RESTFul是什么2.2RESTFul风格与传统方式对比3.SpringMVC中使用RESTFul编程风格(增删改查)的使用3.1准备工作3.2RESTFul风格的“查询”所有(RESTFul规范......
  • vue项目中使用AES实现密码加密解密ECB和CBC模式)
    ECB:是一种基础的加密方式,密文被分割成分组长度相等的块(不足补齐),然后单独一个个加密,一个个输出组成密文。CBC:是一种循环模式,前一个分组的密文和当前分组的明文异或或操作后再加密,这样做的目的是增强破解难度。(不容易主动攻击,安全性好于ECB,是SSL、IPSec的标准) 1.先安装crypto-......