1414
1515"""Internal utilities common to all modules.""" 
1616
17+ import  json 
18+ import  socket 
19+ 
20+ import  googleapiclient 
21+ import  httplib2 
1722import  requests 
23+ import  six 
1824
1925import  firebase_admin 
2026from  firebase_admin  import  exceptions 
2127
2228
23- _STATUS_TO_EXCEPTION_TYPE  =  {
24-     400 : exceptions .InvalidArgumentError ,
25-     401 : exceptions .UnauthenticatedError ,
26-     403 : exceptions .PermissionDeniedError ,
27-     404 : exceptions .NotFoundError ,
28-     409 : exceptions .ConflictError ,
29-     429 : exceptions .ResourceExhaustedError ,
30-     500 : exceptions .InternalError ,
31-     503 : exceptions .UnavailableError ,
29+ _ERROR_CODE_TO_EXCEPTION_TYPE  =  {
30+     exceptions .INVALID_ARGUMENT : exceptions .InvalidArgumentError ,
31+     exceptions .FAILED_PRECONDITION : exceptions .FailedPreconditionError ,
32+     exceptions .OUT_OF_RANGE : exceptions .OutOfRangeError ,
33+     exceptions .UNAUTHENTICATED : exceptions .UnauthenticatedError ,
34+     exceptions .PERMISSION_DENIED : exceptions .PermissionDeniedError ,
35+     exceptions .NOT_FOUND : exceptions .NotFoundError ,
36+     exceptions .ABORTED : exceptions .AbortedError ,
37+     exceptions .ALREADY_EXISTS : exceptions .AlreadyExistsError ,
38+     exceptions .CONFLICT : exceptions .ConflictError ,
39+     exceptions .RESOURCE_EXHAUSTED : exceptions .ResourceExhaustedError ,
40+     exceptions .CANCELLED : exceptions .CancelledError ,
41+     exceptions .DATA_LOSS : exceptions .DataLossError ,
42+     exceptions .UNKNOWN : exceptions .UnknownError ,
43+     exceptions .INTERNAL : exceptions .InternalError ,
44+     exceptions .UNAVAILABLE : exceptions .UnavailableError ,
45+     exceptions .DEADLINE_EXCEEDED : exceptions .DeadlineExceededError ,
46+ }
47+ 
48+ 
49+ _HTTP_STATUS_TO_ERROR_CODE  =  {
50+     400 : exceptions .INVALID_ARGUMENT ,
51+     401 : exceptions .UNAUTHENTICATED ,
52+     403 : exceptions .PERMISSION_DENIED ,
53+     404 : exceptions .NOT_FOUND ,
54+     409 : exceptions .CONFLICT ,
55+     429 : exceptions .RESOURCE_EXHAUSTED ,
56+     500 : exceptions .INTERNAL ,
57+     503 : exceptions .UNAVAILABLE ,
3258}
3359
3460
@@ -45,19 +71,69 @@ def _get_initialized_app(app):
4571        raise  ValueError ('Illegal app argument. Argument must be of type ' 
4672                         ' firebase_admin.App, but given "{0}".' .format (type (app )))
4773
74+ 
4875def  get_app_service (app , name , initializer ):
4976    app  =  _get_initialized_app (app )
5077    return  app ._get_service (name , initializer ) # pylint: disable=protected-access 
5178
52- def  handle_requests_error (error , message = None , status = None ):
79+ 
80+ def  handle_platform_error_from_requests (error , handle_func = None ):
81+     """Constructs a ``FirebaseError`` from the given requests error. 
82+ 
83+     This can be used to handle errors returned by Google Cloud Platform (GCP) APIs. 
84+ 
85+     Args: 
86+         error: An error raised by the requests module while making an HTTP call to a GCP API. 
87+         handle_func: A function that can be used to handle platform errors in a custom way. When 
88+             specified, this function will be called with three arguments. It has the same 
89+             signature as ```_handle_func_requests``, but may return ``None``. 
90+ 
91+     Returns: 
92+         FirebaseError: A ``FirebaseError`` that can be raised to the user code. 
93+     """ 
94+     if  error .response  is  None :
95+         return  handle_requests_error (error )
96+ 
97+     response  =  error .response 
98+     content  =  response .content .decode ()
99+     status_code  =  response .status_code 
100+     error_dict , message  =  _parse_platform_error (content , status_code )
101+     exc  =  None 
102+     if  handle_func :
103+         exc  =  handle_func (error , message , error_dict )
104+ 
105+     return  exc  if  exc  else  _handle_func_requests (error , message , error_dict )
106+ 
107+ 
108+ def  _handle_func_requests (error , message , error_dict ):
109+     """Constructs a ``FirebaseError`` from the given GCP error. 
110+ 
111+     Args: 
112+         error: An error raised by the requests module while making an HTTP call. 
113+         message: A message to be included in the resulting ``FirebaseError``. 
114+         error_dict: Parsed GCP error response. 
115+ 
116+     Returns: 
117+         FirebaseError: A ``FirebaseError`` that can be raised to the user code or None. 
118+     """ 
119+     code  =  error_dict .get ('status' )
120+     return  handle_requests_error (error , message , code )
121+ 
122+ 
123+ def  handle_requests_error (error , message = None , code = None ):
53124    """Constructs a ``FirebaseError`` from the given requests error. 
54125
126+     This method is agnostic of the remote service that produced the error, whether it is a GCP 
127+     service or otherwise. Therefore, this method does not attempt to parse the error response in 
128+     any way. 
129+ 
55130    Args: 
56-         error: An error raised by the reqests  module while making an HTTP call. 
131+         error: An error raised by the requests  module while making an HTTP call. 
57132        message: A message to be included in the resulting ``FirebaseError`` (optional). If not 
58133            specified the string representation of the ``error`` argument is used as the message. 
59-         status: An HTTP status code that will be used to determine the resulting error type 
60-             (optional). If not specified the HTTP status code on the error response is used. 
134+         code: A GCP error code that will be used to determine the resulting error type (optional). 
135+             If not specified the HTTP status code on the error response is used to determine a 
136+             suitable error code. 
61137
62138    Returns: 
63139        FirebaseError: A ``FirebaseError`` that can be raised to the user code. 
@@ -75,9 +151,143 @@ def handle_requests_error(error, message=None, status=None):
75151            message = 'Unknown error while making a remote service call: {0}' .format (error ),
76152            cause = error )
77153
78-     if  not  status :
79-         status  =  error .response .status_code 
154+     if  not  code :
155+         code  =  _http_status_to_error_code ( error .response .status_code ) 
80156    if  not  message :
81157        message  =  str (error )
82-     err_type  =  _STATUS_TO_EXCEPTION_TYPE .get (status , exceptions .UnknownError )
158+ 
159+     err_type  =  _error_code_to_exception_type (code )
83160    return  err_type (message = message , cause = error , http_response = error .response )
161+ 
162+ 
163+ def  handle_platform_error_from_googleapiclient (error , handle_func = None ):
164+     """Constructs a ``FirebaseError`` from the given googleapiclient error. 
165+ 
166+     This can be used to handle errors returned by Google Cloud Platform (GCP) APIs. 
167+ 
168+     Args: 
169+         error: An error raised by the googleapiclient while making an HTTP call to a GCP API. 
170+         handle_func: A function that can be used to handle platform errors in a custom way. When 
171+             specified, this function will be called with three arguments. It has the same 
172+             signature as ```_handle_func_googleapiclient``, but may return ``None``. 
173+ 
174+     Returns: 
175+         FirebaseError: A ``FirebaseError`` that can be raised to the user code. 
176+     """ 
177+     if  not  isinstance (error , googleapiclient .errors .HttpError ):
178+         return  handle_googleapiclient_error (error )
179+ 
180+     content  =  error .content .decode ()
181+     status_code  =  error .resp .status 
182+     error_dict , message  =  _parse_platform_error (content , status_code )
183+     http_response  =  _http_response_from_googleapiclient_error (error )
184+     exc  =  None 
185+     if  handle_func :
186+         exc  =  handle_func (error , message , error_dict , http_response )
187+ 
188+     return  exc  if  exc  else  _handle_func_googleapiclient (error , message , error_dict , http_response )
189+ 
190+ 
191+ def  _handle_func_googleapiclient (error , message , error_dict , http_response ):
192+     """Constructs a ``FirebaseError`` from the given GCP error. 
193+ 
194+     Args: 
195+         error: An error raised by the googleapiclient module while making an HTTP call. 
196+         message: A message to be included in the resulting ``FirebaseError``. 
197+         error_dict: Parsed GCP error response. 
198+         http_response: A requests HTTP response object to associate with the exception. 
199+ 
200+     Returns: 
201+         FirebaseError: A ``FirebaseError`` that can be raised to the user code or None. 
202+     """ 
203+     code  =  error_dict .get ('status' )
204+     return  handle_googleapiclient_error (error , message , code , http_response )
205+ 
206+ 
207+ def  handle_googleapiclient_error (error , message = None , code = None , http_response = None ):
208+     """Constructs a ``FirebaseError`` from the given googleapiclient error. 
209+ 
210+     This method is agnostic of the remote service that produced the error, whether it is a GCP 
211+     service or otherwise. Therefore, this method does not attempt to parse the error response in 
212+     any way. 
213+ 
214+     Args: 
215+         error: An error raised by the googleapiclient module while making an HTTP call. 
216+         message: A message to be included in the resulting ``FirebaseError`` (optional). If not 
217+             specified the string representation of the ``error`` argument is used as the message. 
218+         code: A GCP error code that will be used to determine the resulting error type (optional). 
219+             If not specified the HTTP status code on the error response is used to determine a 
220+             suitable error code. 
221+         http_response: A requests HTTP response object to associate with the exception (optional). 
222+             If not specified, one will be created from the ``error``. 
223+ 
224+     Returns: 
225+         FirebaseError: A ``FirebaseError`` that can be raised to the user code. 
226+     """ 
227+     if  isinstance (error , socket .timeout ) or  (
228+             isinstance (error , socket .error ) and  'timed out'  in  str (error )):
229+         return  exceptions .DeadlineExceededError (
230+             message = 'Timed out while making an API call: {0}' .format (error ),
231+             cause = error )
232+     elif  isinstance (error , httplib2 .ServerNotFoundError ):
233+         return  exceptions .UnavailableError (
234+             message = 'Failed to establish a connection: {0}' .format (error ),
235+             cause = error )
236+     elif  not  isinstance (error , googleapiclient .errors .HttpError ):
237+         return  exceptions .UnknownError (
238+             message = 'Unknown error while making a remote service call: {0}' .format (error ),
239+             cause = error )
240+ 
241+     if  not  code :
242+         code  =  _http_status_to_error_code (error .resp .status )
243+     if  not  message :
244+         message  =  str (error )
245+     if  not  http_response :
246+         http_response  =  _http_response_from_googleapiclient_error (error )
247+ 
248+     err_type  =  _error_code_to_exception_type (code )
249+     return  err_type (message = message , cause = error , http_response = http_response )
250+ 
251+ 
252+ def  _http_response_from_googleapiclient_error (error ):
253+     """Creates a requests HTTP Response object from the given googleapiclient error.""" 
254+     resp  =  requests .models .Response ()
255+     resp .raw  =  six .BytesIO (error .content )
256+     resp .status_code  =  error .resp .status 
257+     return  resp 
258+ 
259+ 
260+ def  _http_status_to_error_code (status ):
261+     """Maps an HTTP status to a platform error code.""" 
262+     return  _HTTP_STATUS_TO_ERROR_CODE .get (status , exceptions .UNKNOWN )
263+ 
264+ 
265+ def  _error_code_to_exception_type (code ):
266+     """Maps a platform error code to an exception type.""" 
267+     return  _ERROR_CODE_TO_EXCEPTION_TYPE .get (code , exceptions .UnknownError )
268+ 
269+ 
270+ def  _parse_platform_error (content , status_code ):
271+     """Parses an HTTP error response from a Google Cloud Platform API and extracts the error code 
272+     and message fields. 
273+ 
274+     Args: 
275+         content: Decoded content of the response body. 
276+         status_code: HTTP status code. 
277+ 
278+     Returns: 
279+         tuple: A tuple containing error code and message. 
280+     """ 
281+     data  =  {}
282+     try :
283+         parsed_body  =  json .loads (content )
284+         if  isinstance (parsed_body , dict ):
285+             data  =  parsed_body 
286+     except  ValueError :
287+         pass 
288+ 
289+     error_dict  =  data .get ('error' , {})
290+     msg  =  error_dict .get ('message' )
291+     if  not  msg :
292+         msg  =  'Unexpected HTTP response with status: {0}; body: {1}' .format (status_code , content )
293+     return  error_dict , msg 
0 commit comments