pyo3/impl_/
frompyobject.rs

1use crate::types::any::PyAnyMethods;
2use crate::Bound;
3use crate::{exceptions::PyTypeError, FromPyObject, PyAny, PyErr, PyResult, Python};
4
5#[cold]
6pub fn failed_to_extract_enum(
7    py: Python<'_>,
8    type_name: &str,
9    variant_names: &[&str],
10    error_names: &[&str],
11    errors: &[PyErr],
12) -> PyErr {
13    // TODO maybe use ExceptionGroup on Python 3.11+ ?
14    let mut err_msg = format!(
15        "failed to extract enum {} ('{}')",
16        type_name,
17        error_names.join(" | ")
18    );
19    for ((variant_name, error_name), error) in variant_names.iter().zip(error_names).zip(errors) {
20        use std::fmt::Write;
21        write!(
22            &mut err_msg,
23            "\n- variant {variant_name} ({error_name}): {error_msg}",
24            variant_name = variant_name,
25            error_name = error_name,
26            error_msg = extract_traceback(py, error.clone_ref(py)),
27        )
28        .unwrap();
29    }
30    PyTypeError::new_err(err_msg)
31}
32
33/// Flattens a chain of errors into a single string.
34fn extract_traceback(py: Python<'_>, mut error: PyErr) -> String {
35    use std::fmt::Write;
36
37    let mut error_msg = error.to_string();
38    while let Some(cause) = error.cause(py) {
39        write!(&mut error_msg, ", caused by {}", cause).unwrap();
40        error = cause
41    }
42    error_msg
43}
44
45pub fn extract_struct_field<'py, T>(
46    obj: &Bound<'py, PyAny>,
47    struct_name: &str,
48    field_name: &str,
49) -> PyResult<T>
50where
51    T: FromPyObject<'py>,
52{
53    match obj.extract() {
54        Ok(value) => Ok(value),
55        Err(err) => Err(failed_to_extract_struct_field(
56            obj.py(),
57            err,
58            struct_name,
59            field_name,
60        )),
61    }
62}
63
64pub fn extract_struct_field_with<'a, 'py, T>(
65    extractor: fn(&'a Bound<'py, PyAny>) -> PyResult<T>,
66    obj: &'a Bound<'py, PyAny>,
67    struct_name: &str,
68    field_name: &str,
69) -> PyResult<T> {
70    match extractor(obj) {
71        Ok(value) => Ok(value),
72        Err(err) => Err(failed_to_extract_struct_field(
73            obj.py(),
74            err,
75            struct_name,
76            field_name,
77        )),
78    }
79}
80
81#[cold]
82fn failed_to_extract_struct_field(
83    py: Python<'_>,
84    inner_err: PyErr,
85    struct_name: &str,
86    field_name: &str,
87) -> PyErr {
88    let new_err = PyTypeError::new_err(format!(
89        "failed to extract field {}.{}",
90        struct_name, field_name
91    ));
92    new_err.set_cause(py, ::std::option::Option::Some(inner_err));
93    new_err
94}
95
96pub fn extract_tuple_struct_field<'py, T>(
97    obj: &Bound<'py, PyAny>,
98    struct_name: &str,
99    index: usize,
100) -> PyResult<T>
101where
102    T: FromPyObject<'py>,
103{
104    match obj.extract() {
105        Ok(value) => Ok(value),
106        Err(err) => Err(failed_to_extract_tuple_struct_field(
107            obj.py(),
108            err,
109            struct_name,
110            index,
111        )),
112    }
113}
114
115pub fn extract_tuple_struct_field_with<'a, 'py, T>(
116    extractor: fn(&'a Bound<'py, PyAny>) -> PyResult<T>,
117    obj: &'a Bound<'py, PyAny>,
118    struct_name: &str,
119    index: usize,
120) -> PyResult<T> {
121    match extractor(obj) {
122        Ok(value) => Ok(value),
123        Err(err) => Err(failed_to_extract_tuple_struct_field(
124            obj.py(),
125            err,
126            struct_name,
127            index,
128        )),
129    }
130}
131
132#[cold]
133fn failed_to_extract_tuple_struct_field(
134    py: Python<'_>,
135    inner_err: PyErr,
136    struct_name: &str,
137    index: usize,
138) -> PyErr {
139    let new_err =
140        PyTypeError::new_err(format!("failed to extract field {}.{}", struct_name, index));
141    new_err.set_cause(py, ::std::option::Option::Some(inner_err));
142    new_err
143}