web-dev-qa-db-fra.com

Puis-je définir max_retries pour requests.request?

Le module de requêtes Python est simple et élégant, mais une chose me perturbe. Il est possible d’obtenir requests.exception.ConnectionError avec un message du type:

Max retries exceeded with url: ...

Cela implique que les requêtes peuvent tenter d'accéder aux données plusieurs fois. Mais il n’ya pas une seule mention de cette possibilité dans la documentation. En regardant le code source, je n'ai trouvé aucun endroit où je puisse modifier la valeur par défaut (probablement 0).

Alors, est-il possible de définir en quelque sorte le nombre maximal de tentatives pour les demandes?

143
teferi

C'est la bibliothèque sous-jacente urllib3 qui effectue la nouvelle tentative. Pour définir un nombre maximal de tentatives différent, utilisez autres adaptateurs de transport :

from requests.adapters import HTTPAdapter

s = requests.Session()
s.mount('http://stackoverflow.com', HTTPAdapter(max_retries=5))

L'argument max_retries prend un entier ou un objet Retry() ; ce dernier vous permet de contrôler avec précision le type de défaillances à réessayer (une valeur entière est transformée en une instance Retry() ne gérant que les défaillances de connexion; les erreurs après l'établissement d'une connexion sont par défaut non gérées, car elles pourraient entraîner des effets secondaires. ).


Ancienne réponse, antérieure à la libération des demandes 1.2.1 :

La bibliothèque requests ne le rend pas vraiment configurable, et n'a pas l'intention de le faire (voir cette demande d'extraction ). Actuellement (demandes 1.1), le nombre de tentatives est défini sur 0. Si vous souhaitez réellement définir une valeur plus élevée, vous devez définir ceci globalement:

import requests

requests.adapters.DEFAULT_RETRIES = 5

Cette constante n'est pas documentée; utilisez-le à vos risques et périls car les versions futures pourraient changer la façon dont cela est géré.

Update : et cela a changé; dans la version 1.2.1 l'option permettant de définir le paramètre max_retries _ sur la classe HTTPAdapter() classe a été ajoutée. Vous devez donc maintenant utiliser d'autres adaptateurs de transport, voir ci-dessus. . L’approche "monkey-patch" ne fonctionne plus, à moins que vous ne corrigiez également les valeurs par défaut de HTTPAdapter.__init__() (très déconseillé).

139
Martijn Pieters

Cela va non seulement changer le max_retries mais également activer une stratégie de backoff qui fait des demandes à tous http: // les adresses dorées pour une période de temps avant de réessayer (cinq fois au total):

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

s = requests.Session()

retries = Retry(total=5,
                backoff_factor=0.1,
                status_forcelist=[ 500, 502, 503, 504 ])

s.mount('http://', HTTPAdapter(max_retries=retries))

s.get('http://httpstat.us/500')

Selon documentation pour Retry : si le facteur backoff est 0.1 , alors sleep () dormira pendant [0.1s, 0.2s, 0.4 s, ...] entre les tentatives. Une nouvelle tentative sera également forcée si le code d'état renvoyé est 500 , 502 , 503 ou 504 .

Diverses autres options pour Retry permettent un contrôle plus granulaire:

  • total - Nombre total de tentatives à autoriser.
  • connect - Nombre d'erreurs liées à la connexion à réessayer.
  • read - Combien de fois réessayer les erreurs de lecture.
  • redirect - Combien de redirections effectuer.
  • method_whitelist - Ensemble de verbes de méthode HTTP en majuscule que nous devrions réessayer.
  • status_forcelist - Un ensemble de codes d'état HTTP sur lesquels nous devrions forcer une nouvelle tentative.
  • backoff_factor - Un facteur de ralentissement à appliquer entre les tentatives.
  • raise_on_redirect - Si, si le nombre de redirections est épuisé, déclencher un MaxRetryError ou renvoyer une réponse avec un code de réponse dans le 3xx plage.
  • raise_on_status - Sens similaire à raise_on_redirect : si nous devons déclencher une exception ou renvoyer une réponse, si le statut tombe à l'état status_forcelist plage et les tentatives ont été épuisées.

NB: raise_on_status == est relativement nouveau et n'a pas encore été transformé en une version de urllib3 ou de requêtes. Le mot clé raise_on_status semble avoir été intégré à la bibliothèque standard au plus dans python version 3.6.

Pour que les demandes réessayent des codes de statut HTTP spécifiques, utilisez status_forcelist . Par exemple, status_forcelist = [503] réessayera le code d'état 503 (service indisponible).

Par défaut, la nouvelle tentative ne se déclenche que pour ces conditions:

  • Impossible d'obtenir une connexion de la piscine.
  • TimeoutError
  • HTTPException soulevé (à partir de http.client dans Python 3 else httplib ). Cela semble être des exceptions HTTP de bas niveau, comme une URL ou un protocole mal formé.
  • SocketError
  • ProtocolError

Notez que ce sont toutes des exceptions qui empêchent la réception d'une réponse HTTP normale. Si any une réponse régulière est générée, aucune nouvelle tentative n'est effectuée. Sans utiliser status_forcelist , même une réponse ayant le statut 500 ne sera pas retentée.

Pour que le comportement soit plus intuitif pour travailler avec une API distante ou un serveur Web, j'utiliserais l'extrait de code ci-dessus, qui force les tentatives sur les statuts 500 , 502 , 503 et 504 , tous n'étant pas rares sur le Web et (éventuellement) récupérable compte tenu d’une période d’arrière-plan suffisante.

ÉDITÉ: Importez la classe Retry directement à partir de urllib3 .

176
datashaman

Attention, la réponse de Martijn Pieters ne convient pas à la version 1.2.1+. Vous ne pouvez pas le configurer globalement sans patcher la bibliothèque.

Vous pouvez le faire à la place:

import requests
from requests.adapters import HTTPAdapter

s = requests.Session()
s.mount('http://www.github.com', HTTPAdapter(max_retries=5))
s.mount('https://www.github.com', HTTPAdapter(max_retries=5))
58
gizmondo

Après avoir un peu lutté avec certaines des réponses ici, j'ai trouvé une bibliothèque appelée backoff qui fonctionnait mieux pour ma situation. Un exemple de base:

import backoff

@backoff.on_exception(
    backoff.expo,
    requests.exceptions.RequestException,
    max_tries=5,
    giveup=lambda e: e.response is not None and e.response.status_code < 500
)
def publish(self, data):
    r = requests.post(url, timeout=10, json=data)
    r.raise_for_status()

Je recommande quand même de donner une chance à la fonctionnalité native de la bibliothèque, mais si vous rencontrez des problèmes ou si vous avez besoin d'un contrôle plus étendu, vous pouvez vous opposer à la suppression.

11
Brad Koch

Une façon plus simple d’obtenir un contrôle plus poussé pourrait consister à regrouper les nouvelles tentatives dans une fonction et à rendre cette fonction récupérable à l’aide d’un décorateur et à la liste blanche des exceptions.

J'ai créé la même chose ici: http://www.praddy.in/retry-decorator-whitelisted-exceptions/

Reproduire le code dans ce lien:

def retry(exceptions, delay=0, times=2):
"""
A decorator for retrying a function call with a specified delay in case of a set of exceptions

Parameter List
-------------
:param exceptions:  A Tuple of all exceptions that need to be caught for retry
                                    e.g. retry(exception_list = (Timeout, Readtimeout))
:param delay: Amount of delay (seconds) needed between successive retries.
:param times: no of times the function should be retried


"""
def outer_wrapper(function):
    @functools.wraps(function)
    def inner_wrapper(*args, **kwargs):
        final_excep = None  
        for counter in xrange(times):
            if counter > 0:
                time.sleep(delay)
            final_excep = None
            try:
                value = function(*args, **kwargs)
                return value
            except (exceptions) as e:
                final_excep = e
                pass #or log it

        if final_excep is not None:
            raise final_excep
    return inner_wrapper

return outer_wrapper

@retry(exceptions=(TimeoutError, ConnectTimeoutError), delay=0, times=3)
def call_api():
4
praddy