您可以继承 Retry 类以添加该功能。
这是给定连接尝试与Retry 实例的完整交互流程:
-
每当引发异常或返回 30x 重定向响应或
Retry.is_retry() 方法返回时,都会使用当前方法、url、响应对象(如果有)和异常(如果引发)调用 Retry.increment()真的。
-
.increment() 将重新引发错误(如果有),并且对象被配置为不重试该特定类别的错误。
-
.increment() 调用 Retry.new() 来创建一个更新的实例,更新所有相关的计数器,并使用新的 RequestHistory() instance(命名元组)修改 history 属性。
-
如果在
Retry.new() 的返回值上调用的Retry.is_exhausted() 为真,.increment() 将引发MaxRetryError 异常。 is_exhausted() 在其跟踪的任何计数器降至 0 以下时返回 true(设置为 None 的计数器将被忽略)。
-
.increment() 返回新的 Retry 实例。
-
Retry.increment() 的返回值替换了旧的 Retry 实例跟踪。如果有重定向,则调用Retry.sleep_for_retry()(如果有Retry-After 标头则休眠),否则调用Retry.sleep()(它调用self.sleep_for_retry() 以兑现Retry-After 标头,否则只是休眠如果有后退政策)。然后使用新的Retry 实例进行递归连接调用。
这给了你 3 个很好的回调点;在.increment() 的开头,创建新的Retry 实例时,以及在super().increment() 周围的上下文管理器中让回调否决异常或在退出时更新返回的重试策略。
这就是在.increment() 开头添加一个钩子的样子:
import logging
logger = getLogger(__name__)
class CallbackRetry(Retry):
def __init__(self, *args, **kwargs):
self._callback = kwargs.pop('callback', None)
super(CallbackRetry, self).__init__(*args, **kwargs)
def new(self, **kw):
# pass along the subclass additional information when creating
# a new instance.
kw['callback'] = self._callback
return super(CallbackRetry, self).new(**kw)
def increment(self, method, url, *args, **kwargs):
if self._callback:
try:
self._callback(url)
except Exception:
logger.exception('Callback raised an exception, ignoring')
return super(CallbackRetry, self).increment(method, url, *args, **kwargs)
注意,url 参数实际上只是 URL 路径,请求的网络地址部分被省略(你必须从 _pool 参数中提取它,它有.scheme、.host 和 .port 属性)。
演示:
>>> def retry_callback(url):
... print('Callback invoked with', url)
...
>>> s = requests.Session()
>>> retries = CallbackRetry(total=5, status_forcelist=[500, 502, 503, 504], callback=retry_callback)
>>> s.mount('http://', HTTPAdapter(max_retries=retries))
>>> s.get('http://httpstat.us/500')
Callback invoked with /500
Callback invoked with /500
Callback invoked with /500
Callback invoked with /500
Callback invoked with /500
Callback invoked with /500
Traceback (most recent call last):
File "/.../lib/python3.6/site-packages/requests/adapters.py", line 440, in send
timeout=timeout
File "/.../lib/python3.6/site-packages/urllib3/connectionpool.py", line 732, in urlopen
body_pos=body_pos, **response_kw)
File "/.../lib/python3.6/site-packages/urllib3/connectionpool.py", line 732, in urlopen
body_pos=body_pos, **response_kw)
File "/.../lib/python3.6/site-packages/urllib3/connectionpool.py", line 732, in urlopen
body_pos=body_pos, **response_kw)
[Previous line repeated 1 more times]
File "/.../lib/python3.6/site-packages/urllib3/connectionpool.py", line 712, in urlopen
retries = retries.increment(method, url, response=response, _pool=self)
File "<stdin>", line 8, in increment
File "/.../lib/python3.6/site-packages/urllib3/util/retry.py", line 388, in increment
raise MaxRetryError(_pool, url, error or ResponseError(cause))
urllib3.exceptions.MaxRetryError: HTTPConnectionPool(host='httpstat.us', port=80): Max retries exceeded with url: /500 (Caused by ResponseError('too many 500 error responses',))
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/.../lib/python3.6/site-packages/requests/sessions.py", line 521, in get
return self.request('GET', url, **kwargs)
File "/.../lib/python3.6/site-packages/requests/sessions.py", line 508, in request
resp = self.send(prep, **send_kwargs)
File "/.../lib/python3.6/site-packages/requests/sessions.py", line 618, in send
r = adapter.send(request, **kwargs)
File "/.../lib/python3.6/site-packages/requests/adapters.py", line 499, in send
raise RetryError(e, request=request)
requests.exceptions.RetryError: HTTPConnectionPool(host='httpstat.us', port=80): Max retries exceeded with url: /500 (Caused by ResponseError('too many 500 error responses',))
在.new() 方法中添加一个钩子可以让您调整策略以进行下一次尝试,以及让您内省.history 属性,但不会让您避免再次引发异常。