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