Skip to content

Commit 4435524

Browse files
HTTPClient refactor (#1170)
1 parent 4464b6a commit 4435524

File tree

3 files changed

+146
-143
lines changed

3 files changed

+146
-143
lines changed

flake8_stripe/flake8_stripe.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ class TypingImportsChecker:
3131
"NotRequired",
3232
"Self",
3333
"Unpack",
34+
"Awaitable",
35+
"Never",
3436
]
3537

3638
allowed_typing_imports = [

stripe/_http_client.py

Lines changed: 107 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import sys
22
import textwrap
3-
import warnings
43
import email
54
import time
65
import random
@@ -13,8 +12,20 @@
1312
from stripe._request_metrics import RequestMetrics
1413
from stripe._error import APIConnectionError
1514

16-
from typing import Any, Dict, List, Optional, Tuple, ClassVar, Union, cast
17-
from typing_extensions import NoReturn, TypedDict
15+
from typing import (
16+
Any,
17+
Dict,
18+
List,
19+
Optional,
20+
Tuple,
21+
ClassVar,
22+
Union,
23+
cast,
24+
)
25+
from typing_extensions import (
26+
NoReturn,
27+
TypedDict,
28+
)
1829

1930
# - Requests is the preferred HTTP library
2031
# - Google App Engine has urlfetch
@@ -85,19 +96,12 @@ def new_default_http_client(*args: Any, **kwargs: Any) -> "HTTPClient":
8596
impl = PycurlClient
8697
else:
8798
impl = Urllib2Client
88-
if sys.version_info < (2, 7, 9):
89-
warnings.warn(
90-
"Warning: the Stripe library is falling back to urllib2 "
91-
"because neither requests nor pycurl are installed. "
92-
"urllib2's SSL implementation doesn't verify server "
93-
"certificates. For improved security, we suggest installing "
94-
"requests."
95-
)
9699

97100
return impl(*args, **kwargs)
98101

99102

100-
class HTTPClient(object):
103+
class HTTPClientBase(object):
104+
101105
name: ClassVar[str]
102106

103107
class _Proxy(TypedDict):
@@ -135,92 +139,6 @@ def __init__(
135139

136140
self._thread_local = threading.local()
137141

138-
# TODO: more specific types here would be helpful
139-
def request_with_retries(
140-
self,
141-
method,
142-
url,
143-
headers,
144-
post_data=None,
145-
*,
146-
_usage: Optional[List[str]] = None,
147-
) -> Tuple[Any, int, Any]:
148-
return self._request_with_retries_internal(
149-
method, url, headers, post_data, is_streaming=False, _usage=_usage
150-
)
151-
152-
def request_stream_with_retries(
153-
self,
154-
method,
155-
url,
156-
headers,
157-
post_data=None,
158-
*,
159-
_usage: Optional[List[str]] = None
160-
) -> Tuple[Any, int, Any]:
161-
return self._request_with_retries_internal(
162-
method, url, headers, post_data, is_streaming=True, _usage=_usage
163-
)
164-
165-
def _request_with_retries_internal(
166-
self, method, url, headers, post_data, is_streaming, *, _usage=None
167-
):
168-
self._add_telemetry_header(headers)
169-
170-
num_retries = 0
171-
172-
while True:
173-
request_start = _now_ms()
174-
175-
try:
176-
if is_streaming:
177-
response = self.request_stream(
178-
method, url, headers, post_data
179-
)
180-
else:
181-
response = self.request(method, url, headers, post_data)
182-
connection_error = None
183-
except APIConnectionError as e:
184-
connection_error = e
185-
response = None
186-
187-
if self._should_retry(response, connection_error, num_retries):
188-
if connection_error:
189-
_util.log_info(
190-
"Encountered a retryable error %s"
191-
% connection_error.user_message
192-
)
193-
num_retries += 1
194-
sleep_time = self._sleep_time_seconds(num_retries, response)
195-
_util.log_info(
196-
(
197-
"Initiating retry %i for request %s %s after "
198-
"sleeping %.2f seconds."
199-
% (num_retries, method, url, sleep_time)
200-
)
201-
)
202-
time.sleep(sleep_time)
203-
else:
204-
if response is not None:
205-
self._record_request_metrics(
206-
response, request_start, _usage
207-
)
208-
209-
return response
210-
else:
211-
assert connection_error is not None
212-
raise connection_error
213-
214-
def request(self, method, url, headers, post_data=None):
215-
raise NotImplementedError(
216-
"HTTPClient subclasses must implement `request`"
217-
)
218-
219-
def request_stream(self, method, url, headers, post_data=None):
220-
raise NotImplementedError(
221-
"HTTPClient subclasses must implement `request_stream`"
222-
)
223-
224142
def _should_retry(self, response, api_connection_error, num_retries):
225143
if num_retries >= self._max_network_retries():
226144
return False
@@ -320,6 +238,96 @@ def _record_request_metrics(self, response, request_start, usage):
320238
request_id, request_duration_ms, usage=usage
321239
)
322240

241+
242+
class HTTPClient(HTTPClientBase):
243+
# TODO: more specific types here would be helpful
244+
def request_with_retries(
245+
self,
246+
method,
247+
url,
248+
headers,
249+
post_data=None,
250+
*,
251+
_usage: Optional[List[str]] = None
252+
) -> Tuple[Any, int, Any]:
253+
return self._request_with_retries_internal(
254+
method, url, headers, post_data, is_streaming=False, _usage=_usage
255+
)
256+
257+
def request_stream_with_retries(
258+
self,
259+
method,
260+
url,
261+
headers,
262+
post_data=None,
263+
*,
264+
_usage: Optional[List[str]] = None
265+
) -> Tuple[Any, int, Any]:
266+
return self._request_with_retries_internal(
267+
method, url, headers, post_data, is_streaming=True, _usage=_usage
268+
)
269+
270+
def _request_with_retries_internal(
271+
self, method, url, headers, post_data, is_streaming, *, _usage=None
272+
):
273+
self._add_telemetry_header(headers)
274+
275+
num_retries = 0
276+
277+
while True:
278+
request_start = _now_ms()
279+
280+
try:
281+
if is_streaming:
282+
response = self.request_stream(
283+
method, url, headers, post_data
284+
)
285+
else:
286+
response = self.request(method, url, headers, post_data)
287+
connection_error = None
288+
except APIConnectionError as e:
289+
connection_error = e
290+
response = None
291+
292+
if self._should_retry(response, connection_error, num_retries):
293+
if connection_error:
294+
_util.log_info(
295+
"Encountered a retryable error %s"
296+
% connection_error.user_message
297+
)
298+
num_retries += 1
299+
sleep_time = self._sleep_time_seconds(num_retries, response)
300+
_util.log_info(
301+
(
302+
"Initiating retry %i for request %s %s after "
303+
"sleeping %.2f seconds."
304+
% (num_retries, method, url, sleep_time)
305+
)
306+
)
307+
time.sleep(sleep_time)
308+
else:
309+
if response is not None:
310+
self._record_request_metrics(
311+
response, request_start, usage=_usage
312+
)
313+
314+
return response
315+
else:
316+
assert connection_error is not None
317+
raise connection_error
318+
319+
def request(self, method, url, headers, post_data=None, *, _usage=None):
320+
raise NotImplementedError(
321+
"HTTPClient subclasses must implement `request`"
322+
)
323+
324+
def request_stream(
325+
self, method, url, headers, post_data=None, *, _usage=None
326+
):
327+
raise NotImplementedError(
328+
"HTTPClient subclasses must implement `request_stream`"
329+
)
330+
323331
def close(self):
324332
raise NotImplementedError(
325333
"HTTPClient subclasses must implement `close`"
@@ -335,6 +343,7 @@ def __init__(
335343
session: Optional["Session"] = None,
336344
verify_ssl_certs: bool = True,
337345
proxy: Optional[Union[str, HTTPClient._Proxy]] = None,
346+
**kwargs
338347
):
339348
super(RequestsClient, self).__init__(
340349
verify_ssl_certs=verify_ssl_certs, proxy=proxy

0 commit comments

Comments
 (0)