Skip to content

Commit

Permalink
v0.4.4 credentials provider
Browse files Browse the repository at this point in the history
  • Loading branch information
denisneuf committed Apr 22, 2023
1 parent 6b2188b commit 455c55a
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 64 deletions.
8 changes: 5 additions & 3 deletions ad_api/auth/access_token_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from .credentials import Credentials
from .access_token_response import AccessTokenResponse
from .exceptions import AuthorizationError
# from .credential_provider import CredentialProvider

import os

cache = TTLCache(maxsize=int(os.environ.get('AD_API_AUTH_CACHE_SIZE', 10)), ttl=3200)
Expand All @@ -20,9 +22,9 @@ class AccessTokenClient(BaseClient):
grant_type = 'refresh_token'
path = '/auth/o2/token'

def __init__(self, account='default', credentials=None, credentials_class=Credentials, proxies=None, verify=True, timeout=None):
super().__init__(account, credentials)
self.cred = credentials_class(self.credentials)
def __init__(self, credentials=None, proxies=None, verify=True, timeout=None):

self.cred = Credentials(credentials)
self.timeout = timeout
self.proxies = proxies
self.verify = verify
Expand Down
15 changes: 5 additions & 10 deletions ad_api/auth/credentials.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
class BaseCredentials:
class Credentials:
def __init__(self, credentials):
self.client_id = credentials.client_id
self.client_secret = credentials.client_secret
self.refresh_token = credentials.refresh_token


class Credentials(BaseCredentials):
def __init__(self, credentials):
super().__init__(credentials)
self.profile_id = credentials.profile_id
self.client_id = credentials['client_id']
self.client_secret = credentials['client_secret']
self.refresh_token = credentials['refresh_token']
self.profile_id = credentials['profile_id']
6 changes: 1 addition & 5 deletions ad_api/base/base_client.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
import ad_api.version as vd
from ad_api.base.credential_provider import CredentialProvider


class BaseClient:
scheme = 'https://'
method = 'GET'
content_type = 'application/x-www-form-urlencoded;charset=UTF-8'
user_agent = 'python-ad-api'
credential_provider_class = CredentialProvider

def __init__(self, account='default', credentials=None):
def __init__(self):
try:
version = vd.__version__
self.user_agent += f'-{version}'
except:
pass
self.credentials = self.credential_provider_class(account, credentials).credentials
21 changes: 8 additions & 13 deletions ad_api/base/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,13 @@
from zipfile import ZipFile
import zipfile
from urllib.parse import urlparse, quote

from ad_api.base.credential_provider import CredentialProvider

log = logging.getLogger(__name__)
role_cache = TTLCache(maxsize=int(os.environ.get('AD_API_AUTH_CACHE_SIZE', 10)), ttl=3200)


class Client(BaseClient):
access_token_client_class = AccessTokenClient
credentials_class = Credentials
grantless_scope = ''

def __init__(
self,
Expand All @@ -39,17 +36,15 @@ def __init__(
debug=False
):

super().__init__(account, credentials)
self.endpoint = marketplace.endpoint
self.debug = debug
self._auth = self.access_token_client_class(
account=account,
credentials=credentials,
credentials_class=self.credentials_class,
self.credentials = CredentialProvider(account, credentials).credentials
self._auth = AccessTokenClient(
credentials=self.credentials,
proxies=proxies,
verify=verify,
timeout=timeout,
)
self.endpoint = marketplace.endpoint
self.debug = debug
self.timeout = timeout
self.proxies = proxies
self.verify = verify
Expand All @@ -58,9 +53,9 @@ def __init__(
def headers(self):
return {
'User-Agent': self.user_agent,
'Amazon-Advertising-API-ClientId': self.credentials.client_id,
'Amazon-Advertising-API-ClientId': self.credentials['client_id'],
'Authorization': 'Bearer %s' % self.auth.access_token,
'Amazon-Advertising-API-Scope': self.credentials.profile_id,
'Amazon-Advertising-API-Scope': self.credentials['profile_id'],
'Content-Type': 'application/json'
}

Expand Down
154 changes: 121 additions & 33 deletions ad_api/base/credential_provider.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
import abc
import functools
import json
import os
import pprint

from typing import Dict, Iterable, Optional, Type

import confuse
from cachetools import Cache
import logging

from .config import Config
logger = logging.getLogger(__name__)
required_credentials = [
'refresh_token',
'client_id',
'client_secret',
'profile_id'
]


class MissingCredentials(Exception):
Expand All @@ -12,54 +25,80 @@ class MissingCredentials(Exception):
pass


class CredentialProvider:
credentials = None
config_class = Config
cache = Cache(maxsize=10)
class BaseCredentialProvider(abc.ABC):

def __init__(self, account='default', credentials=None):
self.account = account
self.read_credentials = [
self.from_env,
self.read_config
]
if credentials:
self.credentials = self.config_class(**credentials)
missing = self.credentials.check_config()
if len(missing):
raise MissingCredentials(f'The following configuration parameters are missing: {missing}')
else:
self.load_credentials()
def __init__(self, credentials: dict or str, *args, **kwargs):
self.credentials = credentials

@abc.abstractmethod
def load_credentials(self):
for read_method in self.read_credentials:
if read_method():
return True
pass


def _get_env(self, key):
return os.environ.get(f'{key}',
os.environ.get(key))

def check_credentials(self):
try:
self.errors = [c for c in required_credentials if
c not in self.credentials.keys() or not self.credentials[c]]
except (AttributeError, TypeError):
raise MissingCredentials(f'Credentials are missing: {", ".join(required_credentials)}')
if not len(self.errors):
return self.credentials
raise MissingCredentials(f'Credentials are missing: {", ".join(self.errors)}')


def from_env(self):

class FromEnvCredentialProvider(BaseCredentialProvider):

def __init__(self, *args, **kwargs):
pass

def load_credentials(self):
account_data = dict(
refresh_token=self._get_env('AD_API_REFRESH_TOKEN'),
client_id=self._get_env('AD_API_CLIENT_ID'),
client_secret=self._get_env('AD_API_CLIENT_SECRET'),
profile_id=self._get_env('AD_API_PROFILE_ID'),
)
self.credentials = self.config_class(**account_data)
return len(self.credentials.check_config()) == 0
self.credentials = account_data
self.check_credentials()
return self.credentials

def _get_env(self, key):
return os.environ.get(f'{key}_{self.account}',
return os.environ.get(f'{key}',
os.environ.get(key))

def read_config(self):
class FromCodeCredentialProvider(BaseCredentialProvider):

def __init__(self, credentials: dict, *args, **kwargs):
super().__init__(credentials)

def load_credentials(self):
self.check_credentials()
return self.credentials


class FromConfigFileCredentialProvider(BaseCredentialProvider):

file = 'credentials.yml' # Will moved to default confuse config.yaml

def __init__(self, account: str, *args, **kwargs):
self.account = account

def load_credentials(self):
try:
config = confuse.Configuration('python-ad-api')
config_filename = os.path.join(config.config_dir(), 'credentials.yml')
config_filename = os.path.join(config.config_dir(), self.file)
config.set_file(config_filename)
account_data = config[self.account].get()
self.credentials = self.config_class(**account_data)
missing = self.credentials.check_config()
if len(missing):
raise MissingCredentials(f'The following configuration parameters are missing: {missing}')
super().__init__(account_data)
self.check_credentials()
return (account_data)


except confuse.exceptions.NotFoundError:
raise MissingCredentials(f'The account {self.account} was not setup in your configuration file.')
except confuse.exceptions.ConfigReadError:
Expand All @@ -68,5 +107,54 @@ def read_config(self):
f'Please set the correct variables, or use a config file (credentials.yml). '
f'See https://confuse.readthedocs.io/en/latest/usage.html#search-paths for search paths.'
)


class CredentialProvider():

def load_credentials(self):
pass

def _get_env(self, key):
return os.environ.get(f'{key}',
os.environ.get(key))

def __init__(
self,
account: str = 'default',
credentials: Optional[Dict[str, str]] = None,
):

client_id_env = self._get_env('AD_API_CLIENT_ID'),
client_secret_env = self._get_env('AD_API_CLIENT_SECRET')

if client_id_env is not None and client_secret_env is not None:

try:
self.credentials = FromEnvCredentialProvider().load_credentials()
except MissingCredentials:
logging.error("MissingCredentials")
logging.error(MissingCredentials)

elif isinstance(credentials, dict):
try:
self.credentials = FromCodeCredentialProvider(credentials).load_credentials()

except MissingCredentials:
logging.error("MissingCredentials")
logging.error(MissingCredentials)

else:
return True

try:
self.credentials = FromConfigFileCredentialProvider(account).load_credentials()

except MissingCredentials:
logging.error("MissingCredentials")
logging.error(MissingCredentials)

class Config:
def __init__(self, **kwargs):
self.refresh_token = kwargs.get('refresh_token')
self.client_id = kwargs.get('client_id')
self.client_secret = kwargs.get('client_secret')
self.profile_id = kwargs.get('profile_id')

0 comments on commit 455c55a

Please sign in to comment.