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