11import sys
22import textwrap
3- import warnings
43import email
54import time
65import random
1312from stripe ._request_metrics import RequestMetrics
1413from 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