Skip to content

Commit

Permalink
Merge pull request kivy#6368 from matham/async-support
Browse files Browse the repository at this point in the history
Add async support to kivy App
  • Loading branch information
matham authored Jul 27, 2019
2 parents 0433d88 + 16cb83a commit e8a0914
Show file tree
Hide file tree
Showing 24 changed files with 1,533 additions and 162 deletions.
6 changes: 3 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ install:
sudo apt-get -y install xvfb pulseaudio xsel;
export CYTHON_INSTALL=$(KIVY_NO_CONSOLELOG=1 python3 -c "from kivy.tools.packaging.cython_cfg import get_cython_versions; print(get_cython_versions()[0])" --config "kivy:log_level:error");
python3 -m pip install -I "$CYTHON_INSTALL";
python3 -m pip install --upgrade pillow pytest coveralls docutils PyInstaller;
python3 -m pip install --upgrade pillow pytest pytest_asyncio coveralls docutils PyInstaller;
fi;
if [ "${RUN}" == "docs" ]; then
python3 -m pip install --upgrade sphinx==1.7.9 sphinxcontrib-blockdiag sphinxcontrib-seqdiag sphinxcontrib-actdiag sphinxcontrib-nwdiag;
Expand Down Expand Up @@ -107,7 +107,7 @@ install:
python3 get-pip.py --user;
export CYTHON_INSTALL=$(KIVY_NO_CONSOLELOG=1 python3 -c "from kivy.tools.packaging.cython_cfg import get_cython_versions; print(get_cython_versions()[0])" --config "kivy:log_level:error");
python3 -m pip install -I --user "$CYTHON_INSTALL";
python3 -m pip install --upgrade --user pillow pytest mock docutils PyInstaller;
python3 -m pip install --upgrade --user pillow pytest pytest_asyncio mock docutils PyInstaller;
fi;

before_script:
Expand Down Expand Up @@ -203,7 +203,7 @@ script:

python$pyver_short -m pip install --upgrade --user pip setuptools wheel;
python$pyver_short -m pip install -I --user "$CYTHON_INSTALL";
python$pyver_short -m pip install --upgrade --user pytest wheel pillow mock docutils;
python$pyver_short -m pip install --upgrade --user pytest pytest_asyncio wheel pillow mock docutils;
python$pyver_short -m pip install --upgrade delocate;

USE_SDL2=1 USE_GSTREAMER=1 python$pyver_short setup.py build_ext --inplace>output.txt;
Expand Down
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ build_script:
Check-Error
}
pip install mock cython pygments docutils pytest pyinstaller kivy_deps.glew_dev kivy_deps.glew kivy_deps.gstreamer_dev kivy_deps.sdl2_dev kivy_deps.sdl2
pip install mock cython pygments docutils pytest pytest_asyncio pyinstaller kivy_deps.glew_dev kivy_deps.glew kivy_deps.gstreamer_dev kivy_deps.sdl2_dev kivy_deps.sdl2
pip --no-cache-dir install kivy_deps.gstreamer
Expand Down
18 changes: 17 additions & 1 deletion doc/sources/guide/environment.rst
Original file line number Diff line number Diff line change
Expand Up @@ -216,5 +216,21 @@ KIVY_BCM_DISPMANX_ID

KIVY_BCM_DISPMANX_LAYER
Change the default Raspberry Pi dispmanx layer. Default value is 0.

.. versionadded:: 1.10.1

Event Loop
----------

KIVY_EVENTLOOP
Whether to use an async or synchronous event loop. See :mod:`kivy.app`
for example usage.

``'sync'``: When the app is run normally in a synchronous manner.
The default if not set.
``'asyncio'``: When the app is run in an asynchronous manner and the standard
library asyncio package should be used.
``'trio'``: When the app is run in an asynchronous manner and the `trio`
package should be used.

.. versionadded:: 2.0.0
90 changes: 90 additions & 0 deletions examples/async/asyncio_advanced.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
'''Example shows the recommended way of how to run Kivy with the Python built
in asyncio event loop as just another async coroutine.
'''
import asyncio

from kivy.app import App
from kivy.lang.builder import Builder

kv = '''
BoxLayout:
orientation: 'vertical'
BoxLayout:
ToggleButton:
id: btn1
group: 'a'
text: 'Sleeping'
allow_no_selection: False
on_state: if self.state == 'down': label.status = self.text
ToggleButton:
id: btn2
group: 'a'
text: 'Swimming'
allow_no_selection: False
on_state: if self.state == 'down': label.status = self.text
ToggleButton:
id: btn3
group: 'a'
text: 'Reading'
allow_no_selection: False
state: 'down'
on_state: if self.state == 'down': label.status = self.text
Label:
id: label
status: 'Reading'
text: 'Beach status is "{}"'.format(self.status)
'''


class AsyncApp(App):

other_task = None

def build(self):
return Builder.load_string(kv)

def app_func(self):
'''This will run both methods asynchronously and then block until they
are finished
'''
self.other_task = asyncio.ensure_future(self.waste_time_freely())

async def run_wrapper():
# we don't actually need to set asyncio as the lib because it is
# the default, but it doesn't hurt to be explicit
await self.async_run(async_lib='asyncio')
print('App done')
self.other_task.cancel()

return asyncio.gather(run_wrapper(), self.other_task)

async def waste_time_freely(self):
'''This method is also run by the asyncio loop and periodically prints
something.
'''
try:
i = 0
while True:
if self.root is not None:
status = self.root.ids.label.status
print('{} on the beach'.format(status))

# get some sleep
if self.root.ids.btn1.state != 'down' and i >= 2:
i = 0
print('Yawn, getting tired. Going to sleep')
self.root.ids.btn1.trigger_action()

i += 1
await asyncio.sleep(2)
except asyncio.CancelledError as e:
print('Wasting time was canceled', e)
finally:
# when canceled, print that it finished
print('Done wasting time')


if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(AsyncApp().app_func())
loop.close()
59 changes: 59 additions & 0 deletions examples/async/asyncio_basic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
'''Example shows the recommended way of how to run Kivy with the Python built
in asyncio event loop as just another async coroutine.
'''
import asyncio

from kivy.app import async_runTouchApp
from kivy.lang.builder import Builder

kv = '''
BoxLayout:
orientation: 'vertical'
Button:
id: btn
text: 'Press me'
BoxLayout:
Label:
id: label
text: 'Button is "{}"'.format(btn.state)
'''


async def run_app_happily(root, other_task):
'''This method, which runs Kivy, is run by the asyncio loop as one of the
coroutines.
'''
# we don't actually need to set asyncio as the lib because it is the
# default, but it doesn't hurt to be explicit
await async_runTouchApp(root, async_lib='asyncio') # run Kivy
print('App done')
# now cancel all the other tasks that may be running
other_task.cancel()


async def waste_time_freely():
'''This method is also run by the asyncio loop and periodically prints
something.
'''
try:
while True:
print('Sitting on the beach')
await asyncio.sleep(2)
except asyncio.CancelledError as e:
print('Wasting time was canceled', e)
finally:
# when canceled, print that it finished
print('Done wasting time')

if __name__ == '__main__':
def root_func():
'''This will run both methods asynchronously and then block until they
are finished
'''
root = Builder.load_string(kv) # root widget
other_task = asyncio.ensure_future(waste_time_freely())
return asyncio.gather(run_app_happily(root, other_task), other_task)

loop = asyncio.get_event_loop()
loop.run_until_complete(root_func())
loop.close()
93 changes: 93 additions & 0 deletions examples/async/trio_advanced.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
'''Example shows the recommended way of how to run Kivy with a trio
event loop as just another async coroutine.
'''
import trio

from kivy.app import App
from kivy.lang.builder import Builder

kv = '''
BoxLayout:
orientation: 'vertical'
BoxLayout:
ToggleButton:
id: btn1
group: 'a'
text: 'Sleeping'
allow_no_selection: False
on_state: if self.state == 'down': label.status = self.text
ToggleButton:
id: btn2
group: 'a'
text: 'Swimming'
allow_no_selection: False
on_state: if self.state == 'down': label.status = self.text
ToggleButton:
id: btn3
group: 'a'
text: 'Reading'
allow_no_selection: False
state: 'down'
on_state: if self.state == 'down': label.status = self.text
Label:
id: label
status: 'Reading'
text: 'Beach status is "{}"'.format(self.status)
'''


class AsyncApp(App):

nursery = None

def build(self):
return Builder.load_string(kv)

async def app_func(self):
'''trio needs to run a function, so this is it. '''

async with trio.open_nursery() as nursery:
'''In trio you create a nursery, in which you schedule async
functions to be run by the nursery simultaneously as tasks.
This will run all two methods starting in random order
asynchronously and then block until they are finished or canceled
at the `with` level. '''
self.nursery = nursery

async def run_wrapper():
# trio needs to be set so that it'll be used for the event loop
await self.async_run(async_lib='trio')
print('App done')
nursery.cancel_scope.cancel()

nursery.start_soon(run_wrapper)
nursery.start_soon(self.waste_time_freely)

async def waste_time_freely(self):
'''This method is also run by trio and periodically prints something.
'''
try:
i = 0
while True:
if self.root is not None:
status = self.root.ids.label.status
print('{} on the beach'.format(status))

# get some sleep
if self.root.ids.btn1.state != 'down' and i >= 2:
i = 0
print('Yawn, getting tired. Going to sleep')
self.root.ids.btn1.trigger_action()

i += 1
await trio.sleep(2)
except trio.Cancelled as e:
print('Wasting time was canceled', e)
finally:
# when canceled, print that it finished
print('Done wasting time')


if __name__ == '__main__':
trio.run(AsyncApp().app_func)
58 changes: 58 additions & 0 deletions examples/async/trio_basic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
'''Example shows the recommended way of how to run Kivy with a trio
event loop as just another async coroutine.
'''
import trio
from kivy.app import async_runTouchApp
from kivy.lang.builder import Builder

kv = '''
BoxLayout:
orientation: 'vertical'
Button:
id: btn
text: 'Press me'
BoxLayout:
Label:
id: label
text: 'Button is "{}"'.format(btn.state)
'''


async def run_app_happily(root, nursery):
'''This method, which runs Kivy, is run by trio as one of the coroutines.
'''
# trio needs to be set so that it'll be used for the event loop
await async_runTouchApp(root, async_lib='trio') # run Kivy
print('App done')
# now cancel all the other tasks that may be running
nursery.cancel_scope.cancel()


async def waste_time_freely():
'''This method is also run by trio and periodically prints something.'''
try:
while True:
print('Sitting on the beach')
await trio.sleep(2)
except trio.Cancelled as e:
print('Wasting time was canceled', e)
finally:
# when canceled, print that it finished
print('Done wasting time')

if __name__ == '__main__':
async def root_func():
'''trio needs to run a function, so this is it. '''

root = Builder.load_string(kv) # root widget
async with trio.open_nursery() as nursery:
'''In trio you create a nursery, in which you schedule async
functions to be run by the nursery simultaneously as tasks.
This will run all two methods starting in random order
asynchronously and then block until they are finished or canceled
at the `with` level. '''
nursery.start_soon(run_app_happily, root, nursery)
nursery.start_soon(waste_time_freely)

trio.run(root_func)
Loading

0 comments on commit e8a0914

Please sign in to comment.