Добавление функции обратного вызова при каждой попытке повтора с использованием запросов / urllib3 - PullRequest
0 голосов
/ 05 июля 2018

Я реализовал механизм повтора для requests сеанса, используя urllib3.util.retry, как предложено для здесь и здесь .

Теперь я пытаюсь выяснить, как лучше всего добавить функцию обратного вызова, которая будет вызываться при каждой попытке повторения.

Чтобы пояснить еще больше, если бы у объекта 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 Ответ

0 голосов
/ 10 июля 2018

Вы можете создать подкласс класса Retry, чтобы добавить эту функцию.

Это полный поток взаимодействия с экземпляром Retry для данной попытки подключения:

  • Retry.increment() вызывается с текущим методом, URL-адресом, объектом ответа (если он есть) и исключением (если оно было вызвано) при каждом возникновении исключения или при возврате 30-кратного ответа о перенаправлении или Retry.is_retry() метод возвращает истину.
    • .increment() повторно выдаст ошибку (если она была), и объект был настроен не повторять этот определенный класс ошибок.
    • .increment() вызывает Retry.new() для создания обновленного экземпляра с обновлением всех соответствующих счетчиков и атрибутом history с новым экземпляром RequestHistory() (именованный кортеж).
    • .increment() вызовет исключение MaxRetryError, если Retry.is_exhausted() вызвано на возвращаемое значение Retry.new() true. is_exhausted() возвращает значение true, если любой из отслеживаемых счетчиков упал ниже 0 (счетчики, установленные на 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, он имеет * Атрибуты 1061 *, .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, но не позволит избежать повторного вызова исключения.

...