代码之家  ›  专栏  ›  技术社区  ›  A. Sarid arch

使用requests/urllib3在每次重试时添加回调函数

  •  6
  • A. Sarid arch  · 技术社区  · 6 年前

    我已经实现了一个重试机制 requests 会话使用 urllib3.util.retry 两者都建议 here here 是的。

    现在,我试图找出什么是添加回调函数的最佳方法,每次重试时都将调用该函数。

    更进一步的解释我自己,如果 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编码,请原谅,但我希望它足够清晰。

    谢谢。

    1 回复  |  直到 6 年前
        1
  •  6
  •   Martijn Pieters    6 年前

    你可以将 Retry 类来添加该功能。

    这是与 重试 给定连接尝试的实例:

    • Retry.increment() 无论何时引发异常或返回30x重定向响应,或 Retry.is_retry() 方法返回true。
      • .increment() 将重新引发错误(如果有),并且对象被配置为不重试该特定类型的错误。
      • .增量() 电话 Retry.new() 若要创建更新的实例,并更新所有相关计数器, history 属性修改为新的 RequestHistory() instance (命名元组)。
      • .增量() 会引起 MaxRetryError 例外情况 Retry.is_exhausted() 调用的返回值为 重试。新建() 是真的。 is_exhausted() 当它跟踪的任何计数器低于0时返回true(计数器设置为 None 被忽略)。
      • .增量() 返回新的 重试 实例。
    • 的返回值 重试。增量() 取代旧的 重试 实例已跟踪。如果有重定向,那么 Retry.sleep_for_retry() 被称为(如果有 Retry-After 页眉),否则 Retry.sleep() 被称为 self.sleep_for_retry() 尊敬 稍后重试 头,否则,如果有退避策略,则只会休眠)。然后使用新的 重试 实例。

    这给了你3个好的回调点;在 .增量() ,当创建新的 重试 实例,并在周围的上下文管理器中 super().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 属性,但不会让您避免重新引发异常。