11# from __future__ import annotations
22
3+ from functools import cached_property
34import json
45import time
56import threading
910from urllib3 import Retry
1011
1112from google .oauth2 import service_account
12- from google .oauth2 .credentials import Credentials
13+ from google .auth .credentials import Credentials
1314import google .auth .transport .requests
1415
1516from pyfcm .errors import (
@@ -41,7 +42,7 @@ def __init__(
4142 Attributes:
4243 service_account_file (str): path to service account JSON file
4344 project_id (str): project ID of Google account
44- credentials (Credentials): Google oauth2 credentials instance, such as ADC
45+ credentials (Credentials): Google auth credentials instance, such as ADC, service account one
4546 proxy_dict (dict): proxy settings dictionary, use proxy (keys: `http`, `https`)
4647 env (dict): environment settings dictionary, for example "app_engine"
4748 json_encoder (BaseJSONEncoder): JSON encoder
@@ -53,9 +54,8 @@ def __init__(
5354 )
5455
5556 self ._service_account_file = service_account_file
56- self ._fcm_end_point = None
5757 self ._project_id = project_id
58- self .credentials = credentials
58+ self ._provided_credentials = credentials
5959 self .custom_adapter = adapter
6060 self .thread_local = threading .local ()
6161
@@ -76,22 +76,28 @@ def __init__(
7676
7777 self .json_encoder = json_encoder
7878
79- @property
79+ @cached_property
80+ def _credentials (self ) -> Credentials :
81+ if self ._provided_credentials is not None :
82+ return self ._provided_credentials
83+
84+ credentials = service_account .Credentials .from_service_account_file (
85+ self ._service_account_file ,
86+ scopes = ["https://www.googleapis.com/auth/firebase.messaging" ],
87+ )
88+ # Service account credentials has project_id (others are not)
89+ self ._project_id = credentials .project_id or self ._project_id
90+ self ._service_account_file = None
91+ return credentials
92+
93+ @cached_property
8094 def fcm_end_point (self ) -> str :
81- if self ._fcm_end_point is not None :
82- return self ._fcm_end_point
83- if self .credentials is None :
84- self ._initialize_credentials ()
85- # prefer the project ID scoped to the supplied credentials.
86- # If, for some reason, the credentials do not specify a project id,
87- # we'll check for an explicitly supplied one, and raise an error otherwise
88- project_id = getattr (self .credentials , "project_id" , None ) or self ._project_id
89- if not project_id :
90- raise AuthenticationError (
91- "Please provide a project_id either explicitly or through Google credentials."
92- )
93- self ._fcm_end_point = self .FCM_END_POINT_BASE + f"/{ project_id } /messages:send"
94- return self ._fcm_end_point
95+ if self ._provided_credentials is None :
96+ # read credentails to resolve project_id if needed
97+ _ = self ._credentials
98+ if self ._project_id is None :
99+ raise RuntimeError ("Please provide a project_id either explicitly or through Google credentials." )
100+ return self .FCM_END_POINT_BASE + f"/{ self ._project_id } /messages:send"
95101
96102 @property
97103 def requests_session (self ):
@@ -171,32 +177,18 @@ def _is_access_token_expired(self, response):
171177
172178 return False
173179
174- def _initialize_credentials (self ):
175- """
176- Initialize credentials and FCM endpoint if not already initialized.
177- """
178- if self .credentials is None :
179- self .credentials = service_account .Credentials .from_service_account_file (
180- self ._service_account_file ,
181- scopes = ["https://www.googleapis.com/auth/firebase.messaging" ],
182- )
183- self ._service_account_file = None
184-
185- def _get_access_token (self ):
180+ def _get_access_token (self ) -> str :
186181 """
187182 Generates access token from credentials.
188183 If token expires then new access token is generated.
189184 Returns:
190185 str: Access token
191186 """
192- if self .credentials is None :
193- self ._initialize_credentials ()
194-
195187 # get OAuth 2.0 access token
196188 try :
197189 request = google .auth .transport .requests .Request ()
198- self .credentials .refresh (request )
199- return self .credentials .token
190+ self ._credentials .refresh (request )
191+ return self ._credentials .token # pyright: ignore[reportReturnType]
200192 except Exception as e :
201193 raise InvalidDataError (e )
202194
0 commit comments