directory_client_core/base.py
import abc
import json
import logging
import urllib.parse as urlparse
from monotonic import monotonic
import requests
from sigauth.helpers import RequestSigner
logger = logging.getLogger(__name__)
class AbstractAPIClient(abc.ABC):
@property
@abc.abstractmethod
def version():
pass
def __init__(self, base_url, api_key, sender_id, timeout):
self.base_url = base_url
self.request_signer = RequestSigner(
secret=api_key, sender_id=sender_id
)
self.timeout = timeout
def put(self, url, data, authenticator=None):
return self.request(
url=url,
method="PUT",
content_type="application/json",
data=json.dumps(data),
authenticator=authenticator,
)
def patch(self, url, data, files=None, authenticator=None):
if files:
response = self.request(
url=url,
method="PATCH",
data=data,
files=files,
authenticator=authenticator
)
else:
response = self.request(
url=url,
method="PATCH",
content_type="application/json",
data=json.dumps(data),
authenticator=authenticator,
)
return response
def get(self, url, params=None, authenticator=None, cache_control=None):
return self.request(
url=url,
method="GET",
params=params,
authenticator=authenticator,
cache_control=cache_control,
)
def post(self, url, data={}, files=None, authenticator=None):
if files:
response = self.request(
url=url,
method="POST",
data=data,
files=files,
authenticator=authenticator,
)
else:
response = self.request(
url=url,
method="POST",
content_type="application/json",
data=json.dumps(data),
authenticator=authenticator,
)
return response
def delete(self, url, data=None, authenticator=None):
return self.request(
url=url,
method="DELETE",
authenticator=authenticator,
data=data,
)
@staticmethod
def build_url(base_url, partial_url):
"""
Makes sure the URL is built properly.
>>> urllib.parse.urljoin('https://test.com/1/', '2/3')
https://test.com/1/2/3
>>> urllib.parse.urljoin('https://test.com/1/', '/2/3')
https://test.com/2/3
>>> urllib.parse.urljoin('https://test.com/1', '2/3')
https://test.com/2/3'
"""
if not base_url.endswith('/'):
base_url += '/'
if partial_url.startswith('/'):
partial_url = partial_url[1:]
return urlparse.urljoin(base_url, partial_url)
def request(
self, method, url, content_type=None, data=None, params=None,
files=None, authenticator=None, cache_control=None,
):
logger.debug("API request {} {}".format(method, url))
headers = {
"User-agent": "EXPORT-DIRECTORY-API-CLIENT/{}".format(self.version)
}
if authenticator:
headers.update(authenticator.headers)
if cache_control:
headers.update(cache_control.headers)
if content_type:
headers["Content-type"] = content_type
url = self.build_url(self.base_url, url)
start_time = monotonic()
try:
return self.send(
method=method,
url=url,
headers=headers,
data=data,
params=params,
files=files,
)
finally:
elapsed_time = monotonic() - start_time
logger.debug(
"API {} request on {} finished in {}".format(
method, url, elapsed_time
)
)
def sign_request(self, prepared_request):
headers = self.request_signer.get_signature_headers(
url=prepared_request.path_url,
body=prepared_request.body,
method=prepared_request.method,
content_type=prepared_request.headers.get('Content-Type'),
)
prepared_request.headers.update(headers)
return prepared_request
def send(self, method, url, request=None, *args, **kwargs):
prepared_request = requests.Request(
method, url, *args, **kwargs
).prepare()
signed_request = self.sign_request(prepared_request=prepared_request)
return requests.Session().send(signed_request, timeout=self.timeout)