pyo3/conversions/
either.rs

1#![cfg(feature = "either")]
2
3//! Conversion to/from
4//! [either](https://docs.rs/either/ "A library for easy idiomatic error handling and reporting in Rust applications")’s
5//! [`Either`] type to a union of two Python types.
6//!
7//! Use of a generic sum type like [either] is common when you want to either accept one of two possible
8//! types as an argument or return one of two possible types from a function, without having to define
9//! a helper type manually yourself.
10//!
11//! # Setup
12//!
13//! To use this feature, add this to your **`Cargo.toml`**:
14//!
15//! ```toml
16//! [dependencies]
17//! ## change * to the version you want to use, ideally the latest.
18//! either = "*"
19#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"),  "\", features = [\"either\"] }")]
20//! ```
21//!
22//! Note that you must use compatible versions of either and PyO3.
23//! The required either version may vary based on the version of PyO3.
24//!
25//! # Example: Convert a `int | str` to `Either<i32, String>`.
26//!
27//! ```rust
28//! use either::Either;
29//! use pyo3::{Python, PyResult, IntoPyObject, types::PyAnyMethods};
30//!
31//! fn main() -> PyResult<()> {
32//!     pyo3::prepare_freethreaded_python();
33//!     Python::with_gil(|py| {
34//!         // Create a string and an int in Python.
35//!         let py_str = "crab".into_pyobject(py)?;
36//!         let py_int = 42i32.into_pyobject(py)?;
37//!         // Now convert it to an Either<i32, String>.
38//!         let either_str: Either<i32, String> = py_str.extract()?;
39//!         let either_int: Either<i32, String> = py_int.extract()?;
40//!         Ok(())
41//!     })
42//! }
43//! ```
44//!
45//! [either](https://docs.rs/either/ "A library for easy idiomatic error handling and reporting in Rust applications")’s
46
47#[cfg(feature = "experimental-inspect")]
48use crate::inspect::types::TypeInfo;
49use crate::{
50    exceptions::PyTypeError, types::any::PyAnyMethods, Bound, FromPyObject, IntoPyObject,
51    IntoPyObjectExt, PyAny, PyErr, PyObject, PyResult, Python,
52};
53#[allow(deprecated)]
54use crate::{IntoPy, ToPyObject};
55use either::Either;
56
57#[cfg_attr(docsrs, doc(cfg(feature = "either")))]
58#[allow(deprecated)]
59impl<L, R> IntoPy<PyObject> for Either<L, R>
60where
61    L: IntoPy<PyObject>,
62    R: IntoPy<PyObject>,
63{
64    #[inline]
65    fn into_py(self, py: Python<'_>) -> PyObject {
66        match self {
67            Either::Left(l) => l.into_py(py),
68            Either::Right(r) => r.into_py(py),
69        }
70    }
71}
72
73#[cfg_attr(docsrs, doc(cfg(feature = "either")))]
74impl<'py, L, R> IntoPyObject<'py> for Either<L, R>
75where
76    L: IntoPyObject<'py>,
77    R: IntoPyObject<'py>,
78{
79    type Target = PyAny;
80    type Output = Bound<'py, Self::Target>;
81    type Error = PyErr;
82
83    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
84        match self {
85            Either::Left(l) => l.into_bound_py_any(py),
86            Either::Right(r) => r.into_bound_py_any(py),
87        }
88    }
89}
90
91#[cfg_attr(docsrs, doc(cfg(feature = "either")))]
92impl<'a, 'py, L, R> IntoPyObject<'py> for &'a Either<L, R>
93where
94    &'a L: IntoPyObject<'py>,
95    &'a R: IntoPyObject<'py>,
96{
97    type Target = PyAny;
98    type Output = Bound<'py, Self::Target>;
99    type Error = PyErr;
100
101    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
102        match self {
103            Either::Left(l) => l.into_bound_py_any(py),
104            Either::Right(r) => r.into_bound_py_any(py),
105        }
106    }
107}
108
109#[cfg_attr(docsrs, doc(cfg(feature = "either")))]
110#[allow(deprecated)]
111impl<L, R> ToPyObject for Either<L, R>
112where
113    L: ToPyObject,
114    R: ToPyObject,
115{
116    #[inline]
117    fn to_object(&self, py: Python<'_>) -> PyObject {
118        match self {
119            Either::Left(l) => l.to_object(py),
120            Either::Right(r) => r.to_object(py),
121        }
122    }
123}
124
125#[cfg_attr(docsrs, doc(cfg(feature = "either")))]
126impl<'py, L, R> FromPyObject<'py> for Either<L, R>
127where
128    L: FromPyObject<'py>,
129    R: FromPyObject<'py>,
130{
131    #[inline]
132    fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult<Self> {
133        if let Ok(l) = obj.extract::<L>() {
134            Ok(Either::Left(l))
135        } else if let Ok(r) = obj.extract::<R>() {
136            Ok(Either::Right(r))
137        } else {
138            // TODO: it might be nice to use the `type_input()` name here once `type_input`
139            // is not experimental, rather than the Rust type names.
140            let err_msg = format!(
141                "failed to convert the value to 'Union[{}, {}]'",
142                std::any::type_name::<L>(),
143                std::any::type_name::<R>()
144            );
145            Err(PyTypeError::new_err(err_msg))
146        }
147    }
148
149    #[cfg(feature = "experimental-inspect")]
150    fn type_input() -> TypeInfo {
151        TypeInfo::union_of(&[L::type_input(), R::type_input()])
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use std::borrow::Cow;
158
159    use crate::exceptions::PyTypeError;
160    use crate::{IntoPyObject, Python};
161
162    use crate::types::PyAnyMethods;
163    use either::Either;
164
165    #[test]
166    fn test_either_conversion() {
167        type E = Either<i32, String>;
168        type E1 = Either<i32, f32>;
169        type E2 = Either<f32, i32>;
170
171        Python::with_gil(|py| {
172            let l = E::Left(42);
173            let obj_l = (&l).into_pyobject(py).unwrap();
174            assert_eq!(obj_l.extract::<i32>().unwrap(), 42);
175            assert_eq!(obj_l.extract::<E>().unwrap(), l);
176
177            let r = E::Right("foo".to_owned());
178            let obj_r = (&r).into_pyobject(py).unwrap();
179            assert_eq!(obj_r.extract::<Cow<'_, str>>().unwrap(), "foo");
180            assert_eq!(obj_r.extract::<E>().unwrap(), r);
181
182            let obj_s = "foo".into_pyobject(py).unwrap();
183            let err = obj_s.extract::<E1>().unwrap_err();
184            assert!(err.is_instance_of::<PyTypeError>(py));
185            assert_eq!(
186                err.to_string(),
187                "TypeError: failed to convert the value to 'Union[i32, f32]'"
188            );
189
190            let obj_i = 42i32.into_pyobject(py).unwrap();
191            assert_eq!(obj_i.extract::<E1>().unwrap(), E1::Left(42));
192            assert_eq!(obj_i.extract::<E2>().unwrap(), E2::Left(42.0));
193
194            let obj_f = 42.0f64.into_pyobject(py).unwrap();
195            assert_eq!(obj_f.extract::<E1>().unwrap(), E1::Right(42.0));
196            assert_eq!(obj_f.extract::<E2>().unwrap(), E2::Left(42.0));
197        });
198    }
199}