================
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"
}