Skip to main content

pyo3/impl_/
frompyobject.rs

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