push_notifications: Use SentPushNotificationResult dataclass.

Refactoring, no functional change.

This commit refactors `send_e2ee_push_notification_apple`
and `send_e2ee_push_notification_android` to return a
`SentPushNotificationResult` dataclass.

It's a cleaner protocol than passing a mutable data structure
`delete_device_ids` as argument and updating it within
functions.

Fixes part of #35368.
This commit is contained in:
Prakhar Pratyush
2025-08-21 13:25:14 +05:30
committed by Tim Abbott
parent 3e7e81581c
commit 3548764e21
2 changed files with 45 additions and 15 deletions

View File

@@ -28,6 +28,7 @@ from zerver.lib.timestamp import datetime_to_timestamp
from zerver.models import PushDevice, UserMessage from zerver.models import PushDevice, UserMessage
from zerver.models.realms import get_realm from zerver.models.realms import get_realm
from zerver.models.scheduled_jobs import NotificationTriggers from zerver.models.scheduled_jobs import NotificationTriggers
from zilencer.lib.push_notifications import SentPushNotificationResult
from zilencer.models import RemoteRealm, RemoteRealmCount from zilencer.models import RemoteRealm, RemoteRealmCount
@@ -260,7 +261,11 @@ class SendPushNotificationTest(E2EEPushNotificationTestCase):
with ( with (
self.mock_fcm() as mock_fcm_messaging, self.mock_fcm() as mock_fcm_messaging,
mock.patch( mock.patch(
"zilencer.lib.push_notifications.send_e2ee_push_notification_apple", return_value=1 "zilencer.lib.push_notifications.send_e2ee_push_notification_apple",
return_value=SentPushNotificationResult(
successfully_sent_count=1,
delete_device_ids=[],
),
), ),
self.assertLogs("zerver.lib.push_notifications", level="INFO") as zerver_logger, self.assertLogs("zerver.lib.push_notifications", level="INFO") as zerver_logger,
self.assertLogs("zilencer.lib.push_notifications", level="WARNING") as zilencer_logger, self.assertLogs("zilencer.lib.push_notifications", level="WARNING") as zilencer_logger,

View File

@@ -1,7 +1,7 @@
import asyncio import asyncio
import logging import logging
from collections.abc import Iterable from collections.abc import Iterable
from dataclasses import asdict from dataclasses import asdict, dataclass
from aioapns import NotificationRequest from aioapns import NotificationRequest
from django.utils.timezone import now as timezone_now from django.utils.timezone import now as timezone_now
@@ -23,14 +23,20 @@ from zilencer.models import RemotePushDevice, RemoteRealm
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@dataclass
class SentPushNotificationResult:
successfully_sent_count: int
delete_device_ids: list[int]
def send_e2ee_push_notification_apple( def send_e2ee_push_notification_apple(
apns_requests: list[NotificationRequest], apns_requests: list[NotificationRequest],
apns_remote_push_devices: list[RemotePushDevice], apns_remote_push_devices: list[RemotePushDevice],
delete_device_ids: list[int], ) -> SentPushNotificationResult:
) -> int:
import aioapns import aioapns
successfully_sent_count = 0 successfully_sent_count = 0
delete_device_ids: list[int] = []
apns_context = get_apns_context() apns_context = get_apns_context()
if apns_context is None: if apns_context is None:
@@ -38,7 +44,10 @@ def send_e2ee_push_notification_apple(
"APNs: Dropping a notification because nothing configured. " "APNs: Dropping a notification because nothing configured. "
"Set ZULIP_SERVICES_URL (or APNS_CERT_FILE)." "Set ZULIP_SERVICES_URL (or APNS_CERT_FILE)."
) )
return successfully_sent_count return SentPushNotificationResult(
successfully_sent_count=successfully_sent_count,
delete_device_ids=delete_device_ids,
)
async def send_all_notifications() -> Iterable[ async def send_all_notifications() -> Iterable[
tuple[RemotePushDevice, aioapns.common.NotificationResult | BaseException] tuple[RemotePushDevice, aioapns.common.NotificationResult | BaseException]
@@ -66,21 +75,28 @@ def send_e2ee_push_notification_apple(
remote_push_device.save(update_fields=["expired_time"]) remote_push_device.save(update_fields=["expired_time"])
delete_device_ids.append(result_info.delete_device_id) delete_device_ids.append(result_info.delete_device_id)
return successfully_sent_count return SentPushNotificationResult(
successfully_sent_count=successfully_sent_count,
delete_device_ids=delete_device_ids,
)
def send_e2ee_push_notification_android( def send_e2ee_push_notification_android(
fcm_requests: list[firebase_messaging.Message], fcm_requests: list[firebase_messaging.Message],
fcm_remote_push_devices: list[RemotePushDevice], fcm_remote_push_devices: list[RemotePushDevice],
delete_device_ids: list[int], ) -> SentPushNotificationResult:
) -> int: successfully_sent_count = 0
delete_device_ids: list[int] = []
try: try:
batch_response = firebase_messaging.send_each(fcm_requests, app=fcm_app) batch_response = firebase_messaging.send_each(fcm_requests, app=fcm_app)
except firebase_exceptions.FirebaseError: except firebase_exceptions.FirebaseError:
logger.warning("Error while pushing to FCM", exc_info=True) logger.warning("Error while pushing to FCM", exc_info=True)
return 0 return SentPushNotificationResult(
successfully_sent_count=successfully_sent_count,
delete_device_ids=delete_device_ids,
)
successfully_sent_count = 0
for idx, response in enumerate(batch_response.responses): for idx, response in enumerate(batch_response.responses):
# We enumerate to have idx to track which token the response # We enumerate to have idx to track which token the response
# corresponds to. send_each() preserves the order of the messages, # corresponds to. send_each() preserves the order of the messages,
@@ -114,7 +130,10 @@ def send_e2ee_push_notification_android(
error, error,
) )
return successfully_sent_count return SentPushNotificationResult(
successfully_sent_count=successfully_sent_count,
delete_device_ids=delete_device_ids,
)
def send_e2ee_push_notifications( def send_e2ee_push_notifications(
@@ -181,15 +200,21 @@ def send_e2ee_push_notifications(
apple_successfully_sent_count = 0 apple_successfully_sent_count = 0
if len(apns_requests) > 0: if len(apns_requests) > 0:
apple_successfully_sent_count = send_e2ee_push_notification_apple( sent_push_notification_result = send_e2ee_push_notification_apple(
apns_requests, apns_remote_push_devices, delete_device_ids apns_requests,
apns_remote_push_devices,
) )
apple_successfully_sent_count = sent_push_notification_result.successfully_sent_count
delete_device_ids.extend(sent_push_notification_result.delete_device_ids)
android_successfully_sent_count = 0 android_successfully_sent_count = 0
if len(fcm_requests) > 0: if len(fcm_requests) > 0:
android_successfully_sent_count = send_e2ee_push_notification_android( sent_push_notification_result = send_e2ee_push_notification_android(
fcm_requests, fcm_remote_push_devices, delete_device_ids fcm_requests,
fcm_remote_push_devices,
) )
android_successfully_sent_count = sent_push_notification_result.successfully_sent_count
delete_device_ids.extend(sent_push_notification_result.delete_device_ids)
return { return {
"apple_successfully_sent_count": apple_successfully_sent_count, "apple_successfully_sent_count": apple_successfully_sent_count,