-
Notifications
You must be signed in to change notification settings - Fork 351
feat(fcm): Enable fid and deprecate token for Send API
#951
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 6 commits
64e98d1
92fa458
f384d6e
0509f0a
5c68f2a
f258b3f
e808deb
1831f6a
73f4b47
3ee857c
eacef77
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -19,6 +19,7 @@ | |||||
| import math | ||||||
| import numbers | ||||||
| import re | ||||||
| import warnings | ||||||
|
|
||||||
| from firebase_admin import _messaging_utils | ||||||
|
|
||||||
|
|
@@ -27,7 +28,7 @@ class Message: | |||||
| """A message that can be sent via Firebase Cloud Messaging. | ||||||
|
|
||||||
| Contains payload information as well as recipient information. In particular, the message must | ||||||
| contain exactly one of token, topic or condition fields. | ||||||
| contain exactly one of fid, token, topic or condition fields. | ||||||
|
|
||||||
| Args: | ||||||
| data: A dictionary of data fields (optional). All keys and values in the dictionary must be | ||||||
|
|
@@ -37,20 +38,29 @@ class Message: | |||||
| webpush: An instance of ``messaging.WebpushConfig`` (optional). | ||||||
| apns: An instance of ``messaging.ApnsConfig`` (optional). | ||||||
| fcm_options: An instance of ``messaging.FCMOptions`` (optional). | ||||||
| token: The registration token of the device to which the message should be sent (optional). | ||||||
| fid: The Firebase installation ID of an FCM registered app instance to which the | ||||||
| message should be sent (optional) | ||||||
| token: Deprecated. Use ``fid`` instead. | ||||||
| topic: Name of the FCM topic to which the message should be sent (optional). Topic name | ||||||
| may contain the ``/topics/`` prefix. | ||||||
| condition: The FCM condition to which the message should be sent (optional). | ||||||
| """ | ||||||
|
|
||||||
| def __init__(self, data=None, notification=None, android=None, webpush=None, apns=None, | ||||||
| fcm_options=None, token=None, topic=None, condition=None): | ||||||
| fcm_options=None, fid=None, token=None, topic=None, condition=None): | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's move def __init__(self, data=None, notification=None, android=None, webpush=None, apns=None,
fcm_options=None, token=None, topic=None, condition=None, fid=None):
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||||||
| if token is not None: | ||||||
| warnings.warn( | ||||||
| "Deprecated. Use 'fid' instead.", | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's change this to
Suggested change
|
||||||
| DeprecationWarning, | ||||||
| stacklevel=2 | ||||||
| ) | ||||||
| self.data = data | ||||||
| self.notification = notification | ||||||
| self.android = android | ||||||
| self.webpush = webpush | ||||||
| self.apns = apns | ||||||
| self.fcm_options = fcm_options | ||||||
| self.fid = fid | ||||||
| self.token = token | ||||||
| self.topic = topic | ||||||
| self.condition = condition | ||||||
|
|
@@ -60,10 +70,11 @@ def __str__(self): | |||||
|
|
||||||
|
|
||||||
| class MulticastMessage: | ||||||
| """A message that can be sent to multiple tokens via Firebase Cloud Messaging. | ||||||
| """A message that can be sent to multiple tokens or fids via Firebase Cloud Messaging. | ||||||
|
|
||||||
| Args: | ||||||
| tokens: A list of registration tokens of targeted devices. | ||||||
| fids: A list of Firebase Installation IDs of targeted app instances (optional) | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: missing |
||||||
| tokens: Deprecated. Use ``fids`` instead (optional). | ||||||
| data: A dictionary of data fields (optional). All keys and values in the dictionary must be | ||||||
| strings. | ||||||
| notification: An instance of ``messaging.Notification`` (optional). | ||||||
|
|
@@ -72,12 +83,32 @@ class MulticastMessage: | |||||
| apns: An instance of ``messaging.ApnsConfig`` (optional). | ||||||
| fcm_options: An instance of ``messaging.FCMOptions`` (optional). | ||||||
| """ | ||||||
| def __init__(self, tokens, data=None, notification=None, android=None, webpush=None, apns=None, | ||||||
| fcm_options=None): | ||||||
| _Validators.check_string_list('MulticastMessage.tokens', tokens) | ||||||
| if len(tokens) > 500: | ||||||
| raise ValueError('MulticastMessage.tokens must not contain more than 500 tokens.') | ||||||
| self.tokens = tokens | ||||||
| def __init__( | ||||||
| self, tokens=None, fids=None, data=None, notification=None, android=None, | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed |
||||||
| webpush=None, apns=None, fcm_options=None): | ||||||
| if tokens is not None: | ||||||
| warnings.warn( | ||||||
| "Deprecated. Use 'fids' instead.", | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| DeprecationWarning, | ||||||
| stacklevel=2 | ||||||
| ) | ||||||
|
|
||||||
| if (tokens is None and fids is None) or (tokens is not None and fids is not None): | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it possible to support the case where the tokens is not None and fids is not None? We can raise an error if Supporting this case will allow people to start using With that being said, this is just a nice-to-have. It's unnecessary if it requires too many changes.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed the codes to support the mix of tokens and fids for |
||||||
| raise ValueError("Must specify either 'tokens' or 'fids'.") | ||||||
|
|
||||||
| if tokens is not None: | ||||||
| _Validators.check_string_list('MulticastMessage.tokens', tokens) | ||||||
| if len(tokens) > 500: | ||||||
| raise ValueError('MulticastMessage.tokens must not contain more than 500 tokens.') | ||||||
| self.tokens = tokens | ||||||
| self.fids = None | ||||||
| else: | ||||||
| _Validators.check_string_list('MulticastMessage.fids', fids) | ||||||
| if len(fids) > 500: | ||||||
| raise ValueError('MulticastMessage.fids must not contain more than 500 fids.') | ||||||
| self.fids = fids | ||||||
| self.tokens = None | ||||||
|
|
||||||
| self.data = data | ||||||
| self.notification = notification | ||||||
| self.android = android | ||||||
|
|
@@ -695,16 +726,17 @@ def default(self, o): # pylint: disable=method-hidden | |||||
| 'Message.condition', o.condition, non_empty=True), | ||||||
| 'data': _Validators.check_string_dict('Message.data', o.data), | ||||||
| 'notification': MessageEncoder.encode_notification(o.notification), | ||||||
| 'fid': _Validators.check_string('Message.fid', o.fid, non_empty=True), | ||||||
| 'token': _Validators.check_string('Message.token', o.token, non_empty=True), | ||||||
| 'topic': _Validators.check_string('Message.topic', o.topic, non_empty=True), | ||||||
| 'webpush': MessageEncoder.encode_webpush(o.webpush), | ||||||
| 'fcm_options': MessageEncoder.encode_fcm_options(o.fcm_options), | ||||||
| } | ||||||
| result['topic'] = MessageEncoder.sanitize_topic_name(result.get('topic')) | ||||||
| result = MessageEncoder.remove_null_values(result) | ||||||
| target_count = sum(t in result for t in ['token', 'topic', 'condition']) | ||||||
| target_count = sum(t in result for t in ['fid', 'token', 'topic', 'condition']) | ||||||
| if target_count != 1: | ||||||
| raise ValueError('Exactly one of token, topic or condition must be specified.') | ||||||
| raise ValueError('Exactly one of fid, token, topic or condition must be specified.') | ||||||
| return result | ||||||
|
|
||||||
| @classmethod | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -73,18 +73,22 @@ class TestMessageStr: | |
| messaging.Message(topic='topic', condition='condition'), | ||
| messaging.Message(condition='condition', token='token'), | ||
| messaging.Message(topic='topic', token='token', condition='condition'), | ||
| messaging.Message(fid='fid', token='token'), | ||
| messaging.Message(fid='fid', topic='topic'), | ||
| messaging.Message(fid='fid', condition='condition'), | ||
| messaging.Message(fid='fid', token='token', topic='topic'), | ||
| ]) | ||
| def test_invalid_target_message(self, msg): | ||
| with pytest.raises(ValueError) as excinfo: | ||
| str(msg) | ||
| assert str( | ||
| excinfo.value) == 'Exactly one of token, topic or condition must be specified.' | ||
| excinfo.value) == 'Exactly one of fid, token, topic or condition must be specified.' | ||
|
|
||
| def test_empty_message(self): | ||
| assert str(messaging.Message(token='value')) == '{"token": "value"}' | ||
| assert str(messaging.Message(topic='value')) == '{"topic": "value"}' | ||
| assert str(messaging.Message(condition='value') | ||
| ) == '{"condition": "value"}' | ||
| assert str(messaging.Message(condition='value')) == '{"condition": "value"}' | ||
| assert str(messaging.Message(fid='value')) == '{"fid": "value"}' | ||
|
|
||
| def test_data_message(self): | ||
| assert str(messaging.Message(topic='topic', data={}) | ||
|
|
@@ -95,6 +99,15 @@ def test_data_message(self): | |
|
|
||
| class TestMulticastMessage: | ||
|
|
||
| def test_invalid_targets(self): | ||
| with pytest.raises(ValueError) as excinfo: | ||
| messaging.MulticastMessage() | ||
| assert str(excinfo.value) == "Must specify either 'tokens' or 'fids'." | ||
|
|
||
| with pytest.raises(ValueError) as excinfo: | ||
| messaging.MulticastMessage(tokens=['token'], fids=['fid']) | ||
| assert str(excinfo.value) == "Must specify either 'tokens' or 'fids'." | ||
|
|
||
| @pytest.mark.parametrize('tokens', NON_LIST_ARGS) | ||
| def test_invalid_tokens_type(self, tokens): | ||
| with pytest.raises(ValueError) as excinfo: | ||
|
|
@@ -119,6 +132,38 @@ def test_tokens_type(self): | |
| message = messaging.MulticastMessage(tokens=['token' for _ in range(0, 500)]) | ||
| assert len(message.tokens) == 500 | ||
|
|
||
| @pytest.mark.parametrize('fids', NON_LIST_ARGS) | ||
| def test_invalid_fids_type(self, fids): | ||
| with pytest.raises(ValueError) as excinfo: | ||
| messaging.MulticastMessage(fids=fids) | ||
| if isinstance(fids, list): | ||
| expected = 'MulticastMessage.fids must not contain non-string values.' | ||
| assert str(excinfo.value) == expected | ||
| else: | ||
| expected = 'MulticastMessage.fids must be a list of strings.' | ||
| assert str(excinfo.value) == expected | ||
|
|
||
| def test_fids_over_500(self): | ||
| with pytest.raises(ValueError) as excinfo: | ||
| messaging.MulticastMessage(fids=['fid' for _ in range(0, 501)]) | ||
| expected = 'MulticastMessage.fids must not contain more than 500 fids.' | ||
| assert str(excinfo.value) == expected | ||
|
|
||
| def test_fids_type(self): | ||
| message = messaging.MulticastMessage(fids=['fid']) | ||
| assert len(message.fids) == 1 | ||
|
|
||
| message = messaging.MulticastMessage(fids=['fid' for _ in range(0, 500)]) | ||
| assert len(message.fids) == 500 | ||
|
|
||
| def test_tokens_deprecation_warning(self): | ||
| with pytest.deprecated_call(): | ||
| messaging.MulticastMessage(tokens=['token']) | ||
|
|
||
| def test_tokens_deprecation_warning_positional(self): | ||
| with pytest.deprecated_call(): | ||
| messaging.MulticastMessage(['token']) | ||
|
|
||
|
|
||
| class TestMessageEncoder: | ||
|
|
||
|
|
@@ -128,18 +173,29 @@ class TestMessageEncoder: | |
| messaging.Message(topic='topic', condition='condition'), | ||
| messaging.Message(condition='condition', token='token'), | ||
| messaging.Message(topic='topic', token='token', condition='condition'), | ||
| messaging.Message(fid='fid', token='token'), | ||
| messaging.Message(fid='fid', topic='topic'), | ||
| messaging.Message(fid='fid', condition='condition'), | ||
| messaging.Message(fid='fid', token='token', topic='topic'), | ||
| ]) | ||
| def test_invalid_target_message(self, msg): | ||
| with pytest.raises(ValueError) as excinfo: | ||
| check_encoding(msg) | ||
| assert str(excinfo.value) == 'Exactly one of token, topic or condition must be specified.' | ||
| expected = 'Exactly one of fid, token, topic or condition must be specified.' | ||
| assert str(excinfo.value) == expected | ||
|
|
||
| @pytest.mark.parametrize('target', NON_STRING_ARGS + ['']) | ||
| def test_invalid_token(self, target): | ||
| with pytest.raises(ValueError) as excinfo: | ||
| check_encoding(messaging.Message(token=target)) | ||
| assert str(excinfo.value) == 'Message.token must be a non-empty string.' | ||
|
|
||
| @pytest.mark.parametrize('target', NON_STRING_ARGS + ['']) | ||
| def test_invalid_fid(self, target): | ||
| with pytest.raises(ValueError) as excinfo: | ||
| check_encoding(messaging.Message(fid=target)) | ||
| assert str(excinfo.value) == 'Message.fid must be a non-empty string.' | ||
|
|
||
| @pytest.mark.parametrize('target', NON_STRING_ARGS + ['']) | ||
| def test_invalid_topic(self, target): | ||
| with pytest.raises(ValueError) as excinfo: | ||
|
|
@@ -159,9 +215,14 @@ def test_malformed_topic_name(self, topic): | |
|
|
||
| def test_empty_message(self): | ||
| check_encoding(messaging.Message(token='value'), {'token': 'value'}) | ||
| check_encoding(messaging.Message(fid='value'), {'fid': 'value'}) | ||
| check_encoding(messaging.Message(topic='value'), {'topic': 'value'}) | ||
| check_encoding(messaging.Message(condition='value'), {'condition': 'value'}) | ||
|
|
||
| def test_token_deprecation_warning(self): | ||
| with pytest.deprecated_call(): | ||
| messaging.Message(token='value') | ||
|
|
||
| @pytest.mark.parametrize('data', NON_DICT_ARGS) | ||
| def test_invalid_data_message(self, data): | ||
| with pytest.raises(ValueError): | ||
|
|
@@ -2212,6 +2273,20 @@ def test_send_each_for_multicast(self): | |
| assert all(r.success for r in batch_response.responses) | ||
| assert not any(r.exception for r in batch_response.responses) | ||
|
|
||
| def test_send_each_for_multicast_fids(self): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we have any tests for
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We didn't have unit tests for |
||
| payload1 = json.dumps({'name': 'message-id1'}) | ||
| payload2 = json.dumps({'name': 'message-id2'}) | ||
| _ = self._instrument_messaging_service( | ||
| response_dict={'foo1': [200, payload1], 'foo2': [200, payload2]}) | ||
| msg = messaging.MulticastMessage(fids=['foo1', 'foo2']) | ||
| batch_response = messaging.send_each_for_multicast(msg, dry_run=True) | ||
| assert batch_response.success_count == 2 | ||
| assert batch_response.failure_count == 0 | ||
| assert len(batch_response.responses) == 2 | ||
| assert [r.message_id for r in batch_response.responses] == ['message-id1', 'message-id2'] | ||
| assert all(r.success for r in batch_response.responses) | ||
| assert not any(r.exception for r in batch_response.responses) | ||
|
|
||
| @pytest.mark.parametrize('status', HTTP_ERROR_CODES) | ||
| def test_send_each_for_multicast_detailed_error(self, status): | ||
| success_payload = json.dumps({'name': 'message-id'}) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: missing
.at the end