10000 fix: Extracting GAPIC API calls into a new module (#581) · Spirans/firebase-admin-python@f38c5f7 · GitHub
[go: up one dir, main page]

Skip to content

Commit f38c5f7

Browse files
authored
fix: Extracting GAPIC API calls into a new module (firebase#581)
1 parent 1a53b04 commit f38c5f7

File tree

4 files changed

+140
-115
lines changed

4 files changed

+140
-115
lines changed

firebase_admin/_gapic_utils.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# Copyright 2021 Google Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Internal utilities for interacting with Google API client."""
16+
17+
import io
18+
import socket
19+
20+
import googleapiclient
21+
import httplib2
22+
import requests
23+
24+
from firebase_admin import exceptions
25+
from firebase_admin import _utils
26+
27+
28+
def handle_platform_error_from_googleapiclient(error, handle_func=None):
29+
"""Constructs a ``FirebaseError`` from the given googleapiclient error.
30+
31+
This can be used to handle errors returned by Google Cloud Platform (GCP) APIs.
32+
33+
Args:
34+
error: An error raised by the googleapiclient while making an HTTP call to a GCP API.
35+
handle_func: A function that can be used to handle platform errors in a custom way. When
36+
specified, this function will be called with three arguments. It has the same
37+
signature as ```_handle_func_googleapiclient``, but may return ``None``.
38+
39+
Returns:
40+
FirebaseError: A ``FirebaseError`` that can be raised to the user code.
41+
"""
42+
if not isinstance(error, googleapiclient.errors.HttpError):
43+
return handle_googleapiclient_error(error)
44+
45+
content = error.content.decode()
46+
status_code = error.resp.status
47+
error_dict, message = _utils._parse_platform_error(content, status_code) # pylint: disable=protected-access
48+
http_response = _http_response_from_googleapiclient_error(error)
49+
exc = None
50+
if handle_func:
51+
exc = handle_func(error, message, error_dict, http_response)
52+
53+
return exc if exc else _handle_func_googleapiclient(error, message, error_dict, http_response)
54+
55+
56+
def _handle_func_googleapiclient(error, message, error_dict, http_response):
57+
"""Constructs a ``FirebaseError`` from the given GCP error.
58+
59+
Args:
60+
error: An error raised by the googleapiclient module while making an HTTP call.
61+
message: A message to be included in the resulting ``FirebaseError``.
62+
error_dict: Parsed GCP error response.
63+
http_response: A requests HTTP response object to associate with the exception.
64+
65+
Returns:
66+
FirebaseError: A ``FirebaseError`` that can be raised to the user code or None.
67+
"""
68+
code = error_dict.get('status')
69+
return handle_googleapiclient_error(error, message, code, http_response)
70+
71+
72+
def handle_googleapiclient_error(error, message=None, code=None, http_response=None):
73+
"""Constructs a ``FirebaseError`` from the given googleapiclient error.
74+
75+
This method is agnostic of the remote service that produced the error, whether it is a GCP
76+
service or otherwise. Therefore, this method does not attempt to parse the error response in
77+
any way.
78+
79+
Args:
80+
error: An error raised by the googleapiclient module while making an HTTP call.
81+
message: A message to be included in the resulting ``FirebaseError`` (optional). If not
82+
specified the string representation of the ``error`` argument is used as the message.
83+
code: A GCP error code that will be used to determine the resulting error type (optional).
84+
If not specified the HTTP status code on the error response is used to determine a
85+
suitable error code.
86+
http_response: A requests HTTP response object to associate with the exception (optional).
87+
If not specified, one will be created from the ``error``.
88+
89+
Returns:
90+
FirebaseError: A ``FirebaseError`` that can be raised to the user code.
91+
"""
92+
if isinstance(error, socket.timeout) or (
93+
isinstance(error, socket.error) and 'timed out' in str(error)):
94+
return exceptions.DeadlineExceededError(
95+
message='Timed out while making an API call: {0}'.format(error),
96+
cause=error)
97+
if isinstance(error, httplib2.ServerNotFoundError):
98+
return exceptions.UnavailableError(
99+
message='Failed to establish a connection: {0}'.format(error),
100+
cause=error)
101+
if not isinstance(error, googleapiclient.errors.HttpError):
102+
return exceptions.UnknownError(
103+
message='Unknown error while making a remote service call: {0}'.format(error),
104+
cause=error)
105+
106+
if not code:
107+
code = _utils._http_status_to_error_code(error.resp.status) # pylint: disable=protected-access
108+
if not message:
109+
message = str(error)
110+
if not http_response:
111+
http_response = _http_response_from_googleapiclient_error(error)
112+
113+
err_type = _utils._error_code_to_exception_type(code) # pylint: disable=protected-access
114+
return err_type(message=message, cause=error, http_response=http_response)
115+
116+
117+
def _http_response_from_googleapiclient_error(error):
118+
"""Creates a requests HTTP Response object from the given googleapiclient error."""
119+
resp = requests.models.Response()
120+
resp.raw = io.BytesIO(error.content)
121+
resp.status_code = error.resp.status
122+
return resp

firebase_admin/_utils.py

Lines changed: 0 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,9 @@
1414

1515
"""Internal utilities common to all modules."""
1616

17-
import io
1817
import json
19-
import socket
2018

2119
import google.auth
22-
import googleapiclient
23-
import httplib2
2420
import requests
2521

2622
import firebase_admin
@@ -206,103 +202,6 @@ def handle_requests_error(error, message=None, code=None):
206202
return err_type(message=message, cause=error, http_response=error.response)
207203

208204

209-
def handle_platform_error_from_googleapiclient(error, handle_func=None):
210-
"""Constructs a ``FirebaseError`` from the given googleapiclient error.
211-
212-
This can be used to handle errors returned by Google Cloud Platform (GCP) APIs.
213-
214-
Args:
215-
error: An error raised by the googleapiclient while making an HTTP call to a GCP API.
216-
handle_func: A function that can be used to handle platform errors in a custom way. When
217-
specified, this function will be called with three arguments. It has the same
218-
signature as ```_handle_func_googleapiclient``, but may return ``None``.
219-
220-
Returns:
221-
FirebaseError: A ``FirebaseError`` that can be raised to the user code.
222-
"""
223-
if not isinstance(error, googleapiclient.errors.HttpError):
224-
return handle_googleapiclient_error(error)
225-
226-
content = error.content.decode()
227-
status_code = error.resp.status
228-
error_dict, message = _parse_platform_error(content, status_code)
229-
http_response = _http_response_from_googleapiclient_error(error)
230-
exc = None
231-
if handle_func:
232-
exc = handle_func(error, message, error_dict, http_response)
233-
234-
return exc if exc else _handle_func_googleapiclient(error, message, error_dict, http_response)
235-
236-
237-
def _handle_func_googleapiclient(error, message, error_dict, http_response):
238-
"""Constructs a ``FirebaseError`` from the given GCP error.
239-
240-
Args:
241-
error: An error raised by the googleapiclient module while making an HTTP call.
242-
message: A message to be included in the resulting ``FirebaseError``.
243-
error_dict: Parsed GCP error response.
244-
http_response: A requests HTTP response object to associate with the exception.
245-
246-
Returns:
247-
FirebaseError: A ``FirebaseError`` that can be raised to the user code or None.
248-
"""
249-
code = error_dict.get('status')
250-
return handle_googleapiclient_error(error, message, code, http_response)
251-
252-
253-
def handle_googleapiclient_error(error, message=None, code=None, http_response=None):
254-
"""Constructs a ``FirebaseError`` from the given googleapiclient error.
255-
256-
This method is agnostic of the remote service that produced the error, whether it is a GCP
257-
service or otherwise. Therefore, this method does not attempt to parse the error response in
258-
any way.
259-
260-
Args:
261-
error: An error raised by the googleapiclient module while making an HTTP call.
262-
message: A message to be included in the resulting ``FirebaseError`` (optional). If not
263-
specified the string representation of the ``error`` argument is used as the message.
264-
code: A GCP error code that will be used to determine the resulting error type (optional).
265-
If not specified the HTTP status code on the error response is used to determine a
266-
suitable error code.
267-
http_response: A requests HTTP response object to associate with the exception (optional).
268-
If not specified, one will be created from the ``error``.
269-
270-
Returns:
271-
FirebaseError: A ``FirebaseError`` that can be raised to the user code.
272-
"""
273-
if isinstance(error, socket.timeout) or (
274-
isinstance(error, socket.error) and 'timed out' in str(error)):
275-
return exceptions.DeadlineExceededError(
276-
message='Timed out while making an API call: {0}'.format(error),
277-
cause=error)
278-
if isinstance(error, httplib2.ServerNotFoundError):
279-
return exceptions.UnavailableError(
280-
message='Failed to establish a connection: {0}'.format(error),
281-
cause=error)
282-
if not isinstance(error, googleapiclient.errors.HttpError):
283-
return exceptions.UnknownError(
284-
message='Unknown error while making a remote service call: {0}'.format(error),
285-
cause=error)
286-
287-
if not code:
288-
code = _http_status_to_error_code(error.resp.status)
289-
if not message:
290-
message = str(error)
291-
if not http_response:
292-
http_response = _http_response_from_googleapiclient_error(error)
293-
294-
err_type = _error_code_to_exception_type(code)
295-
return err_type(message=message, cause=error, http_response=http_response)
296-
297-
298-
def _http_response_from_googleapiclient_error(error):
299-
"""Creates a requests HTTP Response object from the given googleapiclient error."""
300-
resp = requests.models.Response()
301-
resp.raw = io.BytesIO(error.content)
302-
resp.status_code = error.resp.status
303-
return resp
304-
305-
306205
def _http_status_to_error_code(status):
307206
"""Maps an HTTP status to a platform error code."""
308207
return _HTTP_STATUS_TO_ERROR_CODE.get(status, exceptions.UNKNOWN)

firebase_admin/messaging.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from firebase_admin import _http_client
2525
from firebase_admin import _messaging_encoder
2626
from firebase_admin import _messaging_utils
27+
from firebase_admin import _gapic_utils
2728
from firebase_admin import _utils
2829

2930

@@ -466,7 +467,7 @@ def _handle_iid_error(self, error):
466467

467468
def _handle_batch_error(self, error):
468469
"""Handles errors received from the googleapiclient while making batch requests."""
469-
return _utils.handle_platform_error_from_googleapiclient(
470+
return _gapic_utils.handle_platform_error_from_googleapiclient(
470471
error, _MessagingService._build_fcm_error_googleapiclient)
471472

472473
@classmethod

tests/test_exceptions.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from googleapiclient import errors
2525
from firebase_admin import exceptions
2626
from firebase_admin import _utils
27+
from firebase_admin import _gapic_utils
2728

2829

2930
_NOT_FOUND_ERROR_DICT = {
@@ -186,31 +187,31 @@ class TestGoogleApiClient:
186187
socket.error('Read timed out')
187188
])
188189
def test_googleapicleint_timeout_error(self, error):
189-
firebase_error = _utils.handle_googleapiclient_error(error)
190+
firebase_error = _gapic_utils.handle_googleapiclient_error(error)
190191
assert isinstance(firebase_error, exceptions.DeadlineExceededError)
191192
assert str(firebase_error) == 'Timed out while making an API call: {0}'.format(error)
192193
assert firebase_error.cause is error
193194
assert firebase_error.http_response is None
194195

195196
def test_googleapiclient_connection_error(self):
196197
error = httplib2.ServerNotFoundError('Test error')
197-
firebase_error = _utils.handle_googleapiclient_error(error)
198+
firebase_error = _gapic_utils.handle_googleapiclient_error(error)
198199
assert isinstance(firebase_error, exceptions.UnavailableError)
199200
assert str(firebase_error) == 'Failed to establish a connection: Test error'
200201
assert firebase_error.cause is error
201202
assert firebase_error.http_response is None
202203

203204
def test_unknown_transport_error(self):
204205
error = socket.error('Test error')
205-
firebase_error = _utils.handle_googleapiclient_error(error)
206+
firebase_error = _gapic_utils.handle_googleapiclient_error(error)
206207
assert isinstance(firebase_error, exceptions.UnknownError)
207208
assert str(firebase_error) == 'Unknown error while making a remote service call: Test error'
208209
assert firebase_error.cause is error
209210
assert firebase_error.http_response is None
210211

211212
def test_http_response(self):
212213
error = self._create_http_error()
213-
firebase_error = _utils.handle_googleapiclient_error(error)
214+
firebase_error = _gapic_utils.handle_googleapiclient_error(error)
214215
assert isinstance(firebase_error, exceptions.InternalError)
215216
assert str(firebase_error) == str(error)
216217
assert firebase_error.cause is error
@@ -219,7 +220,7 @@ def test_http_response(self):
219220

220221
def test_http_response_with_unknown_status(self):
221222
error = self._create_http_error(status=501)
222-
firebase_error = _utils.handle_googleapiclient_error(error)
223+
firebase_error = _gapic_utils.handle_googleapiclient_error(error)
223224
assert isinstance(firebase_error, exceptions.UnknownError)
224225
assert str(firebase_error) == str(error)
225226
assert firebase_error.cause is error
@@ -228,7 +229,7 @@ def test_http_response_with_unknown_status(self):
228229

229230
def test_http_response_with_message(self):
230231
error = self._create_http_error()
231-
firebase_error = _utils.handle_googleapiclient_error(
232+
firebase_error = _gapic_utils.handle_googleapiclient_error(
232233
error, message='Explicit error message')
233234
assert isinstance(firebase_error, exceptions.InternalError)
234235
assert str(firebase_error) == 'Explicit error message'
@@ -238,7 +239,7 @@ def test_http_response_with_message(self):
238239

239240
def test_http_response_with_code(self):
240241
error = self._create_http_error()
241-
firebase_error = _utils.handle_googleapiclient_error(
242+
firebase_error = _gapic_utils.handle_googleapiclient_error(
242243
error, code=exceptions.UNAVAILABLE)
243244
assert isinstance(firebase_error, exceptions.UnavailableError)
244245
assert str(firebase_error) == str(error)
@@ -248,7 +249,7 @@ def test_http_response_with_code(self):
248249

249250
def test_http_response_with_message_and_code(self):
250251
error = self._create_http_error()
251-
firebase_error = _utils.handle_googleapiclient_error(
252+
firebase_error = _gapic_utils.handle_googleapiclient_error(
252253
error, message='Explicit error message', code=exceptions.UNAVAILABLE)
253254
assert isinstance(firebase_error, exceptions.UnavailableError)
254255
assert str(firebase_error) == 'Explicit error message'
@@ -258,7 +259,7 @@ def test_http_response_with_message_and_code(self):
258259

259260
def test_handle_platform_error(self):
260261
error = self._create_http_error(payload=_NOT_FOUND_PAYLOAD)
261-
firebase_error = _utils.handle_platform_error_from_googleapiclient(error)
262+
firebase_error = _gapic_utils.handle_platform_error_from_googleapiclient(error)
262263
assert isinstance(firebase_error, exceptions.NotFoundError)
263264
assert str(firebase_error) == 'test error'
264265
assert firebase_error.cause is error
@@ -267,15 +268,15 @@ def test_handle_platform_error(self):
267268

268269
def test_handle_platform_error_with_no_response(self):
269270
error = socket.error('Test error')
270-
firebase_error = _utils.handle_platform_error_from_googleapiclient(error)
271+
firebase_error = _gapic_utils.handle_platform_error_from_googleapiclient(error)
271272
assert isinstance(firebase_error, exceptions.UnknownError)
272273
assert str(firebase_error) == 'Unknown error while making a remote service call: Test error'
273274
assert firebase_error.cause is error
274275
assert firebase_error.http_response is None
275276

276277
def test_handle_platform_error_with_no_error_code(self):
277278
error = self._create_http_error(payload='no error code')
278-
firebase_error = _utils.handle_platform_error_from_googleapiclient(error)
279+
firebase_error = _gapic_utils.handle_platform_error_from_googleapiclient(error)
279280
assert isinstance(firebase_error, exceptions.InternalError)
280281
message = 'Unexpected HTTP response with status: 500; body: no error code'
281282
assert str(firebase_error) == message
@@ -291,7 +292,8 @@ def _custom_handler(cause, message, error_dict, http_response):
291292
invocations.append((cause, message, error_dict, http_response))
292293
return exceptions.InvalidArgumentError('Custom message', cause, http_response)
293294

294-
firebase_error = _utils.handle_platform_error_from_googleapiclient(error, _custom_handler)
295+
firebase_error = _gapic_utils.handle_platform_error_from_googleapiclient(
296+
error, _custom_handler)
295297

296298
assert isinstance(firebase_error, exceptions.InvalidArgumentError)
297299
assert str(firebase_error) == 'Custom message'
@@ -313,7 +315,8 @@ def test_handle_platform_error_with_custom_handler_ignore(self):
313315
def _custom_handler(cause, message, error_dict, http_response):
314316
invocations.append((cause, message, error_dict, http_response))
315317

316-
firebase_error = _utils.handle_platform_error_from_googleapiclient(error, _custom_handler)
318+
firebase_error = _gapic_utils.handle_platform_error_from_googleapiclient(
319+
error, _custom_handler)
317320

318321
assert isinstance(firebase_error, exceptions.NotFoundError)
319322
assert str(firebase_error) == 'test error'

0 commit comments

Comments
 (0)
0