import time import logging import gevent from requests import HTTPError from .requests import InstrumentedSession # Wraps all requests in some metric collection and default timeouts requests = InstrumentedSession() requests.timeout = 30 class GoogleAPIClient(object): """Manages access to google apis and maintains an active access token. Make calls using client.request(), which is a wrapper for requests.request(). """ ACCESS_TOKEN_ERROR_RETRY_INTERVAL = 10 # Refresh token 10min before it expires (it normally lasts an hour) ACCESS_TOKEN_REFRESH_TIME_BEFORE_EXPIRY = 600 def __init__(self, client_id, client_secret, refresh_token): self.client_id = client_id self.client_secret = client_secret self.refresh_token = refresh_token self._first_get_access_token = gevent.spawn(self.get_access_token) @property def access_token(self): """Blocks if access token unavailable yet""" self._first_get_access_token.join() return self._access_token def get_access_token(self): """Authenticates against google's API and retrieves a token we will use in subsequent requests. This function gets called automatically when needed, there should be no need to call it yourself.""" while True: try: start_time = time.time() resp = requests.post('https://www.googleapis.com/oauth2/v4/token', data={ 'client_id': self.client_id, 'client_secret': self.client_secret, 'refresh_token': self.refresh_token, 'grant_type': 'refresh_token', }, metric_name='get_access_token') resp.raise_for_status() data = resp.json() self._access_token = data['access_token'] expires_in = (start_time + data['expires_in']) - time.time() if expires_in < self.ACCESS_TOKEN_REFRESH_TIME_BEFORE_EXPIRY: self.logger.warning("Access token expires in {}s, less than normal leeway time of {}s".format( expires_in, self.ACCESS_TOKEN_REFRESH_TIME_BEFORE_EXPIRY, )) gevent.spawn_later(expires_in - self.ACCESS_TOKEN_REFRESH_TIME_BEFORE_EXPIRY, self.get_access_token) except HTTPError as e: logging.error(f"Failed to fetch access token with {e.response.status_code}, retrying: {e.response.content}") except Exception: logging.exception("Failed to fetch access token, retrying") else: break gevent.sleep(self.ACCESS_TOKEN_ERROR_RETRY_INTERVAL) def request(self, method, url, headers={}, **kwargs): # merge in auth header headers = dict(headers, Authorization='Bearer {}'.format(self.access_token)) return requests.request(method, url, headers=headers, **kwargs)