fbnet/command_runner/exceptions.py (91 lines of code) (raw):
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
from functools import wraps
from typing import ClassVar, Callable, Any, TypeVar, cast
import asyncssh
from fbnet.command_runner_asyncio.CommandRunner import ttypes as fcr_ttypes
from fbnet.command_runner_asyncio.CommandRunner.ttypes import FcrErrorCode
class FcrBaseException(Exception):
"""
Base exception class, do not raise this class.
- raise subclass referring to specific type of error
- raise UnknownError if it's actually unknown
"""
_CODE: ClassVar[FcrErrorCode] = FcrErrorCode.UNKNOWN
async def to_thrift_exception(self) -> fcr_ttypes.SessionException:
return fcr_ttypes.SessionException(
message=str(self),
code=self._CODE,
)
class UnknownException(FcrBaseException):
"""
an explicit unknown exception class
"""
_CODE: ClassVar[FcrErrorCode] = FcrErrorCode.UNKNOWN
class ValidationErrorException(FcrBaseException):
_CODE: ClassVar[FcrErrorCode] = FcrErrorCode.VALIDATION_ERROR
class PermissionErrorException(FcrBaseException):
_CODE: ClassVar[FcrErrorCode] = FcrErrorCode.PERMISSION_ERROR
class ValueErrorException(FcrBaseException):
_CODE: ClassVar[FcrErrorCode] = FcrErrorCode.VALUE_ERROR
class UnsupportedDeviceErrorException(FcrBaseException):
_CODE: ClassVar[FcrErrorCode] = FcrErrorCode.UNSUPPORTED_DEVICE_ERROR
class UnsupportedCommandErrorException(FcrBaseException):
_CODE: ClassVar[FcrErrorCode] = FcrErrorCode.UNSUPPORTED_COMMAND_ERROR
class RuntimeErrorException(FcrBaseException):
_CODE: ClassVar[FcrErrorCode] = FcrErrorCode.RUNTIME_ERROR
class AssertionErrorException(FcrBaseException):
_CODE: ClassVar[FcrErrorCode] = FcrErrorCode.ASSERTION_ERROR
class LookupErrorException(FcrBaseException):
_CODE: ClassVar[FcrErrorCode] = FcrErrorCode.LOOKUP_ERROR
class StreamReaderErrorException(FcrBaseException):
_CODE: ClassVar[FcrErrorCode] = FcrErrorCode.STREAM_READER_ERROR
class CommandExecutionTimeoutErrorException(FcrBaseException):
_CODE: ClassVar[FcrErrorCode] = FcrErrorCode.COMMAND_EXECUTION_TIMEOUT_ERROR
class NotImplementedErrorException(FcrBaseException):
_CODE: ClassVar[FcrErrorCode] = FcrErrorCode.NOT_IMPLEMENTED_ERROR
class TypeErrorException(FcrBaseException):
_CODE: ClassVar[FcrErrorCode] = FcrErrorCode.TYPE_ERROR
class AttributeErrorException(FcrBaseException):
_CODE: ClassVar[FcrErrorCode] = FcrErrorCode.ATTRIBUTE_ERROR
class TimeoutErrorException(FcrBaseException):
_CODE: ClassVar[FcrErrorCode] = FcrErrorCode.TIMEOUT_ERROR
class DeviceErrorException(FcrBaseException):
_CODE: ClassVar[FcrErrorCode] = FcrErrorCode.DEVICE_ERROR
class CommandExecutionErrorException(FcrBaseException):
_CODE: ClassVar[FcrErrorCode] = FcrErrorCode.COMMAND_EXECUTION_ERROR
class ConnectionErrorException(FcrBaseException):
_CODE: ClassVar[FcrErrorCode] = FcrErrorCode.CONNECTION_ERROR
class ConnectionTimeoutErrorException(FcrBaseException):
_CODE: ClassVar[FcrErrorCode] = FcrErrorCode.CONNECTION_TIMEOUT_ERROR
def convert_to_fcr_exception(e: Exception) -> FcrBaseException:
"""
Convert all generic exceptions to FcrBaseException types
Leaves FcrBaseException types unchanged
"""
if isinstance(e, FcrBaseException):
return e
elif isinstance(e, PermissionError):
# use str(e) for known exceptions to keep exception messages clean
return PermissionErrorException(str(e))
elif isinstance(e, ValueError):
return ValueErrorException(str(e))
elif isinstance(e, AssertionError):
return AssertionErrorException(str(e))
elif isinstance(e, LookupError) or isinstance(e, KeyError):
return LookupErrorException(str(e))
elif isinstance(e, NotImplementedError):
return NotImplementedErrorException(str(e))
elif isinstance(e, asyncssh.misc.DisconnectError):
return ConnectionErrorException(str(e))
elif isinstance(e, TypeError):
return TypeErrorException(str(e))
elif isinstance(e, AttributeError):
return AttributeErrorException(str(e))
elif isinstance(e, TimeoutError):
return TimeoutErrorException(str(e))
elif isinstance(e, RuntimeError):
# keep RuntimeError as last elif case to avoid interfering
# with conversion of other RuntimeError-derived exceptions
# and catch any unidentified RuntimeError-derived exceptions
return RuntimeErrorException(str(e))
else:
# use repr(e) for unknown exceptions to preserve exception information
return UnknownException(repr(e))
F = TypeVar("F", bound=Callable[..., Any])
def ensure_thrift_exception(fn: F) -> F:
"""
Catch all FcrBaseExceptions and generic exception
Convert to Thrift SessionException and raise
"""
@wraps(fn)
async def wrapper(self, *args, **kwargs):
try:
return await fn(self, *args, **kwargs)
except Exception as e:
if isinstance(e, fcr_ttypes.SessionException):
raise e
# Thrift defined InstanceOverloaded exceptions are only for internal use
# so don't have to convert to Thrift defined SessionException
elif isinstance(e, fcr_ttypes.InstanceOverloaded):
raise e
fcr_ex: fcr_ttypes.SessionException = await convert_to_fcr_exception(
e
).to_thrift_exception()
raise fcr_ex from e
return cast(F, wrapper)