API Reference#

exception MotionEventAlreadyEndedError#

This error occurs when an already-ended touch is passed to an asynckivy API that expects an ongoing touch. For instance:

import asynckivy as ak

class MyWidget(Widget):
    def on_touch_up(self, touch):  # not 'on_touch_down', oops!
        ak.start(self.handle_touch(touch))
        return True

    async def handle_touch(self, touch):
        try:
            async for __ in ak.rest_of_touch_events(widget, touch):
                ...
        except ak.MotionEventAlreadyEndedError:
            ...
anim_attrs(obj, *, duration=1.0, step=0, transition='linear', output_seq_type=<class 'tuple'>, **animated_properties) Awaitable#

Animates attibutes of any object.

import types

obj = types.SimpleNamespace(x=0, size=(200, 300))
await anim_attrs(obj, x=100, size=(400, 400))

The output_seq_type parameter:

obj = types.SimpleNamespace(size=(200, 300))
await anim_attrs(obj, size=(400, 400), output_seq_type=list)
assert type(obj.size) is list

Warning

Unlike kivy.animation.Animation, this one does not support dictionary-type and nested-sequence.

await anim_attrs(obj, pos_hint={'x': 1.})  # not supported
await anim_attrs(obj, nested_sequence=[[10, 20, ]])  # not supported

await anim_attrs(obj, color=(1, 0, 0, 1), pos=(100, 200))  # OK

New in version 0.6.1.

anim_attrs_abbr(obj, *, d=1.0, s=0, t='linear', output_seq_type=<class 'tuple'>, **animated_properties) Awaitable#

anim_attrs() cannot animate attributes named step, duration and transition but this one can.

New in version 0.6.1.

async anim_with_dt(*, step=0)#

An async form of kivy.clock.Clock.schedule_interval(). The following callback-style code:

def callback(dt):
    print(dt)
    if some_condition:
        return False

Clock.schedule_interval(callback, 0.1)

is equivalent to the following async-style code:

async for dt in anim_with_dt(step=0.1):
    print(dt)
    if some_condition:
        break

New in version 0.6.1.

async anim_with_dt_et(*, step=0)#

anim_with_dt() and anim_with_et() combined.

async for dt, et in anim_with_dt_et(...):
    ...

New in version 0.6.1.

async anim_with_dt_et_ratio(*, duration=1.0, step=0)#

anim_with_dt(), anim_with_et() and anim_with_ratio() combined.

async for dt, et, p in anim_with_dt_et_ratio(...):
    ...

New in version 0.6.1.

async anim_with_et(*, step=0)#

Same as anim_with_dt() except this one generates the total elapsed time of the loop instead of the elapsed time between frames.

timeout = 3.0
async for et in anim_with_et(...):
    ...
    if et > timeout:
        break

You can calculate et by yourself if you want to:

et = 0.
timeout = 3.0
async for dt in anim_with_dt(...):
    et += dt
    ...
    if et > timeout:
        break

which should be as performant as the former.

New in version 0.6.1.

async anim_with_ratio(*, duration=1.0, step=0)#

Same as anim_with_et() except this one generates the total progression ratio of the loop.

async for p in anim_with_ratio(duration=3.0):
    print(p * 100, "%")

If you want to progress at a non-consistant rate, kivy.animation.AnimationTransition may be helpful.

from kivy.animation import AnimationTransition

in_cubic = AnimationTransition.in_cubic

async for p in anim_with_ratio(duration=3.0):
    p = in_cubic(p)
    print(p * 100, "%")

New in version 0.6.1.

animate(obj, *, duration=1.0, step=0, transition='linear', **animated_properties) Awaitable#

Animates attibutes of any object. This is basically an async form of kivy.animation.Animation.

import types

obj = types.SimpleNamespace(x=0, size=(200, 300, ))
await animate(obj, x=100, size=(400, 400))

Kivy has two compound animations, kivy.animation.Sequence and kivy.animation.Parallel. You can achieve the same functionality as them in asynckivy as follows:

import asynckivy as ak

async def sequential_animation(widget):
    await ak.animate(widget, x=100)
    await ak.animate(widget, x=0)

async def parallel_animation(widget):
    await ak.wait_all(
        ak.animate(widget, x=100),
        ak.animate(widget, y=100, duration=2),
    )

Deprecated since version 0.6.1: This will be removed before version 1.0.0. Use asynckivy.anim_attrs() or asynckivy.anim_attrs_abbr() instead.

create_texture_from_text(*, markup=False, **label_kwargs) kivy.graphics.texture.Texture#
from kivy.metrics import sp

texture = create_texture_from_text(
    text='Hello',
    font_size=sp(50),
    font_name='Roboto',
    color=(1, 0, 0, 1),
)

The keyword arguments are similar to Label ‘s.

async event(event_dispatcher, event_name, *, filter=None, stop_dispatching=False) Awaitable[tuple]#

Returns an awaitable that can be used to wait for:

  • a Kivy event to occur.

  • a Kivy property’s value to change.

# Wait for a button to be pressed.
await event(button, 'on_press')

# Wait for an 'on_touch_down' event to occur.
__, touch = await event(widget, 'on_touch_down')

# Wait for 'widget.x' to change.
__, x = await ak.event(widget, 'x')

The filter parameter:

# Wait for an 'on_touch_down' event to occur inside a widget.
__, touch = await event(widget, 'on_touch_down', filter=lambda w, t: w.collide_point(*t.opos))

# Wait for 'widget.x' to become greater than 100.
if widget.x <= 100:
    await event(widget, 'x', filter=lambda __, x: x > 100)

The stop_dispatching parameter:

It only works for events not for properties. See Kivy’s Event System for details.

fade_transition(*widgets, duration=1.0, step=0) AsyncContextManager#

Returns an async context manager that:

  • fades-out the given widgets on __aenter__.

  • fades-in the given widgets on __aexit__.

async with fade_transition(widget1, widget2):
    ...

The widgets don’t have to be actual Kivy widgets. Anything that has an attribute named opacity would work.

async interpolate(start, end, *, duration=1.0, step=0, transition='linear') AsyncIterator#

Interpolates between the values start and end in an async-manner. Inspired by wasabi2d’s interpolate.

async for v in interpolate(0, 100, duration=1.0, step=.3):
    print(int(v))

elapsed time

output

0 sec

0

0.3 sec

30

0.6 sec

60

0.9 sec

90

1.2 sec

100

move_on_after(seconds: float) AsyncContextManager[Task]#

Returns an async context manager that applies a time limit to its code block, like trio.move_on_after() does.

async with move_on_after(seconds) as bg_task:
    ...
if bg_task.finished:
    print("The code block was interrupted due to a timeout")
else:
    print("The code block exited gracefully.")

New in version 0.6.1.

n_frames(n: int) Awaitable#

Waits for a specified number of frames.

await n_frames(2)

If you want to wait for one frame, asynckivy.sleep() is preferable for a performance reason.

await sleep(0)
class repeat_sleeping(*, step=0)#

Returns an async context manager that provides an efficient way to repeat sleeping.

When there is a piece of code like this:

while True:
    await sleep(0)
    ...

it can be translated to:

async with repeat_sleeping(step=0) as sleep:
    while True:
        await sleep()
        ...

The latter is more suitable for situations requiring frequent sleeps, such as moving an object in every frame.

Restriction

You are not allowed to perform any kind of async operations inside the with-block except you can await the return value of the function that is bound to the identifier of the as-clause.

async with repeat_sleeping(step=0) as sleep:
    await sleep()  # OK
    await something_else  # NOT ALLOWED
    async with async_context_manager:  # NOT ALLOWED
        ...
    async for __ in async_iterator:  # NOT ALLOWED
        ...
async rest_of_touch_events(...) AsyncIterator[None]#

Returns an async iterator that iterates the number of times on_touch_move occurs, and ends the iteration when on_touch_up occurs.

async for __ in rest_of_touch_events(widget, touch):
    print('on_touch_move')
print('on_touch_up')

This is a wrapper for watch_touch. Although this one, I believe, is more intuitive than watch_touch, it has a couple of disadvantages - see The Problem with Async Generators.

async run_in_executor(executor: ThreadPoolExecutor, func) Awaitable#

Runs a function within a concurrent.futures.ThreadPoolExecutor, and waits for the completion of the function.

executor = ThreadPoolExecutor()
...
return_value = await run_in_executor(executor, func)

See I/O in AsyncKivy for details.

async run_in_thread(func, *, daemon=None) Awaitable#

Creates a new thread, runs a function within it, then waits for the completion of that function.

return_value = await run_in_thread(func)

See I/O in AsyncKivy for details.

sleep(duration) Awaitable[float]#

An async form of kivy.clock.Clock.schedule_once().

dt = await sleep(5)  # wait for 5 seconds
sleep_free(duration) Awaitable[float]#

An async form of kivy.clock.Clock.schedule_once_free().

dt = await sleep_free(5)  # wait for 5 seconds
class suppress_event(event_dispatcher, event_name, *, filter=<function suppress_event.<lambda>>)#

Returns a context manager that prevents the callback functions (including the default handler) bound to an event from being called.

from kivy.uix.button import Button

btn = Button()
btn.bind(on_press=lambda __: print("pressed"))
with suppress_event(btn, 'on_press'):
    btn.dispatch('on_press')

The above code prints nothing because the callback function won’t be called.

Strictly speaking, this context manager doesn’t prevent all callback functions from being called. It only prevents the callback functions that were bound to an event before the context manager enters. Thus, the following code prints pressed.

from kivy.uix.button import Button

btn = Button()
with suppress_event(btn, 'on_press'):
    btn.bind(on_press=lambda __: print("pressed"))
    btn.dispatch('on_press')

Warning

You need to be careful when you suppress an on_touch_xxx event. See Kivy’s Event System for details.

class sync_attr(from_: Tuple[kivy.event.EventDispatcher, str], to_: Tuple[Any, str])#

Returns a context manager that creates one-directional binding between attributes.

import types

widget = Widget()
obj = types.SimpleNamespace()

with sync_attr(from_=(widget, 'x'), to_=(obj, 'xx')):
    widget.x = 10
    assert obj.xx == 10  # synchronized
    obj.xx = 20
    assert widget.x == 10  # but not the other way around

This can be particularly useful when combined with transform().

from kivy.graphics import Rotate

async def rotate_widget(widget, *, angle=360.):
    with transform(widget) as ig:
        ig.add(rotate := Rotate(origin=widget.center))
        with sync_attr(from_=(widget, 'center'), to_=(rotate, 'origin')):
            await anim_attrs(rotate, angle=angle)

New in version 0.6.1.

class sync_attrs(from_: Tuple[kivy.event.EventDispatcher, str], *to_)#

When multiple sync_attr calls take the same from_ argument, they can be merged into a single sync_attrs call. For instance, the following code:

with sync_attr((widget, 'x'), (obj1, 'x')), sync_attr((widget, 'x'), (obj2, 'xx')):
    ...

can be replaced with the following one:

with sync_attrs((widget, 'x'), (obj1, 'x'), (obj2, 'xx')):
    ...

New in version 0.6.1.

async touch_up_event(widget, touch, *, stop_dispatching=False, timeout=1.0) Awaitable#

(experimental state)

Returns an awaitable that can be used to wait for the on_touch_up event of the given touch to occur.

__, touch = await event(widget, 'on_touch_down')
...
await touch_up_event(widget, touch)

You might wonder what the differences are compared to the code below.

__, touch = await event(widget, 'on_touch_down')
...
await event(widget, 'on_touch_up', filter=lambda w, t: t is touch)

The latter has two problems: If the on_touch_up event of the touch occurred before the highlighted line, the execution will halt indefinitely at that point. Even if the event didn’t occur before that line, the execution still might halt because Kivy does not guarantee that all touch events are delivered to all widgets.

This API takes care of both problems in the same way as watch_touch(). If the on_touch_up event has already occurred, it raises a MotionEventAlreadyEndedError exception. And it grabs/ungrabs the touch so that it won’t miss any touch events.

Needless to say, if you want to wait for both on_touch_move and on_touch_up events at the same time, use watch_touch() or rest_of_touch_events() instead.

transform(widget, *, use_outer_canvas=False) ContextManager[kivy.graphics.InstructionGroup]#

Returns a context manager that sandwiches the widget’s existing canvas instructions between a kivy.graphics.PushMatrix and a kivy.graphics.PopMatrix, and inserts an kivy.graphics.InstructionGroup right next to the PushMatrix. Those three instructions will be removed when the context manager exits.

This may be useful when you want to animate a widget.

Usage

from kivy.graphics import Rotate

async def rotate_widget(widget, *, angle=360.):
    with transform(widget) as ig:  # <- InstructionGroup
        ig.add(rotate := Rotate(origin=widget.center))
        await anim_attrs(rotate, angle=angle)

If the position or size of the widget changes during the animation, you might need sync_attr.

The use_outer_canvas parameter

While the context manager is active, the content of the widget’s canvas would be:

# ... represents existing instructions

Widget:
    canvas.before:
        ...
    canvas:
        PushMatrix
        InstructionGroup
        ...
        PopMatrix
    canvas.after:
        ...

but if use_outer_canvas is True, it would be:

Widget:
    canvas.before:
        PushMatrix
        InstructionGroup
        ...
    canvas:
        ...
    canvas.after:
        ...
        PopMatrix
class watch_touch(widget, touch, *, stop_dispatching=False, timeout=1.0)#

Returns an async context manager that provides an easy way to handle touch events.

async with watch_touch(widget, touch) as in_progress:
    while await in_progress():
        print('on_touch_move')
    print('on_touch_up')

The await in_progress() waits for either an on_touch_move event or an on_touch_up event to occur, and returns True or False respectively when they occurred.

Caution

  • You are not allowed to perform any kind of async operations inside the with-block except await in_progress().

    async with watch_touch(widget, touch) as in_progress:
        await in_progress()  # OK
        await something_else  # NOT ALLOWED
        async with async_context_manager:  # NOT ALLOWED
            ...
        async for __ in async_iterator:  # NOT ALLOWED
            ...
    
  • If the widget is the type of widget that grabs touches by itself, such as kivy.uix.button.Button, you probably want to set the stop_dispatching parameter to True in most cases.

  • There are widgets/behaviors that can simulate a touch (e.g. kivy.uix.scrollview.ScrollView, kivy.uix.behaviors.DragBehavior and kivy_garden.draggable.KXDraggableBehavior). If many such widgets are in the parent stack of the widget, this API might mistakenly raise a asynckivy.MotionEventAlreadyEndedError. If that happens, increase the timeout parameter.