dbus-fast 2.46.1__cp311-cp311-musllinux_1_2_aarch64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of dbus-fast might be problematic. Click here for more details.

Files changed (51) hide show
  1. dbus_fast/__init__.py +82 -0
  2. dbus_fast/__version__.py +10 -0
  3. dbus_fast/_private/__init__.py +1 -0
  4. dbus_fast/_private/_cython_compat.py +14 -0
  5. dbus_fast/_private/address.cpython-311-aarch64-linux-musl.so +0 -0
  6. dbus_fast/_private/address.pxd +15 -0
  7. dbus_fast/_private/address.py +117 -0
  8. dbus_fast/_private/constants.py +20 -0
  9. dbus_fast/_private/marshaller.cpython-311-aarch64-linux-musl.so +0 -0
  10. dbus_fast/_private/marshaller.pxd +110 -0
  11. dbus_fast/_private/marshaller.py +229 -0
  12. dbus_fast/_private/unmarshaller.cpython-311-aarch64-linux-musl.so +0 -0
  13. dbus_fast/_private/unmarshaller.pxd +261 -0
  14. dbus_fast/_private/unmarshaller.py +902 -0
  15. dbus_fast/_private/util.py +177 -0
  16. dbus_fast/aio/__init__.py +5 -0
  17. dbus_fast/aio/message_bus.py +579 -0
  18. dbus_fast/aio/message_reader.cpython-311-aarch64-linux-musl.so +0 -0
  19. dbus_fast/aio/message_reader.pxd +13 -0
  20. dbus_fast/aio/message_reader.py +49 -0
  21. dbus_fast/aio/proxy_object.py +207 -0
  22. dbus_fast/auth.py +125 -0
  23. dbus_fast/constants.py +152 -0
  24. dbus_fast/errors.py +81 -0
  25. dbus_fast/glib/__init__.py +3 -0
  26. dbus_fast/glib/message_bus.py +513 -0
  27. dbus_fast/glib/proxy_object.py +318 -0
  28. dbus_fast/introspection.py +682 -0
  29. dbus_fast/message.cpython-311-aarch64-linux-musl.so +0 -0
  30. dbus_fast/message.pxd +76 -0
  31. dbus_fast/message.py +387 -0
  32. dbus_fast/message_bus.cpython-311-aarch64-linux-musl.so +0 -0
  33. dbus_fast/message_bus.pxd +75 -0
  34. dbus_fast/message_bus.py +1326 -0
  35. dbus_fast/proxy_object.py +357 -0
  36. dbus_fast/py.typed +0 -0
  37. dbus_fast/send_reply.py +61 -0
  38. dbus_fast/service.cpython-311-aarch64-linux-musl.so +0 -0
  39. dbus_fast/service.pxd +50 -0
  40. dbus_fast/service.py +699 -0
  41. dbus_fast/signature.cpython-311-aarch64-linux-musl.so +0 -0
  42. dbus_fast/signature.pxd +31 -0
  43. dbus_fast/signature.py +482 -0
  44. dbus_fast/unpack.cpython-311-aarch64-linux-musl.so +0 -0
  45. dbus_fast/unpack.pxd +13 -0
  46. dbus_fast/unpack.py +24 -0
  47. dbus_fast/validators.py +199 -0
  48. dbus_fast-2.46.1.dist-info/METADATA +262 -0
  49. dbus_fast-2.46.1.dist-info/RECORD +51 -0
  50. dbus_fast-2.46.1.dist-info/WHEEL +4 -0
  51. dbus_fast-2.46.1.dist-info/licenses/LICENSE +22 -0
@@ -0,0 +1,579 @@
1
+ from __future__ import annotations
2
+
3
+ import array
4
+ import asyncio
5
+ import contextlib
6
+ import logging
7
+ import socket
8
+ from collections import deque
9
+ from collections.abc import Callable
10
+ from copy import copy
11
+ from functools import partial
12
+ from typing import Any
13
+
14
+ from .. import introspection as intr
15
+ from ..auth import Authenticator, AuthExternal
16
+ from ..constants import (
17
+ BusType,
18
+ MessageFlag,
19
+ MessageType,
20
+ NameFlag,
21
+ ReleaseNameReply,
22
+ RequestNameReply,
23
+ )
24
+ from ..errors import AuthError
25
+ from ..message import Message
26
+ from ..message_bus import BaseMessageBus, _block_unexpected_reply
27
+ from ..service import ServiceInterface, _Method
28
+ from .message_reader import build_message_reader
29
+ from .proxy_object import ProxyObject
30
+
31
+ NO_REPLY_EXPECTED_VALUE = MessageFlag.NO_REPLY_EXPECTED.value
32
+
33
+ _LOGGER = logging.getLogger(__name__)
34
+
35
+
36
+ def _generate_hello_serialized(next_serial: int) -> bytes:
37
+ return bytes(
38
+ Message(
39
+ destination="org.freedesktop.DBus",
40
+ path="/org/freedesktop/DBus",
41
+ interface="org.freedesktop.DBus",
42
+ member="Hello",
43
+ serial=next_serial,
44
+ )._marshall(False)
45
+ )
46
+
47
+
48
+ HELLO_1_SERIALIZED = _generate_hello_serialized(1)
49
+
50
+
51
+ def _future_set_exception(fut: asyncio.Future, exc: Exception) -> None:
52
+ if fut is not None and not fut.done():
53
+ fut.set_exception(exc)
54
+
55
+
56
+ def _future_set_result(fut: asyncio.Future, result: Any) -> None:
57
+ if fut is not None and not fut.done():
58
+ fut.set_result(result)
59
+
60
+
61
+ class _MessageWriter:
62
+ """A class to handle writing messages to the message bus."""
63
+
64
+ def __init__(self, bus: MessageBus) -> None:
65
+ """A class to handle writing messages to the message bus."""
66
+ self.messages: deque[
67
+ tuple[bytearray, list[int] | None, asyncio.Future | None]
68
+ ] = deque()
69
+ self.negotiate_unix_fd = bus._negotiate_unix_fd
70
+ self.bus = bus
71
+ self.sock = bus._sock
72
+ self.loop = bus._loop
73
+ self.buf: memoryview | None = None
74
+ self.fd = bus._fd
75
+ self.offset = 0
76
+ self.unix_fds: list[int] | None = None
77
+ self.fut: asyncio.Future | None = None
78
+
79
+ def write_callback(self, remove_writer: bool = True) -> None:
80
+ """The callback to write messages to the message bus."""
81
+ sock = self.sock
82
+ try:
83
+ while True:
84
+ if self.buf is None:
85
+ # If there is no buffer, get the next message
86
+ if not self.messages:
87
+ # nothing more to write
88
+ if remove_writer:
89
+ self.loop.remove_writer(self.fd)
90
+ return
91
+
92
+ # Get the next message
93
+ buf, unix_fds, fut = self.messages.popleft()
94
+ self.unix_fds = unix_fds
95
+ self.buf = memoryview(buf)
96
+ self.offset = 0
97
+ self.fut = fut
98
+
99
+ if self.unix_fds and self.negotiate_unix_fd:
100
+ ancdata = [
101
+ (
102
+ socket.SOL_SOCKET,
103
+ socket.SCM_RIGHTS,
104
+ array.array("i", self.unix_fds),
105
+ )
106
+ ]
107
+ self.offset += sock.sendmsg([self.buf[self.offset :]], ancdata)
108
+ self.unix_fds = None
109
+ else:
110
+ self.offset += sock.send(self.buf[self.offset :])
111
+
112
+ if self.offset < len(self.buf):
113
+ # wait for writable
114
+ return
115
+
116
+ # finished writing
117
+ self.buf = None
118
+ _future_set_result(self.fut, None)
119
+ except Exception as e:
120
+ if self.bus._user_disconnect:
121
+ _future_set_result(self.fut, None)
122
+ else:
123
+ _future_set_exception(self.fut, e)
124
+ self.bus._finalize(e)
125
+
126
+ def buffer_message(
127
+ self, msg: Message, future: asyncio.Future | None = None
128
+ ) -> None:
129
+ """Buffer a message to be sent later."""
130
+ unix_fds = msg.unix_fds
131
+ self.messages.append(
132
+ (
133
+ msg._marshall(self.negotiate_unix_fd),
134
+ copy(unix_fds) if unix_fds else None,
135
+ future,
136
+ )
137
+ )
138
+
139
+ def _write_without_remove_writer(self) -> None:
140
+ """Call the write callback without removing the writer."""
141
+ self.write_callback(remove_writer=False)
142
+
143
+ def schedule_write(
144
+ self, msg: Message | None = None, future: asyncio.Future | None = None
145
+ ) -> None:
146
+ """Schedule a message to be written."""
147
+ queue_is_empty = not self.messages
148
+ if msg is not None:
149
+ self.buffer_message(msg, future)
150
+
151
+ if self.bus.unique_name:
152
+ # Optimization: try to send now if the queue
153
+ # is empty. With bleak this usually means we
154
+ # can send right away 99% of the time which
155
+ # is a huge improvement in latency.
156
+ if queue_is_empty:
157
+ self._write_without_remove_writer()
158
+
159
+ if (
160
+ self.buf is not None
161
+ or self.messages
162
+ or not self.fut
163
+ or not self.fut.done()
164
+ ):
165
+ self.loop.add_writer(self.fd, self.write_callback)
166
+
167
+
168
+ class MessageBus(BaseMessageBus):
169
+ """The message bus implementation for use with asyncio.
170
+
171
+ The message bus class is the entry point into all the features of the
172
+ library. It sets up a connection to the DBus daemon and exposes an
173
+ interface to send and receive messages and expose services.
174
+
175
+ You must call :func:`connect() <dbus_fast.aio.MessageBus.connect>` before
176
+ using this message bus.
177
+
178
+ :param bus_type: The type of bus to connect to. Affects the search path for
179
+ the bus address.
180
+ :type bus_type: :class:`BusType <dbus_fast.BusType>`
181
+ :param bus_address: A specific bus address to connect to. Should not be
182
+ used under normal circumstances.
183
+ :param auth: The authenticator to use, defaults to an instance of
184
+ :class:`AuthExternal <dbus_fast.auth.AuthExternal>`.
185
+ :type auth: :class:`Authenticator <dbus_fast.auth.Authenticator>`
186
+ :param negotiate_unix_fd: Allow the bus to send and receive Unix file
187
+ descriptors (DBus type 'h'). This must be supported by the transport.
188
+ :type negotiate_unix_fd: bool
189
+
190
+ :ivar unique_name: The unique name of the message bus connection. It will
191
+ be :class:`None` until the message bus connects.
192
+ :vartype unique_name: str
193
+ :ivar connected: True if this message bus is expected to be able to send
194
+ and receive messages.
195
+ :vartype connected: bool
196
+ """
197
+
198
+ __slots__ = ("_auth", "_disconnect_future", "_loop", "_pending_futures", "_writer")
199
+
200
+ def __init__(
201
+ self,
202
+ bus_address: str | None = None,
203
+ bus_type: BusType = BusType.SESSION,
204
+ auth: Authenticator | None = None,
205
+ negotiate_unix_fd: bool = False,
206
+ ) -> None:
207
+ super().__init__(bus_address, bus_type, ProxyObject, negotiate_unix_fd)
208
+ self._loop = asyncio.get_running_loop()
209
+
210
+ self._writer = _MessageWriter(self)
211
+
212
+ if auth is None:
213
+ self._auth = AuthExternal()
214
+ else:
215
+ self._auth = auth
216
+
217
+ self._disconnect_future = self._loop.create_future()
218
+ self._pending_futures: set[asyncio.Future] = set()
219
+
220
+ async def connect(self) -> MessageBus:
221
+ """Connect this message bus to the DBus daemon.
222
+
223
+ This method must be called before the message bus can be used.
224
+
225
+ :returns: This message bus for convenience.
226
+ :rtype: :class:`MessageBus <dbus_fast.aio.MessageBus>`
227
+
228
+ :raises:
229
+ - :class:`AuthError <dbus_fast.AuthError>` - If authorization to \
230
+ the DBus daemon failed.
231
+ - :class:`Exception` - If there was a connection error.
232
+ """
233
+ await self._authenticate()
234
+
235
+ future = self._loop.create_future()
236
+
237
+ self._loop.add_reader(
238
+ self._fd,
239
+ build_message_reader(
240
+ self._sock,
241
+ self._process_message,
242
+ self._finalize,
243
+ self._negotiate_unix_fd,
244
+ ),
245
+ )
246
+
247
+ def on_hello(reply, err):
248
+ try:
249
+ if err:
250
+ raise err
251
+ self.unique_name = reply.body[0]
252
+ self._writer.schedule_write()
253
+ _future_set_result(future, self)
254
+ except Exception as e:
255
+ _future_set_exception(future, e)
256
+ self.disconnect()
257
+ self._finalize(err)
258
+
259
+ next_serial = self.next_serial()
260
+ self._method_return_handlers[next_serial] = on_hello
261
+ if next_serial == 1:
262
+ serialized = HELLO_1_SERIALIZED
263
+ else:
264
+ serialized = _generate_hello_serialized(next_serial)
265
+ self._stream.write(serialized)
266
+ self._stream.flush()
267
+ return await future
268
+
269
+ async def introspect(
270
+ self,
271
+ bus_name: str,
272
+ path: str,
273
+ timeout: float = 30.0,
274
+ validate_property_names: bool = True,
275
+ ) -> intr.Node:
276
+ """Get introspection data for the node at the given path from the given
277
+ bus name.
278
+
279
+ Calls the standard ``org.freedesktop.DBus.Introspectable.Introspect``
280
+ on the bus for the path.
281
+
282
+ :param bus_name: The name to introspect.
283
+ :type bus_name: str
284
+ :param path: The path to introspect.
285
+ :type path: str
286
+ :param timeout: The timeout to introspect.
287
+ :type timeout: float
288
+ :param validate_property_names: Whether to validate property names or not.
289
+ :type validate_property_names: bool
290
+
291
+ :returns: The introspection data for the name at the path.
292
+ :rtype: :class:`Node <dbus_fast.introspection.Node>`
293
+
294
+ :raises:
295
+ - :class:`InvalidObjectPathError <dbus_fast.InvalidObjectPathError>` \
296
+ - If the given object path is not valid.
297
+ - :class:`InvalidBusNameError <dbus_fast.InvalidBusNameError>` - If \
298
+ the given bus name is not valid.
299
+ - :class:`DBusError <dbus_fast.DBusError>` - If the service threw \
300
+ an error for the method call or returned an invalid result.
301
+ - :class:`Exception` - If a connection error occurred.
302
+ - :class:`asyncio.TimeoutError` - Waited for future but time run out.
303
+ """
304
+ future = self._loop.create_future()
305
+
306
+ super().introspect(
307
+ bus_name,
308
+ path,
309
+ partial(self._reply_handler, future),
310
+ check_callback_type=False,
311
+ validate_property_names=validate_property_names,
312
+ )
313
+
314
+ timer_handle = self._loop.call_later(
315
+ timeout, _future_set_exception, future, asyncio.TimeoutError
316
+ )
317
+ try:
318
+ return await future
319
+ finally:
320
+ timer_handle.cancel()
321
+
322
+ async def request_name(
323
+ self, name: str, flags: NameFlag = NameFlag.NONE
324
+ ) -> RequestNameReply:
325
+ """Request that this message bus owns the given name.
326
+
327
+ :param name: The name to request.
328
+ :type name: str
329
+ :param flags: Name flags that affect the behavior of the name request.
330
+ :type flags: :class:`NameFlag <dbus_fast.NameFlag>`
331
+
332
+ :returns: The reply to the name request.
333
+ :rtype: :class:`RequestNameReply <dbus_fast.RequestNameReply>`
334
+
335
+ :raises:
336
+ - :class:`InvalidBusNameError <dbus_fast.InvalidBusNameError>` - If \
337
+ the given bus name is not valid.
338
+ - :class:`DBusError <dbus_fast.DBusError>` - If the service threw \
339
+ an error for the method call or returned an invalid result.
340
+ - :class:`Exception` - If a connection error occurred.
341
+ """
342
+ future = self._loop.create_future()
343
+
344
+ super().request_name(
345
+ name, flags, partial(self._reply_handler, future), check_callback_type=False
346
+ )
347
+
348
+ return await future
349
+
350
+ async def release_name(self, name: str) -> ReleaseNameReply:
351
+ """Request that this message bus release the given name.
352
+
353
+ :param name: The name to release.
354
+ :type name: str
355
+
356
+ :returns: The reply to the release request.
357
+ :rtype: :class:`ReleaseNameReply <dbus_fast.ReleaseNameReply>`
358
+
359
+ :raises:
360
+ - :class:`InvalidBusNameError <dbus_fast.InvalidBusNameError>` - If \
361
+ the given bus name is not valid.
362
+ - :class:`DBusError <dbus_fast.DBusError>` - If the service threw \
363
+ an error for the method call or returned an invalid result.
364
+ - :class:`Exception` - If a connection error occurred.
365
+ """
366
+ future = self._loop.create_future()
367
+
368
+ super().release_name(
369
+ name, partial(self._reply_handler, future), check_callback_type=False
370
+ )
371
+
372
+ return await future
373
+
374
+ async def call(self, msg: Message) -> Message | None:
375
+ """Send a method call and wait for a reply from the DBus daemon.
376
+
377
+ :param msg: The method call message to send.
378
+ :type msg: :class:`Message <dbus_fast.Message>`
379
+
380
+ :returns: A message in reply to the message sent. If the message does
381
+ not expect a reply based on the message flags or type, returns
382
+ ``None`` after the message is sent.
383
+ :rtype: :class:`Message <dbus_fast.Message>` or :class:`None` if no reply is expected.
384
+
385
+ :raises:
386
+ - :class:`Exception` - If a connection error occurred.
387
+ """
388
+ if (
389
+ msg.flags.value & NO_REPLY_EXPECTED_VALUE
390
+ or msg.message_type is not MessageType.METHOD_CALL
391
+ ):
392
+ await self.send(msg)
393
+ return None
394
+
395
+ future = self._loop.create_future()
396
+
397
+ self._call(msg, partial(self._reply_handler, future))
398
+
399
+ await future
400
+
401
+ return future.result()
402
+
403
+ def send(self, msg: Message) -> asyncio.Future:
404
+ """Asynchronously send a message on the message bus.
405
+
406
+ .. note:: This method may change to a couroutine function in the 1.0
407
+ release of the library.
408
+
409
+ :param msg: The message to send.
410
+ :type msg: :class:`Message <dbus_fast.Message>`
411
+
412
+ :returns: A future that resolves when the message is sent or a
413
+ connection error occurs.
414
+ :rtype: :class:`Future <asyncio.Future>`
415
+ """
416
+ if not msg.serial:
417
+ msg.serial = self.next_serial()
418
+
419
+ future = self._loop.create_future()
420
+ self._writer.schedule_write(msg, future)
421
+ return future
422
+
423
+ def get_proxy_object(
424
+ self, bus_name: str, path: str, introspection: intr.Node
425
+ ) -> ProxyObject:
426
+ return super().get_proxy_object(bus_name, path, introspection)
427
+
428
+ async def wait_for_disconnect(self):
429
+ """Wait for the message bus to disconnect.
430
+
431
+ :returns: :class:`None` when the message bus has disconnected.
432
+ :rtype: :class:`None`
433
+
434
+ :raises:
435
+ - :class:`Exception` - If connection was terminated unexpectedly or \
436
+ an internal error occurred in the library.
437
+ """
438
+ return await self._disconnect_future
439
+
440
+ def _future_exception_no_reply(self, fut: asyncio.Future) -> None:
441
+ """Log an exception from a future that was not expected."""
442
+ self._pending_futures.discard(fut)
443
+ try:
444
+ fut.result()
445
+ except asyncio.CancelledError:
446
+ pass
447
+ except Exception as e:
448
+ _LOGGER.exception("unexpected exception in future", exc_info=e)
449
+
450
+ def _make_method_handler(
451
+ self, interface: ServiceInterface, method: _Method
452
+ ) -> Callable[[Message, Callable[[Message], None]], None]:
453
+ if not asyncio.iscoroutinefunction(method.fn):
454
+ return super()._make_method_handler(interface, method)
455
+
456
+ negotiate_unix_fd = self._negotiate_unix_fd
457
+ msg_body_to_args = ServiceInterface._msg_body_to_args
458
+ fn_result_to_body = ServiceInterface._fn_result_to_body
459
+
460
+ def _coroutine_method_handler(
461
+ msg: Message, send_reply: Callable[[Message], None]
462
+ ) -> None:
463
+ """A coroutine method handler."""
464
+ args = msg_body_to_args(msg) if msg.unix_fds else msg.body
465
+ fut: asyncio.Future = asyncio.ensure_future(method.fn(interface, *args))
466
+ # Hold a strong reference to the future to ensure
467
+ # it is not garbage collected before it is done.
468
+ self._pending_futures.add(fut)
469
+ if (
470
+ send_reply is _block_unexpected_reply
471
+ or msg.flags.value & NO_REPLY_EXPECTED_VALUE
472
+ ):
473
+ fut.add_done_callback(self._future_exception_no_reply)
474
+ return
475
+
476
+ # We only create the closure function if we are actually going to reply
477
+ def _done(fut: asyncio.Future) -> None:
478
+ """The callback for when the method is done."""
479
+ with send_reply:
480
+ result = fut.result()
481
+ body, unix_fds = fn_result_to_body(
482
+ result, method.out_signature_tree, replace_fds=negotiate_unix_fd
483
+ )
484
+ send_reply(
485
+ Message.new_method_return(
486
+ msg, method.out_signature, body, unix_fds
487
+ )
488
+ )
489
+
490
+ fut.add_done_callback(_done)
491
+ # Discard the future only after running the done callback
492
+ fut.add_done_callback(self._pending_futures.discard)
493
+
494
+ return _coroutine_method_handler
495
+
496
+ async def _auth_readline(self) -> str:
497
+ buf = b""
498
+ while buf[-2:] != b"\r\n":
499
+ # The auth protocol is line based, so we can read until we get a
500
+ # newline.
501
+ buf += await self._loop.sock_recv(self._sock, 1024)
502
+ return buf[:-2].decode()
503
+
504
+ async def _authenticate(self) -> None:
505
+ await self._loop.sock_sendall(self._sock, b"\0")
506
+
507
+ first_line = self._auth._authentication_start(
508
+ negotiate_unix_fd=self._negotiate_unix_fd
509
+ )
510
+
511
+ if first_line is not None:
512
+ if type(first_line) is not str:
513
+ raise AuthError("authenticator gave response not type str")
514
+ await self._loop.sock_sendall(
515
+ self._sock, Authenticator._format_line(first_line)
516
+ )
517
+
518
+ while True:
519
+ response = self._auth._receive_line(await self._auth_readline())
520
+ if response is not None:
521
+ await self._loop.sock_sendall(
522
+ self._sock, Authenticator._format_line(response)
523
+ )
524
+ self._stream.flush()
525
+ if response == "BEGIN":
526
+ # The first octet received by the server after the \r\n of the BEGIN command
527
+ # from the client must be the first octet of the authenticated/encrypted stream
528
+ # of D-Bus messages.
529
+ break
530
+
531
+ def disconnect(self) -> None:
532
+ """Disconnect the message bus by closing the underlying connection asynchronously.
533
+
534
+ All pending and future calls will error with a connection error.
535
+ """
536
+ super().disconnect()
537
+ try:
538
+ self._sock.close()
539
+ except Exception:
540
+ _LOGGER.warning("could not close socket", exc_info=True)
541
+
542
+ def _finalize(self, err: Exception | None = None) -> None:
543
+ try:
544
+ self._loop.remove_reader(self._fd)
545
+ except Exception:
546
+ _LOGGER.warning("could not remove message reader", exc_info=True)
547
+ try:
548
+ self._loop.remove_writer(self._fd)
549
+ except Exception:
550
+ _LOGGER.warning("could not remove message writer", exc_info=True)
551
+
552
+ had_handlers = bool(self._method_return_handlers or self._user_message_handlers)
553
+
554
+ super()._finalize(err)
555
+
556
+ if self._disconnect_future.done():
557
+ return
558
+
559
+ if err and not self._user_disconnect:
560
+ _future_set_exception(self._disconnect_future, err)
561
+ # If this happens during a reply, the message handlers
562
+ # will have the exception set and wait_for_disconnect will
563
+ # never be called so we need to manually set the exception
564
+ # as retrieved to avoid asyncio warnings when the future
565
+ # is garbage collected.
566
+ if had_handlers:
567
+ with contextlib.suppress(Exception):
568
+ self._disconnect_future.exception()
569
+ else:
570
+ _future_set_result(self._disconnect_future, None)
571
+
572
+ def _reply_handler(
573
+ self, future: asyncio.Future, reply: Any | None, err: Exception | None
574
+ ) -> None:
575
+ """The reply handler for method calls."""
576
+ if err:
577
+ _future_set_exception(future, err)
578
+ else:
579
+ _future_set_result(future, reply)
@@ -0,0 +1,13 @@
1
+ """cdefs for message_reader.py"""
2
+
3
+ import cython
4
+
5
+ from .._private.unmarshaller cimport Unmarshaller
6
+
7
+
8
+ cpdef _message_reader(
9
+ Unmarshaller unmarshaller,
10
+ object process,
11
+ object finalize,
12
+ bint negotiate_unix_fd
13
+ )
@@ -0,0 +1,49 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import socket
5
+ from collections.abc import Callable
6
+ from functools import partial
7
+
8
+ from .._private.unmarshaller import Unmarshaller
9
+ from ..message import Message
10
+
11
+ _LOGGER = logging.getLogger(__name__)
12
+
13
+
14
+ def _message_reader(
15
+ unmarshaller: Unmarshaller,
16
+ process: Callable[[Message], None],
17
+ finalize: Callable[[Exception | None], None],
18
+ negotiate_unix_fd: bool,
19
+ ) -> None:
20
+ """Reads messages from the unmarshaller and passes them to the process function."""
21
+ try:
22
+ while True:
23
+ message = unmarshaller._unmarshall()
24
+ if message is None:
25
+ return
26
+ try:
27
+ process(message)
28
+ except Exception:
29
+ _LOGGER.error("Unexpected error processing message: %s", exc_info=True)
30
+ # If we are not negotiating unix fds, we can stop reading as soon as we have
31
+ # the buffer is empty as asyncio will call us again when there is more data.
32
+ if (
33
+ not negotiate_unix_fd
34
+ and not unmarshaller._has_another_message_in_buffer()
35
+ ):
36
+ return
37
+ except Exception as e:
38
+ finalize(e)
39
+
40
+
41
+ def build_message_reader(
42
+ sock: socket.socket | None,
43
+ process: Callable[[Message], None],
44
+ finalize: Callable[[Exception | None], None],
45
+ negotiate_unix_fd: bool,
46
+ ) -> Callable[[], None]:
47
+ """Build a callable that reads messages from the unmarshaller and passes them to the process function."""
48
+ unmarshaller = Unmarshaller(None, sock, negotiate_unix_fd)
49
+ return partial(_message_reader, unmarshaller, process, finalize, negotiate_unix_fd)