use monty_types::{ExcData, ExcType, JsonErrorData, MontyException, MontyObject, UnicodeErrorObject};
use pyo3::{
PyTypeCheck,
exceptions::{self},
prelude::*,
sync::PyOnceLock,
types::{PyBytes, PyString},
};
use super::dataclass::get_frozen_instance_error;
#[must_use]
pub fn exc_monty_to_py(py: Python<'_>, mut exc: MontyException) -> PyErr {
let exc_type = exc.exc_type();
let exc_data = exc.take_data();
let msg = exc.into_message().unwrap_or_default();
match exc_type {
ExcType::Exception => exceptions::PyException::new_err(msg),
ExcType::BaseException => exceptions::PyBaseException::new_err(msg),
ExcType::SystemExit => exceptions::PySystemExit::new_err(msg),
ExcType::KeyboardInterrupt => exceptions::PyKeyboardInterrupt::new_err(msg),
ExcType::ArithmeticError => exceptions::PyArithmeticError::new_err(msg),
ExcType::OverflowError => exceptions::PyOverflowError::new_err(msg),
ExcType::ZeroDivisionError => exceptions::PyZeroDivisionError::new_err(msg),
ExcType::LookupError => exceptions::PyLookupError::new_err(msg),
ExcType::IndexError => exceptions::PyIndexError::new_err(msg),
ExcType::KeyError => exceptions::PyKeyError::new_err(msg),
ExcType::RuntimeError => exceptions::PyRuntimeError::new_err(msg),
ExcType::NotImplementedError => exceptions::PyNotImplementedError::new_err(msg),
ExcType::RecursionError => exceptions::PyRecursionError::new_err(msg),
ExcType::AssertionError => exceptions::PyAssertionError::new_err(msg),
ExcType::AttributeError => exceptions::PyAttributeError::new_err(msg),
ExcType::FrozenInstanceError => {
if let Ok(exc_cls) = get_frozen_instance_error(py)
&& let Ok(exc_instance) = exc_cls.call1((PyString::new(py, &msg),))
{
return PyErr::from_value(exc_instance);
}
exceptions::PyAttributeError::new_err(msg)
}
ExcType::MemoryError => exceptions::PyMemoryError::new_err(msg),
ExcType::NameError => exceptions::PyNameError::new_err(msg),
ExcType::UnboundLocalError => exceptions::PyUnboundLocalError::new_err(msg),
ExcType::StopIteration => exceptions::PyStopIteration::new_err(msg),
ExcType::SyntaxError => exceptions::PySyntaxError::new_err(msg),
ExcType::TimeoutError => exceptions::PyTimeoutError::new_err(msg),
ExcType::TypeError => exceptions::PyTypeError::new_err(msg),
ExcType::ValueError => exceptions::PyValueError::new_err(msg),
ExcType::UnicodeDecodeError | ExcType::UnicodeEncodeError => unicode_error_to_py(py, exc_type, exc_data, msg),
ExcType::JsonDecodeError => json_decode_error_to_py(py, exc_data, msg),
ExcType::ImportError => exceptions::PyImportError::new_err(msg),
ExcType::ModuleNotFoundError => exceptions::PyModuleNotFoundError::new_err(msg),
ExcType::OSError => exceptions::PyOSError::new_err(msg),
ExcType::FileNotFoundError => exceptions::PyFileNotFoundError::new_err(msg),
ExcType::FileExistsError => exceptions::PyFileExistsError::new_err(msg),
ExcType::IsADirectoryError => exceptions::PyIsADirectoryError::new_err(msg),
ExcType::NotADirectoryError => exceptions::PyNotADirectoryError::new_err(msg),
ExcType::PermissionError => exceptions::PyPermissionError::new_err(msg),
ExcType::UnsupportedOperation => {
if let Ok(exc_cls) = get_unsupported_operation(py)
&& let Ok(exc_instance) = exc_cls.call1((PyString::new(py, &msg),))
{
PyErr::from_value(exc_instance)
} else {
exceptions::PyOSError::new_err(msg)
}
}
ExcType::RePatternError => {
if let Ok(re_pattern_error) = get_re_pattern_error(py)
&& let Ok(exc_instance) = re_pattern_error.call1((PyString::new(py, &msg),))
{
PyErr::from_value(exc_instance)
} else {
exceptions::PyRuntimeError::new_err(msg)
}
}
}
}
fn unicode_error_to_py(py: Python<'_>, exc_type: ExcType, exc_data: ExcData, msg: String) -> PyErr {
if let ExcData::Unicode(data) = exc_data {
let exc_cls = if exc_type == ExcType::UnicodeDecodeError {
py.get_type::<exceptions::PyUnicodeDecodeError>()
} else {
py.get_type::<exceptions::PyUnicodeEncodeError>()
};
let object = match &data.object {
UnicodeErrorObject::Bytes(bytes) => PyBytes::new(py, bytes).into_any(),
UnicodeErrorObject::Str(s) => PyString::new(py, s).into_any(),
};
if let Ok(exc_instance) = exc_cls.call1((data.encoding, object, data.start, data.end, data.reason)) {
return PyErr::from_value(exc_instance);
}
}
exceptions::PyValueError::new_err(msg)
}
fn json_decode_error_to_py(py: Python<'_>, exc_data: ExcData, msg: String) -> PyErr {
if let ExcData::Json(data) = exc_data
&& let Ok(exc_cls) = get_json_decode_error(py)
&& let Ok(exc_instance) = exc_cls.call1((&data.msg, data.doc.as_deref().unwrap_or(""), data.pos))
&& exc_instance.setattr("lineno", data.lineno).is_ok()
&& exc_instance.setattr("colno", data.colno).is_ok()
&& exc_instance.setattr("args", (PyString::new(py, &msg),)).is_ok()
{
PyErr::from_value(exc_instance)
} else {
exceptions::PyValueError::new_err(msg)
}
}
pub fn exc_py_to_monty(py: Python<'_>, py_err: &PyErr) -> MontyException {
let exc = py_err.value(py);
let exc_type = py_err_to_exc_type(exc);
let arg = exc.str().ok().map(|s| s.to_string_lossy().into_owned());
let data = if exc_type == ExcType::JsonDecodeError {
json_data_from_py(exc)
} else {
ExcData::None
};
MontyException::new(exc_type, arg).with_data(data)
}
fn json_data_from_py(exc: &Bound<'_, exceptions::PyBaseException>) -> ExcData {
let extract = || -> PyResult<JsonErrorData> {
let doc: String = exc.getattr("doc")?.extract()?;
Ok(JsonErrorData {
msg: exc.getattr("msg")?.extract()?,
doc: (doc.len() <= JsonErrorData::MAX_DOC_LEN).then_some(doc),
pos: exc.getattr("pos")?.extract()?,
lineno: exc.getattr("lineno")?.extract()?,
colno: exc.getattr("colno")?.extract()?,
})
};
extract().map_or(ExcData::None, |data| ExcData::Json(Box::new(data)))
}
#[must_use]
pub fn exc_to_monty_object(exc: &Bound<'_, exceptions::PyBaseException>) -> MontyObject {
let exc_type = py_err_to_exc_type(exc);
let arg = exc.str().ok().map(|s| s.to_string_lossy().into_owned());
MontyObject::Exception { exc_type, arg }
}
fn py_err_to_exc_type(exc: &Bound<'_, exceptions::PyBaseException>) -> ExcType {
if exceptions::PyException::type_check(exc) {
if exceptions::PyTypeError::type_check(exc) {
ExcType::TypeError
} else if exceptions::PyValueError::type_check(exc) {
if is_json_decode_error(exc) {
ExcType::JsonDecodeError
} else if exceptions::PyUnicodeDecodeError::type_check(exc) {
ExcType::UnicodeDecodeError
} else if exceptions::PyUnicodeEncodeError::type_check(exc) {
ExcType::UnicodeEncodeError
} else if is_unsupported_operation(exc) {
ExcType::UnsupportedOperation
} else {
ExcType::ValueError
}
} else if exceptions::PyAssertionError::type_check(exc) {
ExcType::AssertionError
} else if exceptions::PySyntaxError::type_check(exc) {
ExcType::SyntaxError
} else if exceptions::PyLookupError::type_check(exc) {
if exceptions::PyKeyError::type_check(exc) {
ExcType::KeyError
} else if exceptions::PyIndexError::type_check(exc) {
ExcType::IndexError
} else {
ExcType::LookupError
}
} else if exceptions::PyArithmeticError::type_check(exc) {
if exceptions::PyZeroDivisionError::type_check(exc) {
ExcType::ZeroDivisionError
} else if exceptions::PyOverflowError::type_check(exc) {
ExcType::OverflowError
} else {
ExcType::ArithmeticError
}
} else if exceptions::PyRuntimeError::type_check(exc) {
if exceptions::PyNotImplementedError::type_check(exc) {
ExcType::NotImplementedError
} else if exceptions::PyRecursionError::type_check(exc) {
ExcType::RecursionError
} else {
ExcType::RuntimeError
}
} else if exceptions::PyAttributeError::type_check(exc) {
if is_frozen_instance_error(exc) {
ExcType::FrozenInstanceError
} else {
ExcType::AttributeError
}
} else if exceptions::PyNameError::type_check(exc) {
if exceptions::PyUnboundLocalError::type_check(exc) {
ExcType::UnboundLocalError
} else {
ExcType::NameError
}
} else if exceptions::PyOSError::type_check(exc) {
if exceptions::PyFileNotFoundError::type_check(exc) {
ExcType::FileNotFoundError
} else if exceptions::PyFileExistsError::type_check(exc) {
ExcType::FileExistsError
} else if exceptions::PyIsADirectoryError::type_check(exc) {
ExcType::IsADirectoryError
} else if exceptions::PyNotADirectoryError::type_check(exc) {
ExcType::NotADirectoryError
} else if exceptions::PyPermissionError::type_check(exc) {
ExcType::PermissionError
} else if exceptions::PyTimeoutError::type_check(exc) {
ExcType::TimeoutError
} else {
ExcType::OSError
}
} else if exceptions::PyImportError::type_check(exc) {
if exceptions::PyModuleNotFoundError::type_check(exc) {
ExcType::ModuleNotFoundError
} else {
ExcType::ImportError
}
} else if exceptions::PyMemoryError::type_check(exc) {
ExcType::MemoryError
} else if exceptions::PyStopIteration::type_check(exc) {
ExcType::StopIteration
} else if is_re_pattern_error(exc) {
ExcType::RePatternError
} else {
ExcType::Exception
}
} else if exceptions::PySystemExit::type_check(exc) {
ExcType::SystemExit
} else if exceptions::PyKeyboardInterrupt::type_check(exc) {
ExcType::KeyboardInterrupt
} else {
ExcType::BaseException
}
}
fn is_frozen_instance_error(exc: &Bound<'_, exceptions::PyBaseException>) -> bool {
if let Ok(frozen_error_cls) = get_frozen_instance_error(exc.py()) {
exc.is_instance(frozen_error_cls).unwrap_or(false)
} else {
false
}
}
fn is_json_decode_error(exc: &Bound<'_, exceptions::PyBaseException>) -> bool {
if let Ok(json_decode_error_cls) = get_json_decode_error(exc.py()) {
exc.is_instance(json_decode_error_cls).unwrap_or(false)
} else {
false
}
}
fn is_re_pattern_error(exc: &Bound<'_, exceptions::PyBaseException>) -> bool {
if let Ok(re_pattern_error_cls) = get_re_pattern_error(exc.py()) {
exc.is_instance(re_pattern_error_cls).unwrap_or(false)
} else {
false
}
}
fn get_re_pattern_error(py: Python<'_>) -> PyResult<&Bound<'_, PyAny>> {
static RE_PATTERN_ERROR: PyOnceLock<Py<PyAny>> = PyOnceLock::new();
if py.version_info() >= (3, 13) {
RE_PATTERN_ERROR.import(py, "re", "PatternError")
} else {
RE_PATTERN_ERROR.import(py, "re", "error")
}
}
fn get_json_decode_error(py: Python<'_>) -> PyResult<&Bound<'_, PyAny>> {
static JSON_DECODE_ERROR: PyOnceLock<Py<PyAny>> = PyOnceLock::new();
JSON_DECODE_ERROR.import(py, "json", "JSONDecodeError")
}
fn get_unsupported_operation(py: Python<'_>) -> PyResult<&Bound<'_, PyAny>> {
static UNSUPPORTED_OPERATION: PyOnceLock<Py<PyAny>> = PyOnceLock::new();
UNSUPPORTED_OPERATION.import(py, "io", "UnsupportedOperation")
}
fn is_unsupported_operation(exc: &Bound<'_, exceptions::PyBaseException>) -> bool {
if let Ok(cls) = get_unsupported_operation(exc.py()) {
exc.is_instance(cls).unwrap_or(false)
} else {
false
}
}