Path: blob/master/venv/Lib/site-packages/urllib3/util/retry.py
811 views
from __future__ import absolute_import1import time2import logging3from collections import namedtuple4from itertools import takewhile5import email6import re78from ..exceptions import (9ConnectTimeoutError,10MaxRetryError,11ProtocolError,12ReadTimeoutError,13ResponseError,14InvalidHeader,15ProxyError,16)17from ..packages import six181920log = logging.getLogger(__name__)212223# Data structure for representing the metadata of requests that result in a retry.24RequestHistory = namedtuple(25"RequestHistory", ["method", "url", "error", "status", "redirect_location"]26)272829class Retry(object):30""" Retry configuration.3132Each retry attempt will create a new Retry object with updated values, so33they can be safely reused.3435Retries can be defined as a default for a pool::3637retries = Retry(connect=5, read=2, redirect=5)38http = PoolManager(retries=retries)39response = http.request('GET', 'http://example.com/')4041Or per-request (which overrides the default for the pool)::4243response = http.request('GET', 'http://example.com/', retries=Retry(10))4445Retries can be disabled by passing ``False``::4647response = http.request('GET', 'http://example.com/', retries=False)4849Errors will be wrapped in :class:`~urllib3.exceptions.MaxRetryError` unless50retries are disabled, in which case the causing exception will be raised.5152:param int total:53Total number of retries to allow. Takes precedence over other counts.5455Set to ``None`` to remove this constraint and fall back on other56counts. It's a good idea to set this to some sensibly-high value to57account for unexpected edge cases and avoid infinite retry loops.5859Set to ``0`` to fail on the first retry.6061Set to ``False`` to disable and imply ``raise_on_redirect=False``.6263:param int connect:64How many connection-related errors to retry on.6566These are errors raised before the request is sent to the remote server,67which we assume has not triggered the server to process the request.6869Set to ``0`` to fail on the first retry of this type.7071:param int read:72How many times to retry on read errors.7374These errors are raised after the request was sent to the server, so the75request may have side-effects.7677Set to ``0`` to fail on the first retry of this type.7879:param int redirect:80How many redirects to perform. Limit this to avoid infinite redirect81loops.8283A redirect is a HTTP response with a status code 301, 302, 303, 307 or84308.8586Set to ``0`` to fail on the first retry of this type.8788Set to ``False`` to disable and imply ``raise_on_redirect=False``.8990:param int status:91How many times to retry on bad status codes.9293These are retries made on responses, where status code matches94``status_forcelist``.9596Set to ``0`` to fail on the first retry of this type.9798:param iterable method_whitelist:99Set of uppercased HTTP method verbs that we should retry on.100101By default, we only retry on methods which are considered to be102idempotent (multiple requests with the same parameters end with the103same state). See :attr:`Retry.DEFAULT_METHOD_WHITELIST`.104105Set to a ``False`` value to retry on any verb.106107:param iterable status_forcelist:108A set of integer HTTP status codes that we should force a retry on.109A retry is initiated if the request method is in ``method_whitelist``110and the response status code is in ``status_forcelist``.111112By default, this is disabled with ``None``.113114:param float backoff_factor:115A backoff factor to apply between attempts after the second try116(most errors are resolved immediately by a second try without a117delay). urllib3 will sleep for::118119{backoff factor} * (2 ** ({number of total retries} - 1))120121seconds. If the backoff_factor is 0.1, then :func:`.sleep` will sleep122for [0.0s, 0.2s, 0.4s, ...] between retries. It will never be longer123than :attr:`Retry.BACKOFF_MAX`.124125By default, backoff is disabled (set to 0).126127:param bool raise_on_redirect: Whether, if the number of redirects is128exhausted, to raise a MaxRetryError, or to return a response with a129response code in the 3xx range.130131:param bool raise_on_status: Similar meaning to ``raise_on_redirect``:132whether we should raise an exception, or return a response,133if status falls in ``status_forcelist`` range and retries have134been exhausted.135136:param tuple history: The history of the request encountered during137each call to :meth:`~Retry.increment`. The list is in the order138the requests occurred. Each list item is of class :class:`RequestHistory`.139140:param bool respect_retry_after_header:141Whether to respect Retry-After header on status codes defined as142:attr:`Retry.RETRY_AFTER_STATUS_CODES` or not.143144:param iterable remove_headers_on_redirect:145Sequence of headers to remove from the request when a response146indicating a redirect is returned before firing off the redirected147request.148"""149150DEFAULT_METHOD_WHITELIST = frozenset(151["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE"]152)153154RETRY_AFTER_STATUS_CODES = frozenset([413, 429, 503])155156DEFAULT_REDIRECT_HEADERS_BLACKLIST = frozenset(["Authorization"])157158#: Maximum backoff time.159BACKOFF_MAX = 120160161def __init__(162self,163total=10,164connect=None,165read=None,166redirect=None,167status=None,168method_whitelist=DEFAULT_METHOD_WHITELIST,169status_forcelist=None,170backoff_factor=0,171raise_on_redirect=True,172raise_on_status=True,173history=None,174respect_retry_after_header=True,175remove_headers_on_redirect=DEFAULT_REDIRECT_HEADERS_BLACKLIST,176):177178self.total = total179self.connect = connect180self.read = read181self.status = status182183if redirect is False or total is False:184redirect = 0185raise_on_redirect = False186187self.redirect = redirect188self.status_forcelist = status_forcelist or set()189self.method_whitelist = method_whitelist190self.backoff_factor = backoff_factor191self.raise_on_redirect = raise_on_redirect192self.raise_on_status = raise_on_status193self.history = history or tuple()194self.respect_retry_after_header = respect_retry_after_header195self.remove_headers_on_redirect = frozenset(196[h.lower() for h in remove_headers_on_redirect]197)198199def new(self, **kw):200params = dict(201total=self.total,202connect=self.connect,203read=self.read,204redirect=self.redirect,205status=self.status,206method_whitelist=self.method_whitelist,207status_forcelist=self.status_forcelist,208backoff_factor=self.backoff_factor,209raise_on_redirect=self.raise_on_redirect,210raise_on_status=self.raise_on_status,211history=self.history,212remove_headers_on_redirect=self.remove_headers_on_redirect,213respect_retry_after_header=self.respect_retry_after_header,214)215params.update(kw)216return type(self)(**params)217218@classmethod219def from_int(cls, retries, redirect=True, default=None):220""" Backwards-compatibility for the old retries format."""221if retries is None:222retries = default if default is not None else cls.DEFAULT223224if isinstance(retries, Retry):225return retries226227redirect = bool(redirect) and None228new_retries = cls(retries, redirect=redirect)229log.debug("Converted retries value: %r -> %r", retries, new_retries)230return new_retries231232def get_backoff_time(self):233""" Formula for computing the current backoff234235:rtype: float236"""237# We want to consider only the last consecutive errors sequence (Ignore redirects).238consecutive_errors_len = len(239list(240takewhile(lambda x: x.redirect_location is None, reversed(self.history))241)242)243if consecutive_errors_len <= 1:244return 0245246backoff_value = self.backoff_factor * (2 ** (consecutive_errors_len - 1))247return min(self.BACKOFF_MAX, backoff_value)248249def parse_retry_after(self, retry_after):250# Whitespace: https://tools.ietf.org/html/rfc7230#section-3.2.4251if re.match(r"^\s*[0-9]+\s*$", retry_after):252seconds = int(retry_after)253else:254retry_date_tuple = email.utils.parsedate(retry_after)255if retry_date_tuple is None:256raise InvalidHeader("Invalid Retry-After header: %s" % retry_after)257retry_date = time.mktime(retry_date_tuple)258seconds = retry_date - time.time()259260if seconds < 0:261seconds = 0262263return seconds264265def get_retry_after(self, response):266""" Get the value of Retry-After in seconds. """267268retry_after = response.getheader("Retry-After")269270if retry_after is None:271return None272273return self.parse_retry_after(retry_after)274275def sleep_for_retry(self, response=None):276retry_after = self.get_retry_after(response)277if retry_after:278time.sleep(retry_after)279return True280281return False282283def _sleep_backoff(self):284backoff = self.get_backoff_time()285if backoff <= 0:286return287time.sleep(backoff)288289def sleep(self, response=None):290""" Sleep between retry attempts.291292This method will respect a server's ``Retry-After`` response header293and sleep the duration of the time requested. If that is not present, it294will use an exponential backoff. By default, the backoff factor is 0 and295this method will return immediately.296"""297298if self.respect_retry_after_header and response:299slept = self.sleep_for_retry(response)300if slept:301return302303self._sleep_backoff()304305def _is_connection_error(self, err):306""" Errors when we're fairly sure that the server did not receive the307request, so it should be safe to retry.308"""309if isinstance(err, ProxyError):310err = err.original_error311return isinstance(err, ConnectTimeoutError)312313def _is_read_error(self, err):314""" Errors that occur after the request has been started, so we should315assume that the server began processing it.316"""317return isinstance(err, (ReadTimeoutError, ProtocolError))318319def _is_method_retryable(self, method):320""" Checks if a given HTTP method should be retried upon, depending if321it is included on the method whitelist.322"""323if self.method_whitelist and method.upper() not in self.method_whitelist:324return False325326return True327328def is_retry(self, method, status_code, has_retry_after=False):329""" Is this method/status code retryable? (Based on whitelists and control330variables such as the number of total retries to allow, whether to331respect the Retry-After header, whether this header is present, and332whether the returned status code is on the list of status codes to333be retried upon on the presence of the aforementioned header)334"""335if not self._is_method_retryable(method):336return False337338if self.status_forcelist and status_code in self.status_forcelist:339return True340341return (342self.total343and self.respect_retry_after_header344and has_retry_after345and (status_code in self.RETRY_AFTER_STATUS_CODES)346)347348def is_exhausted(self):349""" Are we out of retries? """350retry_counts = (self.total, self.connect, self.read, self.redirect, self.status)351retry_counts = list(filter(None, retry_counts))352if not retry_counts:353return False354355return min(retry_counts) < 0356357def increment(358self,359method=None,360url=None,361response=None,362error=None,363_pool=None,364_stacktrace=None,365):366""" Return a new Retry object with incremented retry counters.367368:param response: A response object, or None, if the server did not369return a response.370:type response: :class:`~urllib3.response.HTTPResponse`371:param Exception error: An error encountered during the request, or372None if the response was received successfully.373374:return: A new ``Retry`` object.375"""376if self.total is False and error:377# Disabled, indicate to re-raise the error.378raise six.reraise(type(error), error, _stacktrace)379380total = self.total381if total is not None:382total -= 1383384connect = self.connect385read = self.read386redirect = self.redirect387status_count = self.status388cause = "unknown"389status = None390redirect_location = None391392if error and self._is_connection_error(error):393# Connect retry?394if connect is False:395raise six.reraise(type(error), error, _stacktrace)396elif connect is not None:397connect -= 1398399elif error and self._is_read_error(error):400# Read retry?401if read is False or not self._is_method_retryable(method):402raise six.reraise(type(error), error, _stacktrace)403elif read is not None:404read -= 1405406elif response and response.get_redirect_location():407# Redirect retry?408if redirect is not None:409redirect -= 1410cause = "too many redirects"411redirect_location = response.get_redirect_location()412status = response.status413414else:415# Incrementing because of a server error like a 500 in416# status_forcelist and a the given method is in the whitelist417cause = ResponseError.GENERIC_ERROR418if response and response.status:419if status_count is not None:420status_count -= 1421cause = ResponseError.SPECIFIC_ERROR.format(status_code=response.status)422status = response.status423424history = self.history + (425RequestHistory(method, url, error, status, redirect_location),426)427428new_retry = self.new(429total=total,430connect=connect,431read=read,432redirect=redirect,433status=status_count,434history=history,435)436437if new_retry.is_exhausted():438raise MaxRetryError(_pool, url, error or ResponseError(cause))439440log.debug("Incremented Retry for (url='%s'): %r", url, new_retry)441442return new_retry443444def __repr__(self):445return (446"{cls.__name__}(total={self.total}, connect={self.connect}, "447"read={self.read}, redirect={self.redirect}, status={self.status})"448).format(cls=type(self), self=self)449450451# For backwards compatibility (equivalent to pre-v1.9):452Retry.DEFAULT = Retry(3)453454455