Skip to content

Commit 92fa458

Browse files
committed
add fids to MulticastMessage and token deprecate warning and add unit tests
1 parent 64e98d1 commit 92fa458

3 files changed

Lines changed: 126 additions & 28 deletions

File tree

firebase_admin/_messaging_encoder.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class Message:
2828
"""A message that can be sent via Firebase Cloud Messaging.
2929
3030
Contains payload information as well as recipient information. In particular, the message must
31-
contain exactly one of token, topic or condition fields.
31+
contain exactly one of fid, token, topic or condition fields.
3232
3333
Args:
3434
data: A dictionary of data fields (optional). All keys and values in the dictionary must be
@@ -70,10 +70,11 @@ def __str__(self):
7070

7171

7272
class MulticastMessage:
73-
"""A message that can be sent to multiple tokens via Firebase Cloud Messaging.
73+
"""A message that can be sent to multiple tokens or fids via Firebase Cloud Messaging.
7474
7575
Args:
76-
tokens: A list of registration tokens of targeted devices.
76+
fids: A list of Firebase Installation IDs of targeted app instances (optional)
77+
tokens: Deprecated. Use ``fids`` instead (optional).
7778
data: A dictionary of data fields (optional). All keys and values in the dictionary must be
7879
strings.
7980
notification: An instance of ``messaging.Notification`` (optional).
@@ -82,12 +83,31 @@ class MulticastMessage:
8283
apns: An instance of ``messaging.ApnsConfig`` (optional).
8384
fcm_options: An instance of ``messaging.FCMOptions`` (optional).
8485
"""
85-
def __init__(self, tokens, data=None, notification=None, android=None, webpush=None, apns=None,
86+
def __init__(self, fids=None, tokens=None, data=None, notification=None, android=None, webpush=None, apns=None,
8687
fcm_options=None):
87-
_Validators.check_string_list('MulticastMessage.tokens', tokens)
88-
if len(tokens) > 500:
89-
raise ValueError('MulticastMessage.tokens must not contain more than 500 tokens.')
90-
self.tokens = tokens
88+
if tokens is not None:
89+
warnings.warn(
90+
"Deprecated. Use 'fids' instead.",
91+
DeprecationWarning,
92+
stacklevel=2
93+
)
94+
95+
if (tokens is None and fids is None) or (tokens is not None and fids is not None):
96+
raise ValueError("Must specify either 'tokens' or 'fids'.")
97+
98+
if tokens is not None:
99+
_Validators.check_string_list('MulticastMessage.tokens', tokens)
100+
if len(tokens) > 500:
101+
raise ValueError('MulticastMessage.tokens must not contain more than 500 tokens.')
102+
self.tokens = tokens
103+
self.fids = None
104+
else:
105+
_Validators.check_string_list('MulticastMessage.fids', fids)
106+
if len(fids) > 500:
107+
raise ValueError('MulticastMessage.fids must not contain more than 500 fids.')
108+
self.fids = fids
109+
self.tokens = None
110+
91111
self.data = data
92112
self.notification = notification
93113
self.android = android

firebase_admin/messaging.py

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import json
2121
import asyncio
2222
import logging
23+
import warnings
2324
import requests
2425
import httpx
2526

@@ -177,7 +178,7 @@ async def send_each_for_multicast_async(
177178
dry_run: bool = False,
178179
app: Optional[App] = None
179180
) -> BatchResponse:
180-
"""Sends the given mutlicast message to each token asynchronously via Firebase Cloud Messaging
181+
"""Sends the given multicast message to each token or fid asynchronously via Firebase Cloud Messaging
181182
(FCM).
182183
183184
If the ``dry_run`` mode is enabled, the message will not be actually delivered to the
@@ -197,19 +198,32 @@ async def send_each_for_multicast_async(
197198
"""
198199
if not isinstance(multicast_message, MulticastMessage):
199200
raise ValueError('Message must be an instance of messaging.MulticastMessage class.')
200-
messages = [Message(
201-
data=multicast_message.data,
202-
notification=multicast_message.notification,
203-
android=multicast_message.android,
204-
webpush=multicast_message.webpush,
205-
apns=multicast_message.apns,
206-
fcm_options=multicast_message.fcm_options,
207-
token=token
208-
) for token in multicast_message.tokens]
201+
if multicast_message.tokens is not None:
202+
with warnings.catch_warnings():
203+
warnings.simplefilter("ignore", DeprecationWarning)
204+
messages = [Message(
205+
data=multicast_message.data,
206+
notification=multicast_message.notification,
207+
android=multicast_message.android,
208+
webpush=multicast_message.webpush,
209+
apns=multicast_message.apns,
210+
fcm_options=multicast_message.fcm_options,
211+
token=token
212+
) for token in multicast_message.tokens]
213+
else:
214+
messages = [Message(
215+
data=multicast_message.data,
216+
notification=multicast_message.notification,
217+
android=multicast_message.android,
218+
webpush=multicast_message.webpush,
219+
apns=multicast_message.apns,
220+
fcm_options=multicast_message.fcm_options,
221+
fid=fid
222+
) for fid in multicast_message.fids]
209223
return await _get_messaging_service(app).send_each_async(messages, dry_run)
210224

211225
def send_each_for_multicast(multicast_message, dry_run=False, app=None):
212-
"""Sends the given mutlicast message to each token via Firebase Cloud Messaging (FCM).
226+
"""Sends the given multicast message to each token or fid via Firebase Cloud Messaging (FCM).
213227
214228
If the ``dry_run`` mode is enabled, the message will not be actually delivered to the
215229
recipients. Instead, FCM performs all the usual validations and emulates the send operation.
@@ -228,15 +242,28 @@ def send_each_for_multicast(multicast_message, dry_run=False, app=None):
228242
"""
229243
if not isinstance(multicast_message, MulticastMessage):
230244
raise ValueError('Message must be an instance of messaging.MulticastMessage class.')
231-
messages = [Message(
232-
data=multicast_message.data,
233-
notification=multicast_message.notification,
234-
android=multicast_message.android,
235-
webpush=multicast_message.webpush,
236-
apns=multicast_message.apns,
237-
fcm_options=multicast_message.fcm_options,
238-
token=token
239-
) for token in multicast_message.tokens]
245+
if multicast_message.tokens is not None:
246+
with warnings.catch_warnings():
247+
warnings.simplefilter("ignore", DeprecationWarning)
248+
messages = [Message(
249+
data=multicast_message.data,
250+
notification=multicast_message.notification,
251+
android=multicast_message.android,
252+
webpush=multicast_message.webpush,
253+
apns=multicast_message.apns,
254+
fcm_options=multicast_message.fcm_options,
255+
token=token
256+
) for token in multicast_message.tokens]
257+
else:
258+
messages = [Message(
259+
data=multicast_message.data,
260+
notification=multicast_message.notification,
261+
android=multicast_message.android,
262+
webpush=multicast_message.webpush,
263+
apns=multicast_message.apns,
264+
fcm_options=multicast_message.fcm_options,
265+
fid=fid
266+
) for fid in multicast_message.fids]
240267
return _get_messaging_service(app).send_each(messages, dry_run)
241268

242269
def subscribe_to_topic(tokens, topic, app=None):

tests/test_messaging.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,15 @@ def test_data_message(self):
9999

100100
class TestMulticastMessage:
101101

102+
def test_invalid_targets(self):
103+
with pytest.raises(ValueError) as excinfo:
104+
messaging.MulticastMessage()
105+
assert str(excinfo.value) == "Must specify either 'tokens' or 'fids'."
106+
107+
with pytest.raises(ValueError) as excinfo:
108+
messaging.MulticastMessage(tokens=['token'], fids=['fid'])
109+
assert str(excinfo.value) == "Must specify either 'tokens' or 'fids'."
110+
102111
@pytest.mark.parametrize('tokens', NON_LIST_ARGS)
103112
def test_invalid_tokens_type(self, tokens):
104113
with pytest.raises(ValueError) as excinfo:
@@ -123,6 +132,34 @@ def test_tokens_type(self):
123132
message = messaging.MulticastMessage(tokens=['token' for _ in range(0, 500)])
124133
assert len(message.tokens) == 500
125134

135+
@pytest.mark.parametrize('fids', NON_LIST_ARGS)
136+
def test_invalid_fids_type(self, fids):
137+
with pytest.raises(ValueError) as excinfo:
138+
messaging.MulticastMessage(fids=fids)
139+
if isinstance(fids, list):
140+
expected = 'MulticastMessage.fids must not contain non-string values.'
141+
assert str(excinfo.value) == expected
142+
else:
143+
expected = 'MulticastMessage.fids must be a list of strings.'
144+
assert str(excinfo.value) == expected
145+
146+
def test_fids_over_500(self):
147+
with pytest.raises(ValueError) as excinfo:
148+
messaging.MulticastMessage(fids=['fid' for _ in range(0, 501)])
149+
expected = 'MulticastMessage.fids must not contain more than 500 fids.'
150+
assert str(excinfo.value) == expected
151+
152+
def test_fids_type(self):
153+
message = messaging.MulticastMessage(fids=['fid'])
154+
assert len(message.fids) == 1
155+
156+
message = messaging.MulticastMessage(fids=['fid' for _ in range(0, 500)])
157+
assert len(message.fids) == 500
158+
159+
def test_tokens_deprecation_warning(self):
160+
with pytest.deprecated_call():
161+
messaging.MulticastMessage(tokens=['token'])
162+
126163

127164
class TestMessageEncoder:
128165

@@ -2231,6 +2268,20 @@ def test_send_each_for_multicast(self):
22312268
assert all(r.success for r in batch_response.responses)
22322269
assert not any(r.exception for r in batch_response.responses)
22332270

2271+
def test_send_each_for_multicast_fids(self):
2272+
payload1 = json.dumps({'name': 'message-id1'})
2273+
payload2 = json.dumps({'name': 'message-id2'})
2274+
_ = self._instrument_messaging_service(
2275+
response_dict={'foo1': [200, payload1], 'foo2': [200, payload2]})
2276+
msg = messaging.MulticastMessage(fids=['foo1', 'foo2'])
2277+
batch_response = messaging.send_each_for_multicast(msg, dry_run=True)
2278+
assert batch_response.success_count == 2
2279+
assert batch_response.failure_count == 0
2280+
assert len(batch_response.responses) == 2
2281+
assert [r.message_id for r in batch_response.responses] == ['message-id1', 'message-id2']
2282+
assert all(r.success for r in batch_response.responses)
2283+
assert not any(r.exception for r in batch_response.responses)
2284+
22342285
@pytest.mark.parametrize('status', HTTP_ERROR_CODES)
22352286
def test_send_each_for_multicast_detailed_error(self, status):
22362287
success_payload = json.dumps({'name': 'message-id'})

0 commit comments

Comments
 (0)