-
Notifications
You must be signed in to change notification settings - Fork 778
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3456 from aldanor/feature/either
Add conversion support for `either::Either`
- Loading branch information
Showing
6 changed files
with
159 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Add optional conversion support for `either::Either<L, R>` sum type (under "either" feature). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
#![cfg(feature = "either")] | ||
|
||
//! Conversion to/from | ||
//! [either](https://docs.rs/either/ "A library for easy idiomatic error handling and reporting in Rust applications")’s | ||
//! [`Either`] type to a union of two Python types. | ||
//! | ||
//! Use of a generic sum type like [either] is common when you want to either accept one of two possible | ||
//! types as an argument or return one of two possible types from a function, without having to define | ||
//! a helper type manually yourself. | ||
//! | ||
//! # Setup | ||
//! | ||
//! To use this feature, add this to your **`Cargo.toml`**: | ||
//! | ||
//! ```toml | ||
//! [dependencies] | ||
//! ## change * to the version you want to use, ideally the latest. | ||
//! either = "*" | ||
#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"either\"] }")] | ||
//! ``` | ||
//! | ||
//! Note that you must use compatible versions of either and PyO3. | ||
//! The required either version may vary based on the version of PyO3. | ||
//! | ||
//! # Example: Convert a `int | str` to `Either<i32, String>`. | ||
//! | ||
//! ```rust | ||
//! use either::Either; | ||
//! use pyo3::{Python, ToPyObject}; | ||
//! | ||
//! fn main() { | ||
//! pyo3::prepare_freethreaded_python(); | ||
//! Python::with_gil(|py| { | ||
//! // Create a string and an int in Python. | ||
//! let py_str = "crab".to_object(py); | ||
//! let py_int = 42.to_object(py); | ||
//! // Now convert it to an Either<i32, String>. | ||
//! let either_str: Either<i32, String> = py_str.extract(py).unwrap(); | ||
//! let either_int: Either<i32, String> = py_int.extract(py).unwrap(); | ||
//! }); | ||
//! } | ||
//! ``` | ||
//! | ||
//! [either](https://docs.rs/either/ "A library for easy idiomatic error handling and reporting in Rust applications")’s | ||
use crate::{ | ||
exceptions::PyTypeError, inspect::types::TypeInfo, FromPyObject, IntoPy, PyAny, PyObject, | ||
PyResult, Python, ToPyObject, | ||
}; | ||
use either::Either; | ||
|
||
#[cfg_attr(docsrs, doc(cfg(feature = "either")))] | ||
impl<L, R> IntoPy<PyObject> for Either<L, R> | ||
where | ||
L: IntoPy<PyObject>, | ||
R: IntoPy<PyObject>, | ||
{ | ||
#[inline] | ||
fn into_py(self, py: Python<'_>) -> PyObject { | ||
match self { | ||
Either::Left(l) => l.into_py(py), | ||
Either::Right(r) => r.into_py(py), | ||
} | ||
} | ||
} | ||
|
||
#[cfg_attr(docsrs, doc(cfg(feature = "either")))] | ||
impl<L, R> ToPyObject for Either<L, R> | ||
where | ||
L: ToPyObject, | ||
R: ToPyObject, | ||
{ | ||
#[inline] | ||
fn to_object(&self, py: Python<'_>) -> PyObject { | ||
match self { | ||
Either::Left(l) => l.to_object(py), | ||
Either::Right(r) => r.to_object(py), | ||
} | ||
} | ||
} | ||
|
||
#[cfg_attr(docsrs, doc(cfg(feature = "either")))] | ||
impl<'source, L, R> FromPyObject<'source> for Either<L, R> | ||
where | ||
L: FromPyObject<'source>, | ||
R: FromPyObject<'source>, | ||
{ | ||
#[inline] | ||
fn extract(obj: &'source PyAny) -> PyResult<Self> { | ||
if let Ok(l) = obj.extract::<L>() { | ||
Ok(Either::Left(l)) | ||
} else if let Ok(r) = obj.extract::<R>() { | ||
Ok(Either::Right(r)) | ||
} else { | ||
let err_msg = format!("failed to convert the value to '{}'", Self::type_input()); | ||
Err(PyTypeError::new_err(err_msg)) | ||
} | ||
} | ||
|
||
fn type_input() -> TypeInfo { | ||
TypeInfo::union_of(&[L::type_input(), R::type_input()]) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use crate::exceptions::PyTypeError; | ||
use crate::{Python, ToPyObject}; | ||
|
||
use either::Either; | ||
|
||
#[test] | ||
fn test_either_conversion() { | ||
type E = Either<i32, String>; | ||
type E1 = Either<i32, f32>; | ||
type E2 = Either<f32, i32>; | ||
|
||
Python::with_gil(|py| { | ||
let l = E::Left(42); | ||
let obj_l = l.to_object(py); | ||
assert_eq!(obj_l.extract::<i32>(py).unwrap(), 42); | ||
assert_eq!(obj_l.extract::<E>(py).unwrap(), l); | ||
|
||
let r = E::Right("foo".to_owned()); | ||
let obj_r = r.to_object(py); | ||
assert_eq!(obj_r.extract::<&str>(py).unwrap(), "foo"); | ||
assert_eq!(obj_r.extract::<E>(py).unwrap(), r); | ||
|
||
let obj_s = "foo".to_object(py); | ||
let err = obj_s.extract::<E1>(py).unwrap_err(); | ||
assert!(err.is_instance_of::<PyTypeError>(py)); | ||
assert_eq!( | ||
err.to_string(), | ||
"TypeError: failed to convert the value to 'Union[int, float]'" | ||
); | ||
|
||
let obj_i = 42.to_object(py); | ||
assert_eq!(obj_i.extract::<E1>(py).unwrap(), E1::Left(42)); | ||
assert_eq!(obj_i.extract::<E2>(py).unwrap(), E2::Left(42.0)); | ||
|
||
let obj_f = 42.0.to_object(py); | ||
assert_eq!(obj_f.extract::<E1>(py).unwrap(), E1::Right(42.0)); | ||
assert_eq!(obj_f.extract::<E2>(py).unwrap(), E2::Left(42.0)); | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
pub mod anyhow; | ||
pub mod chrono; | ||
pub mod either; | ||
pub mod eyre; | ||
pub mod hashbrown; | ||
pub mod indexmap; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters