How to retry urllib2.request when fails?
Asked Answered
F

4

40

When urllib2.request reaches timeout, a urllib2.URLError exception is raised. What is the pythonic way to retry establishing a connection?

Flotilla answered 25/2, 2012 at 17:46 Comment(5)
This question should answer yours: #2713024Danette
I didn't ask how to catch the expection. I wanted to know if there is a pythonic way to retry establish the connection.Flotilla
Sorry, I assumed the problem was in detecting the timeout had been reached, not in re-establising the connection. Could you not call urlopen() in the exception block?Danette
That is possible, but doesn't seem very pythonic. If I'd like to retry three times, I'll have to nest the try-except blocks, and it looks ugly.Flotilla
This might be of some help then: #568122Danette
R
72

I would use a retry decorator. There are other ones out there, but this one works pretty well. Here's how you can use it:

@retry(urllib2.URLError, tries=4, delay=3, backoff=2)
def urlopen_with_retry():
    return urllib2.urlopen("http://example.com")

This will retry the function if URLError is raised. Check the link above for documentation on the parameters, but basically it will retry a maximum of 4 times, with an exponential backoff delay doubling each time, e.g. 3 seconds, 6 seconds, 12 seconds.

Rhadamanthus answered 25/2, 2012 at 18:27 Comment(6)
This is a really cool snippet. Do you know an alternative, but as a context manager ?Ptolemy
Hmm, I think you could probably rewrite it as a context manager pretty easily, but I don't have one offhand.Rhadamanthus
It's no easy to do, since there is not easy way to capture the block inside the with statement. You need some deep introspection.Ptolemy
No, I don't think that's true. Exceptions are re-raised inside a context manager after the yield.Rhadamanthus
The problem is not the exception, but the code raising the exception. How do you retry a code if you can't run it ? There is no notion of anonymous bloc in Python. It's doable, but not intuitive.Ptolemy
Ah, I see. It would be even harder if the calling code was not a with block. I have no idea how to do that.Rhadamanthus
N
9

There are a few libraries out there that specialize in this.

One is backoff, which is designed with a particularly functional sensibility. Decorators are passed arbitrary callables returning generators which yield successive delay values. A simple exponential backoff with a maximum retry time of 32 seconds could be defined as:

@backoff.on_exception(backoff.expo,
                      urllib2.URLError,
                      max_value=32)
def url_open(url):
    return urllib2.urlopen("http://example.com")

Another is retrying which has very similar functionality but an API where retry parameters are specified by way of predefined keyword args.

Neveda answered 31/1, 2015 at 4:55 Comment(0)
L
5

To retry on timeout you could catch the exception as @Karl Barker suggested in the comment:

assert ntries >= 1
for i in range(1, ntries+1):
    try:
        page = urlopen(request, timeout=timeout)
        break # success
    except URLError as err:
        if i == ntries or not isinstance(err.reason, socket.timeout):
           raise # propagate last timeout or non-timeout errors
# use page here
Liaison answered 25/2, 2012 at 18:36 Comment(2)
Unfortunately, err is not defined in the else: block, so if your code reaches the else block you'll get UnboundLocalError: local variable 'err' referenced before assignmentBearnard
@elboulangero: I've updated the answer to raise inside the except clause.Liaison
C
5

For Python3, you can use urllib3.Retry:

from urllib3 import Retry, PoolManager


retries = Retry(connect=5, read=2, redirect=5, backoff_factor=0.1)
http = PoolManager(retries=retries)
response = http.request('GET', 'http://example.com/')

If the backoff_factor is 0.1, then :func:.sleep will sleep for [0.0s, 0.2s, 0.4s, ...] between retries. It will never be longer than :attr:Retry.BACKOFF_MAX. urllib3 will sleep for::

        {backoff factor} * (2 ** ({number of total retries} - 1))
Ciprian answered 27/7, 2020 at 9:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.