asynckivy


Nameasynckivy JSON
Version 0.7.0 PyPI version JSON
download
home_pagehttps://github.com/asyncgui/asynckivy
SummaryAsync library for Kivy
upload_time2024-08-29 13:40:35
maintainerNone
docs_urlNone
authorNattōsai Mitō
requires_python<4.0,>=3.9
licenseMIT
keywords async kivy
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # 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.start(what_you_want_to_do(...))
```

## Installation

Pin the minor version.

```text
poetry add asynckivy@~0.7
pip install "asynckivy>=0.7,<0.8"
```

## 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.start(some_task(some_button))
```

For more details, read the [documentation](https://asyncgui.github.io/asynckivy/).

## Tested on

- CPython 3.9 + Kivy 2.3.0
- CPython 3.10 + Kivy 2.3.0
- CPython 3.11 + Kivy 2.3.0
- CPython 3.12 + Kivy 2.3.0 (3.12.0 is not supported due to [this issue](https://github.com/python/cpython/issues/111058))

## Why this even exists

Kivy supports two legitimate async libraries, [asyncio][asyncio] and [Trio][trio], starting from version 2.0.0, so developing another one seems like [reinventing the wheel][reinventing].
Actually, I started this one just to learn how the async/await syntax works, so it initially was "reinventing the wheel".

But after playing with Trio and Kivy for a while, I noticed that Trio is not suitable for the situation where fast reactions are required e.g. touch events.
The same is true of asyncio.
You can confirm that by running `investigation/why_xxx_is_not_suitable_for_handling_touch_events.py`, and mashing a mouse button as quickly as possible.
You'll see sometimes `up` is not paired with `down`.
You'll see the coordinates aren't relative to the `RelativeLayout` even though the `target` belongs to it.

The cause of those problems is that `trio.Event.set()` and `asyncio.Event.set()` don't *immediately* resume the tasks waiting for the `Event` to be set.
They just schedule the tasks to resume.
Same thing can be said to `nursery.start_soon()` and `asyncio.create_task()`.

Trio and asyncio are async **I/O** libraries after all.
They probably don't have to immediately resumes/starts tasks, which I think necessary for touch handling in Kivy.
(If you fail to handle touches promptly, their state might undergo changes, leaving no time to wait for tasks to resume/start).
Their core design might not be suitable for GUI in the first place.
That's why I'm still developing this `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/50/62/be185b612bc8470277de71f765b1d92c64a481a087c93d983a380decc563/asynckivy-0.7.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.start(what_you_want_to_do(...))\n```\n\n## Installation\n\nPin the minor version.\n\n```text\npoetry add asynckivy@~0.7\npip install \"asynckivy>=0.7,<0.8\"\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.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.0\n- CPython 3.10 + Kivy 2.3.0\n- CPython 3.11 + Kivy 2.3.0\n- CPython 3.12 + Kivy 2.3.0 (3.12.0 is not supported due to [this issue](https://github.com/python/cpython/issues/111058))\n\n## Why this even exists\n\nKivy supports two legitimate async libraries, [asyncio][asyncio] and [Trio][trio], starting from version 2.0.0, so developing another one seems like [reinventing the wheel][reinventing].\nActually, I started this one just to learn how the async/await syntax works, so it initially was \"reinventing the wheel\".\n\nBut after playing with Trio and Kivy for a while, I noticed that Trio is not suitable for the situation where fast reactions are required e.g. touch events.\nThe same is true of asyncio.\nYou can confirm that by running `investigation/why_xxx_is_not_suitable_for_handling_touch_events.py`, and mashing a mouse button as quickly as possible.\nYou'll see sometimes `up` is not paired with `down`.\nYou'll see the coordinates aren't relative to the `RelativeLayout` even though the `target` belongs to it.\n\nThe cause of those problems is that `trio.Event.set()` and `asyncio.Event.set()` don't *immediately* resume the tasks waiting for the `Event` to be set.\nThey just schedule the tasks to resume.\nSame thing can be said to `nursery.start_soon()` and `asyncio.create_task()`.\n\nTrio and asyncio are async **I/O** libraries after all.\nThey probably don't have to immediately resumes/starts tasks, which I think necessary for touch handling in Kivy.\n(If you fail to handle touches promptly, their state might undergo changes, leaving no time to wait for tasks to resume/start).\nTheir core design might not be suitable for GUI in the first place.\nThat's why I'm still developing this `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.7.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": "324001236aaac02a422e97ed8fe3fc20bbf93bd499ddab433a509158e4ba565e",
                "md5": "2da6f85ae3df25947a1ac4f14e158b78",
                "sha256": "4855d730ff3283203dcb325bc4018301737a307732f1075b6ef61c2db590bd10"
            },
            "downloads": -1,
            "filename": "asynckivy-0.7.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "2da6f85ae3df25947a1ac4f14e158b78",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<4.0,>=3.9",
            "size": 17041,
            "upload_time": "2024-08-29T13:40:34",
            "upload_time_iso_8601": "2024-08-29T13:40:34.160192Z",
            "url": "https://files.pythonhosted.org/packages/32/40/01236aaac02a422e97ed8fe3fc20bbf93bd499ddab433a509158e4ba565e/asynckivy-0.7.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "5062be185b612bc8470277de71f765b1d92c64a481a087c93d983a380decc563",
                "md5": "a958e5cd19400e9a319cecac23fecd29",
                "sha256": "83396cecceb513393040b40a3cf6d920ed6190e78660aae5edf48c9d3fd1140b"
            },
            "downloads": -1,
            "filename": "asynckivy-0.7.0.tar.gz",
            "has_sig": false,
            "md5_digest": "a958e5cd19400e9a319cecac23fecd29",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": "<4.0,>=3.9",
            "size": 14849,
            "upload_time": "2024-08-29T13:40:35",
            "upload_time_iso_8601": "2024-08-29T13:40:35.499188Z",
            "url": "https://files.pythonhosted.org/packages/50/62/be185b612bc8470277de71f765b1d92c64a481a087c93d983a380decc563/asynckivy-0.7.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-08-29 13:40:35",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "asyncgui",
    "github_project": "asynckivy",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "asynckivy"
}
        
Elapsed time: 0.36508s