|
12 | 12 |
|
13 | 13 | from . import background_tasks, core, helpers
|
14 | 14 | from .awaitable_response import AwaitableResponse
|
| 15 | +from .client import Client |
15 | 16 | from .context import context
|
16 | 17 | from .dataclasses import KWONLY_SLOTS
|
17 | 18 | from .logging import log
|
@@ -46,46 +47,44 @@ def __init__(self) -> None:
|
46 | 47 | self.callbacks: list[Callback[P]] = []
|
47 | 48 | self.instances.append(self)
|
48 | 49 |
|
49 |
| - def subscribe(self, callback: Callable[P, Any] | Callable[[], Any]) -> None: |
| 50 | + def subscribe(self, callback: Callable[P, Any] | Callable[[], Any], *, |
| 51 | + unsubscribe_on_disconnect: bool | None = None) -> None: |
50 | 52 | """Subscribe to the event.
|
51 | 53 |
|
52 |
| - Note that the callback should not be used to update UI since it would probably cause a memory leak. |
53 |
| - Use the ``subscribe_ui`` method instead. |
| 54 | + The ``unsubscribe_on_disconnect`` can be used to explicitly define |
| 55 | + whether the callback should be automatically unsubscribed when the client disconnects. |
| 56 | + By default, the callback is automatically unsubscribed if subscribed from within a UI context |
| 57 | + to prevent memory leaks. |
54 | 58 |
|
55 | 59 | :param callback: the callback which will be called when the event is fired
|
| 60 | + :param unsubscribe_on_disconnect: whether to unsubscribe the callback when the client disconnects |
| 61 | + (default: ``None`` meaning the callback is automatically unsubscribed if subscribed from within a UI context) |
56 | 62 | """
|
57 | 63 | frame = inspect.currentframe()
|
58 | 64 | assert frame is not None
|
59 | 65 | frame = frame.f_back
|
60 | 66 | assert frame is not None
|
61 |
| - self.callbacks.append(Callback(func=callback, filepath=frame.f_code.co_filename, line=frame.f_lineno)) |
62 |
| - |
63 |
| - def subscribe_ui(self, callback: Callable[P, Any] | Callable[[], Any]) -> None: |
64 |
| - """Subscribe to the event and automatically unsubscribe when the client disconnects. |
65 |
| -
|
66 |
| - This method is particularly useful for UI callbacks since it cleans up unused references to the callback |
67 |
| - which would otherwise cause a memory leak. |
68 |
| -
|
69 |
| - :param callback: the callback which will be called when the event is fired |
70 |
| - """ |
71 |
| - self.subscribe(callback) |
| 67 | + callback_ = Callback[P](func=callback, filepath=frame.f_code.co_filename, line=frame.f_lineno) |
72 | 68 | try:
|
73 |
| - self.callbacks[-1].slot = weakref.ref(context.slot) |
74 |
| - except RuntimeError as e: |
75 |
| - raise RuntimeError('Calling `subscribe_ui` outside of a UI context is not supported.') from e |
76 |
| - client = context.client |
77 |
| - |
78 |
| - async def register_disconnect() -> None: |
79 |
| - try: |
80 |
| - await client.connected(timeout=10.0) |
81 |
| - client.on_disconnect(lambda: self.unsubscribe(callback)) |
82 |
| - except TimeoutError: |
83 |
| - log.warning('Could not register a disconnect handler for callback %s', callback) |
84 |
| - self.unsubscribe(callback) |
85 |
| - if core.loop and core.loop.is_running(): |
86 |
| - background_tasks.create(register_disconnect()) |
87 |
| - else: |
88 |
| - core.app.on_startup(register_disconnect()) |
| 69 | + callback_.slot = weakref.ref(context.slot) |
| 70 | + client: Client | None = context.client |
| 71 | + except RuntimeError: |
| 72 | + client = None |
| 73 | + if callback_.slot is None and unsubscribe_on_disconnect is True: |
| 74 | + raise RuntimeError('Calling `subscribe` with `unsubscribe_on_disconnect=True` outside of a UI context ' |
| 75 | + 'is not supported.') |
| 76 | + if client is not None and unsubscribe_on_disconnect is not False: |
| 77 | + async def register_disconnect() -> None: |
| 78 | + try: |
| 79 | + await client.connected(timeout=10.0) |
| 80 | + client.on_disconnect(lambda: self.unsubscribe(callback)) |
| 81 | + except TimeoutError: |
| 82 | + log.warning('Could not register a disconnect handler for callback %s', callback) |
| 83 | + if core.loop and core.loop.is_running(): |
| 84 | + background_tasks.create(register_disconnect()) |
| 85 | + else: |
| 86 | + core.app.on_startup(register_disconnect()) |
| 87 | + self.callbacks.append(callback_) |
89 | 88 |
|
90 | 89 | def unsubscribe(self, callback: Callable[P, Any] | Callable[[], Any]) -> None:
|
91 | 90 | """Unsubscribe a callback from the event.
|
|
0 commit comments