Skip to content

feat(fcm): Enable fid and deprecate token for Send API#951

Open
yvonnep165 wants to merge 8 commits intomainfrom
yp-add-fid-arg
Open

feat(fcm): Enable fid and deprecate token for Send API#951
yvonnep165 wants to merge 8 commits intomainfrom
yp-add-fid-arg

Conversation

@yvonnep165
Copy link
Copy Markdown

This change deprecates token (in Message) and tokens (in MulticastMessage) as message targets, transitioning support to fid (Firebase Installation ID) and fids as the primary targets instead.

The active multicast helper functions (send_each_for_multicast and send_each_for_multicast_async) are updated to support both target types. These helpers leverage Python's warnings.catch_warnings() to suppress duplicate internal warning logs during loop instantiation, ensuring a clean developer experience by raising only a single deprecation warning at the point of object construction.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for Firebase Installation IDs (fid and fids) in the Message and MulticastMessage classes, marking the existing token and tokens fields as deprecated. Feedback highlights a critical backward-compatibility issue where changing the MulticastMessage constructor signature breaks positional arguments for existing users. Additionally, it is recommended to extract duplicated logic for converting multicast messages into individual message objects into a shared helper function to improve maintainability.

Comment thread firebase_admin/_messaging_encoder.py Outdated
Comment on lines +86 to +109
def __init__(self, fids=None, tokens=None, 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
if tokens is not None:
warnings.warn(
"Deprecated. Use 'fids' instead.",
DeprecationWarning,
stacklevel=2
)

if (tokens is None and fids is None) or (tokens is not None and fids is not None):
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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The change to the MulticastMessage.__init__ method signature introduces a significant backward-incompatible change. The new signature is def __init__(self, fids=None, tokens=None, ...) where previously it was def __init__(self, tokens, ...). This breaks calls that use positional arguments, as MulticastMessage(['my-token']) will now silently and incorrectly interpret ['my-token'] as a list of fids.

To avoid this breaking change, I suggest reverting the argument order and adjusting the logic to handle the mutual exclusivity of tokens and fids. The tokens parameter should be made optional, and fids can be added at the end. This ensures backward compatibility for existing users.

    def __init__(self, tokens=None, data=None, notification=None, android=None, webpush=None, apns=None,
                 fcm_options=None, fids=None):
        if tokens is not None:
            warnings.warn(
                "Deprecated. Use 'fids' instead.",
                DeprecationWarning,
                stacklevel=2
            )

        if (tokens is not None) + (fids is not None) != 1:
            raise ValueError("Must specify exactly one of '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

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Comment thread firebase_admin/messaging.py Outdated
Comment on lines +201 to +222
if multicast_message.tokens is not None:
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
messages = [Message(
data=multicast_message.data,
notification=multicast_message.notification,
android=multicast_message.android,
webpush=multicast_message.webpush,
apns=multicast_message.apns,
fcm_options=multicast_message.fcm_options,
token=token
) for token in multicast_message.tokens]
else:
messages = [Message(
data=multicast_message.data,
notification=multicast_message.notification,
android=multicast_message.android,
webpush=multicast_message.webpush,
apns=multicast_message.apns,
fcm_options=multicast_message.fcm_options,
fid=fid
) for fid in multicast_message.fids]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This block of logic for converting a MulticastMessage into a list of Message objects is duplicated in send_each_for_multicast (lines 245-266). To improve maintainability and follow the DRY (Don't Repeat Yourself) principle, consider extracting this logic into a private helper function that can be called from both send_each_for_multicast_async and send_each_for_multicast.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Helper function is added

Copy link
Copy Markdown
Member

@lahirumaramba lahirumaramba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! Looks great! :)
Added a few comments.

Comment thread firebase_admin/_messaging_encoder.py Outdated

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):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move fid=None to end of the list to remove any potential backward compatibility issues if someone is currently passing arguments positionally rather than by keyword.

def __init__(self, data=None, notification=None, android=None, webpush=None, apns=None,
             fcm_options=None, token=None, topic=None, condition=None, fid=None):

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment thread firebase_admin/_messaging_encoder.py Outdated
fcm_options=None, fid=None, token=None, topic=None, condition=None):
if token is not None:
warnings.warn(
"Deprecated. Use 'fid' instead.",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's change this to Message.token is deprecated. Use fid instead.

Suggested change
"Deprecated. Use 'fid' instead.",
"Message.token is deprecated. Use fid instead.",

Comment thread firebase_admin/_messaging_encoder.py Outdated
webpush=None, apns=None, fcm_options=None):
if tokens is not None:
warnings.warn(
"Deprecated. Use 'fids' instead.",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"Deprecated. Use 'fids' instead.",
"MulticastMessage.tokens is deprecated. Use fids instead.",

Comment thread firebase_admin/_messaging_encoder.py Outdated
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)
Copy link
Copy Markdown
Member

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

Comment thread firebase_admin/_messaging_encoder.py Outdated

Args:
tokens: A list of registration tokens of targeted devices.
fids: A list of Firebase Installation IDs of targeted app instances (optional)
Copy link
Copy Markdown
Member

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

Comment thread tests/test_messaging.py
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):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we have any tests for send_each_for_multicast_async?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We didn't have unit tests for send_each_for_multicast_async previously but only included it in integration tests. Added some unit tests with respx to mock raw HTTP/2 concurrent POST requests to Google's servers

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release:stage Stage a release candidate release-note

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants