Skip to content

Commit aab9ff4

Browse files
committed
jeepney: add asyncio server
1 parent 6aceac3 commit aab9ff4

3 files changed

Lines changed: 136 additions & 0 deletions

File tree

dbus_objects/integration/jeepney.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,3 +240,72 @@ async def listen(self) -> None:
240240
await self._handle_msg(msg)
241241
except KeyboardInterrupt:
242242
self._logger.info('exiting...')
243+
244+
245+
class AsyncIODBusServer(_JeepneyServerBase):
246+
def __init__(self, bus: str, name: str) -> None:
247+
'''
248+
Async DBus server built on top of Jeepney+asyncio
249+
250+
:param bus: DBus bus (hint: usually SESSION or SYSTEM)
251+
:param name: DBus name
252+
'''
253+
super().__init__(bus, name)
254+
self._logger = logging.getLogger(self.__class__.__name__)
255+
# TODO: support signals
256+
# self.emit_signal_callback = self.emit_signal
257+
258+
# We can't have an async __init__ method, so we use this as an alternative.
259+
@classmethod
260+
async def new(cls, bus: str, name: str) -> AsyncIODBusServer:
261+
inst = cls(bus, name)
262+
await inst._conn_start()
263+
return inst
264+
265+
async def _conn_start(self) -> None:
266+
'''
267+
Start DBus connection
268+
'''
269+
import jeepney.io.asyncio
270+
271+
self._conn = await jeepney.io.asyncio.open_dbus_connection(self._bus)
272+
async with jeepney.io.asyncio.DBusRouter(self._conn) as router:
273+
bus_proxy = jeepney.io.asyncio.Proxy(jeepney.message_bus, router)
274+
await bus_proxy.RequestName(self._name)
275+
276+
async def _handle_msg(self, msg: jeepney.Message) -> None:
277+
'''
278+
Handle message
279+
280+
:param msg: message to handle
281+
'''
282+
return_msg = self._jeepney_handle_msg(msg)
283+
if return_msg:
284+
await self._conn.send(return_msg)
285+
286+
async def emit_signal(self, signal: dbus_objects._DBusSignal, path: str, body: Any) -> None:
287+
self._logger.debug(f'emitting signal: {signal.name} {body}')
288+
await self._conn.send_message(self._get_signal_msg(signal, path, body))
289+
290+
async def close(self) -> None:
291+
'''
292+
Close the DBus connection
293+
'''
294+
await self._conn.close()
295+
296+
async def listen(self) -> None:
297+
'''
298+
Start listening and handling messages
299+
'''
300+
self._log_topology()
301+
try:
302+
while True:
303+
try:
304+
msg = await self._conn.receive()
305+
except ConnectionResetError:
306+
self._logger.debug('connection reset abruptly, restarting...')
307+
await self._conn_start()
308+
else:
309+
await self._handle_msg(msg)
310+
except KeyboardInterrupt:
311+
self._logger.info('exiting...')

setup.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,13 @@ jeepney =
3333
jeepney >= 0.5
3434
test =
3535
pytest
36+
pytest-asyncio
3637
pytest-subtests
3738
pytest-cov
3839
pytest-trio
3940
jeepney >= 0.5
4041
trio
42+
async-timeout
4143
xmldiff
4244
docs =
4345
furo

tests/test_jeepney_asyncio.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# SPDX-License-Identifier: MIT
2+
3+
import asyncio
4+
import async_timeout
5+
import contextlib
6+
import jeepney
7+
import jeepney.io.asyncio
8+
import pytest
9+
10+
from dbus_objects.integration.jeepney import AsyncIODBusServer
11+
12+
13+
pytestmark = [
14+
pytest.mark.asyncio,
15+
]
16+
17+
18+
@pytest.fixture()
19+
async def jeepney_asyncio_server(obj, event_loop):
20+
server = await AsyncIODBusServer.new(
21+
bus='SESSION',
22+
name='io.github.ffy00.dbus-objects.jeepney_asyncio_test',
23+
)
24+
25+
server.register_object('/io/github/ffy00/dbus_objects/example', obj)
26+
27+
listen = event_loop.create_task(server.listen())
28+
try:
29+
yield
30+
31+
finally:
32+
listen.cancel()
33+
await server.close()
34+
with contextlib.suppress(asyncio.CancelledError):
35+
await listen
36+
37+
38+
@pytest.fixture()
39+
def jeepney_asyncio_client():
40+
yield jeepney.DBusAddress(
41+
'/io/github/ffy00/dbus_objects/example',
42+
bus_name='io.github.ffy00.dbus-objects.jeepney_asyncio_test',
43+
interface='com.example.object.ExampleObject',
44+
)
45+
46+
47+
@pytest.fixture()
48+
async def jeepney_asyncio_router():
49+
async with jeepney.io.asyncio.open_dbus_router(bus='SESSION') as router:
50+
yield router
51+
52+
53+
async def test_create_error():
54+
with pytest.raises(jeepney.DBusErrorResponse):
55+
await AsyncIODBusServer.new(bus='SESSION', name='org.freedesktop.DBus')
56+
57+
58+
async def test_listen_asyncio(jeepney_asyncio_client, jeepney_asyncio_router, jeepney_asyncio_server):
59+
msg = jeepney.new_method_call(jeepney_asyncio_client, 'Ping', '', tuple())
60+
61+
async with async_timeout.timeout(3):
62+
reply = await jeepney_asyncio_router.send_and_get_reply(msg)
63+
64+
assert reply.header.message_type is jeepney.MessageType.method_return
65+
assert reply.body[0] == 'Pong!'

0 commit comments

Comments
 (0)