clearml/trains/backend_interface/base.py
2019-09-03 12:58:01 +03:00

169 lines
5.6 KiB
Python

import abc
import requests.exceptions
import six
from ..backend_api import Session, CallResult
from ..backend_api.session.session import MaxRequestSizeError
from ..backend_api.session.response import ResponseMeta
from ..backend_api.session import BatchRequest
from ..backend_api.session.defs import ENV_ACCESS_KEY, ENV_SECRET_KEY
from ..config import config_obj
from ..config.defs import LOG_LEVEL_ENV_VAR
from ..debugging import get_logger
from ..version import __version__
from .session import SendError, SessionInterface
class InterfaceBase(SessionInterface):
""" Base class for a backend manager class """
_default_session = None
@property
def session(self):
return self._session
@property
def log(self):
return self._log
def __init__(self, session=None, log=None, **kwargs):
super(InterfaceBase, self).__init__()
self._session = session or self._get_default_session()
self._log = log or self._create_log()
def _create_log(self):
log = get_logger(str(self.__class__.__name__))
try:
log.setLevel(LOG_LEVEL_ENV_VAR.get(default=log.level))
except TypeError as ex:
raise ValueError('Invalid log level defined in environment variable `%s`: %s' % (LOG_LEVEL_ENV_VAR, ex))
return log
@classmethod
def _send(cls, session, req, ignore_errors=False, raise_on_errors=True, log=None, async_enable=False):
""" Convenience send() method providing a standardized error reporting """
while True:
error_msg = ''
try:
res = session.send(req, async_enable=async_enable)
if res.meta.result_code in (200, 202) or ignore_errors:
return res
if isinstance(req, BatchRequest):
error_msg = 'Action failed %s' % res.meta
else:
error_msg = 'Action failed %s (%s)' \
% (res.meta, ', '.join('%s=%s' % p for p in req.to_dict().items()))
if log:
log.error(error_msg)
except requests.exceptions.BaseHTTPError as e:
res = None
if log:
log.warning('Failed sending %s: %s' % (str(type(req)), str(e)))
except MaxRequestSizeError as e:
res = CallResult(meta=ResponseMeta.from_raw_data(status_code=400, text=str(e)))
error_msg = 'Failed sending: %s' % str(e)
except requests.exceptions.ConnectionError:
# We couldn't send the request for more than the retries times configure in the api configuration file,
# so we will end the loop and raise the exception to the upper level.
# Notice: this is a connectivity error and not a backend error.
if raise_on_errors:
raise
res = None
except Exception as e:
res = None
if log:
log.warning('Failed sending %s: %s' % (str(type(req)), str(e)))
if res and res.meta.result_code <= 500:
# Proper backend error/bad status code - raise or return
if raise_on_errors:
raise SendError(res, error_msg)
return res
def send(self, req, ignore_errors=False, raise_on_errors=True, async_enable=False):
return self._send(session=self.session, req=req, ignore_errors=ignore_errors, raise_on_errors=raise_on_errors,
log=self.log, async_enable=async_enable)
@classmethod
def _get_default_session(cls):
if not InterfaceBase._default_session:
InterfaceBase._default_session = Session(
initialize_logging=False,
client='sdk-%s' % __version__,
config=config_obj,
api_key=ENV_ACCESS_KEY.get(),
secret_key=ENV_SECRET_KEY.get(),
)
return InterfaceBase._default_session
@classmethod
def _set_default_session(cls, session):
"""
Set a new default session to the system
Warning: Use only for debug and testing
:param session: The new default session
"""
InterfaceBase._default_session = session
@property
def default_session(self):
if hasattr(self, '_session'):
return self._session
return self._get_default_session()
@six.add_metaclass(abc.ABCMeta)
class IdObjectBase(InterfaceBase):
def __init__(self, id, session=None, log=None, **kwargs):
super(IdObjectBase, self).__init__(session, log, **kwargs)
self._data = None
self._id = None
self.id = self.normalize_id(id)
@property
def id(self):
return self._id
@id.setter
def id(self, value):
should_reload = value is not None and self._id is not None and value != self._id
self._id = value
if should_reload:
self.reload()
@property
def data(self):
if self._data is None:
self.reload()
return self._data
@abc.abstractmethod
def _reload(self):
pass
def reload(self):
if not self.id:
raise ValueError('Failed reloading %s: missing id' % type(self).__name__)
# noinspection PyBroadException
try:
self._data = self._reload()
except Exception:
pass
@classmethod
def normalize_id(cls, id):
return id.strip() if id else None
@classmethod
def resolve_id(cls, obj):
if isinstance(obj, cls):
return obj.id
return obj