qt-async-threads


Nameqt-async-threads JSON
Version 0.6.0 PyPI version JSON
download
home_pagehttps://qt-async-threads.readthedocs.io/
SummaryUse convenient async/await syntax to spawn threads in Qt applications
upload_time2024-02-11 13:41:37
maintainer
docs_urlNone
authorBruno Oliveira
requires_python>=3.10
licenseMIT
keywords qt async threads source=https://github.com/nicoddemus/qt-async-threads docs=https://qt-async-threads.readthedocs.io
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            ================
qt-async-threads
================

.. image:: https://img.shields.io/pypi/v/qt-async-threads.svg
    :target: https://pypi.org/project/qt-async-threads/

.. image:: https://img.shields.io/conda/vn/conda-forge/qt-async-threads.svg
    :target: https://anaconda.org/conda-forge/qt-async-threads

.. image:: https://img.shields.io/pypi/pyversions/qt-async-threads.svg
    :target: https://pypi.org/project/qt-async-threads/

.. image:: https://github.com/nicoddemus/qt-async-threads/workflows/test/badge.svg
    :target: https://github.com/nicoddemus/qt-async-threads/actions?query=workflow%3Atest

.. image:: https://results.pre-commit.ci/badge/github/nicoddemus/qt-async-threads/main.svg
    :target: https://results.pre-commit.ci/latest/github/nicoddemus/qt-async-threads/main
    :alt: pre-commit.ci status

.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
    :target: https://github.com/psf/black

.. image:: https://readthedocs.org/projects/qt-async-threads/badge/?version=latest
    :target: https://qt-async-threads.readthedocs.io/en/latest/?badge=latest

----

``qt-async-threads`` allows Qt applications to use convenient ``async/await`` syntax to run
computational intensive or IO operations in threads, selectively changing the code slightly
to provide a more responsive UI.

The objective of this library is to provide a simple and convenient way to improve
UI responsiveness in existing Qt applications by using ``async/await``, while
at the same time not requiring large scale refactorings.

Supports `PyQt5`_, `PyQt6`_, `PySide2`_, and `PySide6`_ thanks to `qtpy`_.

Example
=======

The widget below downloads pictures of cats when the user clicks on a button (some parts omitted for brevity):

.. code-block:: python

    class CatsWidget(QWidget):
        def __init__(self, parent: QWidget) -> None:
            ...
            self.download_button.clicked.connect(self._on_download_button_clicked)

        def _on_download_button_clicked(self, checked: bool = False) -> None:
            self.progress_label.setText("Searching...")

            api_url = "https://api.thecatapi.com/v1/images/search"

            for i in range(10):
                try:
                    # Search.
                    search_response = requests.get(api_url)
                    self.progress_label.setText("Found, downloading...")

                    # Download.
                    url = search_response.json()[0]["url"]
                    download_response = requests.get(url)
                except ConnectionError as e:
                    QMessageBox.critical(self, "Error", f"Error: {e}")
                    return

                self._save_image_file(download_response)
                self.progress_label.setText(f"Done downloading image {i}.")

            self.progress_label.setText(f"Done, {downloaded_count} cats downloaded")


This works well, but while the pictures are being downloaded the UI will freeze a bit,
becoming unresponsive.

With ``qt-async-threads``, we can easily change the code to:

.. code-block:: python

    class CatsWidget(QWidget):
        def __init__(self, runner: QtAsyncRunner, parent: QWidget) -> None:
            ...
            # QtAsyncRunner allows us to submit code to threads, and
            # provide a way to connect async functions to Qt slots.
            self.runner = runner

            # `to_sync` returns a slot that Qt's signals can call, but will
            # allow it to asynchronously run code in threads.
            self.download_button.clicked.connect(
                self.runner.to_sync(self._on_download_button_clicked)
            )

        async def _on_download_button_clicked(self, checked: bool = False) -> None:
            self.progress_label.setText("Searching...")

            api_url = "https://api.thecatapi.com/v1/images/search"

            for i in range(10):
                try:
                    # Search.
                    # `self.runner.run` calls requests.get() in a thread,
                    # but without blocking the main event loop.
                    search_response = await self.runner.run(requests.get, api_url)
                    self.progress_label.setText("Found, downloading...")

                    # Download.
                    url = search_response.json()[0]["url"]
                    download_response = await self.runner.run(requests.get, url)
                except ConnectionError as e:
                    QMessageBox.critical(self, "Error", f"Error: {e}")
                    return

                self._save_image_file(download_response)
                self.progress_label.setText(f"Done downloading image {i}.")

            self.progress_label.setText(f"Done, {downloaded_count} cats downloaded")

By using a `QtAsyncRunner`_ instance and changing the slot to an ``async`` function, the ``runner.run`` calls
will run the requests in a thread, without blocking the Qt event loop, making the UI snappy and responsive.

Thanks to the ``async``/``await`` syntax, we can keep the entire flow in the same function as before,
including handling exceptions naturally.

We could rewrite the first example using a `ThreadPoolExecutor`_ or `QThreads`_,
but that would require a significant rewrite.



Documentation
=============

For full documentation, please see https://qt-async-threads.readthedocs.io/en/latest.

Differences with other libraries
================================

There are excellent libraries that allow to use async frameworks with Qt:

* `qasync`_ integrates with `asyncio`_
* `qtrio`_ integrates with `trio`_

Those libraries fully integrate with their respective frameworks, allowing the application to asynchronously communicate
with sockets, threads, file system, tasks, cancellation systems, use other async libraries
(such as `httpx`_), etc.

They are very powerful in their own right, however they have one downside in that they require your ``main``
entry point to also be ``async``, which might be hard to accommodate in an existing application.

``qt-async-threads``, on the other hand, focuses only on one feature: allow the user to leverage ``async``/``await``
syntax to *handle threads more naturally*, without the need for major refactorings in existing applications.

License
=======

Distributed under the terms of the `MIT`_ license.

.. _MIT: https://github.com/pytest-dev/pytest-mock/blob/master/LICENSE
.. _PyQt5: https://pypi.org/project/PyQt5/
.. _PyQt6: https://pypi.org/project/PyQt6/
.. _PySide2: https://pypi.org/project/PySide2/
.. _PySide6: https://pypi.org/project/PySide6/
.. _QThreads: https://doc.qt.io/qt-5/qthread.html
.. _QtAsyncRunner: https://qt-async-threads.readthedocs.io/en/latest/reference.html#qt_async_threads.QtAsyncRunner
.. _ThreadPoolExecutor: https://docs.python.org/3/library/concurrent.futures.html#threadpoolexecutor
.. _asyncio: https://docs.python.org/3/library/asyncio.html
.. _httpx: https://www.python-httpx.org
.. _qasync: https://pypi.org/project/qasync
.. _qtpy: https://pypi.org/project/qtpy/
.. _qtrio: https://pypi.org/project/qtrio
.. _trio: https://pypi.org/project/trio

            

Raw data

            {
    "_id": null,
    "home_page": "https://qt-async-threads.readthedocs.io/",
    "name": "qt-async-threads",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.10",
    "maintainer_email": "",
    "keywords": "qt, async, threads,Source=https://github.com/nicoddemus/qt-async-threads,Docs=https://qt-async-threads.readthedocs.io",
    "author": "Bruno Oliveira",
    "author_email": "nicoddemus@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/e3/0a/2e894dc389a6a0680d87ce9b05bada21ddeabb2c0994e969428991caceac/qt-async-threads-0.6.0.tar.gz",
    "platform": null,
    "description": "================\nqt-async-threads\n================\n\n.. image:: https://img.shields.io/pypi/v/qt-async-threads.svg\n    :target: https://pypi.org/project/qt-async-threads/\n\n.. image:: https://img.shields.io/conda/vn/conda-forge/qt-async-threads.svg\n    :target: https://anaconda.org/conda-forge/qt-async-threads\n\n.. image:: https://img.shields.io/pypi/pyversions/qt-async-threads.svg\n    :target: https://pypi.org/project/qt-async-threads/\n\n.. image:: https://github.com/nicoddemus/qt-async-threads/workflows/test/badge.svg\n    :target: https://github.com/nicoddemus/qt-async-threads/actions?query=workflow%3Atest\n\n.. image:: https://results.pre-commit.ci/badge/github/nicoddemus/qt-async-threads/main.svg\n    :target: https://results.pre-commit.ci/latest/github/nicoddemus/qt-async-threads/main\n    :alt: pre-commit.ci status\n\n.. image:: https://img.shields.io/badge/code%20style-black-000000.svg\n    :target: https://github.com/psf/black\n\n.. image:: https://readthedocs.org/projects/qt-async-threads/badge/?version=latest\n    :target: https://qt-async-threads.readthedocs.io/en/latest/?badge=latest\n\n----\n\n``qt-async-threads`` allows Qt applications to use convenient ``async/await`` syntax to run\ncomputational intensive or IO operations in threads, selectively changing the code slightly\nto provide a more responsive UI.\n\nThe objective of this library is to provide a simple and convenient way to improve\nUI responsiveness in existing Qt applications by using ``async/await``, while\nat the same time not requiring large scale refactorings.\n\nSupports `PyQt5`_, `PyQt6`_, `PySide2`_, and `PySide6`_ thanks to `qtpy`_.\n\nExample\n=======\n\nThe widget below downloads pictures of cats when the user clicks on a button (some parts omitted for brevity):\n\n.. code-block:: python\n\n    class CatsWidget(QWidget):\n        def __init__(self, parent: QWidget) -> None:\n            ...\n            self.download_button.clicked.connect(self._on_download_button_clicked)\n\n        def _on_download_button_clicked(self, checked: bool = False) -> None:\n            self.progress_label.setText(\"Searching...\")\n\n            api_url = \"https://api.thecatapi.com/v1/images/search\"\n\n            for i in range(10):\n                try:\n                    # Search.\n                    search_response = requests.get(api_url)\n                    self.progress_label.setText(\"Found, downloading...\")\n\n                    # Download.\n                    url = search_response.json()[0][\"url\"]\n                    download_response = requests.get(url)\n                except ConnectionError as e:\n                    QMessageBox.critical(self, \"Error\", f\"Error: {e}\")\n                    return\n\n                self._save_image_file(download_response)\n                self.progress_label.setText(f\"Done downloading image {i}.\")\n\n            self.progress_label.setText(f\"Done, {downloaded_count} cats downloaded\")\n\n\nThis works well, but while the pictures are being downloaded the UI will freeze a bit,\nbecoming unresponsive.\n\nWith ``qt-async-threads``, we can easily change the code to:\n\n.. code-block:: python\n\n    class CatsWidget(QWidget):\n        def __init__(self, runner: QtAsyncRunner, parent: QWidget) -> None:\n            ...\n            # QtAsyncRunner allows us to submit code to threads, and\n            # provide a way to connect async functions to Qt slots.\n            self.runner = runner\n\n            # `to_sync` returns a slot that Qt's signals can call, but will\n            # allow it to asynchronously run code in threads.\n            self.download_button.clicked.connect(\n                self.runner.to_sync(self._on_download_button_clicked)\n            )\n\n        async def _on_download_button_clicked(self, checked: bool = False) -> None:\n            self.progress_label.setText(\"Searching...\")\n\n            api_url = \"https://api.thecatapi.com/v1/images/search\"\n\n            for i in range(10):\n                try:\n                    # Search.\n                    # `self.runner.run` calls requests.get() in a thread,\n                    # but without blocking the main event loop.\n                    search_response = await self.runner.run(requests.get, api_url)\n                    self.progress_label.setText(\"Found, downloading...\")\n\n                    # Download.\n                    url = search_response.json()[0][\"url\"]\n                    download_response = await self.runner.run(requests.get, url)\n                except ConnectionError as e:\n                    QMessageBox.critical(self, \"Error\", f\"Error: {e}\")\n                    return\n\n                self._save_image_file(download_response)\n                self.progress_label.setText(f\"Done downloading image {i}.\")\n\n            self.progress_label.setText(f\"Done, {downloaded_count} cats downloaded\")\n\nBy using a `QtAsyncRunner`_ instance and changing the slot to an ``async`` function, the ``runner.run`` calls\nwill run the requests in a thread, without blocking the Qt event loop, making the UI snappy and responsive.\n\nThanks to the ``async``/``await`` syntax, we can keep the entire flow in the same function as before,\nincluding handling exceptions naturally.\n\nWe could rewrite the first example using a `ThreadPoolExecutor`_ or `QThreads`_,\nbut that would require a significant rewrite.\n\n\n\nDocumentation\n=============\n\nFor full documentation, please see https://qt-async-threads.readthedocs.io/en/latest.\n\nDifferences with other libraries\n================================\n\nThere are excellent libraries that allow to use async frameworks with Qt:\n\n* `qasync`_ integrates with `asyncio`_\n* `qtrio`_ integrates with `trio`_\n\nThose libraries fully integrate with their respective frameworks, allowing the application to asynchronously communicate\nwith sockets, threads, file system, tasks, cancellation systems, use other async libraries\n(such as `httpx`_), etc.\n\nThey are very powerful in their own right, however they have one downside in that they require your ``main``\nentry point to also be ``async``, which might be hard to accommodate in an existing application.\n\n``qt-async-threads``, on the other hand, focuses only on one feature: allow the user to leverage ``async``/``await``\nsyntax to *handle threads more naturally*, without the need for major refactorings in existing applications.\n\nLicense\n=======\n\nDistributed under the terms of the `MIT`_ license.\n\n.. _MIT: https://github.com/pytest-dev/pytest-mock/blob/master/LICENSE\n.. _PyQt5: https://pypi.org/project/PyQt5/\n.. _PyQt6: https://pypi.org/project/PyQt6/\n.. _PySide2: https://pypi.org/project/PySide2/\n.. _PySide6: https://pypi.org/project/PySide6/\n.. _QThreads: https://doc.qt.io/qt-5/qthread.html\n.. _QtAsyncRunner: https://qt-async-threads.readthedocs.io/en/latest/reference.html#qt_async_threads.QtAsyncRunner\n.. _ThreadPoolExecutor: https://docs.python.org/3/library/concurrent.futures.html#threadpoolexecutor\n.. _asyncio: https://docs.python.org/3/library/asyncio.html\n.. _httpx: https://www.python-httpx.org\n.. _qasync: https://pypi.org/project/qasync\n.. _qtpy: https://pypi.org/project/qtpy/\n.. _qtrio: https://pypi.org/project/qtrio\n.. _trio: https://pypi.org/project/trio\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Use convenient async/await syntax to spawn threads in Qt applications",
    "version": "0.6.0",
    "project_urls": {
        "Homepage": "https://qt-async-threads.readthedocs.io/"
    },
    "split_keywords": [
        "qt",
        " async",
        " threads",
        "source=https://github.com/nicoddemus/qt-async-threads",
        "docs=https://qt-async-threads.readthedocs.io"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "fa34358d069cddfc62699ba12302529ab47c87262e88ac1e41b2cb055bedf3d0",
                "md5": "e8efbb8fa8352b5a7d35e8735069cdbf",
                "sha256": "4cbb534573247f1212bc6575cbc8bebef8a5b814313eff364fa812e506f91a3f"
            },
            "downloads": -1,
            "filename": "qt_async_threads-0.6.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "e8efbb8fa8352b5a7d35e8735069cdbf",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.10",
            "size": 13210,
            "upload_time": "2024-02-11T13:41:35",
            "upload_time_iso_8601": "2024-02-11T13:41:35.609522Z",
            "url": "https://files.pythonhosted.org/packages/fa/34/358d069cddfc62699ba12302529ab47c87262e88ac1e41b2cb055bedf3d0/qt_async_threads-0.6.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "e30a2e894dc389a6a0680d87ce9b05bada21ddeabb2c0994e969428991caceac",
                "md5": "52ebec0a6586ebf9fe56b17eec7e27bb",
                "sha256": "921973c8d7f67b1b9891f0114f335adb1bacaf7eb7b4bc1fa54afbbfb7711279"
            },
            "downloads": -1,
            "filename": "qt-async-threads-0.6.0.tar.gz",
            "has_sig": false,
            "md5_digest": "52ebec0a6586ebf9fe56b17eec7e27bb",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.10",
            "size": 33421,
            "upload_time": "2024-02-11T13:41:37",
            "upload_time_iso_8601": "2024-02-11T13:41:37.271229Z",
            "url": "https://files.pythonhosted.org/packages/e3/0a/2e894dc389a6a0680d87ce9b05bada21ddeabb2c0994e969428991caceac/qt-async-threads-0.6.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-02-11 13:41:37",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "lcname": "qt-async-threads"
}
        
Elapsed time: 0.45271s