Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Run an async web server along with PyQt5



  • Hello,
    I'm trying to run an async web server like Sanic along side my PyQt5 app, but the web server api requires that i run a function .run which runs an event loop, and i'm having trouble running it along side the PyQt5 event loop:

    I tried to run it inside a Qthread but even then it makes the app freeze because the .run function never returns until the app is closed.

    My example Qthread class:

    
    class api_server(qtc.QObject):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.api = Sanic(name='test')
            self.api.add_route(self.test, "/")
    
        def run(self):
            
            self.api.run(host="0.0.0.0", port=8080)
     
        async def test(self,request):
            return json({"hello": "world"})
    

    Then in my main qtw.QMainWindow init method I'm doing the following:

    self._api_server_thread = qtc.QThread()
    self._api_server = api.api_server()
    self._api_server_thread.started.connect(self._api_server.run)
    self._api_server.moveToThread(self._api_server_thread)
    self._api_server_thread.start()
    

    Regards


  • Lifetime Qt Champion

    Hi,

    Why do you need a full blown web server as part of your GUI application ?

    That said you may have to consider using the Python subprocess module rather than multithreading.



  • @SGaist said in Run an async web server along with PyQt5:

    Hi,

    Why do you need a full blown web server as part of your GUI application ?

    To provide an api for another app running on a different machine and since my gui app is already connected the sqlite database i want to run the server from the same code to keep resource usage to a minimum

    That said you may have to consider using the Python subprocess module rather than multithreading.

    That means i need to run the server from a different python file which means i can't reference the server from inside my app code.

    There was a proposed qt component a couple of years ago which would have been perfect for my use case, sadly it was never merged upstream

    Regards


  • Lifetime Qt Champion

    That doesn't answer my question about the why.

    Understanding the why may help found how.

    From memory QHttpServer is a separate module. If you really want it in Python, you can create the necessary bindings yourself.



  • @SGaist said in Run an async web server along with PyQt5:

    That doesn't answer my question about the why.

    Understanding the why may help found how.

    The gui app is connected to an SQLITE database, along side the main app function, it should also provide an api that can be called from a different machine, this api can be used to control the main app(provide records from the database, delete, insert as well as running functions that changes the Widget appearance which is why i need the server to be called from my pyqt app code

    From memory QHttpServer is a separate module. If you really want it in Python, you can create the necessary bindings yourself.

    That would be too much of a hassle and i have no prior experience in that space

    Regards


  • Lifetime Qt Champion

    In that case, I would suggest to have these two cleanly separated and have your GUI connect to the API server as well for example through a local socket.

    They can both connect to the same database if required. Establish a protocol for whatever needs to be changed in your GUI based on what is called your API server.

    This has the added advantage that your GUI can crash without killing the server and vice-versa.



  • @rhx9 A simple solution is to use qasync:

    # https://github.com/sanic-org/sanic/blob/main/examples/run_async.py
    import asyncio
    from signal import signal, SIGINT
    import sys
    
    from sanic import Sanic
    from sanic.response import json
    
    from PyQt5.QtCore import QDateTime, Qt, QTimer
    from PyQt5.QtWidgets import QApplication, QLabel
    
    import qasync
    
    
    app = Sanic(__name__)
    
    
    @app.route("/")
    async def test(request):
        return json({"hello": "world"})
    
    
    def main():
        qtapp = QApplication(sys.argv)
        loop = qasync.QEventLoop(qtapp)
        asyncio.set_event_loop(loop)
    
        w = QLabel(alignment=Qt.AlignmentFlag.AlignCenter)
        w.resize(640, 480)
        w.show()
    
        def handle_timeout():
            w.setText(QDateTime.currentDateTime().toString())
    
        timer = QTimer()
        timer.setInterval(1000)
        timer.timeout.connect(handle_timeout)
        timer.start()
        handle_timeout()
    
        server = app.create_server(host="0.0.0.0", port=8000, return_asyncio_server=True)
        loop = asyncio.get_event_loop()
        task = asyncio.ensure_future(server)
        server = loop.run_until_complete(task)
        loop.run_until_complete(server.startup())
        signal(SIGINT, lambda s, f: loop.stop())
    
        try:
            loop.run_forever()
        finally:
            loop.stop()
    
    
    if __name__ == "__main__":
        main()
    

Log in to reply