Coverage for zenmqtt/subscriptions.py: 81%
32 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-12 21:55 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-12 21:55 +0000
1from logging import getLogger
2from typing import Awaitable, Callable, Final, Sequence
4from zenmqtt.mqtt.reason_codes import FAILURE_REASON_CODE
5from zenmqtt.mqtt.subscribe import (
6 Subscription,
7 SubscriptionProperties,
8 SubscriptionRequest,
9 to_subscription,
10)
12logger = getLogger(__name__)
14# UNSUBACK reason code: the topic wasn't subscribed, it's not an error
15_NO_SUBSCRIPTION_EXISTED: Final[int] = 0x11
17Subscribe = Callable[
18 [Sequence[Subscription], SubscriptionProperties], Awaitable[object]
19]
22class SubscriptionRegistry:
23 """
24 Subscriptions accepted by the server; they are restored after reconnect if
25 the server lost the session.
26 """
28 def __init__(self) -> None:
29 self._subscriptions: dict[str, tuple[Subscription, SubscriptionProperties]] = {}
31 def add(
32 self,
33 requests: Sequence[SubscriptionRequest],
34 properties: SubscriptionProperties,
35 reason_codes: Sequence[int],
36 ) -> None:
37 """Remembers the subscriptions which SUBACK accepted."""
38 for request, reason_code in zip(requests, reason_codes):
39 if reason_code < FAILURE_REASON_CODE:
40 subscription = to_subscription(request)
41 self._subscriptions[subscription.topic] = (subscription, properties)
43 def remove(self, topics: Sequence[str], reason_codes: Sequence[int]) -> None:
44 """Forgets the subscriptions which UNSUBACK removed."""
45 for topic, reason_code in zip(topics, reason_codes):
46 if ( 46 ↛ 45line 46 didn't jump to line 45 because the condition on line 46 was always true
47 reason_code < FAILURE_REASON_CODE
48 or reason_code == _NO_SUBSCRIPTION_EXISTED
49 ):
50 self._subscriptions.pop(topic, None)
52 async def restore(self, subscribe: Subscribe) -> None:
53 """
54 Subscribes again, one SUBSCRIBE per group of subscriptions with the same
55 properties. Errors are logged, the other groups are restored anyway.
56 """
57 groups: list[tuple[SubscriptionProperties, list[Subscription]]] = []
59 for subscription, properties in self._subscriptions.values():
60 for group_properties, subscriptions in groups: 60 ↛ 61line 60 didn't jump to line 61 because the loop on line 60 never started
61 if group_properties == properties:
62 subscriptions.append(subscription)
63 break
64 else:
65 groups.append((properties, [subscription]))
67 for properties, subscriptions in groups:
68 try:
69 await subscribe(subscriptions, properties)
70 except Exception as exc:
71 logger.error(
72 "mqtt_client.restore_subscriptions.failed: %s",
73 [subscription.topic for subscription in subscriptions],
74 exc_info=exc,
75 )