【问题标题】:Adding callback function on each retry attempt using requests/urllib3使用 requests/urllib3 在每次重试时添加回调函数
【发布时间】:2018-07-05 10:11:35
【问题描述】:

按照herehere 的建议,我已经使用urllib3.util.retryrequests 会话实施了重试机制。

现在,我正在尝试找出添加回调函数的最佳方法,该回调函数将在每次重试尝试时调用。

为了进一步解释我自己,如果 Retry 对象或请求 get 方法有办法添加回调函数,那就太好了。可能是这样的:

import requests
from requests.packages.urllib3.util.retry import Retry
from requests.adapters import HTTPAdapter

def retry_callback(url):
    print url   

s = requests.Session()
retries = Retry(total=5, status_forcelist=[ 500, 502, 503, 504 ])
s.mount('http://', HTTPAdapter(max_retries=retries))

url = 'http://httpstat.us/500'
s.get(url, callback=retry_callback, callback_params=[url])

我知道打印 url 我可以使用日志记录,但这只是一个更复杂用途的简单示例。

【问题讨论】:

    标签: python callback python-requests urllib3


    【解决方案1】:

    您可以继承 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 属性,但不会让您避免再次引发异常。

    【讨论】:

    • 哇。谢谢你这么详细的回答。因此,如果我还想添加一个 callback_params 参数,我可以像使用 callback 一样进行操作,并在我调用回调函数本身时传递它们,对吧?
    • @A.Sarid:是的,您可以向子类添加任意数量的附加属性,并根据需要使用这些属性。在调用super().new() 方法创建副本之前,请更新new() 方法以将任何此类属性复制到kw 字典。
    • @A.Sarid:就个人而言,我不会添加这样的params 功能。它使您在类中的回调处理复杂化,并且不需要。相反,传入一个可以处理默认参数集的回调函数。
    • 谢谢!是的,默认的参数集很好,但如果我想在创建 CallbackRetry 类时提供这些参数,我最好的选择是什么?
    • @A.Sarid:你必须建立一个参数列表并使用*argsargs = [getattr(self, argname) for argname in self.callback_params]self.callback(*args)将它传递给回调。我强烈建议你不要这样做。改用包装器回调,包装器接受所有参数,然后仅使用url 或类似名称调用您的实际回调:callback = lambda method, url, *args, **kwargs: real_callback(url)(在urlRetry 子类将传入的第二个参数的情况下) .
    猜你喜欢
    • 2013-05-11
    • 1970-01-01
    • 2020-08-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-15
    • 1970-01-01
    相关资源
    最近更新 更多