A decorator to handle rate limiting from the Twitter API. If
    a rate limit error is encountered we will sleep until we can
    issue the API call again.

def rate_limit(f):
    """
    A decorator to handle rate limiting from the Twitter API. If
    a rate limit error is encountered we will sleep until we can
    issue the API call again.
    """
    def new_f(*args, **kwargs):
        errors = 0
        while True:
            resp = f(*args, **kwargs)
            if resp.status_code == 200:
                errors = 0
                return resp
            elif resp.status_code == 401:
                # Hack to retain the original exception, but augment it with
                # additional context for the user to interpret it. In a Python
                # 3 only future we can raise a new exception of the same type
                # with a new message from the old error.
                try:
                    resp.raise_for_status()
                except requests.HTTPError as e:
                    message = "\nThis is a protected or locked account, or" +\
                              " the credentials provided are no longer valid."
                    e.args = (e.args[0] + message,) + e.args[1:]
                    log.warning("401 Authentication required for %s", resp.url)
                    raise
            elif resp.status_code == 429:
                reset = int(resp.headers['x-rate-limit-reset'])
                now = time.time()
                seconds = reset - now + 10
                if seconds < 1:
                    seconds = 10
                log.warning("rate limit exceeded: sleeping %s secs", seconds)
                time.sleep(seconds)
            elif resp.status_code >= 500:
                errors += 1
                if errors > 30:
                    log.warning("too many errors from Twitter, giving up")
                    resp.raise_for_status()
                seconds = 60 * errors
                log.warning("%s from Twitter API, sleeping %s",
                             resp.status_code, seconds)
                time.sleep(seconds)
            else:
                resp.raise_for_status()
    return new_f