import future
# -- Standard lib ------------------------------------------------------------
import http.client as httplib
# -- Project specific --------------------------------------------------------
from . import singleton
[docs]class ExceptionInfo:
    """
    Exception information to show useful responses to user when they happen.
    """
    def __init__(self, code, exc_type,
                 status=httplib.INTERNAL_SERVER_ERROR,
                 msg=None):
        """
        Constructor.
        :param code: Vesta exception code
        :param exc_type: Exception type
        :param status: HTTP status code to send with the request response
        :param msg: Generic message to show for a particular exception
                    If None, the specific message contains in the exception
                    will be used. So if a generic one is provided the
                    specific message will be overridden with it.
        """
        self.code = code
        self.exc_type = exc_type
        self.status = status
        self.msg = msg 
@singleton.Singleton
class VestaExceptions(object):
    """
    Exceptions register class.
    """
    def __init__(self):
        """
        Populate the known exceptions list.
        """
        self._known_exceptions = [
            # -----------------------------------------------------------------
            # 1xx exception codes are reserved for built-in exceptions
            # -----------------------------------------------------------------
            ExceptionInfo(code=100, exc_type='NoneType'),
            ExceptionInfo(code=101, exc_type='Exception'),
            ExceptionInfo(code=102, exc_type='sqlite3.Error'),
            ExceptionInfo(code=103, exc_type='ConfigParser.Error'),
            ExceptionInfo(code=104, exc_type='TypeError'),
            ExceptionInfo(code=105, exc_type='ValueError'),
            ExceptionInfo(code=106, exc_type='socket.gaierror'),
            ExceptionInfo(code=107, exc_type='IOError'),
            ExceptionInfo(code=108, exc_type='KeyError'),
            ExceptionInfo(code=109, exc_type='TaskRevokedError'),
            # -----------------------------------------------------------------
            # 2xx exception codes are reserved for VRP package
            # -----------------------------------------------------------------
            ExceptionInfo(code=200, exc_type='SettingsException'),
            ExceptionInfo(code=201, exc_type='VersionException'),
            ExceptionInfo(code=202, exc_type='UnknownServiceError',
                          status=httplib.BAD_REQUEST),
            ExceptionInfo(code=203, exc_type='UnknownUUIDError',
                          status=httplib.BAD_REQUEST),
            ExceptionInfo(code=204, exc_type='VersionMismatchError'),
            ExceptionInfo(code=205, exc_type='AMQPError',
                          status=httplib.REQUEST_TIMEOUT),
            ExceptionInfo(code=206, exc_type='MissingParameterError',
                          status=httplib.BAD_REQUEST),
            ExceptionInfo(code=207, exc_type='DocumentUrlNotValidException',
                          status=httplib.BAD_REQUEST),
            # -----------------------------------------------------------------
            # 3xx exception codes are reserved for Service package
            # -----------------------------------------------------------------
            ExceptionInfo(code=300, exc_type='InvalidAnnotationFormat'),
            ExceptionInfo(code=301, exc_type='DownloadError'),
            ExceptionInfo(code=302, exc_type='UploadError'),
            ExceptionInfo(code=303, exc_type='ConfigFileNotFound'),
            ExceptionInfo(code=304, exc_type='InvalidDocumentPath'),
            ExceptionInfo(code=305, exc_type='InvalidDocumentType'),
            ExceptionInfo(code=306, exc_type='RuntimeError'),
            # -----------------------------------------------------------------
            # 4xx exception codes are reserved for VLB package
            # -----------------------------------------------------------------
            ExceptionInfo(code=400, exc_type='InsufficientResources'),
            ExceptionInfo(code=401, exc_type='MinimumWorkersReached'),
            # -----------------------------------------------------------------
            # 5xx exception codes are reserved for MSS package
            # -----------------------------------------------------------------
            ExceptionInfo(code=500, exc_type='OverflowError'),
            ExceptionInfo(code=501, exc_type='NotImplementedError'),
            ExceptionInfo(code=502, exc_type='FFMpegError'),
            ExceptionInfo(code=503, exc_type='FFMpegConvertError'),
            ExceptionInfo(code=504, exc_type='ConverterError'),
            ExceptionInfo(code=505, exc_type='TranscoderError'),
            # A message must absolutely be set here because specific message
            # contains sensible data
            ExceptionInfo(code=506, exc_type='SwiftException',
                          msg='Cannot renew the swift token'),
            # -----------------------------------------------------------------
            # 600 and above exceptions codes are free to be used by workers
            # -----------------------------------------------------------------
            # 600-620 exceptions codes are taken by transition/face worker
            ExceptionInfo(code=600, exc_type='CInternalError'),
            # 630-639 onwards shared by diarization, STT, TextMatching.
            ExceptionInfo(code=630, exc_type="WavPathInvalid"),
            ExceptionInfo(code=631, exc_type="WavFormatInvalid"),
            ExceptionInfo(code=632, exc_type="WavHeaderInvalid"),
            ExceptionInfo(code=633, exc_type="SubprocessProblem",
                          msg="Diarisation worker internal problem"),
            # 640 onwards taken by STT.
            ExceptionInfo(code=640, exc_type="PathInvalidError"),
            ExceptionInfo(code=641, exc_type="SegmentsTooLongException"),
            ExceptionInfo(code=642, exc_type="SubprocessError"),
            ExceptionInfo(code=643, exc_type="TranscriptionEmptyError"),
        ]
        # Make an exception dict based on exception type from the list above
        self._known_exceptions_dict = dict((exc.exc_type, exc) for
                                           exc in self._known_exceptions)
    def __find_matching_exception(self, exception):
        """
        Try to find a matching exception class in the known exception
        dictionary.
        :param exception: The exception class
        :returns: An exception object (A generic one is returned if not found)
        """
        exception_type_name = type(exception).__name__
        # First, try a direct match
        if exception_type_name in self._known_exceptions_dict:
            return self._known_exceptions_dict[exception_type_name]
        # Then make a search including some or all of the module directories
        try:
            module_dirs = exception.__module__.split('.')
            for mod_dir in reversed(module_dirs):
                exception_type_name = ''.join([mod_dir,
                                               '.',
                                               exception_type_name])
                if exception_type_name in self._known_exceptions_dict:
                    return self._known_exceptions_dict[exception_type_name]
        except AttributeError:
            # Not all exceptions have a __module__ attribute
            pass
        # Return a generic exception to handle this unknown exception
        return self._known_exceptions_dict['Exception']
    def get_exception_code(self, exception):
        """
        Returns the Vesta exception code associated to this exception.
        :param exception: Exception instance
        :returns: Exception code
        """
        return self.__find_matching_exception(exception).code
    def get_html_status(self, exception):
        """
        Returns the html status code associated to this exception.
        :param exception: Exception instance
        :returns: HTML status code
        """
        return self.__find_matching_exception(exception).status
    def get_generic_message(self, exception):
        """
        Returns the generic message associated to this exception.
        :param exception: Exception instance
        :returns: Exception message
        """
        return self.__find_matching_exception(exception).msg
[docs]class VRPException(Exception):
    """
    Base exception type for current package.
    """
    status_code = 400
    def __init__(self, message, status_code=None, payload=None):
        Exception.__init__(self)
        self.message = message
        if status_code is not None:
            self.status_code = status_code
        self.payload = payload
    def to_dict(self):
        rv = dict(self.payload or ())
        rv['message'] = self.message
        return rv 
[docs]class UnknownServiceError(VRPException):
    """
    Indicates that a Service name is of unknown type.
    """
    def __init__(self, service):
        msg = ('The request has been made for a service that is not supported'
               ' : {service}'.format(service=service))
        super(UnknownServiceError, self).__init__(msg) 
[docs]class UnknownUUIDError(VRPException):
    """
    Indicates that the requested UUID is not yet registered.
    """
    def __init__(self, uuid):
        msg = ('User requested a UUID which did not exist : {uuid}'
               .format(uuid=uuid))
        super(UnknownUUIDError, self).__init__(msg) 
[docs]class MissingParameterError(VRPException):
    """
    Indicates that a required parameter is missing.
    """
    def __init__(self, method, uri, missing_param):
        msg = ('A {method} on the URI "{uri}" requires the following '
               'parameter : {param}'.format(method=method,
                                            uri=uri,
                                            param=missing_param))
        super(MissingParameterError, self).__init__(msg) 
[docs]class VersionMismatchError(VRPException):
    """
    Indicates that a service version declared in the REST configuration
    mismatches the version obtained from worker message payload.
    """
    pass 
[docs]class AMQPError(VRPException):
    """
    Indicates that communications with AMQP failed.
    """
    def __init__(self):
        msg = "AMQP backend didn't response quickly enough."
        super(AMQPError, self).__init__(msg) 
[docs]class DocumentUrlNotValidException(VRPException):
    """
    Indicates that given URL for a document is invalid.
    """
    def __init__(self, invalid_url):
        msg = ('User provided an invalid source URL : {url}'
               .format(url=invalid_url))
        super(DocumentUrlNotValidException, self).__init__(msg) 
[docs]class SettingsException(VRPException):
    """
    Indicates that an error occurred during settings parsing.
    """
    pass 
[docs]class VersionException(VRPException):
    """
    Indicates that a version mismatch was found.
    """
    pass