# AsyncKivy
[Youtube](https://www.youtube.com/playlist?list=PLNdhqAjzeEGjTpmvNck4Uykps8s9LmRTJ)
[日本語doc](README_jp.md)
`asynckivy` is an async library that saves you from ugly callback-style code,
like most of async libraries do.
Let's say you want to do:
1. `print('A')`
1. wait for 1sec
1. `print('B')`
1. wait for a button to be pressed
1. `print('C')`
in that order.
Your code would look like this:
```python
from kivy.clock import Clock
def what_you_want_to_do(button):
print('A')
def one_sec_later(__):
print('B')
button.bind(on_press=on_button_press)
Clock.schedule_once(one_sec_later, 1)
def on_button_press(button):
button.unbind(on_press=on_button_press)
print('C')
what_you_want_to_do(...)
```
It's not easy to understand.
If you use `asynckivy`, the code above will become:
```python
import asynckivy as ak
async def what_you_want_to_do(button):
print('A')
await ak.sleep(1)
print('B')
await ak.event(button, 'on_press')
print('C')
ak.managed_start(what_you_want_to_do(...))
```
## Installation
Pin the minor version.
```text
poetry add asynckivy@~0.9
pip install "asynckivy>=0.9,<0.10"
```
## Usage
```python
import asynckivy as ak
async def some_task(button):
# waits for 2 seconds to elapse
dt = await ak.sleep(2)
print(f'{dt} seconds have elapsed')
# waits for a button to be pressed
await ak.event(button, 'on_press')
# waits for the value of 'button.x' to change
__, x = await ak.event(button, 'x')
print(f'button.x is now {x}')
# waits for the value of 'button.x' to become greater than 100
if button.x <= 100:
__, x = await ak.event(button, 'x', filter=lambda __, x: x>100)
print(f'button.x is now {x}')
# waits for either 5 seconds to elapse or a button to be pressed.
# i.e. waits at most 5 seconds for a button to be pressed
tasks = await ak.wait_any(
ak.sleep(5),
ak.event(button, 'on_press'),
)
print("Timeout" if tasks[0].finished else "The button was pressed")
# same as the above
async with ak.move_on_after(5) as bg_task:
await ak.event(button, 'on_press')
print("Timeout" if bg_task.finished else "The button was pressed")
# waits for both 5 seconds to elapse and a button to be pressed.
tasks = await ak.wait_all(
ak.sleep(5),
ak.event(button, 'on_press'),
)
# nest as you want.
# waits for a button to be pressed, and either 5 seconds to elapse or 'other_async_func' to complete.
tasks = await ak.wait_all(
ak.event(button, 'on_press'),
ak.wait_any(
ak.sleep(5),
other_async_func(),
),
)
child_tasks = tasks[1].result
print("5 seconds elapsed" if child_tasks[0].finished else "other_async_func has completed")
ak.managed_start(some_task(some_button))
```
For more details, read the [documentation](https://asyncgui.github.io/asynckivy/).
## Tested on
- CPython 3.9 + Kivy 2.3
- CPython 3.10 + Kivy 2.3
- CPython 3.11 + Kivy 2.3
- CPython 3.12 + Kivy 2.3
- CPython 3.13 + Kivy 2.3
## Why this even exists
Starting from version 2.0.0, Kivy supports two legitimate async libraries: [asyncio][asyncio] and [Trio][trio].
At first glance, developing another one might seem like [reinventing the wheel][reinventing].
Actually, I originally started this project just to learn how the async/await syntax works--
so at first, it really was 'reinventing the wheel'.
But after experimenting with Trio in combination with Kivy for a while,
I noticed that Trio isn't suitable for situations requiring fast reactions, such as handling touch events.
The same applies to asyncio.
You can confirm this by running `investigation/why_xxx_is_not_suitable_for_handling_touch_events.py` and rapidly clicking a mouse button.
You'll notice that sometimes `'up'` isn't paired with a corresponding `'down'` in the console output.
You'll also see that the touch coordinates aren't relative to a `RelativeLayout`,
even though the widget receiving the touches belongs to it.
The cause of these problems is that `trio.Event.set()` and `asyncio.Event.set()` don't *immediately* resume the tasks waiting for the `Event` to be set--
they merely schedule them to resume.
The same is true for `nursery.start_soon()` and `asyncio.create_task()`.
Trio and asyncio are async **I/O** libraries after all.
They probably don't need to resume or start tasks immediately, but I believe this is essential for touch handling in Kivy.
If touch events aren't processed promptly, their state might change before tasks even have a chance to handle them.
Their core design might not be ideal for GUI applications in the first place.
That's why I continue to develop the asynckivy library to this day.
[asyncio]:https://docs.python.org/3/library/asyncio.html
[trio]:https://trio.readthedocs.io/en/stable/
[reinventing]:https://en.wikipedia.org/wiki/Reinventing_the_wheel
Raw data
{
"_id": null,
"home_page": "https://github.com/asyncgui/asynckivy",
"name": "asynckivy",
"maintainer": null,
"docs_url": null,
"requires_python": "<4.0,>=3.9",
"maintainer_email": null,
"keywords": "async, kivy",
"author": "Natt\u014dsai Mit\u014d",
"author_email": "flow4re2c@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/67/eb/27979235821f3bd39c04c9459db8ff47d8d529b87686bcff8cedae3084ec/asynckivy-0.9.0.tar.gz",
"platform": null,
"description": "# AsyncKivy\n\n[Youtube](https://www.youtube.com/playlist?list=PLNdhqAjzeEGjTpmvNck4Uykps8s9LmRTJ) \n[\u65e5\u672c\u8a9edoc](README_jp.md) \n\n`asynckivy` is an async library that saves you from ugly callback-style code,\nlike most of async libraries do.\nLet's say you want to do:\n\n1. `print('A')`\n1. wait for 1sec\n1. `print('B')`\n1. wait for a button to be pressed\n1. `print('C')`\n\nin that order.\nYour code would look like this:\n\n```python\nfrom kivy.clock import Clock\n\ndef what_you_want_to_do(button):\n print('A')\n\n def one_sec_later(__):\n print('B')\n button.bind(on_press=on_button_press)\n Clock.schedule_once(one_sec_later, 1)\n\n def on_button_press(button):\n button.unbind(on_press=on_button_press)\n print('C')\n\nwhat_you_want_to_do(...)\n```\n\nIt's not easy to understand.\nIf you use `asynckivy`, the code above will become:\n\n```python\nimport asynckivy as ak\n\nasync def what_you_want_to_do(button):\n print('A')\n await ak.sleep(1)\n print('B')\n await ak.event(button, 'on_press')\n print('C')\n\nak.managed_start(what_you_want_to_do(...))\n```\n\n## Installation\n\nPin the minor version.\n\n```text\npoetry add asynckivy@~0.9\npip install \"asynckivy>=0.9,<0.10\"\n```\n\n## Usage\n\n```python\nimport asynckivy as ak\n\nasync def some_task(button):\n # waits for 2 seconds to elapse\n dt = await ak.sleep(2)\n print(f'{dt} seconds have elapsed')\n\n # waits for a button to be pressed\n await ak.event(button, 'on_press')\n\n # waits for the value of 'button.x' to change\n __, x = await ak.event(button, 'x')\n print(f'button.x is now {x}')\n\n # waits for the value of 'button.x' to become greater than 100\n if button.x <= 100:\n __, x = await ak.event(button, 'x', filter=lambda __, x: x>100)\n print(f'button.x is now {x}')\n\n # waits for either 5 seconds to elapse or a button to be pressed.\n # i.e. waits at most 5 seconds for a button to be pressed\n tasks = await ak.wait_any(\n ak.sleep(5),\n ak.event(button, 'on_press'),\n )\n print(\"Timeout\" if tasks[0].finished else \"The button was pressed\")\n\n # same as the above\n async with ak.move_on_after(5) as bg_task:\n await ak.event(button, 'on_press')\n print(\"Timeout\" if bg_task.finished else \"The button was pressed\")\n\n # waits for both 5 seconds to elapse and a button to be pressed.\n tasks = await ak.wait_all(\n ak.sleep(5),\n ak.event(button, 'on_press'),\n )\n\n # nest as you want.\n # waits for a button to be pressed, and either 5 seconds to elapse or 'other_async_func' to complete.\n tasks = await ak.wait_all(\n ak.event(button, 'on_press'),\n ak.wait_any(\n ak.sleep(5),\n other_async_func(),\n ),\n )\n child_tasks = tasks[1].result\n print(\"5 seconds elapsed\" if child_tasks[0].finished else \"other_async_func has completed\")\n\nak.managed_start(some_task(some_button))\n```\n\nFor more details, read the [documentation](https://asyncgui.github.io/asynckivy/).\n\n## Tested on\n\n- CPython 3.9 + Kivy 2.3\n- CPython 3.10 + Kivy 2.3\n- CPython 3.11 + Kivy 2.3\n- CPython 3.12 + Kivy 2.3\n- CPython 3.13 + Kivy 2.3\n\n## Why this even exists\n\nStarting from version 2.0.0, Kivy supports two legitimate async libraries: [asyncio][asyncio] and [Trio][trio].\nAt first glance, developing another one might seem like [reinventing the wheel][reinventing].\nActually, I originally started this project just to learn how the async/await syntax works--\nso at first, it really was 'reinventing the wheel'.\n\nBut after experimenting with Trio in combination with Kivy for a while,\nI noticed that Trio isn't suitable for situations requiring fast reactions, such as handling touch events.\nThe same applies to asyncio.\nYou can confirm this by running `investigation/why_xxx_is_not_suitable_for_handling_touch_events.py` and rapidly clicking a mouse button.\nYou'll notice that sometimes `'up'` isn't paired with a corresponding `'down'` in the console output.\nYou'll also see that the touch coordinates aren't relative to a `RelativeLayout`,\neven though the widget receiving the touches belongs to it.\n\nThe cause of these problems is that `trio.Event.set()` and `asyncio.Event.set()` don't *immediately* resume the tasks waiting for the `Event` to be set--\nthey merely schedule them to resume.\nThe same is true for `nursery.start_soon()` and `asyncio.create_task()`.\n\nTrio and asyncio are async **I/O** libraries after all.\nThey probably don't need to resume or start tasks immediately, but I believe this is essential for touch handling in Kivy.\nIf touch events aren't processed promptly, their state might change before tasks even have a chance to handle them.\nTheir core design might not be ideal for GUI applications in the first place.\nThat's why I continue to develop the asynckivy library to this day.\n\n[asyncio]:https://docs.python.org/3/library/asyncio.html\n[trio]:https://trio.readthedocs.io/en/stable/\n[reinventing]:https://en.wikipedia.org/wiki/Reinventing_the_wheel\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Async library for Kivy",
"version": "0.9.0",
"project_urls": {
"Homepage": "https://github.com/asyncgui/asynckivy",
"Repository": "https://github.com/asyncgui/asynckivy"
},
"split_keywords": [
"async",
" kivy"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "fb9305e5cd930424a1e4a3f51102c6bc1aeab50d0f3798b63eca5b094509ab82",
"md5": "48191a1b8340c1bed4a2b3c5c0ab7d37",
"sha256": "cfc9e3f606dfb95fcd9611ee7be69e1da3ce6974679fbb61a6250e3a43ae3d0c"
},
"downloads": -1,
"filename": "asynckivy-0.9.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "48191a1b8340c1bed4a2b3c5c0ab7d37",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4.0,>=3.9",
"size": 19085,
"upload_time": "2025-08-06T04:17:13",
"upload_time_iso_8601": "2025-08-06T04:17:13.269154Z",
"url": "https://files.pythonhosted.org/packages/fb/93/05e5cd930424a1e4a3f51102c6bc1aeab50d0f3798b63eca5b094509ab82/asynckivy-0.9.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "67eb27979235821f3bd39c04c9459db8ff47d8d529b87686bcff8cedae3084ec",
"md5": "5be8b84b92415b10b6a1a363c4a38071",
"sha256": "6358dbe49bffa3be8507395a256789e41e234cda8df5baa39747d66aae1f92e6"
},
"downloads": -1,
"filename": "asynckivy-0.9.0.tar.gz",
"has_sig": false,
"md5_digest": "5be8b84b92415b10b6a1a363c4a38071",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4.0,>=3.9",
"size": 16018,
"upload_time": "2025-08-06T04:17:14",
"upload_time_iso_8601": "2025-08-06T04:17:14.799425Z",
"url": "https://files.pythonhosted.org/packages/67/eb/27979235821f3bd39c04c9459db8ff47d8d529b87686bcff8cedae3084ec/asynckivy-0.9.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-06 04:17:14",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "asyncgui",
"github_project": "asynckivy",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "asynckivy"
}