Вы можете создать подкласс класса Retry
, чтобы добавить эту функциональность.
Это полный процесс взаимодействия с экземпляром Retry
для данной попытки подключения:
Retry.increment()
is called with the current method, url, response object (if there is one), and exception (if one was raised) whenever an exception is raised, or a 30x redirection response was returned, or the Retry.is_retry()
method returns true.
.increment()
will re-raise the error (if there was one) and the object was configured not to retry that specific class of errors.
.increment()
вызывает Retry.new()
для создания обновленного экземпляра с обновленными всеми релевантными счетчиками и измененным атрибутом history
новым RequestHistory()
instance (именованный кортеж).
.increment()
вызовет исключение MaxRetryError
, если Retry.is_exhausted()
, вызванное возвращаемым значением Retry.new()
, истинно. 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
, у него есть атрибуты .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
, но не позволит вам избежать повторного возбуждения исключения.
person
Martijn Pieters
schedule
09.07.2018