From 7ff8cbb6ea9a55de8e2d169bdf2590a876d82099 Mon Sep 17 00:00:00 2001 From: hiranya911 Date: Mon, 10 Jun 2019 16:01:10 -0700 Subject: [PATCH 1/3] Added the exceptions module --- firebase_admin/_utils.py | 44 +++++++++ firebase_admin/exceptions.py | 155 ++++++++++++++++++++++++++++++++ firebase_admin/instance_id.py | 15 +--- integration/test_instance_id.py | 3 +- tests/test_instance_id.py | 51 ++++++++--- 5 files changed, 245 insertions(+), 23 deletions(-) create mode 100644 firebase_admin/exceptions.py diff --git a/firebase_admin/_utils.py b/firebase_admin/_utils.py index b28853868..ecc0f19d4 100644 --- a/firebase_admin/_utils.py +++ b/firebase_admin/_utils.py @@ -14,7 +14,22 @@ """Internal utilities common to all modules.""" +import requests + import firebase_admin +from firebase_admin import exceptions + + +_STATUS_TO_EXCEPTION_TYPE = { + 400: exceptions.InvalidArgumentError, + 401: exceptions.UnautenticatedError, + 403: exceptions.PermissionDeniedError, + 404: exceptions.NotFoundError, + 409: exceptions.ConflictError, + 429: exceptions.ResourceExhaustedError, + 500: exceptions.InternalError, + 503: exceptions.UnavailableError, +} def _get_initialized_app(app): @@ -33,3 +48,32 @@ def _get_initialized_app(app): def get_app_service(app, name, initializer): app = _get_initialized_app(app) return app._get_service(name, initializer) # pylint: disable=protected-access + +def handle_requests_error(error, message=None, status=None): + """Constructs a FirebaseError from the given requests error.""" + if isinstance(error, requests.exceptions.Timeout): + return exceptions.DeadlineExceededError( + message='Timed out while making an API call: {1}'.format( + _low_level_message(error, message)), + cause=error) + elif isinstance(error, requests.exceptions.ConnectionError): + return exceptions.UnavailableError( + message='Failed to establish a connection: {1}'.format( + _low_level_message(error, message)), + cause=error) + elif error.response is None: + return exceptions.UnknownError( + message='Unknown error while making a remote service call: {1}'.format( + _low_level_message(error, message)), + cause=error) + + if not status: + status = error.response.status_code + err_type = _STATUS_TO_EXCEPTION_TYPE.get(status, exceptions.UnknownError) + return err_type(message=message, cause=error, http_response=error.response) + +def _low_level_message(error, message=None): + low_level_message = str(error) + if message is not None: + low_level_message = '{0} {1}'.format(message, error) + return low_level_message diff --git a/firebase_admin/exceptions.py b/firebase_admin/exceptions.py new file mode 100644 index 000000000..236b34aa5 --- /dev/null +++ b/firebase_admin/exceptions.py @@ -0,0 +1,155 @@ +# Copyright 20190 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Firebase Exceptions module. + +This module defines the base types for exceptions and the platform-wide error codes as outlined in +https://cloud.google.com/apis/design/errors. +""" + + +INVALID_ARGUMENT = 'INVALID_ARGUMENT' +FAILED_PRECONDITION = 'FAILED_PRECONDITION' +OUT_OF_RANGE = 'OUT_OF_RANGE' +UNAUTHENTICATED = 'UNAUTHENTICATED' +PERMISSION_DENIED = 'PERMISSION_DENIED' +NOT_FOUND = 'NOT_FOUND' +CONFLICT = 'CONFLICT' +ABORTED = 'ABORTED' +ALREADY_EXISTS = 'ALREADY_EXISTS' +RESOURCE_EXHAUSTED = 'RESOURCE_EXHAUSTED' +CANCELLED = 'CANCELLED' +DATA_LOSS = 'DATA_LOSS' +UNKNOWN = 'UNKNOWN' +INTERNAL = 'INTERNAL' +UNAVAILABLE = 'UNAVAILABLE' +DEADLINE_EXCEEDED = 'DEADLINE_EXCEEDED' + + +class FirebaseError(Exception): + """Base class for all errors raised by the Admin SDK.""" + + def __init__(self, code, message, cause=None, http_response=None): + Exception.__init__(self, message) + self._code = code + self._cause = cause + self._http_response = http_response + + @property + def code(self): + return self._code + + @property + def cause(self): + return self._cause + + @property + def http_response(self): + return self._http_response + + +class InvalidArgumentError(FirebaseError): + + def __init__(self, message, cause=None, http_response=None): + FirebaseError.__init__(self, INVALID_ARGUMENT, message, cause, http_response) + + +class FailedPreconditionError(FirebaseError): + + def __init__(self, message, cause=None, http_response=None): + FirebaseError.__init__(self, FAILED_PRECONDITION, message, cause, http_response) + + +class OutOfRangeError(FirebaseError): + + def __init__(self, message, cause=None, http_response=None): + FirebaseError.__init__(self, OUT_OF_RANGE, message, cause, http_response) + + +class UnautenticatedError(FirebaseError): + + def __init__(self, message, cause=None, http_response=None): + FirebaseError.__init__(self, UNAUTHENTICATED, message, cause, http_response) + + +class PermissionDeniedError(FirebaseError): + + def __init__(self, message, cause=None, http_response=None): + FirebaseError.__init__(self, PERMISSION_DENIED, message, cause, http_response) + + +class NotFoundError(FirebaseError): + + def __init__(self, message, cause=None, http_response=None): + FirebaseError.__init__(self, NOT_FOUND, message, cause, http_response) + + +class ConflictError(FirebaseError): + + def __init__(self, message, cause=None, http_response=None): + FirebaseError.__init__(self, CONFLICT, message, cause, http_response) + + +class AbortedError(FirebaseError): + + def __init__(self, message, cause=None, http_response=None): + FirebaseError.__init__(self, ABORTED, message, cause, http_response) + + +class AlreadyExistsError(FirebaseError): + + def __init__(self, message, cause=None, http_response=None): + FirebaseError.__init__(self, ALREADY_EXISTS, message, cause, http_response) + + +class ResourceExhaustedError(FirebaseError): + + def __init__(self, message, cause=None, http_response=None): + FirebaseError.__init__(self, RESOURCE_EXHAUSTED, message, cause, http_response) + + +class CancelledError(FirebaseError): + + def __init__(self, message, cause=None, http_response=None): + FirebaseError.__init__(self, CANCELLED, message, cause, http_response) + + +class DataLossError(FirebaseError): + + def __init__(self, message, cause=None, http_response=None): + FirebaseError.__init__(self, DATA_LOSS, message, cause, http_response) + + +class UnknownError(FirebaseError): + + def __init__(self, message, cause=None, http_response=None): + FirebaseError.__init__(self, UNKNOWN, message, cause, http_response) + + +class InternalError(FirebaseError): + + def __init__(self, message, cause=None, http_response=None): + FirebaseError.__init__(self, INTERNAL, message, cause, http_response) + + +class UnavailableError(FirebaseError): + + def __init__(self, message, cause=None, http_response=None): + FirebaseError.__init__(self, UNAVAILABLE, message, cause, http_response) + + +class DeadlineExceededError(FirebaseError): + + def __init__(self, message, cause=None, http_response=None): + FirebaseError.__init__(self, DEADLINE_EXCEEDED, message, cause, http_response) diff --git a/firebase_admin/instance_id.py b/firebase_admin/instance_id.py index b290e9e7f..e9134fc28 100644 --- a/firebase_admin/instance_id.py +++ b/firebase_admin/instance_id.py @@ -53,14 +53,6 @@ def delete_instance_id(instance_id, app=None): _get_iid_service(app).delete_instance_id(instance_id) -class ApiCallError(Exception): - """Represents an Exception encountered while invoking the Firebase instance ID service.""" - - def __init__(self, message, error): - Exception.__init__(self, message) - self.detail = error - - class _InstanceIdService(object): """Provides methods for interacting with the remote instance ID service.""" @@ -94,14 +86,15 @@ def delete_instance_id(self, instance_id): try: self._client.request('delete', path) except requests.exceptions.RequestException as error: - raise ApiCallError(self._extract_message(instance_id, error), error) + msg = self._extract_message(instance_id, error) + raise _utils.handle_requests_error(error, msg) def _extract_message(self, instance_id, error): if error.response is None: - return str(error) + return None status = error.response.status_code msg = self.error_codes.get(status) if msg: return 'Instance ID "{0}": {1}'.format(instance_id, msg) else: - return str(error) + return 'Instance ID "{0}": {1}'.format(instance_id, error) diff --git a/integration/test_instance_id.py b/integration/test_instance_id.py index 1a176a9a0..99b6787d3 100644 --- a/integration/test_instance_id.py +++ b/integration/test_instance_id.py @@ -16,10 +16,11 @@ import pytest +from firebase_admin import exceptions from firebase_admin import instance_id def test_delete_non_existing(): - with pytest.raises(instance_id.ApiCallError) as excinfo: + with pytest.raises(exceptions.NotFoundError) as excinfo: # legal instance IDs are /[cdef][A-Za-z0-9_-]{9}[AEIMQUYcgkosw048]/ instance_id.delete_instance_id('fictive-ID0') assert str(excinfo.value) == 'Instance ID "fictive-ID0": Failed to find the instance ID.' diff --git a/tests/test_instance_id.py b/tests/test_instance_id.py index e8e8edd27..a5776116a 100644 --- a/tests/test_instance_id.py +++ b/tests/test_instance_id.py @@ -17,15 +17,37 @@ import pytest import firebase_admin +from firebase_admin import exceptions from firebase_admin import instance_id from tests import testutils http_errors = { - 404: 'Instance ID "test_iid": Failed to find the instance ID.', - 409: 'Instance ID "test_iid": Already deleted.', - 429: 'Instance ID "test_iid": Request throttled out by the backend server.', - 500: 'Instance ID "test_iid": Internal server error.', + 400: ( + 'Instance ID "test_iid": Malformed instance ID argument.', + exceptions.InvalidArgumentError), + 401: ( + 'Instance ID "test_iid": Request not authorized.', + exceptions.UnautenticatedError), + 403: ( + ('Instance ID "test_iid": Project does not match instance ID or the client does not have ' + 'sufficient privileges.'), + exceptions.PermissionDeniedError), + 404: ( + 'Instance ID "test_iid": Failed to find the instance ID.', + exceptions.NotFoundError), + 409: ( + 'Instance ID "test_iid": Already deleted.', + exceptions.ConflictError), + 429: ( + 'Instance ID "test_iid": Request throttled out by the backend server.', + exceptions.ResourceExhaustedError), + 500: ( + 'Instance ID "test_iid": Internal server error.', + exceptions.InternalError), + 503: ( + 'Instance ID "test_iid": Backend servers are over capacity. Try again later.', + exceptions.UnavailableError), } class TestDeleteInstanceId(object): @@ -74,11 +96,17 @@ def test_delete_instance_id_error(self, status): cred = testutils.MockCredential() app = firebase_admin.initialize_app(cred, {'projectId': 'explicit-project-id'}) _, recorder = self._instrument_iid_service(app, status, 'some error') - with pytest.raises(instance_id.ApiCallError) as excinfo: + msg, exc = http_errors.get(status) + with pytest.raises(exc) as excinfo: instance_id.delete_instance_id('test_iid') - assert str(excinfo.value) == http_errors.get(status) - assert excinfo.value.detail is not None - assert len(recorder) == 1 + assert str(excinfo.value) == msg + assert excinfo.value.cause is not None + assert excinfo.value.http_response is not None + if status != 401: + assert len(recorder) == 1 + else: + # 401 responses are automatically retried by google-auth + assert len(recorder) == 3 assert recorder[0].method == 'DELETE' assert recorder[0].url == self._get_url('explicit-project-id', 'test_iid') @@ -86,12 +114,13 @@ def test_delete_instance_id_unexpected_error(self): cred = testutils.MockCredential() app = firebase_admin.initialize_app(cred, {'projectId': 'explicit-project-id'}) _, recorder = self._instrument_iid_service(app, 501, 'some error') - with pytest.raises(instance_id.ApiCallError) as excinfo: + with pytest.raises(exceptions.UnknownError) as excinfo: instance_id.delete_instance_id('test_iid') url = self._get_url('explicit-project-id', 'test_iid') - message = '501 Server Error: None for url: {0}'.format(url) + message = 'Instance ID "test_iid": 501 Server Error: None for url: {0}'.format(url) assert str(excinfo.value) == message - assert excinfo.value.detail is not None + assert excinfo.value.cause is not None + assert excinfo.value.http_response is not None assert len(recorder) == 1 assert recorder[0].method == 'DELETE' assert recorder[0].url == url From a7ea415e49faf2fa09836591e249fc3c4b16e479 Mon Sep 17 00:00:00 2001 From: hiranya911 Date: Mon, 10 Jun 2019 16:35:04 -0700 Subject: [PATCH 2/3] Cleaned up the error handling logic; Added tests --- CHANGELOG.md | 5 ++- firebase_admin/_utils.py | 17 +++---- tests/test_exceptions.py | 96 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 13 deletions(-) create mode 100644 tests/test_exceptions.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fc1a759e..86acfcdc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Unreleased -- +- [added] Added the new `firebase_admin.exceptions` module containing the + base exception types and global error codes. +- [changed] Updated the `firebase_admin.instance_id` module to use the new + shared exception types. The type `instance_id.ApiCallError` was removed. # v2.17.0 diff --git a/firebase_admin/_utils.py b/firebase_admin/_utils.py index ecc0f19d4..626f30b86 100644 --- a/firebase_admin/_utils.py +++ b/firebase_admin/_utils.py @@ -53,27 +53,20 @@ def handle_requests_error(error, message=None, status=None): """Constructs a FirebaseError from the given requests error.""" if isinstance(error, requests.exceptions.Timeout): return exceptions.DeadlineExceededError( - message='Timed out while making an API call: {1}'.format( - _low_level_message(error, message)), + message='Timed out while making an API call: {0}'.format(error), cause=error) elif isinstance(error, requests.exceptions.ConnectionError): return exceptions.UnavailableError( - message='Failed to establish a connection: {1}'.format( - _low_level_message(error, message)), + message='Failed to establish a connection: {0}'.format(error), cause=error) elif error.response is None: return exceptions.UnknownError( - message='Unknown error while making a remote service call: {1}'.format( - _low_level_message(error, message)), + message='Unknown error while making a remote service call: {0}'.format(error), cause=error) if not status: status = error.response.status_code + if not message: + message = str(error) err_type = _STATUS_TO_EXCEPTION_TYPE.get(status, exceptions.UnknownError) return err_type(message=message, cause=error, http_response=error.response) - -def _low_level_message(error, message=None): - low_level_message = str(error) - if message is not None: - low_level_message = '{0} {1}'.format(message, error) - return low_level_message diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py new file mode 100644 index 000000000..f2897ab3c --- /dev/null +++ b/tests/test_exceptions.py @@ -0,0 +1,96 @@ +# Copyright 2019 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import requests +from requests import models + +from firebase_admin import exceptions +from firebase_admin import _utils + + +def test_timeout_error(): + error = requests.exceptions.Timeout('Test error') + firebase_error = _utils.handle_requests_error(error) + assert isinstance(firebase_error, exceptions.DeadlineExceededError) + assert str(firebase_error) == 'Timed out while making an API call: Test error' + assert firebase_error.cause is error + assert firebase_error.http_response is None + +def test_connection_error(): + error = requests.exceptions.ConnectionError('Test error') + firebase_error = _utils.handle_requests_error(error) + assert isinstance(firebase_error, exceptions.UnavailableError) + assert str(firebase_error) == 'Failed to establish a connection: Test error' + assert firebase_error.cause is error + assert firebase_error.http_response is None + +def test_unknown_transport_error(): + error = requests.exceptions.RequestException('Test error') + firebase_error = _utils.handle_requests_error(error) + assert isinstance(firebase_error, exceptions.UnknownError) + assert str(firebase_error) == 'Unknown error while making a remote service call: Test error' + assert firebase_error.cause is error + assert firebase_error.http_response is None + +def test_http_response(): + resp = models.Response() + resp.status_code = 500 + error = requests.exceptions.RequestException('Test error', response=resp) + firebase_error = _utils.handle_requests_error(error) + assert isinstance(firebase_error, exceptions.InternalError) + assert str(firebase_error) == 'Test error' + assert firebase_error.cause is error + assert firebase_error.http_response is resp + +def test_http_response_with_unknown_status(): + resp = models.Response() + resp.status_code = 501 + error = requests.exceptions.RequestException('Test error', response=resp) + firebase_error = _utils.handle_requests_error(error) + assert isinstance(firebase_error, exceptions.UnknownError) + assert str(firebase_error) == 'Test error' + assert firebase_error.cause is error + assert firebase_error.http_response is resp + +def test_http_response_with_message(): + resp = models.Response() + resp.status_code = 500 + error = requests.exceptions.RequestException('Test error', response=resp) + firebase_error = _utils.handle_requests_error(error, message='Explicit error message') + assert isinstance(firebase_error, exceptions.InternalError) + assert str(firebase_error) == 'Explicit error message' + assert firebase_error.cause is error + assert firebase_error.http_response is resp + +def test_http_response_with_status(): + resp = models.Response() + resp.status_code = 500 + error = requests.exceptions.RequestException('Test error', response=resp) + firebase_error = _utils.handle_requests_error(error, status=503) + assert isinstance(firebase_error, exceptions.UnavailableError) + assert str(firebase_error) == 'Test error' + assert firebase_error.cause is error + assert firebase_error.http_response is resp + +def test_http_response_with_message_and_status(): + resp = models.Response() + resp.status_code = 500 + error = requests.exceptions.RequestException('Test error', response=resp) + firebase_error = _utils.handle_requests_error( + error, message='Explicit error message', status=503) + assert isinstance(firebase_error, exceptions.UnavailableError) + assert str(firebase_error) == 'Explicit error message' + assert firebase_error.cause is error + assert firebase_error.http_response is resp From 4867cfbc97cefa94b34780e5a333849b73502741 Mon Sep 17 00:00:00 2001 From: hiranya911 Date: Tue, 11 Jun 2019 17:16:02 -0700 Subject: [PATCH 3/3] Updated docs; Fixed some typos --- firebase_admin/_utils.py | 15 +++++++++++++-- firebase_admin/exceptions.py | 31 +++++++++++++++++++++++++++++-- tests/test_instance_id.py | 2 +- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/firebase_admin/_utils.py b/firebase_admin/_utils.py index 626f30b86..61fe8eb1c 100644 --- a/firebase_admin/_utils.py +++ b/firebase_admin/_utils.py @@ -22,7 +22,7 @@ _STATUS_TO_EXCEPTION_TYPE = { 400: exceptions.InvalidArgumentError, - 401: exceptions.UnautenticatedError, + 401: exceptions.UnauthenticatedError, 403: exceptions.PermissionDeniedError, 404: exceptions.NotFoundError, 409: exceptions.ConflictError, @@ -50,7 +50,18 @@ def get_app_service(app, name, initializer): return app._get_service(name, initializer) # pylint: disable=protected-access def handle_requests_error(error, message=None, status=None): - """Constructs a FirebaseError from the given requests error.""" + """Constructs a ``FirebaseError`` from the given requests error. + + Args: + error: An error raised by the reqests module while making an HTTP call. + message: A message to be included in the resulting ``FirebaseError`` (optional). If not + specified the string representation of the ``error`` argument is used as the message. + status: An HTTP status code that will be used to determine the resulting error type + (optional). If not specified the HTTP status code on the error response is used. + + Returns: + FirebaseError: A ``FirebaseError`` that can be raised to the user code. + """ if isinstance(error, requests.exceptions.Timeout): return exceptions.DeadlineExceededError( message='Timed out while making an API call: {0}'.format(error), diff --git a/firebase_admin/exceptions.py b/firebase_admin/exceptions.py index 236b34aa5..f1297dbb3 100644 --- a/firebase_admin/exceptions.py +++ b/firebase_admin/exceptions.py @@ -1,4 +1,4 @@ -# Copyright 20190 Google Inc. +# Copyright 2019 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -60,96 +60,123 @@ def http_response(self): class InvalidArgumentError(FirebaseError): + """Client specified an invalid argument.""" def __init__(self, message, cause=None, http_response=None): FirebaseError.__init__(self, INVALID_ARGUMENT, message, cause, http_response) class FailedPreconditionError(FirebaseError): + """Request can not be executed in the current system state, such as deleting a non-empty + directory.""" def __init__(self, message, cause=None, http_response=None): FirebaseError.__init__(self, FAILED_PRECONDITION, message, cause, http_response) class OutOfRangeError(FirebaseError): + """Client specified an invalid range.""" def __init__(self, message, cause=None, http_response=None): FirebaseError.__init__(self, OUT_OF_RANGE, message, cause, http_response) -class UnautenticatedError(FirebaseError): +class UnauthenticatedError(FirebaseError): + """Request not authenticated due to missing, invalid, or expired OAuth token.""" def __init__(self, message, cause=None, http_response=None): FirebaseError.__init__(self, UNAUTHENTICATED, message, cause, http_response) class PermissionDeniedError(FirebaseError): + """Client does not have sufficient permission. + + This can happen because the OAuth token does not have the right scopes, the client doesn't + have permission, or the API has not been enabled for the client project. + """ def __init__(self, message, cause=None, http_response=None): FirebaseError.__init__(self, PERMISSION_DENIED, message, cause, http_response) class NotFoundError(FirebaseError): + """A specified resource is not found, or the request is rejected by undisclosed reasons, such + as whitelisting.""" def __init__(self, message, cause=None, http_response=None): FirebaseError.__init__(self, NOT_FOUND, message, cause, http_response) class ConflictError(FirebaseError): + """Concurrency conflict, such as read-modify-write conflict.""" def __init__(self, message, cause=None, http_response=None): FirebaseError.__init__(self, CONFLICT, message, cause, http_response) class AbortedError(FirebaseError): + """Concurrency conflict, such as read-modify-write conflict.""" def __init__(self, message, cause=None, http_response=None): FirebaseError.__init__(self, ABORTED, message, cause, http_response) class AlreadyExistsError(FirebaseError): + """The resource that a client tried to create already exists.""" def __init__(self, message, cause=None, http_response=None): FirebaseError.__init__(self, ALREADY_EXISTS, message, cause, http_response) class ResourceExhaustedError(FirebaseError): + """Either out of resource quota or reaching rate limiting.""" def __init__(self, message, cause=None, http_response=None): FirebaseError.__init__(self, RESOURCE_EXHAUSTED, message, cause, http_response) class CancelledError(FirebaseError): + """Request cancelled by the client.""" def __init__(self, message, cause=None, http_response=None): FirebaseError.__init__(self, CANCELLED, message, cause, http_response) class DataLossError(FirebaseError): + """Unrecoverable data loss or data corruption.""" def __init__(self, message, cause=None, http_response=None): FirebaseError.__init__(self, DATA_LOSS, message, cause, http_response) class UnknownError(FirebaseError): + """Unknown server error.""" def __init__(self, message, cause=None, http_response=None): FirebaseError.__init__(self, UNKNOWN, message, cause, http_response) class InternalError(FirebaseError): + """Internal server error.""" def __init__(self, message, cause=None, http_response=None): FirebaseError.__init__(self, INTERNAL, message, cause, http_response) class UnavailableError(FirebaseError): + """Service unavailable. Typically the server is down.""" def __init__(self, message, cause=None, http_response=None): FirebaseError.__init__(self, UNAVAILABLE, message, cause, http_response) class DeadlineExceededError(FirebaseError): + """Request deadline exceeded. + + This will happen only if the caller sets a deadline that is shorter than the method's + default deadline (i.e. requested deadline is not enough for the server to process the + request) and the request did not finish within the deadline. + """ def __init__(self, message, cause=None, http_response=None): FirebaseError.__init__(self, DEADLINE_EXCEEDED, message, cause, http_response) diff --git a/tests/test_instance_id.py b/tests/test_instance_id.py index a5776116a..83e66491a 100644 --- a/tests/test_instance_id.py +++ b/tests/test_instance_id.py @@ -28,7 +28,7 @@ exceptions.InvalidArgumentError), 401: ( 'Instance ID "test_iid": Request not authorized.', - exceptions.UnautenticatedError), + exceptions.UnauthenticatedError), 403: ( ('Instance ID "test_iid": Project does not match instance ID or the client does not have ' 'sufficient privileges.'),