Skip to main content

pyo3/err/
cast_error.rs

1use std::borrow::Cow;
2
3use crate::{
4    exceptions,
5    types::{
6        PyAnyMethods, PyNone, PyStringMethods, PyTuple, PyTupleMethods, PyType, PyTypeMethods,
7    },
8    Borrowed, Bound, IntoPyObjectExt, Py, PyAny, PyErr, PyErrArguments, PyTypeInfo, Python,
9};
10
11/// Error that indicates an object was not an instance of a given target type.
12#[derive(Debug)]
13pub struct CastError<'a, 'py> {
14    /// The original object that failed the `isinstance` check.
15    from: Borrowed<'a, 'py, PyAny>,
16    /// The type we tried (and failed) to convert to.
17    /// (see `PyTypeCheck::classinfo_object`)
18    classinfo: Bound<'py, PyAny>,
19}
20
21impl<'a, 'py> CastError<'a, 'py> {
22    /// Create a new `CastError` representing a failure to interpret a smart pointer to
23    /// `from` as a type from the given `classinfo`.
24    ///
25    /// As with [`PyTypeCheck::classinfo_object`][crate::PyTypeCheck::classinfo_object],
26    /// valid `classinfo` values are those which can be used with `isinstance`, such as `type`
27    /// objects, tuples of `type` objects, or `typing.Union` instances.
28    #[inline]
29    pub fn new(from: Borrowed<'a, 'py, PyAny>, classinfo: Bound<'py, PyAny>) -> Self {
30        Self { from, classinfo }
31    }
32}
33
34/// Equivalent to [`CastError`] for operations where the smart pointer cast transfers ownership
35/// of the original object.
36///
37/// The original object can be retrieved using [`into_inner`][CastIntoError::into_inner].
38#[derive(Debug)]
39pub struct CastIntoError<'py> {
40    from: Bound<'py, PyAny>,
41    classinfo: Bound<'py, PyAny>,
42}
43
44impl<'py> CastIntoError<'py> {
45    /// Create a new `CastError` representing a failure to interpret a smart pointer to
46    /// `from` as a type from the given `classinfo`.
47    ///
48    /// Equivalent to [`CastError::new`] for owned objects.
49    #[inline]
50    pub fn new(from: Bound<'py, PyAny>, classinfo: Bound<'py, PyAny>) -> Self {
51        Self { from, classinfo }
52    }
53
54    /// Consumes this `CastIntoError` and returns the original object, allowing continued
55    /// use of it after a failed conversion.
56    ///
57    /// See [`cast_into`][Bound::cast_into] for an example.
58    pub fn into_inner(self) -> Bound<'py, PyAny> {
59        self.from
60    }
61}
62
63struct CastErrorArguments {
64    from: Py<PyAny>,
65    classinfo: Py<PyAny>,
66}
67
68impl PyErrArguments for CastErrorArguments {
69    fn arguments(self, py: Python<'_>) -> Py<PyAny> {
70        format!(
71            "{}",
72            DisplayCastError {
73                from: &self.from.into_bound(py),
74                classinfo: &self.classinfo.into_bound(py),
75            }
76        )
77        .into_py_any(py)
78        .expect("failed to create Python string")
79    }
80}
81
82/// Convert `CastError` to Python `TypeError`.
83impl std::convert::From<CastError<'_, '_>> for PyErr {
84    fn from(err: CastError<'_, '_>) -> PyErr {
85        let args = CastErrorArguments {
86            from: err.from.to_owned().unbind(),
87            classinfo: err.classinfo.unbind(),
88        };
89
90        exceptions::PyTypeError::new_err(args)
91    }
92}
93
94impl std::error::Error for CastError<'_, '_> {}
95
96impl std::fmt::Display for CastError<'_, '_> {
97    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
98        DisplayCastError {
99            from: &self.from,
100            classinfo: &self.classinfo,
101        }
102        .fmt(f)
103    }
104}
105
106/// Convert `CastIntoError` to Python `TypeError`.
107impl std::convert::From<CastIntoError<'_>> for PyErr {
108    fn from(err: CastIntoError<'_>) -> PyErr {
109        let args = CastErrorArguments {
110            from: err.from.to_owned().unbind(),
111            classinfo: err.classinfo.unbind(),
112        };
113
114        exceptions::PyTypeError::new_err(args)
115    }
116}
117
118impl std::error::Error for CastIntoError<'_> {}
119
120impl std::fmt::Display for CastIntoError<'_> {
121    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
122        DisplayCastError {
123            from: &self.from.to_owned(),
124            classinfo: &self.classinfo,
125        }
126        .fmt(f)
127    }
128}
129
130struct DisplayCastError<'a, 'py> {
131    from: &'a Bound<'py, PyAny>,
132    classinfo: &'a Bound<'py, PyAny>,
133}
134
135impl std::fmt::Display for DisplayCastError<'_, '_> {
136    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137        let to = DisplayClassInfo(self.classinfo);
138        if self.from.is_none() {
139            write!(f, "'None' is not an instance of '{to}'")
140        } else {
141            let from = self.from.get_type().qualname();
142            let from = from
143                .as_ref()
144                .map(|name| name.to_string_lossy())
145                .unwrap_or(Cow::Borrowed("<failed to extract type name>"));
146            write!(f, "'{from}' object is not an instance of '{to}'")
147        }
148    }
149}
150
151struct DisplayClassInfo<'a, 'py>(&'a Bound<'py, PyAny>);
152
153impl std::fmt::Display for DisplayClassInfo<'_, '_> {
154    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155        if let Ok(t) = self.0.cast::<PyType>() {
156            if t.is(PyNone::type_object(t.py())) {
157                f.write_str("None")
158            } else {
159                t.qualname()
160                    .map_err(|_| std::fmt::Error)?
161                    .to_string_lossy()
162                    .fmt(f)
163            }
164        } else if let Ok(t) = self.0.cast::<PyTuple>() {
165            for (i, t) in t.iter().enumerate() {
166                if i > 0 {
167                    f.write_str(" | ")?;
168                }
169                write!(f, "{}", DisplayClassInfo(&t))?;
170            }
171            Ok(())
172        } else {
173            self.0.fmt(f)
174        }
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use crate::{
181        types::{PyBool, PyString},
182        PyTypeInfo,
183    };
184
185    use super::*;
186
187    #[test]
188    fn test_display_cast_error() {
189        Python::attach(|py| {
190            let obj = PyBool::new(py, true).to_any();
191            let classinfo = py.get_type::<PyString>().into_any();
192            let err = CastError::new(obj, classinfo);
193            assert_eq!(err.to_string(), "'bool' object is not an instance of 'str'");
194        })
195    }
196
197    #[test]
198    fn test_display_cast_error_with_none() {
199        Python::attach(|py| {
200            let obj = py.None().into_bound(py);
201            let classinfo = py.get_type::<PyString>().into_any();
202            let err = CastError::new(obj.as_borrowed(), classinfo);
203            assert_eq!(err.to_string(), "'None' is not an instance of 'str'");
204        })
205    }
206
207    #[test]
208    fn test_display_cast_error_with_tuple() {
209        Python::attach(|py| {
210            let obj = PyBool::new(py, true).to_any();
211            let classinfo = PyTuple::new(
212                py,
213                &[
214                    py.get_type::<PyString>().into_any(),
215                    crate::types::PyNone::type_object(py).into_any(),
216                ],
217            )
218            .unwrap()
219            .into_any();
220            let err = CastError::new(obj, classinfo);
221            assert_eq!(
222                err.to_string(),
223                "'bool' object is not an instance of 'str | None'"
224            );
225        })
226    }
227
228    #[test]
229    fn test_display_cast_into_error() {
230        Python::attach(|py| {
231            let obj = PyBool::new(py, true).to_any();
232            let classinfo = py.get_type::<PyString>().into_any();
233            let err = CastIntoError::new(obj.to_owned(), classinfo);
234            assert_eq!(err.to_string(), "'bool' object is not an instance of 'str'");
235        })
236    }
237
238    #[test]
239    fn test_pyerr_from_cast_error() {
240        Python::attach(|py| {
241            let obj = PyBool::new(py, true).to_any();
242            let classinfo = py.get_type::<PyString>().into_any();
243            let err = CastError::new(obj, classinfo);
244            let py_err: PyErr = err.into();
245            assert_eq!(
246                py_err.to_string(),
247                "TypeError: 'bool' object is not an instance of 'str'"
248            );
249        })
250    }
251
252    #[test]
253    fn test_pyerr_from_cast_into_error() {
254        Python::attach(|py| {
255            let obj = PyBool::new(py, true).to_any();
256            let classinfo = py.get_type::<PyString>().into_any();
257            let err = CastIntoError::new(obj.to_owned(), classinfo);
258            let py_err: PyErr = err.into();
259            assert_eq!(
260                py_err.to_string(),
261                "TypeError: 'bool' object is not an instance of 'str'"
262            );
263        })
264    }
265}
⚠️ Internal Docs ⚠️ Not Public API 👉 Official Docs Here