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