Compare commits
3 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
6565b92f3b | ||
![]() |
61aba4812b | ||
![]() |
b0885f7b84 |
3 changed files with 65 additions and 99 deletions
|
@ -1,4 +1,4 @@
|
||||||
from .client import ApiSendMail, Re2oAPIClient
|
from .client import Re2oAPIClient
|
||||||
from . import exceptions
|
from . import exceptions
|
||||||
|
|
||||||
__all__ = ['Re2oAPIClient', 'ApiSendMail', 'exceptions']
|
__all__ = ['Re2oAPIClient', 'exceptions']
|
||||||
|
|
|
@ -5,9 +5,6 @@ from pathlib import Path
|
||||||
import stat
|
import stat
|
||||||
import json
|
import json
|
||||||
import requests
|
import requests
|
||||||
import smtplib
|
|
||||||
from email.mime.multipart import MIMEMultipart
|
|
||||||
from email.mime.text import MIMEText
|
|
||||||
from requests.exceptions import HTTPError
|
from requests.exceptions import HTTPError
|
||||||
|
|
||||||
from . import exceptions
|
from . import exceptions
|
||||||
|
@ -35,23 +32,23 @@ class Re2oAPIClient:
|
||||||
username: The username to use.
|
username: The username to use.
|
||||||
password: The password to use.
|
password: The password to use.
|
||||||
token_file: An optional path to the file where re2o tokens are
|
token_file: An optional path to the file where re2o tokens are
|
||||||
stored. Used both for retrieving the token and saving it, so
|
stored. Used both for retrieving the token and saving it, so
|
||||||
the file must be accessible for reading and writing. The
|
the file must be accessible for reading and writing. The
|
||||||
default value is `None`, which indicated to use
|
default value is `None`, which indicated to use
|
||||||
`$HOME/{DEFAULT_TOKEN_FILENAME}`.
|
`$HOME/{DEFAULT_TOKEN_FILENAME}`.
|
||||||
use_tls: A boolean to indicate whether the client should us TLS
|
use_tls: A boolean to indicate whether the client should us TLS
|
||||||
(recommended for production). The default is `True`.
|
(recommended for production). The default is `True`.
|
||||||
log_level: Control the logging level to use. The default is
|
log_level: Control the logging level to use. The default is
|
||||||
`logging.CRITICAL+10`. So nothing is logged.
|
`logging.CRITICAL+10`. So nothing is logged.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
requests.exceptions.ConnectionError: Unable to resolve the
|
requests.exceptions.ConnectionError: Unable to resolve the
|
||||||
provided hostname.
|
provided hostname.
|
||||||
requests.exceptions.HTTPError: The server used does not have a
|
requests.exceptions.HTTPError: The server used does not have a
|
||||||
valid Re2o API.
|
valid Re2o API.
|
||||||
re2oapi.exceptions.InvalidCredentials: The credentials provided
|
re2oapi.exceptions.InvalidCredentials: The credentials provided
|
||||||
are not valid according to the Re2o server.
|
are not valid according to the Re2o server.
|
||||||
""".format(DEFAULT_TOKEN_FILENAME=DEFAULT_TOKEN_FILENAME)
|
""".format(DEFAULT_TOKEN_FILENAME=DEFAULT_TOKEN_FILENAME)
|
||||||
|
|
||||||
# Enable logging
|
# Enable logging
|
||||||
self.log = logging.getLogger(__name__)
|
self.log = logging.getLogger(__name__)
|
||||||
|
@ -63,7 +60,7 @@ class Re2oAPIClient:
|
||||||
)
|
)
|
||||||
handler.setFormatter(formatter)
|
handler.setFormatter(formatter)
|
||||||
self.log.addHandler(handler)
|
self.log.addHandler(handler)
|
||||||
self.log.setLevel(log_level)
|
self.log.setLevel(log_level)
|
||||||
|
|
||||||
self.log.info("Starting new Re2o API client.")
|
self.log.info("Starting new Re2o API client.")
|
||||||
self.log.debug("hostname = " + str(hostname))
|
self.log.debug("hostname = " + str(hostname))
|
||||||
|
@ -89,8 +86,8 @@ class Re2oAPIClient:
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True is the token expiration time is within less than
|
True is the token expiration time is within less than
|
||||||
{TIME_FOR_RENEW} seconds.
|
{TIME_FOR_RENEW} seconds.
|
||||||
""".format(TIME_FOR_RENEW=TIME_FOR_RENEW)
|
""".format(TIME_FOR_RENEW=TIME_FOR_RENEW)
|
||||||
|
|
||||||
return self.token['expiration'] < \
|
return self.token['expiration'] < \
|
||||||
datetime.datetime.now(datetime.timezone.utc) + \
|
datetime.datetime.now(datetime.timezone.utc) + \
|
||||||
|
@ -133,7 +130,7 @@ class Re2oAPIClient:
|
||||||
else:
|
else:
|
||||||
self.log.debug("Token successfully retrieved from token "
|
self.log.debug("Token successfully retrieved from token "
|
||||||
"file '{}'.".format(self.token_file))
|
"file '{}'.".format(self.token_file))
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def _save_token_to_file(self):
|
def _save_token_to_file(self):
|
||||||
self.log.debug("Saving token to token file '{}'."
|
self.log.debug("Saving token to token file '{}'."
|
||||||
|
@ -152,20 +149,20 @@ class Re2oAPIClient:
|
||||||
# Insert the new token in the data (replace old token if it exists)
|
# Insert the new token in the data (replace old token if it exists)
|
||||||
if self.hostname not in data.keys():
|
if self.hostname not in data.keys():
|
||||||
data[self.hostname] = {}
|
data[self.hostname] = {}
|
||||||
data[self.hostname][self._username] = {
|
data[self.hostname][self._username] = {
|
||||||
'token': self.token['token'],
|
'token': self.token['token'],
|
||||||
'expiration': self.token['expiration'].isoformat()
|
'expiration': self.token['expiration'].isoformat()
|
||||||
}
|
}
|
||||||
|
|
||||||
# Rewrite the token file and ensure only the user can read it
|
# Rewrite the token file and ensure only the user can read it
|
||||||
# Fails silently if the file cannot be written.
|
# Fails silently if the file cannot be written.
|
||||||
try:
|
try:
|
||||||
with self.token_file.open('w') as f:
|
with self.token_file.open('w') as f:
|
||||||
json.dump(data, f)
|
json.dump(data, f)
|
||||||
self.token_file.chmod(stat.S_IWRITE | stat.S_IREAD)
|
self.token_file.chmod(stat.S_IWRITE | stat.S_IREAD)
|
||||||
except Exception:
|
except Exception:
|
||||||
self.log.error("Token file '{}' could not be written. Passing."
|
self.log.error("Token file '{}' could not be written. Passing."
|
||||||
.format(self.token_file))
|
.format(self.token_file))
|
||||||
else:
|
else:
|
||||||
self.log.debug("Token sucessfully writen in token file '{}'."
|
self.log.debug("Token sucessfully writen in token file '{}'."
|
||||||
.format(self.token_file))
|
.format(self.token_file))
|
||||||
|
@ -210,8 +207,8 @@ class Re2oAPIClient:
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
re2oapi.exceptions.InvalidCredentials: The token needs to be
|
re2oapi.exceptions.InvalidCredentials: The token needs to be
|
||||||
renewed but the given credentials are not valid.
|
renewed but the given credentials are not valid.
|
||||||
"""
|
"""
|
||||||
if self.need_renew_token:
|
if self.need_renew_token:
|
||||||
# Renew the token only if needed
|
# Renew the token only if needed
|
||||||
self._force_renew_token()
|
self._force_renew_token()
|
||||||
|
@ -222,7 +219,6 @@ class Re2oAPIClient:
|
||||||
|
|
||||||
# Update headers to force the 'Authorization' field with the right token
|
# Update headers to force the 'Authorization' field with the right token
|
||||||
self.log.debug("Forcing authentication token.")
|
self.log.debug("Forcing authentication token.")
|
||||||
self.log.debug("Token =" + str(self.get_token()))
|
|
||||||
headers.update({
|
headers.update({
|
||||||
'Authorization': 'Token {}'.format(self.get_token())
|
'Authorization': 'Token {}'.format(self.get_token())
|
||||||
})
|
})
|
||||||
|
@ -235,19 +231,10 @@ class Re2oAPIClient:
|
||||||
# Perform the request
|
# Perform the request
|
||||||
self.log.info("Performing request {} {}".format(method.upper(), url))
|
self.log.info("Performing request {} {}".format(method.upper(), url))
|
||||||
response = getattr(requests, method)(
|
response = getattr(requests, method)(
|
||||||
url, headers=headers, params=params,
|
url, headers=headers, params=params, *args, **kwargs
|
||||||
allow_redirects=False, *args, **kwargs
|
|
||||||
)
|
)
|
||||||
self.log.debug("Response code: "+str(response.status_code))
|
self.log.debug("Response code: "+str(response.status_code))
|
||||||
|
|
||||||
if response.is_redirect:
|
|
||||||
self.log.debug("Redirection detected.")
|
|
||||||
response = getattr(requests, method)(
|
|
||||||
response.headers['Location'], headers=headers, params=params,
|
|
||||||
allow_redirects=False, *args, **kwargs
|
|
||||||
)
|
|
||||||
self.log.debug("Response code after redirection: "+str(response.status_code))
|
|
||||||
|
|
||||||
if response.status_code == requests.codes.unauthorized:
|
if response.status_code == requests.codes.unauthorized:
|
||||||
# Force re-login to the server (case of a wrong token but valid
|
# Force re-login to the server (case of a wrong token but valid
|
||||||
# credentials) and then retry the request without catching errors.
|
# credentials) and then retry the request without catching errors.
|
||||||
|
@ -258,20 +245,20 @@ class Re2oAPIClient:
|
||||||
'Authorization': 'Token {}'.format(self.get_token())
|
'Authorization': 'Token {}'.format(self.get_token())
|
||||||
})
|
})
|
||||||
self.log.info("Re-performing the request {} {}"
|
self.log.info("Re-performing the request {} {}"
|
||||||
.format(method.upper(), url))
|
.format(method.upper(), url))
|
||||||
response = getattr(requests, method)(
|
response = getattr(requests, method)(
|
||||||
url, headers=headers, params=params, *args, **kwargs
|
url, headers=headers, params=params, *args, **kwargs
|
||||||
)
|
)
|
||||||
self.log.debug("Response code: "+str(response.status_code))
|
self.log.debug("Response code: "+str(response.status_code))
|
||||||
|
|
||||||
if response.status_code == requests.codes.forbidden:
|
if response.status_code == requests.codes.forbidden:
|
||||||
e = exceptions.PermissionDenied(method, url, self._username, response.reason)
|
e = exceptions.PermissionDenied(method, url, self._username)
|
||||||
self.log.debug(e)
|
self.log.debug(e)
|
||||||
raise e
|
raise e
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
ret = response.json()
|
ret = response.json()
|
||||||
self.log.debug("Request {} {} successful.".format(method, response.url))
|
self.log.debug("Request {} {} successful.".format(method, url))
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def delete(self, *args, **kwargs):
|
def delete(self, *args, **kwargs):
|
||||||
|
@ -291,10 +278,10 @@ class Re2oAPIClient:
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
requests.exceptions.RequestException: An error occured while
|
requests.exceptions.RequestException: An error occured while
|
||||||
performing the request.
|
performing the request.
|
||||||
exceptions.PermissionDenied: The user does not have the right
|
exceptions.PermissionDenied: The user does not have the right
|
||||||
to perform this request.
|
to perform this request.
|
||||||
"""
|
"""
|
||||||
return self._request('delete', *args, **kwargs)
|
return self._request('delete', *args, **kwargs)
|
||||||
|
|
||||||
def get(self, *args, **kwargs):
|
def get(self, *args, **kwargs):
|
||||||
|
@ -314,10 +301,10 @@ class Re2oAPIClient:
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
requests.exceptions.RequestException: An error occured while
|
requests.exceptions.RequestException: An error occured while
|
||||||
performing the request.
|
performing the request.
|
||||||
exceptions.PermissionDenied: The user does not have the right
|
exceptions.PermissionDenied: The user does not have the right
|
||||||
to perform this request.
|
to perform this request.
|
||||||
"""
|
"""
|
||||||
return self._request('get', *args, **kwargs)
|
return self._request('get', *args, **kwargs)
|
||||||
|
|
||||||
def head(self, *args, **kwargs):
|
def head(self, *args, **kwargs):
|
||||||
|
@ -337,10 +324,10 @@ class Re2oAPIClient:
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
requests.exceptions.RequestException: An error occured while
|
requests.exceptions.RequestException: An error occured while
|
||||||
performing the request.
|
performing the request.
|
||||||
exceptions.PermissionDenied: The user does not have the right
|
exceptions.PermissionDenied: The user does not have the right
|
||||||
to perform this request.
|
to perform this request.
|
||||||
"""
|
"""
|
||||||
return self._request('get', *args, **kwargs)
|
return self._request('get', *args, **kwargs)
|
||||||
|
|
||||||
def option(self, *args, **kwargs):
|
def option(self, *args, **kwargs):
|
||||||
|
@ -360,10 +347,10 @@ class Re2oAPIClient:
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
requests.exceptions.RequestException: An error occured while
|
requests.exceptions.RequestException: An error occured while
|
||||||
performing the request.
|
performing the request.
|
||||||
exceptions.PermissionDenied: The user does not have the right
|
exceptions.PermissionDenied: The user does not have the right
|
||||||
to perform this request.
|
to perform this request.
|
||||||
"""
|
"""
|
||||||
return self._request('get', *args, **kwargs)
|
return self._request('get', *args, **kwargs)
|
||||||
|
|
||||||
def patch(self, *args, **kwargs):
|
def patch(self, *args, **kwargs):
|
||||||
|
@ -383,10 +370,10 @@ class Re2oAPIClient:
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
requests.exceptions.RequestException: An error occured while
|
requests.exceptions.RequestException: An error occured while
|
||||||
performing the request.
|
performing the request.
|
||||||
exceptions.PermissionDenied: The user does not have the right
|
exceptions.PermissionDenied: The user does not have the right
|
||||||
to perform this request.
|
to perform this request.
|
||||||
"""
|
"""
|
||||||
return self._request('patch', *args, **kwargs)
|
return self._request('patch', *args, **kwargs)
|
||||||
|
|
||||||
def post(self, *args, **kwargs):
|
def post(self, *args, **kwargs):
|
||||||
|
@ -406,10 +393,10 @@ class Re2oAPIClient:
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
requests.exceptions.RequestException: An error occured while
|
requests.exceptions.RequestException: An error occured while
|
||||||
performing the request.
|
performing the request.
|
||||||
exceptions.PermissionDenied: The user does not have the right
|
exceptions.PermissionDenied: The user does not have the right
|
||||||
to perform this request.
|
to perform this request.
|
||||||
"""
|
"""
|
||||||
return self._request('post', *args, **kwargs)
|
return self._request('post', *args, **kwargs)
|
||||||
|
|
||||||
def put(self, *args, **kwargs):
|
def put(self, *args, **kwargs):
|
||||||
|
@ -429,10 +416,10 @@ class Re2oAPIClient:
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
requests.exceptions.RequestException: An error occured while
|
requests.exceptions.RequestException: An error occured while
|
||||||
performing the request.
|
performing the request.
|
||||||
exceptions.PermissionDenied: The user does not have the right
|
exceptions.PermissionDenied: The user does not have the right
|
||||||
to perform this request.
|
to perform this request.
|
||||||
"""
|
"""
|
||||||
return self._request('put', *args, **kwargs)
|
return self._request('put', *args, **kwargs)
|
||||||
|
|
||||||
def get_url_for(self, endpoint):
|
def get_url_for(self, endpoint):
|
||||||
|
@ -441,15 +428,15 @@ class Re2oAPIClient:
|
||||||
Args:
|
Args:
|
||||||
endpoint: The path of the endpoint.
|
endpoint: The path of the endpoint.
|
||||||
**kwargs: A dictionnary with the parameter to use to build the
|
**kwargs: A dictionnary with the parameter to use to build the
|
||||||
URL (using .format() syntax)
|
URL (using .format() syntax)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The full URL to use.
|
The full URL to use.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
re2oapi.exception.NameNotExists: The provided name does not
|
re2oapi.exception.NameNotExists: The provided name does not
|
||||||
correspond to any endpoint.
|
correspond to any endpoint.
|
||||||
"""
|
"""
|
||||||
return '{proto}://{host}/{namespace}/{endpoint}'.format(
|
return '{proto}://{host}/{namespace}/{endpoint}'.format(
|
||||||
proto=('https' if self.use_tls else 'http'),
|
proto=('https' if self.use_tls else 'http'),
|
||||||
host=self.hostname,
|
host=self.hostname,
|
||||||
|
@ -471,12 +458,12 @@ class Re2oAPIClient:
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
requests.exceptions.RequestException: An error occured while
|
requests.exceptions.RequestException: An error occured while
|
||||||
performing the request.
|
performing the request.
|
||||||
exceptions.PermissionDenied: The user does not have the right
|
exceptions.PermissionDenied: The user does not have the right
|
||||||
to perform this request.
|
to perform this request.
|
||||||
"""
|
"""
|
||||||
self.log.info("Starting listing objects under '{}'"
|
self.log.info("Starting listing objects under '{}'"
|
||||||
.format(endpoint))
|
.format(endpoint))
|
||||||
self.log.debug("max_results = "+str(max_results))
|
self.log.debug("max_results = "+str(max_results))
|
||||||
|
|
||||||
# For optimization, list all results in one page unless the user
|
# For optimization, list all results in one page unless the user
|
||||||
|
@ -494,7 +481,7 @@ class Re2oAPIClient:
|
||||||
|
|
||||||
# Get all next pages and append the results
|
# Get all next pages and append the results
|
||||||
while response['next'] is not None and \
|
while response['next'] is not None and \
|
||||||
(max_results is None or len(results) < max_results):
|
(max_results is None or len(results) < max_results):
|
||||||
response = self.get(response['next'])
|
response = self.get(response['next'])
|
||||||
results += response['results']
|
results += response['results']
|
||||||
|
|
||||||
|
@ -517,12 +504,12 @@ class Re2oAPIClient:
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
requests.exceptions.RequestException: An error occured while
|
requests.exceptions.RequestException: An error occured while
|
||||||
performing the request.
|
performing the request.
|
||||||
exceptions.PermissionDenied: The user does not have the right
|
exceptions.PermissionDenied: The user does not have the right
|
||||||
to perform this request.
|
to perform this request.
|
||||||
"""
|
"""
|
||||||
self.log.info("Starting counting objects under '{}'"
|
self.log.info("Starting counting objects under '{}'"
|
||||||
.format(endpoint))
|
.format(endpoint))
|
||||||
|
|
||||||
# For optimization, ask for only 1 result (so the server will take
|
# For optimization, ask for only 1 result (so the server will take
|
||||||
# less time to process the request) unless the user is forcing the
|
# less time to process the request) unless the user is forcing the
|
||||||
|
@ -554,10 +541,10 @@ class Re2oAPIClient:
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
requests.exceptions.RequestException: An error occured while
|
requests.exceptions.RequestException: An error occured while
|
||||||
performing the request.
|
performing the request.
|
||||||
exceptions.PermissionDenied: The user does not have the right
|
exceptions.PermissionDenied: The user does not have the right
|
||||||
to perform this request.
|
to perform this request.
|
||||||
"""
|
"""
|
||||||
self.log.info("Starting viewing an object under '{}'"
|
self.log.info("Starting viewing an object under '{}'"
|
||||||
.format(endpoint))
|
.format(endpoint))
|
||||||
ret = self.get(
|
ret = self.get(
|
||||||
|
@ -568,24 +555,3 @@ class Re2oAPIClient:
|
||||||
self.log.debug("Viewing object under '{}' successful"
|
self.log.debug("Viewing object under '{}' successful"
|
||||||
.format(endpoint))
|
.format(endpoint))
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
class ApiSendMail:
|
|
||||||
"""Basic api for sending mails"""
|
|
||||||
def __init__(self, server, port, starttls=False):
|
|
||||||
"""Give here the server, the port and tls or not"""
|
|
||||||
self.connection = smtplib.SMTP(server, port)
|
|
||||||
if starttls:
|
|
||||||
self.connection.starttls()
|
|
||||||
|
|
||||||
def send_mail(self, email_from, email_to, subject, body, mode='html'):
|
|
||||||
"""Sending mail from from, to, subject and body"""
|
|
||||||
self.msg = MIMEMultipart()
|
|
||||||
self.msg['From'] = email_from
|
|
||||||
self.msg['To'] = email_to
|
|
||||||
self.msg['Subject'] = subject
|
|
||||||
self.msg.attach(MIMEText(body, mode))
|
|
||||||
self.connection.sendmail(email_from, email_to, self.msg.as_string())
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
self.connection.quit()
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ class InvalidCredentials(APIClientGenericError):
|
||||||
|
|
||||||
|
|
||||||
class PermissionDenied(APIClientGenericError):
|
class PermissionDenied(APIClientGenericError):
|
||||||
template = "The {} request to '{}' was denied for {} (reason: {})."
|
template = "The {} request to '{}' was denied for {}."
|
||||||
|
|
||||||
|
|
||||||
class TokenFileNotFound(APIClientGenericError):
|
class TokenFileNotFound(APIClientGenericError):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue