Skip to content

Commit 2910b6b

Browse files
ohmayrgcf-owl-bot[bot]arithmetic1728clundin25release-please[bot]
authored
feat: Add support for asynchronous AuthorizedSession api (#1577)
* chore: initial setup for async auth sessions api (#1571) * chore: initial setup for async auth sessions api * fix whitespace * add init file * update file names to aiohttp * update import statement * feat: Implement asynchronous timeout context manager (#1569) * feat: implement async timeout guard * add docstring * clean whitespace * update import file name * add missing return statement * update test cases * update test cases * include underlying timeout exception in trace * avoid the cost of actual time * feat: Implement asynchronous `AuthorizedSession` api response class (#1575) * feat: implement asynchronous response class for AuthorizedSessions API * check if aiohttp is installed and avoid tests dependency * update content to be async * update docstring to be specific to aiohttp * add type checking and avoid leaking underlying API responses * add test case for iterating chunks * add read method to response interface * address PR comments * fix lint issues * feat: Implement asynchronous `AuthorizedSession` api request class (#1579) * feat: implement request class for asynchoronous AuthorizedSession API * add type checking and address TODOs * remove default values from interface methods * aiohttp reponse close method must not be awaited * cleanup * update Request class docstring * feat: Implement asynchronous `AuthorizedSession` class (#1580) * feat: Implement Asynchronous AuthorizedSession class * add comment for implementing locks within refresh * move timeout guard to sessions * add unit tests and code cleanup * implement async exponential backoff iterator * cleanup * add testing for http methods and cleanup * update number of retries to 3 * refactor test cases * fix linter and mypy issues * fix pytest code coverage * fix: avoid leaking api error for closed session * add error handling for response * cleanup default values and add test coverage * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * cleanup: minor code cleanup (#1589) * chore: Add aiohttp requirements test constraint. (#1566) See #1565 for more information. * chore(main): release 2.33.0 (#1560) * chore(main): release 2.33.0 * fix: retry token request on retryable status code (#1563) * fix: retry token request on retryable status code * feat(auth): Update get_client_ssl_credentials to support X.509 workload certs (#1558) * feat(auth): Update get_client_ssl_credentials to support X.509 workload certs * feat(auth): Update has_default_client_cert_source * feat(auth): Fix formatting * feat(auth): Fix test__mtls_helper.py * feat(auth): Fix function name in tests * chore: Refresh system test creds. * feat(auth): Fix style * feat(auth): Fix casing * feat(auth): Fix linter issue * feat(auth): Fix coverage issue --------- Co-authored-by: Carl Lundin <[email protected]> Co-authored-by: Carl Lundin <[email protected]> * chore: Update ECP deps. (#1583) * chore(main): release 2.34.0 (#1574) * cleanup: minor code cleanup * fix lint issues --------- Co-authored-by: Carl Lundin <[email protected]> Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: Andy Zhao <[email protected]> Co-authored-by: Carl Lundin <[email protected]> * update secrets from forked repo --------- Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com> Co-authored-by: arithmetic1728 <[email protected]> Co-authored-by: Carl Lundin <[email protected]> Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: Andy Zhao <[email protected]> Co-authored-by: Carl Lundin <[email protected]>
1 parent 6f75dd5 commit 2910b6b

File tree

9 files changed

+1187
-16
lines changed

9 files changed

+1187
-16
lines changed

google/auth/_exponential_backoff.py

Lines changed: 61 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import asyncio
1516
import random
1617
import time
1718

@@ -38,9 +39,8 @@
3839
"""
3940

4041

41-
class ExponentialBackoff:
42-
"""An exponential backoff iterator. This can be used in a for loop to
43-
perform requests with exponential backoff.
42+
class _BaseExponentialBackoff:
43+
"""An exponential backoff iterator base class.
4444
4545
Args:
4646
total_attempts Optional[int]:
@@ -84,9 +84,40 @@ def __init__(
8484
self._multiplier = multiplier
8585
self._backoff_count = 0
8686

87-
def __iter__(self):
87+
@property
88+
def total_attempts(self):
89+
"""The total amount of backoff attempts that will be made."""
90+
return self._total_attempts
91+
92+
@property
93+
def backoff_count(self):
94+
"""The current amount of backoff attempts that have been made."""
95+
return self._backoff_count
96+
97+
def _reset(self):
8898
self._backoff_count = 0
8999
self._current_wait_in_seconds = self._initial_wait_seconds
100+
101+
def _calculate_jitter(self):
102+
jitter_variance = self._current_wait_in_seconds * self._randomization_factor
103+
jitter = random.uniform(
104+
self._current_wait_in_seconds - jitter_variance,
105+
self._current_wait_in_seconds + jitter_variance,
106+
)
107+
108+
return jitter
109+
110+
111+
class ExponentialBackoff(_BaseExponentialBackoff):
112+
"""An exponential backoff iterator. This can be used in a for loop to
113+
perform requests with exponential backoff.
114+
"""
115+
116+
def __init__(self, *args, **kwargs):
117+
super(ExponentialBackoff, self).__init__(*args, **kwargs)
118+
119+
def __iter__(self):
120+
self._reset()
90121
return self
91122

92123
def __next__(self):
@@ -97,23 +128,37 @@ def __next__(self):
97128
if self._backoff_count <= 1:
98129
return self._backoff_count
99130

100-
jitter_variance = self._current_wait_in_seconds * self._randomization_factor
101-
jitter = random.uniform(
102-
self._current_wait_in_seconds - jitter_variance,
103-
self._current_wait_in_seconds + jitter_variance,
104-
)
131+
jitter = self._calculate_jitter()
105132

106133
time.sleep(jitter)
107134

108135
self._current_wait_in_seconds *= self._multiplier
109136
return self._backoff_count
110137

111-
@property
112-
def total_attempts(self):
113-
"""The total amount of backoff attempts that will be made."""
114-
return self._total_attempts
115138

116-
@property
117-
def backoff_count(self):
118-
"""The current amount of backoff attempts that have been made."""
139+
class AsyncExponentialBackoff(_BaseExponentialBackoff):
140+
"""An async exponential backoff iterator. This can be used in a for loop to
141+
perform async requests with exponential backoff.
142+
"""
143+
144+
def __init__(self, *args, **kwargs):
145+
super(AsyncExponentialBackoff, self).__init__(*args, **kwargs)
146+
147+
def __aiter__(self):
148+
self._reset()
149+
return self
150+
151+
async def __anext__(self):
152+
if self._backoff_count >= self._total_attempts:
153+
raise StopAsyncIteration
154+
self._backoff_count += 1
155+
156+
if self._backoff_count <= 1:
157+
return self._backoff_count
158+
159+
jitter = self._calculate_jitter()
160+
161+
await asyncio.sleep(jitter)
162+
163+
self._current_wait_in_seconds *= self._multiplier
119164
return self._backoff_count

google/auth/aio/transport/__init__.py

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
# Copyright 2024 Google LLC
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+
"""Transport - Asynchronous HTTP client library support.
16+
17+
:mod:`google.auth.aio` is designed to work with various asynchronous client libraries such
18+
as aiohttp. In order to work across these libraries with different
19+
interfaces some abstraction is needed.
20+
21+
This module provides two interfaces that are implemented by transport adapters
22+
to support HTTP libraries. :class:`Request` defines the interface expected by
23+
:mod:`google.auth` to make asynchronous requests. :class:`Response` defines the interface
24+
for the return value of :class:`Request`.
25+
"""
26+
27+
import abc
28+
from typing import AsyncGenerator, Mapping, Optional
29+
30+
import google.auth.transport
31+
32+
33+
_DEFAULT_TIMEOUT_SECONDS = 180
34+
35+
DEFAULT_RETRYABLE_STATUS_CODES = google.auth.transport.DEFAULT_RETRYABLE_STATUS_CODES
36+
"""Sequence[int]: HTTP status codes indicating a request can be retried.
37+
"""
38+
39+
40+
DEFAULT_MAX_RETRY_ATTEMPTS = 3
41+
"""int: How many times to retry a request."""
42+
43+
44+
class Response(metaclass=abc.ABCMeta):
45+
"""Asynchronous HTTP Response Interface."""
46+
47+
@property
48+
@abc.abstractmethod
49+
def status_code(self) -> int:
50+
"""
51+
The HTTP response status code.
52+
53+
Returns:
54+
int: The HTTP response status code.
55+
56+
"""
57+
raise NotImplementedError("status_code must be implemented.")
58+
59+
@property
60+
@abc.abstractmethod
61+
def headers(self) -> Mapping[str, str]:
62+
"""The HTTP response headers.
63+
64+
Returns:
65+
Mapping[str, str]: The HTTP response headers.
66+
"""
67+
raise NotImplementedError("headers must be implemented.")
68+
69+
@abc.abstractmethod
70+
async def content(self, chunk_size: int) -> AsyncGenerator[bytes, None]:
71+
"""The raw response content.
72+
73+
Args:
74+
chunk_size (int): The size of each chunk.
75+
76+
Yields:
77+
AsyncGenerator[bytes, None]: An asynchronous generator yielding
78+
response chunks as bytes.
79+
"""
80+
raise NotImplementedError("content must be implemented.")
81+
82+
@abc.abstractmethod
83+
async def read(self) -> bytes:
84+
"""Read the entire response content as bytes.
85+
86+
Returns:
87+
bytes: The entire response content.
88+
"""
89+
raise NotImplementedError("read must be implemented.")
90+
91+
@abc.abstractmethod
92+
async def close(self):
93+
"""Close the response after it is fully consumed to resource."""
94+
raise NotImplementedError("close must be implemented.")
95+
96+
97+
class Request(metaclass=abc.ABCMeta):
98+
"""Interface for a callable that makes HTTP requests.
99+
100+
Specific transport implementations should provide an implementation of
101+
this that adapts their specific request / response API.
102+
103+
.. automethod:: __call__
104+
"""
105+
106+
@abc.abstractmethod
107+
async def __call__(
108+
self,
109+
url: str,
110+
method: str,
111+
body: Optional[bytes],
112+
headers: Optional[Mapping[str, str]],
113+
timeout: float,
114+
**kwargs
115+
) -> Response:
116+
"""Make an HTTP request.
117+
118+
Args:
119+
url (str): The URI to be requested.
120+
method (str): The HTTP method to use for the request. Defaults
121+
to 'GET'.
122+
body (Optional[bytes]): The payload / body in HTTP request.
123+
headers (Mapping[str, str]): Request headers.
124+
timeout (float): The number of seconds to wait for a
125+
response from the server. If not specified or if None, the
126+
transport-specific default timeout will be used.
127+
kwargs: Additional arguments passed on to the transport's
128+
request method.
129+
130+
Returns:
131+
google.auth.aio.transport.Response: The HTTP response.
132+
133+
Raises:
134+
google.auth.exceptions.TransportError: If any exception occurred.
135+
"""
136+
# pylint: disable=redundant-returns-doc, missing-raises-doc
137+
# (pylint doesn't play well with abstract docstrings.)
138+
raise NotImplementedError("__call__ must be implemented.")
139+
140+
async def close(self) -> None:
141+
"""
142+
Close the underlying session.
143+
"""
144+
raise NotImplementedError("close must be implemented.")

0 commit comments

Comments
 (0)