use std::borrow::{Cow, ToOwned};
use pyo3::{
conversion::{FromPyObjectBound, IntoPyObjectExt as _},
exceptions::PyTypeError,
prelude::*,
types::{PyDict, PyString},
};
#[derive(Debug, Clone, Copy)]
pub struct NotRequired<T>(pub Option<T>);
impl<T> Default for NotRequired<T> {
fn default() -> Self {
NotRequired(None)
}
}
impl<'py, T> FromPyObject<'py> for NotRequired<T>
where
for<'a, 'py_a> T: FromPyObjectBound<'a, 'py_a>,
{
fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
let value = ob.extract::<T>()?;
Ok(NotRequired(Some(value)))
}
}
impl<'py, T> NotRequired<T>
where
for<'a> &'a T: IntoPyObject<'py>,
T: IntoPyObject<'py>,
Self: ToOwned<Owned = Self>,
{
pub fn into_py_with(
f: impl FnOnce(Python<'py>) -> PyResult<Bound<'py, PyAny>>,
) -> impl FnOnce(Cow<'_, Self>, Python<'py>) -> PyResult<Bound<'py, PyAny>> {
move |value, py| match value {
Cow::Borrowed(v) => match &v.0 {
Some(inner) => inner.into_bound_py_any(py),
None => f(py),
},
Cow::Owned(v) => match v.0 {
Some(inner) => inner.into_bound_py_any(py),
None => f(py),
},
}
}
#[inline]
pub fn into_py_with_none(slf: Cow<'_, Self>, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
fn none(py: Python<'_>) -> PyResult<Bound<'_, PyAny>> {
Ok(py.None().into_bound(py))
}
Self::into_py_with(none)(slf, py)
}
#[inline]
pub fn into_py_with_default(slf: Cow<'_, Self>, py: Python<'py>) -> PyResult<Bound<'py, PyAny>>
where
T: Default,
{
fn default<'py, T>(py: Python<'py>) -> PyResult<Bound<'py, PyAny>>
where
T: Default + IntoPyObject<'py>,
{
T::default().into_bound_py_any(py)
}
Self::into_py_with(default::<'py, T>)(slf, py)
}
#[inline]
pub fn into_py_with_err(slf: Cow<'_, Self>, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
fn not_required_into_pyobject_err(py: Python<'_>) -> PyResult<Bound<'_, PyAny>> {
const NOT_REQUIRED_INTO_PYOBJECT_ERR: &str =
"`NotRequired` value does not exist, cannot convert to PyObject";
Err(PyTypeError::new_err(
pyo3::intern!(py, NOT_REQUIRED_INTO_PYOBJECT_ERR)
.clone()
.unbind(),
))
}
Self::into_py_with(not_required_into_pyobject_err)(slf, py)
}
}
pub trait FromPyDict: Sized {
fn from_py_dict(dict: &Bound<'_, PyDict>) -> PyResult<Self>;
}
#[doc(hidden)]
pub fn __get_item_with_default<T>(
dict: &Bound<'_, PyDict>,
key: &Bound<'_, PyString>,
) -> PyResult<T>
where
for<'a, 'py> T: FromPyObjectBound<'a, 'py> + Default,
{
let value = match dict.get_item(key)? {
Some(value) => value.extract::<T>()?,
None => Default::default(),
};
Ok(value)
}
#[doc(hidden)]
pub fn __get_item<T>(dict: &Bound<'_, PyDict>, key: &Bound<'_, PyString>) -> PyResult<T>
where
for<'a, 'py> T: FromPyObjectBound<'a, 'py>,
{
let value = dict.as_any().get_item(key)?.extract::<T>()?;
Ok(value)
}
#[doc(hidden)]
pub fn __failed_to_extract_struct_field<T>(
py: Python<'_>,
result: PyResult<T>,
struct_name: &'static str,
field_name: &'static str,
) -> PyResult<T> {
result.map_err(|err| {
let new_err = PyTypeError::new_err(format!(
"failed to extract field {struct_name}.{field_name}"
));
new_err.set_cause(py, Some(err));
new_err
})
}
#[macro_export]
macro_rules! __derive_from_py_dict {
($dict:expr, $key:expr, #) => {
$crate::from_py_dict::__get_item($dict, $key)
};
($dict:expr, $key:expr, #default) => {
$crate::from_py_dict::__get_item_with_default($dict, $key)
};
($dict:expr, $key:expr, #$attribute:ident) => {
compile_error!(concat!(
"Invalid attribute: #[pyo3(",
stringify!($attribute),
")]. Only the optional `#[pyo3(default)]` attribute is accepted."
))
};
(
$name:ty {
$(
$( #[cfg($cfg_meta:meta)] )?
$( #[pyo3($pyo3_meta:ident)] )?
$field:ident,
)*
}
) => {
impl $crate::from_py_dict::FromPyDict for $name {
fn from_py_dict(dict: &::pyo3::Bound<'_, ::pyo3::types::PyDict>) -> ::pyo3::PyResult<Self> {
use $name as __name;
Ok(__name {
$(
$( #[cfg($cfg_meta)] )*
$field: $crate::from_py_dict::__failed_to_extract_struct_field(
dict.py(),
{
let key = ::pyo3::intern!(dict.py(), stringify!($field));
$crate::from_py_dict::derive_from_py_dict!(dict, key, #$($pyo3_meta)?)
},
stringify!($name),
stringify!($field),
)?,
)*
})
}
}
};
}
pub use __derive_from_py_dict as derive_from_py_dict;