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