2

I am new to python and I am writing code that uses OAuth to authenticate and when the token expires after 60 minutes, it needs to get a new one.

try:
    if uploadedContent is not None:
       thing.action(uploadedContent)

except LoginOrScopeRequired:
    print("Logging in...")
    set_access_credentials({"identity", "submit"}, get_access_token())

I currently have this code to handle getting a new token if it expires, but the problem is that if there was an exception it skips over the action it needed to do. I understand that I could take what was inside the try block and append it to end of the except block, but it there a more elegant way to do this?

Some of my research led to the with statement, but I didn't understand with well enough to know if it would solve my problem. So is appending it to the end the best solution or is there something better?

inspectorG4dget
  • 110,290
  • 27
  • 149
  • 241
m0meni
  • 16,006
  • 16
  • 82
  • 141

2 Answers2

4

It is considered Idiomatic Python to do this with a function decorator/wrapper:

Example:

#!/usr/bin/env python


from functools import wraps


def retry_on_error(ntries=1):
    """
    A decorator that returns a wrapper function that calls
    the wrapped function repeatedly up to ntries if an
    exception is encountered.
    """

    def decorator(f):  # wrapping the original function
        @wraps(f)  # make the wrapped function look like the original
        def wrapper(*args, **kwargs):  # our function wrapped that calls the original
            for i in xrange(ntries):
                try:
                    return f(*args, **kwargs)
                except Exception as e:
                    print("Error executing {0:s} retrying {1:d}/{2:d}".format(f.__name__, i, ntries))
                    print("Error was {0:s}".format(e))

        return wrapper

    return decorator  # returning the new wrapped function


@retry_on_error()
def f():
    n = getattr(f, "n", 0)
    try:
        if not n:
            raise ValueError("n < 0")
    finally:
        setattr(f, "n", n + 1)

Output:

$ python -i foo.py
>>> f()
Error executing f retrying 0/1
Error was n < 0
>>> f()
>>> 

See: Python Decorators for other examples.

Update: There is also a nice library that implements this functionality with a few more features: retrying as well as several other related/similar questions How to retry after exception in python? and Pythonic way of retry running a function

Update #2: I've commented the decorator a bit so hopefully you can understand what's going on in each step of the process. Admittedly decorators aren't that easy to understand at first so I recommend you read Understanding Python Decorators in 12 easy step

Community
  • 1
  • 1
James Mills
  • 18,669
  • 3
  • 49
  • 62
  • I'm looking for an excuse to use decorators, but what advantage does this add over the while loop in another answer? It seems much more verbose and complex. Not deprecating it by the way. I'm genuinely curious. – m0meni May 22 '15 at 02:14
  • I'm all for reuse and flexibility, and now I have a reason to learn decorators. Thank you very much! – m0meni May 22 '15 at 02:18
3

Some languages like ruby let you put a retry statement in the exception catch block that makes this incredibly simple. Unfortunately in Python you will need to wrap this in a while statement:

success = False
while not success
    try:
        if uploadedContent is not None:
           thing.action(uploadedContent)
        success = True
    except LoginOrScopeRequired:
        print("Logging in...")
        set_access_credentials({"identity", "submit"}, get_access_token())

Note that the line success = True will only be reached if no exception occurs.

EDIT

You will also want to keep track of the number of attempts in a counter to ensure this doesn't loop forever and exits after 3 retries for example.

Martin Konecny
  • 57,827
  • 19
  • 139
  • 159