Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions pyfcm/baseapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
import requests
from requests.adapters import HTTPAdapter
from urllib3 import Retry
from os import path

from google.oauth2 import service_account
from google.oauth2.credentials import Credentials
import google.auth.transport.requests

Expand Down Expand Up @@ -177,7 +177,11 @@ def _initialize_credentials(self):
Initialize credentials and FCM endpoint if not already initialized.
"""
if self.credentials is None:
self.credentials = service_account.Credentials.from_service_account_file(
if not path.isfile(self._service_account_file):
raise InvalidDataError(f"The service account file you passed does not exist at '{self._service_account_file}'. "
"Ensure it does not have any typos and exists."
)
self.credentials = Credentials.from_service_account_file(

@Niccari Niccari May 16, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is a runtime regression. Credentials here is google.oauth2.credentials.Credentials (the user/OAuth2 access-token class), which has no from_service_account_file classmethod. With a valid service_account_file, this raises:

AttributeError: type object 'Credentials' has no attribute 'from_service_account_file'

Only google.oauth2.service_account.Credentials provides from_service_account_file, which is why the original code imported and used service_account. The removal of from google.oauth2 import service_account (line 11) needs to be reverted as well.

Suggested change
self.credentials = Credentials.from_service_account_file(
self.credentials = service_account.Credentials.from_service_account_file(

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.

You're right. I saw two credential files in the upstream Google lib and pulled in the wrong one.

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.

Thanks again. I've brought in the changes from your branch.

self._service_account_file,
scopes=["https://www.googleapis.com/auth/firebase.messaging"],
)
Expand Down
7 changes: 7 additions & 0 deletions tests/test_fcm.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ def test_push_service_without_credentials():
except errors.AuthenticationError:
pass

def test_push_service_with_incorrect_service_account_file():
try:
fcm = FCMNotification(service_account_file='./foo.json', project_id=None, credentials=None)
fcm.notify()
assert False, "Should raise InvalidDataError without correct service account file path"
except errors.InvalidDataError:
pass

def test_push_service_directly_passed_credentials(push_service):
# We should infer the project ID/endpoint from credentials
Expand Down