diff --git a/pytaboola/client.py b/pytaboola/client.py index a8a3bc3..149ed2d 100644 --- a/pytaboola/client.py +++ b/pytaboola/client.py @@ -1,6 +1,8 @@ import json import logging import requests +import tenacity +from requests.exceptions import ReadTimeout, ConnectTimeout, ConnectionError from pytaboola.errors import Unauthorized from pytaboola.utils import parse_response @@ -16,13 +18,19 @@ class TaboolaClient: base_url = 'https://backstage.taboola.com' def __init__(self, client_id, client_secret=None, - access_token=None, refresh_token=None): + access_token=None, refresh_token=None, + timeout=None): + """ + timeout (in seconds) will be passed to requests.request(). + If timeout is not set, then default to None. Which will wait forever. + """ assert client_secret or access_token, "Must provide either the client secret or an access token" self.access_token = access_token self.refresh_token = refresh_token self.client_id = client_id self.client_secret = client_secret + self.timeout = timeout if not self.access_token: self.refresh() @@ -84,6 +92,12 @@ def authorization_header(self): def token_details(self): return self.execute('GET', 'backstage/api/1.0/token-details/') + @tenacity.retry( + stop=tenacity.stop_after_attempt(6), + wait=tenacity.wait_exponential(multiplier=1, exp_base=2), + retry=tenacity.retry_if_exception_type((ReadTimeout, ConnectTimeout, ConnectionError)), + before_sleep=tenacity.before_sleep_log(logger, logging.INFO), + ) def execute(self, method, uri, query_params=None, allow_refresh=True, raw=False, authenticated=True, **payload): @@ -98,7 +112,8 @@ def execute(self, method, uri, query_params=None, data = payload if raw else json.dumps(payload) result = requests.request(method, url, data=data, params=query_params, - headers=headers) + headers=headers, + timeout=self.timeout) return parse_response(result) except Unauthorized: if not allow_refresh: diff --git a/pytaboola/services/service.py b/pytaboola/services/service.py index b01edc6..e05423b 100644 --- a/pytaboola/services/service.py +++ b/pytaboola/services/service.py @@ -4,6 +4,24 @@ logger = logging.getLogger(__name__) +class AdvertiserService(BaseService): + ''' + Only return advertisers under a container to handle + situations where account permissions overlap or + are shared across multiple users. + ''' + + def __init__(self, client, container_id): + super().__init__(client) + self.container_id = container_id + + def build_uri(self, endpoint=None): + return '{}/{}/advertisers'.format(self.endpoint, self.container_id) + + def list(self): + return self.execute('GET', self.build_uri()) + + class AccountService(BaseService): endpoint = '{}/{}'.format(BaseService.endpoint, 'users/current/allowed-accounts/') @@ -34,8 +52,8 @@ def __init__(self, client, account_id, campaign_id): def build_uri(self, endpoint=None): base_endpoint = '{}/{}/campaigns/{}/items/'.format(self.endpoint, - self.campaign_id, - self.account_id) + self.account_id, + self.campaign_id) if not endpoint: return base_endpoint while endpoint.startswith('/'):