Merge pull request #3456 from aldanor/feature/either
Add conversion support for `either::Either`
This commit is contained in:
commit
1203921d5c
|
@ -37,6 +37,7 @@ futures-util = "0.3"
|
|||
# crate integrations that can be added using the eponymous features
|
||||
anyhow = { version = "1.0", optional = true }
|
||||
chrono = { version = "0.4.25", default-features = false, optional = true }
|
||||
either = { version = "1.9", optional = true }
|
||||
eyre = { version = ">= 0.4, < 0.7", optional = true }
|
||||
hashbrown = { version = ">= 0.9, < 0.15", optional = true }
|
||||
indexmap = { version = ">= 1.6, < 3", optional = true }
|
||||
|
@ -111,6 +112,7 @@ full = [
|
|||
"smallvec",
|
||||
"serde",
|
||||
"indexmap",
|
||||
"either",
|
||||
"eyre",
|
||||
"anyhow",
|
||||
"experimental-inspect",
|
||||
|
@ -129,7 +131,7 @@ members = [
|
|||
|
||||
[package.metadata.docs.rs]
|
||||
no-default-features = true
|
||||
features = ["macros", "num-bigint", "num-complex", "hashbrown", "serde", "multiple-pymethods", "indexmap", "eyre", "chrono", "rust_decimal"]
|
||||
features = ["macros", "num-bigint", "num-complex", "hashbrown", "serde", "multiple-pymethods", "indexmap", "eyre", "either", "chrono", "rust_decimal"]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[workspace.lints.clippy]
|
||||
|
|
|
@ -109,6 +109,10 @@ Adds a dependency on [chrono](https://docs.rs/chrono). Enables a conversion from
|
|||
- [NaiveTime](https://docs.rs/chrono/latest/chrono/naive/struct.NaiveTime.html) -> [`PyTime`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyTime.html)
|
||||
- [DateTime](https://docs.rs/chrono/latest/chrono/struct.DateTime.html) -> [`PyDateTime`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyDateTime.html)
|
||||
|
||||
### `either`
|
||||
|
||||
Adds a dependency on [either](https://docs.rs/either). Enables a conversions into [either](https://docs.rs/either)’s [`Either`](https://docs.rs/either/latest/either/struct.Report.html) type.
|
||||
|
||||
### `eyre`
|
||||
|
||||
Adds a dependency on [eyre](https://docs.rs/eyre). Enables a conversion from [eyre](https://docs.rs/eyre)’s [`Report`](https://docs.rs/eyre/latest/eyre/struct.Report.html) type to [`PyErr`]({{#PYO3_DOCS_URL}}/pyo3/struct.PyErr.html), for easy error handling.
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Add optional conversion support for `either::Either<L, R>` sum type (under "either" feature).
|
|
@ -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));
|
||||
});
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
pub mod anyhow;
|
||||
pub mod chrono;
|
||||
pub mod either;
|
||||
pub mod eyre;
|
||||
pub mod hashbrown;
|
||||
pub mod indexmap;
|
||||
|
|
|
@ -82,6 +82,7 @@
|
|||
//! The following features enable interactions with other crates in the Rust ecosystem:
|
||||
//! - [`anyhow`]: Enables a conversion from [anyhow]’s [`Error`][anyhow_error] type to [`PyErr`].
|
||||
//! - [`chrono`]: Enables a conversion from [chrono]'s structures to the equivalent Python ones.
|
||||
//! - [`either`]: Enables conversions between Python objects and [either]'s [`Either`] type.
|
||||
//! - [`eyre`]: Enables a conversion from [eyre]’s [`Report`] type to [`PyErr`].
|
||||
//! - [`hashbrown`]: Enables conversions between Python objects and [hashbrown]'s [`HashMap`] and
|
||||
//! [`HashSet`] types.
|
||||
|
@ -257,6 +258,9 @@
|
|||
//! [`Serialize`]: https://docs.rs/serde/latest/serde/trait.Serialize.html
|
||||
//! [chrono]: https://docs.rs/chrono/ "Date and Time for Rust."
|
||||
//! [`chrono`]: ./chrono/index.html "Documentation about the `chrono` feature."
|
||||
//! [either]: https://docs.rs/either/ "A type that represents one of two alternatives."
|
||||
//! [`either`]: ./either/index.html "Documentation about the `either` feature."
|
||||
//! [`Either`]: https://docs.rs/either/latest/either/enum.Either.html
|
||||
//! [eyre]: https://docs.rs/eyre/ "A library for easy idiomatic error handling and reporting in Rust applications."
|
||||
//! [`Report`]: https://docs.rs/eyre/latest/eyre/struct.Report.html
|
||||
//! [`eyre`]: ./eyre/index.html "Documentation about the `eyre` feature."
|
||||
|
|
Loading…
Reference in New Issue