Size: 4130
Comment:
|
← Revision 17 as of 2024-02-19 18:33:59 ⇥
Size: 12321
Comment: Add python-sdbus example. Python-sdbus is a modern D-Bus library supporting both asyncio and blocking calls, unified client-server classes and type hints.
|
Deletions are marked like this. | Additions are marked like this. |
Line 1: | Line 1: |
The [[http://www.freedesktop.org/wiki/Software_2fdbus|D-Bus library]] is a messaging library used by the GNOME desktop for interprocess communication. There's a Python binding for it, but documentation is non-existent so I've collected these examples in hopes that they'll be useful and that people will update them as needed. ''Warning'': the D-Bus APIs change a lot, breaking code. These examples were run with version 0.60 of the Python interface (0.60-6ubuntu8, on Ubuntu Dapper); they probably won't run on other versions. This [[http://thaiopensource.org/development/suriyan/wiki/DbusNotes|draft tutorial]] is the clearest introduction to D-Bus that I've seen. The `dbus-viewer` program lets you browse through the services and interfaces available on your system. = Introspection = {{{ # You must initialize the gobject/dbus support for threading # before doing anything. import gobject gobject.threads_init() from dbus import glib glib.init_threads() # Create a session bus. import dbus bus = dbus.SessionBus() |
The [[http://www.freedesktop.org/wiki/Software/dbus|D-Bus library]] is a messaging library used by various desktop environments (GNOME, KDE, etc) for interprocess communication. There are multiple Python bindings for DBus: * GDbus and QtDbus are wrappers over the C/C++ APIs of GLib and Qt * [[https://github.com/LEW21/pydbus|pydbus]] is a modern, pythonic API with the goal of being as invisible a layer between the exported API and its user as possible * [[https://github.com/rhinstaller/dasbus|dasbus]] is a Python3-only alternative to pydbus with additional features and better flexibility * [[https://dbus.freedesktop.org/doc/dbus-python/|dbus-python]] is a legacy API, built with a deprecated dbus-glib library, and involving a lot of type-guessing (despite "explicit is better than implicit" and "resist the temptation to guess"). * [[https://pypi.python.org/pypi/txdbus|txdbus]] is a native Python implementation of the D-Bus protocol for the Twisted networking framework. * [[https://pypi.org/project/dbus-next/|dbus-next]] is a native Python library for D-Bus with support for multiple IO backends suitable for GUI develeopment and cross platform projects. * [[https://github.com/python-sdbus/python-sdbus|python-sdbus]] modern D-Bus library supporting both asyncio and blocking calls, unified client-server classes and type hints. See also: [[https://www.freedesktop.org/wiki/Software/DBusBindings/#python|DBusBindings]] on Freedesktop wiki. The `dbus-viewer` and `qdbusviewer` programs let you browse through the services and interfaces available on your system. = pydbus = For more information see [[https://github.com/LEW21/pydbus/blob/master/README.rst|pydbus's Readme]]. == Introspection == {{{ from pydbus import SessionBus bus = SessionBus() |
Line 27: | Line 26: |
remote_object = bus.get_object("org.freedesktop.DBus", # Connection name "/org/freedesktop/DBus" # Object's path ) |
remote_object = bus.get( "org.freedesktop.DBus", # Bus name "/org/freedesktop/DBus" # Object path ) |
Line 33: | Line 33: |
print ("Introspection data:\n") print remote_object.Introspect() }}} |
print("Introspection data:\n") print(remote_object.Introspect()) }}} |
Line 38: | Line 37: |
Line 58: | Line 58: |
If you get any of the connection or object names wrong, the exceptions you get are very obscure. For example, when I add an incorrect trailing slash to the object in the example above (making "/org/freedesktop/DBus/"), the exceptions are: {{{ 8223: arguments to dbus_message_new_method_call() were incorrect, assertion "_dbus_check_is_valid_path (path)" failed in file dbus-message.c line 797. This is normally a bug in some application using the D-BUS library. 8223: arguments to dbus_message_set_destination() were incorrect, assertion "message != NULL" failed in file dbus-message.c line 2728. This is normally a bug in some application using the D-BUS library. 8223: arguments to dbus_connection_send_with_reply() were incorrect, assertion "message != NULL" failed in file dbus-connection.c line 2430. This is normally a bug in some application using the D-BUS library. Traceback (most recent call last): File "/home/amk/db.py", line 19, in ? data = remote_object.Introspect() File "/usr/lib/python2.4/site-packages/dbus/proxies.py", line 201, in __getattr__ self._pending_introspect.block() AttributeError: 'NoneType' object has no attribute 'block' }}} Notice that the exception is triggered in the `.Introspect()` call, not in the erroneous `bus.get_object()` call. = Calling an interface method = |
== Calling an interface method == |
Line 80: | Line 60: |
{{{ # Get a particular interface iface = dbus.Interface(remote_object, 'org.freedesktop.DBus') print iface.ListNames() }}} |
{{{ print(remote_object.ListNames()) }}} |
Line 87: | Line 65: |
{{{ [u'org.freedesktop.DBus', u':1.3', u'org.freedesktop.Notifications', u'org.gnome.PowerManager', u':1.24', u':1.4', u':1.0', u'org.gnome.ScreenSaver', u':1.5', u':1.1', u':1.48', u':1.2'] }}} |
{{{ ['org.freedesktop.DBus', 'org.freedesktop.Notifications', 'org.freedesktop.PowerManagement', ':1.8', ':1.9', 'org.kde.kaccess', 'org.kde.kded', 'org.kde.StatusNotifierItem-655-1', 'org.freedesktop.systemd1', 'org.ktorrent.ktorrent', 'org.kde.StatusNotifierItem-656-1', 'org.kde.konversation', 'org.pulseaudio.Server', 'org.kde.KScreen', 'org.kde.krunner', 'org.kde.konsole', ':1.40', 'org.a11y.Bus', ':1.41', ':1.42', ':1.20', ':1.43', 'org.kde.klauncher5', ':1.21', ':1.23', 'org.kde.dolphin-3012', 'org.freedesktop.PowerManagement.Inhibit', 'org.kde.Solid.PowerManagement', ':1.24', ':1.25', ':1.49', 'org.kde.kmix', 'org.kde.screensaver', 'org.kde.KWin', 'org.bluez.obex', ':1.29', 'ca.desrt.dconf', 'org.kde.kgpg', 'org.freedesktop.ScreenSaver', 'org.kde.plasmashell', 'org.kde.plasmanetworkmanagement', 'org.kde.StatusNotifierItem-666-1', 'org.kde.kglobalaccel', 'org.freedesktop.FileManager1', 'org.kde.kwalletd5', 'org.PulseAudio1', 'org.kde.polkit-kde-authentication-agent-1', ':1.93', 'org.kde.kded5', 'org.kde.ActivityManager', 'org.kde.keyboard', 'org.kde.kate-3030', ':1.31', 'org.kde.kuiserver', ':1.32', ':1.55', ':1.33', ':1.11', 'org.kde.kwin.Screenshot', ':1.56', ':1.34', 'org.kde.StatusNotifierWatcher', 'org.kde.JobViewServer', ':1.35', ':1.0', ':1.13', ':1.58', 'org.kde.StatusNotifierHost-616', ':1.14', ':1.59', ':1.15', ':1.38', ':1.16', 'org.kde.ksmserver', ':1.39', ':1.17', ':1.5', 'org.kde.Solid.PowerManagement.PolicyAgent', ':1.18', 'org.kde.klauncher', ':1.6', ':1.19'] }}} |
Line 94: | Line 70: |
Line 96: | Line 73: |
power = bus.get_object('org.gnome.PowerManager', '/org/gnome/PowerManager') iface = dbus.Interface(power, 'org.gnome.PowerManager') |
power = bus.get('org.gnome.PowerManager', '/org/gnome/PowerManager') |
Line 101: | Line 76: |
if iface.CanHibernate(): iface.Hibernate() }}} |
if power.CanHibernate(): power.Hibernate() }}} = dasbus = For more information see [[https://dasbus.readthedocs.io/|dasbus's documentation]]. == Introspection of a remote object == Introspection returns an XML string containing information about interfaces, methods, properties and signals of the remote object. {{{ from dasbus.connection import SessionMessageBus bus = SessionMessageBus() # Create an object that will be a proxy for a particular remote object. remote_object = bus.get_proxy( "org.freedesktop.DBus", # The bus name "/org/freedesktop/DBus" # The object path ) # Call the Introspect method of the remote object. print(remote_object.Introspect()) }}} == Accessing a remote property == The following example prints the current hostname. {{{ from dasbus.connection import SystemMessageBus bus = SystemMessageBus() proxy = bus.get_proxy( "org.freedesktop.hostname1", "/org/freedesktop/hostname1" ) print(proxy.Hostname) }}} == Calling a remote method == The following example sends a notification to the notification server. {{{ from dasbus.connection import SessionMessageBus bus = SessionMessageBus() proxy = bus.get_proxy( "org.freedesktop.Notifications", "/org/freedesktop/Notifications" ) id = proxy.Notify( "", 0, "face-smile", "My notification", "Hello World!", [], {}, 0 ) print("The notification {} was sent.".format(id)) }}} = dbus-next = For more information see [[https://python-dbus-next.readthedocs.io/|dbus-next's documentation]]. == The client interface == To use a service on the bus, the library constructs a proxy object you can use to call methods, get and set properties, and listen to signals. This example connects to a media player and controls it with the MPRIS DBus interface using python's asyncio backend. {{{#!highlight python from dbus_next.aio import MessageBus import asyncio loop = asyncio.get_event_loop() async def main(): bus = await MessageBus().connect() # the introspection xml would normally be included in your project, but # this is convenient for development introspection = await bus.introspect('org.mpris.MediaPlayer2.vlc', '/org/mpris/MediaPlayer2') obj = bus.get_proxy_object('org.mpris.MediaPlayer2.vlc', '/org/mpris/MediaPlayer2', introspection) player = obj.get_interface('org.mpris.MediaPlayer2.Player') properties = obj.get_interface('org.freedesktop.DBus.Properties') # call methods on the interface (this causes the media player to play) await player.call_play() volume = await player.get_volume() print(f'current volume: {volume}, setting to 0.5') await player.set_volume(0.5) # listen to signals def on_properties_changed(interface_name, changed_properties, invalidated_properties): for changed, variant in changed_properties.items(): print(f'property changed: {changed} - {variant.value}') properties.on_properties_changed(on_properties_changed) await loop.create_future() loop.run_until_complete(main()) }}} == The service interface == To define a service on the bus, use the ServiceInterface class and decorate class methods to specify DBus methods, properties, and signals with their type signatures. {{{#!highlight python from dbus_next.service import ServiceInterface, method, dbus_property, signal, Variant from dbus_next.aio MessageBus import asyncio class ExampleInterface(ServiceInterface): def __init__(self, name): super().__init__(name) self._string_prop = 'kevin' @method() def Echo(self, what: 's') -> 's': return what @method() def GetVariantDict() -> 'a{sv}': return { 'foo': Variant('s', 'bar'), 'bat': Variant('x', -55), 'a_list': Variant('as', ['hello', 'world']) } @dbus_property() def string_prop(self) -> 's': return self._string_prop @string_prop.setter def string_prop_setter(self, val: 's'): self._string_prop = val @signal() def signal_simple(self) -> 's': return 'hello' async def main(): bus = await MessageBus().connect() interface = ExampleInterface('test.interface') bus.export('/test/path', interface) # now that we are ready to handle requests, we can request name from D-Bus await bus.request_name('test.name') # wait indefinitely await asyncio.get_event_loop().create_future() asyncio.get_event_loop().run_until_complete(main()) }}} = python-sdbus = See [[https://python-sdbus.readthedocs.io/en/latest/index.html|python-sdbus documentation for API reference and quickstart guides]]. Python-sdbus has a unified client and server classes where one class can be used both as a client proxy and as a server. == Interface class definition == {{{#!highlight python from sdbus import (DbusInterfaceCommonAsync, dbus_method_async, dbus_property_async, dbus_signal_async) # This is file only contains interface definition for easy import # in server and client files class ExampleInterface( DbusInterfaceCommonAsync, interface_name='org.example.interface' ): @dbus_method_async( input_signature='s', result_signature='s', ) async def upper(self, string: str) -> str: return string.upper() @dbus_property_async( property_signature='s', ) def hello_world(self) -> str: return 'Hello, World!' @dbus_signal_async( signal_signature='i' ) def clock(self) -> int: raise NotImplementedError }}} == Server == {{{#!highlight python from asyncio import new_event_loop, sleep from random import randint from time import time from example_interface import ExampleInterface from sdbus import request_default_bus_name_async loop = new_event_loop() export_object = ExampleInterface() async def clock() -> None: """ This coroutine will sleep a random time and emit a signal with current clock """ while True: await sleep(randint(2, 7)) # Sleep a random time current_time = int(time()) # The interface we defined uses integers export_object.clock.emit(current_time) async def startup() -> None: """Perform async startup actions""" # Acquire a known name on the bus # Clients will use that name to address this server await request_default_bus_name_async('org.example.test') # Export the object to D-Bus export_object.export_to_dbus('/') loop.run_until_complete(startup()) task_clock = loop.create_task(clock()) loop.run_forever() }}} == Client == {{{#!highlight python from asyncio import new_event_loop from example_interface import ExampleInterface # Create a new proxied object example_object = ExampleInterface.new_proxy('org.example.test', '/') async def print_clock() -> None: # Use async for loop to print clock signals we receive async for x in example_object.clock: print('Got clock: ', x) async def call_upper() -> None: s = 'test string' s_after = await example_object.upper(s) print('Initial string: ', s) print('After call: ', s_after) async def get_hello_world() -> None: print('Remote property: ', await example_object.hello_world) loop = new_event_loop() # Always bind your tasks to a variable task_upper = loop.create_task(call_upper()) task_clock = loop.create_task(print_clock()) task_hello_world = loop.create_task(get_hello_world()) loop.run_forever() }}} |
The D-Bus library is a messaging library used by various desktop environments (GNOME, KDE, etc) for interprocess communication.
There are multiple Python bindings for DBus:
GDbus and QtDbus are wrappers over the C/C++ APIs of GLib and Qt
pydbus is a modern, pythonic API with the goal of being as invisible a layer between the exported API and its user as possible
dasbus is a Python3-only alternative to pydbus with additional features and better flexibility
dbus-python is a legacy API, built with a deprecated dbus-glib library, and involving a lot of type-guessing (despite "explicit is better than implicit" and "resist the temptation to guess").
txdbus is a native Python implementation of the D-Bus protocol for the Twisted networking framework.
dbus-next is a native Python library for D-Bus with support for multiple IO backends suitable for GUI develeopment and cross platform projects.
python-sdbus modern D-Bus library supporting both asyncio and blocking calls, unified client-server classes and type hints.
See also: DBusBindings on Freedesktop wiki.
The dbus-viewer and qdbusviewer programs let you browse through the services and interfaces available on your system.
pydbus
For more information see pydbus's Readme.
Introspection
from pydbus import SessionBus bus = SessionBus() # Create an object that will proxy for a particular remote object. remote_object = bus.get( "org.freedesktop.DBus", # Bus name "/org/freedesktop/DBus" # Object path ) # Introspection returns an XML document containing information # about the methods supported by an interface. print("Introspection data:\n") print(remote_object.Introspect())
Output:
Introspection data: <!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> <node> <interface name="org.freedesktop.DBus.Introspectable"> <method name="Introspect"> <arg name="data" direction="out" type="s"/> </method> </interface> <interface name="org.freedesktop.DBus"> <method name="RequestName"> <arg direction="in" type="s"/> <arg direction="in" type="u"/> <arg direction="out" type="u"/> </method> ...
Calling an interface method
After executing the introspection example:
print(remote_object.ListNames())
Output:
['org.freedesktop.DBus', 'org.freedesktop.Notifications', 'org.freedesktop.PowerManagement', ':1.8', ':1.9', 'org.kde.kaccess', 'org.kde.kded', 'org.kde.StatusNotifierItem-655-1', 'org.freedesktop.systemd1', 'org.ktorrent.ktorrent', 'org.kde.StatusNotifierItem-656-1', 'org.kde.konversation', 'org.pulseaudio.Server', 'org.kde.KScreen', 'org.kde.krunner', 'org.kde.konsole', ':1.40', 'org.a11y.Bus', ':1.41', ':1.42', ':1.20', ':1.43', 'org.kde.klauncher5', ':1.21', ':1.23', 'org.kde.dolphin-3012', 'org.freedesktop.PowerManagement.Inhibit', 'org.kde.Solid.PowerManagement', ':1.24', ':1.25', ':1.49', 'org.kde.kmix', 'org.kde.screensaver', 'org.kde.KWin', 'org.bluez.obex', ':1.29', 'ca.desrt.dconf', 'org.kde.kgpg', 'org.freedesktop.ScreenSaver', 'org.kde.plasmashell', 'org.kde.plasmanetworkmanagement', 'org.kde.StatusNotifierItem-666-1', 'org.kde.kglobalaccel', 'org.freedesktop.FileManager1', 'org.kde.kwalletd5', 'org.PulseAudio1', 'org.kde.polkit-kde-authentication-agent-1', ':1.93', 'org.kde.kded5', 'org.kde.ActivityManager', 'org.kde.keyboard', 'org.kde.kate-3030', ':1.31', 'org.kde.kuiserver', ':1.32', ':1.55', ':1.33', ':1.11', 'org.kde.kwin.Screenshot', ':1.56', ':1.34', 'org.kde.StatusNotifierWatcher', 'org.kde.JobViewServer', ':1.35', ':1.0', ':1.13', ':1.58', 'org.kde.StatusNotifierHost-616', ':1.14', ':1.59', ':1.15', ':1.38', ':1.16', 'org.kde.ksmserver', ':1.39', ':1.17', ':1.5', 'org.kde.Solid.PowerManagement.PolicyAgent', ':1.18', 'org.kde.klauncher', ':1.6', ':1.19']
The following example makes your system hibernate:
# Get the power management object power = bus.get('org.gnome.PowerManager', '/org/gnome/PowerManager') # Hibernate the system if power.CanHibernate(): power.Hibernate()
dasbus
For more information see dasbus's documentation.
Introspection of a remote object
Introspection returns an XML string containing information about interfaces, methods, properties and signals of the remote object.
from dasbus.connection import SessionMessageBus bus = SessionMessageBus() # Create an object that will be a proxy for a particular remote object. remote_object = bus.get_proxy( "org.freedesktop.DBus", # The bus name "/org/freedesktop/DBus" # The object path ) # Call the Introspect method of the remote object. print(remote_object.Introspect())
Accessing a remote property
The following example prints the current hostname.
from dasbus.connection import SystemMessageBus bus = SystemMessageBus() proxy = bus.get_proxy( "org.freedesktop.hostname1", "/org/freedesktop/hostname1" ) print(proxy.Hostname)
Calling a remote method
The following example sends a notification to the notification server.
from dasbus.connection import SessionMessageBus bus = SessionMessageBus() proxy = bus.get_proxy( "org.freedesktop.Notifications", "/org/freedesktop/Notifications" ) id = proxy.Notify( "", 0, "face-smile", "My notification", "Hello World!", [], {}, 0 ) print("The notification {} was sent.".format(id))
dbus-next
For more information see dbus-next's documentation.
The client interface
To use a service on the bus, the library constructs a proxy object you can use to call methods, get and set properties, and listen to signals.
This example connects to a media player and controls it with the MPRIS DBus interface using python's asyncio backend.
1 from dbus_next.aio import MessageBus
2
3 import asyncio
4
5 loop = asyncio.get_event_loop()
6
7
8 async def main():
9 bus = await MessageBus().connect()
10 # the introspection xml would normally be included in your project, but
11 # this is convenient for development
12 introspection = await bus.introspect('org.mpris.MediaPlayer2.vlc', '/org/mpris/MediaPlayer2')
13
14 obj = bus.get_proxy_object('org.mpris.MediaPlayer2.vlc', '/org/mpris/MediaPlayer2', introspection)
15 player = obj.get_interface('org.mpris.MediaPlayer2.Player')
16 properties = obj.get_interface('org.freedesktop.DBus.Properties')
17
18 # call methods on the interface (this causes the media player to play)
19 await player.call_play()
20
21 volume = await player.get_volume()
22 print(f'current volume: {volume}, setting to 0.5')
23
24 await player.set_volume(0.5)
25
26 # listen to signals
27 def on_properties_changed(interface_name, changed_properties, invalidated_properties):
28 for changed, variant in changed_properties.items():
29 print(f'property changed: {changed} - {variant.value}')
30
31 properties.on_properties_changed(on_properties_changed)
32
33 await loop.create_future()
34
35 loop.run_until_complete(main())
The service interface
To define a service on the bus, use the ServiceInterface class and decorate class methods to specify DBus methods, properties, and signals with their type signatures.
1 from dbus_next.service import ServiceInterface, method, dbus_property, signal, Variant
2 from dbus_next.aio MessageBus
3
4 import asyncio
5
6 class ExampleInterface(ServiceInterface):
7 def __init__(self, name):
8 super().__init__(name)
9 self._string_prop = 'kevin'
10
11 @method()
12 def Echo(self, what: 's') -> 's':
13 return what
14
15 @method()
16 def GetVariantDict() -> 'a{sv}':
17 return {
18 'foo': Variant('s', 'bar'),
19 'bat': Variant('x', -55),
20 'a_list': Variant('as', ['hello', 'world'])
21 }
22
23 @dbus_property()
24 def string_prop(self) -> 's':
25 return self._string_prop
26
27 @string_prop.setter
28 def string_prop_setter(self, val: 's'):
29 self._string_prop = val
30
31 @signal()
32 def signal_simple(self) -> 's':
33 return 'hello'
34
35 async def main():
36 bus = await MessageBus().connect()
37 interface = ExampleInterface('test.interface')
38 bus.export('/test/path', interface)
39 # now that we are ready to handle requests, we can request name from D-Bus
40 await bus.request_name('test.name')
41 # wait indefinitely
42 await asyncio.get_event_loop().create_future()
43
44 asyncio.get_event_loop().run_until_complete(main())
python-sdbus
See python-sdbus documentation for API reference and quickstart guides.
Python-sdbus has a unified client and server classes where one class can be used both as a client proxy and as a server.
Interface class definition
1 from sdbus import (DbusInterfaceCommonAsync, dbus_method_async,
2 dbus_property_async, dbus_signal_async)
3
4 # This is file only contains interface definition for easy import
5 # in server and client files
6
7 class ExampleInterface(
8 DbusInterfaceCommonAsync,
9 interface_name='org.example.interface'
10 ):
11 @dbus_method_async(
12 input_signature='s',
13 result_signature='s',
14 )
15 async def upper(self, string: str) -> str:
16 return string.upper()
17
18 @dbus_property_async(
19 property_signature='s',
20 )
21 def hello_world(self) -> str:
22 return 'Hello, World!'
23
24 @dbus_signal_async(
25 signal_signature='i'
26 )
27 def clock(self) -> int:
28 raise NotImplementedError
Server
1 from asyncio import new_event_loop, sleep
2 from random import randint
3 from time import time
4
5 from example_interface import ExampleInterface
6
7 from sdbus import request_default_bus_name_async
8
9 loop = new_event_loop()
10
11 export_object = ExampleInterface()
12
13
14 async def clock() -> None:
15 """
16 This coroutine will sleep a random time and emit
17 a signal with current clock
18 """
19 while True:
20 await sleep(randint(2, 7)) # Sleep a random time
21 current_time = int(time()) # The interface we defined uses integers
22 export_object.clock.emit(current_time)
23
24
25 async def startup() -> None:
26 """Perform async startup actions"""
27 # Acquire a known name on the bus
28 # Clients will use that name to address this server
29 await request_default_bus_name_async('org.example.test')
30 # Export the object to D-Bus
31 export_object.export_to_dbus('/')
32
33
34 loop.run_until_complete(startup())
35 task_clock = loop.create_task(clock())
36 loop.run_forever()
Client
1 from asyncio import new_event_loop
2
3 from example_interface import ExampleInterface
4
5 # Create a new proxied object
6 example_object = ExampleInterface.new_proxy('org.example.test', '/')
7
8
9 async def print_clock() -> None:
10 # Use async for loop to print clock signals we receive
11 async for x in example_object.clock:
12 print('Got clock: ', x)
13
14
15 async def call_upper() -> None:
16 s = 'test string'
17 s_after = await example_object.upper(s)
18
19 print('Initial string: ', s)
20 print('After call: ', s_after)
21
22
23 async def get_hello_world() -> None:
24 print('Remote property: ', await example_object.hello_world)
25
26 loop = new_event_loop()
27
28 # Always bind your tasks to a variable
29 task_upper = loop.create_task(call_upper())
30 task_clock = loop.create_task(print_clock())
31 task_hello_world = loop.create_task(get_hello_world())
32
33 loop.run_forever()