32
32
from six .moves import urllib
33
33
34
34
import firebase_admin
35
+ from firebase_admin import exceptions
35
36
from firebase_admin import _http_client
36
37
from firebase_admin import _sseclient
37
38
from firebase_admin import _utils
@@ -209,7 +210,7 @@ def get(self, etag=False, shallow=False):
209
210
210
211
Raises:
211
212
ValueError: If both ``etag`` and ``shallow`` are set to True.
212
- ApiCallError : If an error occurs while communicating with the remote database server.
213
+ FirebaseError : If an error occurs while communicating with the remote database server.
213
214
"""
214
215
if etag :
215
216
if shallow :
@@ -236,7 +237,7 @@ def get_if_changed(self, etag):
236
237
237
238
Raises:
238
239
ValueError: If the ETag is not a string.
239
- ApiCallError : If an error occurs while communicating with the remote database server.
240
+ FirebaseError : If an error occurs while communicating with the remote database server.
240
241
"""
241
242
if not isinstance (etag , six .string_types ):
242
243
raise ValueError ('ETag must be a string.' )
@@ -258,7 +259,7 @@ def set(self, value):
258
259
Raises:
259
260
ValueError: If the provided value is None.
260
261
TypeError: If the value is not JSON-serializable.
261
- ApiCallError : If an error occurs while communicating with the remote database server.
262
+ FirebaseError : If an error occurs while communicating with the remote database server.
262
263
"""
263
264
if value is None :
264
265
raise ValueError ('Value must not be None.' )
@@ -281,7 +282,7 @@ def set_if_unchanged(self, expected_etag, value):
281
282
282
283
Raises:
283
284
ValueError: If the value is None, or if expected_etag is not a string.
284
- ApiCallError : If an error occurs while communicating with the remote database server.
285
+ FirebaseError : If an error occurs while communicating with the remote database server.
285
286
"""
286
287
# pylint: disable=missing-raises-doc
287
288
if not isinstance (expected_etag , six .string_types ):
@@ -293,11 +294,11 @@ def set_if_unchanged(self, expected_etag, value):
293
294
headers = self ._client .headers (
294
295
'put' , self ._add_suffix (), json = value , headers = {'if-match' : expected_etag })
295
296
return True , value , headers .get ('ETag' )
296
- except ApiCallError as error :
297
- detail = error .detail
298
- if detail . response is not None and 'ETag' in detail . response .headers :
299
- etag = detail . response .headers ['ETag' ]
300
- snapshot = detail . response .json ()
297
+ except exceptions . FailedPreconditionError as error :
298
+ http_response = error .http_response
299
+ if http_response is not None and 'ETag' in http_response .headers :
300
+ etag = http_response .headers ['ETag' ]
301
+ snapshot = http_response .json ()
301
302
return False , snapshot , etag
302
303
else :
303
304
raise error
@@ -317,7 +318,7 @@ def push(self, value=''):
317
318
Raises:
318
319
ValueError: If the value is None.
319
320
TypeError: If the value is not JSON-serializable.
320
- ApiCallError : If an error occurs while communicating with the remote database server.
321
+ FirebaseError : If an error occurs while communicating with the remote database server.
321
322
"""
322
323
if value is None :
323
324
raise ValueError ('Value must not be None.' )
@@ -333,7 +334,7 @@ def update(self, value):
333
334
334
335
Raises:
335
336
ValueError: If value is empty or not a dictionary.
336
- ApiCallError : If an error occurs while communicating with the remote database server.
337
+ FirebaseError : If an error occurs while communicating with the remote database server.
337
338
"""
338
339
if not value or not isinstance (value , dict ):
339
340
raise ValueError ('Value argument must be a non-empty dictionary.' )
@@ -345,7 +346,7 @@ def delete(self):
345
346
"""Deletes this node from the database.
346
347
347
348
Raises:
348
- ApiCallError : If an error occurs while communicating with the remote database server.
349
+ FirebaseError : If an error occurs while communicating with the remote database server.
349
350
"""
350
351
self ._client .request ('delete' , self ._add_suffix ())
351
352
@@ -371,7 +372,7 @@ def listen(self, callback):
371
372
ListenerRegistration: An object that can be used to stop the event listener.
372
373
373
374
Raises:
374
- ApiCallError : If an error occurs while starting the initial HTTP connection.
375
+ FirebaseError : If an error occurs while starting the initial HTTP connection.
375
376
"""
376
377
session = _sseclient .KeepAuthSession (self ._client .credential )
377
378
return self ._listen_with_session (callback , session )
@@ -387,9 +388,9 @@ def transaction(self, transaction_update):
387
388
value of this reference into a new value. If another client writes to this location before
388
389
the new value is successfully saved, the update function is called again with the new
389
390
current value, and the write will be retried. In case of repeated failures, this method
390
- will retry the transaction up to 25 times before giving up and raising a TransactionError.
391
- The update function may also force an early abort by raising an exception instead of
392
- returning a value.
391
+ will retry the transaction up to 25 times before giving up and raising a
392
+ TransactionAbortedError. The update function may also force an early abort by raising an
393
+ exception instead of returning a value.
393
394
394
395
Args:
395
396
transaction_update: A function which will be passed the current data stored at this
@@ -402,7 +403,7 @@ def transaction(self, transaction_update):
402
403
object: New value of the current database Reference (only if the transaction commits).
403
404
404
405
Raises:
405
- TransactionError : If the transaction aborts after exhausting all retry attempts.
406
+ TransactionAbortedError : If the transaction aborts after exhausting all retry attempts.
406
407
ValueError: If transaction_update is not a function.
407
408
"""
408
409
if not callable (transaction_update ):
@@ -416,7 +417,8 @@ def transaction(self, transaction_update):
416
417
if success :
417
418
return new_data
418
419
tries += 1
419
- raise TransactionError ('Transaction aborted after failed retries.' )
420
+
421
+ raise TransactionAbortedError ('Transaction aborted after failed retries.' )
420
422
421
423
def order_by_child (self , path ):
422
424
"""Returns a Query that orders data by child values.
@@ -468,7 +470,7 @@ def _listen_with_session(self, callback, session):
468
470
sse = _sseclient .SSEClient (url , session )
469
471
return ListenerRegistration (callback , sse )
470
472
except requests .exceptions .RequestException as error :
471
- raise ApiCallError ( _Client .extract_error_message ( error ), error )
473
+ raise _Client .handle_rtdb_error ( error )
472
474
473
475
474
476
class Query (object ):
@@ -614,28 +616,19 @@ def get(self):
614
616
object: Decoded JSON result of the Query.
615
617
616
618
Raises:
617
- ApiCallError : If an error occurs while communicating with the remote database server.
619
+ FirebaseError : If an error occurs while communicating with the remote database server.
618
620
"""
619
621
result = self ._client .body ('get' , self ._pathurl , params = self ._querystr )
620
622
if isinstance (result , (dict , list )) and self ._order_by != '$priority' :
621
623
return _Sorter (result , self ._order_by ).get ()
622
624
return result
623
625
624
626
625
- class ApiCallError (Exception ):
626
- """Represents an Exception encountered while invoking the Firebase database server API."""
627
-
628
- def __init__ (self , message , error ):
629
- Exception .__init__ (self , message )
630
- self .detail = error
631
-
632
-
633
- class TransactionError (Exception ):
634
- """Represents an Exception encountered while performing a transaction."""
627
+ class TransactionAbortedError (exceptions .AbortedError ):
628
+ """A transaction was aborted aftr exceeding the maximum number of retries."""
635
629
636
630
def __init__ (self , message ):
637
- Exception .__init__ (self , message )
638
-
631
+ exceptions .AbortedError .__init__ (self , message )
639
632
640
633
641
634
class _Sorter (object ):
@@ -934,7 +927,7 @@ def request(self, method, url, **kwargs):
934
927
Response: An HTTP response object.
935
928
936
929
Raises:
937
- ApiCallError : If an error occurs while making the HTTP call.
930
+ FirebaseError : If an error occurs while making the HTTP call.
938
931
"""
939
932
query = '&' .join ('{0}={1}' .format (key , self .params [key ]) for key in self .params )
940
933
extra_params = kwargs .get ('params' )
@@ -950,33 +943,39 @@ def request(self, method, url, **kwargs):
950
943
try :
951
944
return super (_Client , self ).request (method , url , ** kwargs )
952
945
except requests .exceptions .RequestException as error :
953
- raise ApiCallError (_Client .extract_error_message (error ), error )
946
+ raise _Client .handle_rtdb_error (error )
947
+
948
+ @classmethod
949
+ def handle_rtdb_error (cls , error ):
950
+ """Converts an error encountered while calling RTDB into a FirebaseError."""
951
+ if error .response is None :
952
+ return _utils .handle_requests_error (error )
953
+
954
+ message = cls ._extract_error_message (error .response )
955
+ return _utils .handle_requests_error (error , message = message )
954
956
955
957
@classmethod
956
- def extract_error_message (cls , error ):
957
- """Extracts an error message from an exception .
958
+ def _extract_error_message (cls , response ):
959
+ """Extracts an error message from an error response .
958
960
959
- If the server has not sent any response, simply converts the exception into a string.
960
961
If the server has sent a JSON response with an 'error' field, which is the typical
961
962
behavior of the Realtime Database REST API, parses the response to retrieve the error
962
963
message. If the server has sent a non-JSON response, returns the full response
963
964
as the error message.
964
-
965
- Args:
966
- error: An exception raised by the requests library.
967
-
968
- Returns:
969
- str: A string error message extracted from the exception.
970
965
"""
971
- if error .response is None :
972
- return str (error )
966
+ message = None
973
967
try :
974
- data = error .response .json ()
968
+ # RTDB error format: {"error": "text message"}
969
+ data = response .json ()
975
970
if isinstance (data , dict ):
976
- return '{0} \n Reason: {1}' . format ( error , data .get ('error' , 'unknown' ) )
971
+ message = data .get ('error' )
977
972
except ValueError :
978
973
pass
979
- return '{0}\n Reason: {1}' .format (error , error .response .content .decode ())
974
+
975
+ if not message :
976
+ message = 'Unexpected response from database: {0}' .format (response .content .decode ())
977
+
978
+ return message
980
979
981
980
982
981
class _EmulatorAdminCredentials (google .auth .credentials .Credentials ):
0 commit comments