pyo3/conversions/
either.rs1#![cfg(feature = "either")]
2
3#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"either\"] }")]
20#[cfg(feature = "experimental-inspect")]
48use crate::inspect::PyStaticExpr;
49#[cfg(feature = "experimental-inspect")]
50use crate::type_hint_union;
51use crate::{
52 exceptions::PyTypeError, Borrowed, Bound, FromPyObject, IntoPyObject, IntoPyObjectExt, PyAny,
53 PyErr, Python,
54};
55use either::Either;
56
57#[cfg_attr(docsrs, doc(cfg(feature = "either")))]
58impl<'py, L, R> IntoPyObject<'py> for Either<L, R>
59where
60 L: IntoPyObject<'py>,
61 R: IntoPyObject<'py>,
62{
63 type Target = PyAny;
64 type Output = Bound<'py, Self::Target>;
65 type Error = PyErr;
66
67 #[cfg(feature = "experimental-inspect")]
68 const OUTPUT_TYPE: PyStaticExpr = type_hint_union!(L::OUTPUT_TYPE, R::OUTPUT_TYPE);
69
70 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
71 match self {
72 Either::Left(l) => l.into_bound_py_any(py),
73 Either::Right(r) => r.into_bound_py_any(py),
74 }
75 }
76}
77
78#[cfg_attr(docsrs, doc(cfg(feature = "either")))]
79impl<'a, 'py, L, R> IntoPyObject<'py> for &'a Either<L, R>
80where
81 &'a L: IntoPyObject<'py>,
82 &'a R: IntoPyObject<'py>,
83{
84 type Target = PyAny;
85 type Output = Bound<'py, Self::Target>;
86 type Error = PyErr;
87
88 #[cfg(feature = "experimental-inspect")]
89 const OUTPUT_TYPE: PyStaticExpr = type_hint_union!(<&L>::OUTPUT_TYPE, <&R>::OUTPUT_TYPE);
90
91 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
92 match self {
93 Either::Left(l) => l.into_bound_py_any(py),
94 Either::Right(r) => r.into_bound_py_any(py),
95 }
96 }
97}
98
99#[cfg_attr(docsrs, doc(cfg(feature = "either")))]
100impl<'a, 'py, L, R> FromPyObject<'a, 'py> for Either<L, R>
101where
102 L: FromPyObject<'a, 'py>,
103 R: FromPyObject<'a, 'py>,
104{
105 type Error = PyErr;
106
107 #[cfg(feature = "experimental-inspect")]
108 const INPUT_TYPE: PyStaticExpr = type_hint_union!(L::INPUT_TYPE, R::INPUT_TYPE);
109
110 #[inline]
111 fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
112 if let Ok(l) = obj.extract::<L>() {
113 Ok(Either::Left(l))
114 } else if let Ok(r) = obj.extract::<R>() {
115 Ok(Either::Right(r))
116 } else {
117 let err_msg = format!(
120 "failed to convert the value to 'Union[{}, {}]'",
121 std::any::type_name::<L>(),
122 std::any::type_name::<R>()
123 );
124 Err(PyTypeError::new_err(err_msg))
125 }
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use std::borrow::Cow;
132
133 use crate::exceptions::PyTypeError;
134 use crate::{IntoPyObject, Python};
135
136 use crate::types::PyAnyMethods;
137 use either::Either;
138
139 #[test]
140 fn test_either_conversion() {
141 type E = Either<i32, String>;
142 type E1 = Either<i32, f32>;
143 type E2 = Either<f32, i32>;
144
145 Python::attach(|py| {
146 let l = E::Left(42);
147 let obj_l = (&l).into_pyobject(py).unwrap();
148 assert_eq!(obj_l.extract::<i32>().unwrap(), 42);
149 assert_eq!(obj_l.extract::<E>().unwrap(), l);
150
151 let r = E::Right("foo".to_owned());
152 let obj_r = (&r).into_pyobject(py).unwrap();
153 assert_eq!(obj_r.extract::<Cow<'_, str>>().unwrap(), "foo");
154 assert_eq!(obj_r.extract::<E>().unwrap(), r);
155
156 let obj_s = "foo".into_pyobject(py).unwrap();
157 let err = obj_s.extract::<E1>().unwrap_err();
158 assert!(err.is_instance_of::<PyTypeError>(py));
159 assert_eq!(
160 err.to_string(),
161 "TypeError: failed to convert the value to 'Union[i32, f32]'"
162 );
163
164 let obj_i = 42i32.into_pyobject(py).unwrap();
165 assert_eq!(obj_i.extract::<E1>().unwrap(), E1::Left(42));
166 assert_eq!(obj_i.extract::<E2>().unwrap(), E2::Left(42.0));
167
168 let obj_f = 42.0f64.into_pyobject(py).unwrap();
169 assert_eq!(obj_f.extract::<E1>().unwrap(), E1::Right(42.0));
170 assert_eq!(obj_f.extract::<E2>().unwrap(), E2::Left(42.0));
171 });
172 }
173}