1use std::borrow::{Cow, ToOwned};
4
5use pyo3::{
6 conversion::{FromPyObjectBound, IntoPyObjectExt as _},
7 exceptions::PyTypeError,
8 prelude::*,
9 types::{PyDict, PyString},
10};
11
12#[derive(Debug, Clone, Copy)]
16pub struct NotRequired<T>(pub Option<T>);
17
18impl<T> Default for NotRequired<T> {
20 fn default() -> Self {
21 NotRequired(None)
22 }
23}
24
25impl<'py, T> FromPyObject<'py> for NotRequired<T>
26where
27 for<'a, 'py_a> T: FromPyObjectBound<'a, 'py_a>,
28{
29 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
30 let value = ob.extract::<T>()?;
31 Ok(NotRequired(Some(value)))
32 }
33}
34
35impl<'py, T> NotRequired<T>
36where
37 for<'a> &'a T: IntoPyObject<'py>,
38 T: IntoPyObject<'py>,
39 Self: ToOwned<Owned = Self>,
43 {
45 pub fn into_py_with(
50 f: impl FnOnce(Python<'py>) -> PyResult<Bound<'py, PyAny>>,
51 ) -> impl FnOnce(Cow<'_, Self>, Python<'py>) -> PyResult<Bound<'py, PyAny>> {
52 move |value, py| match value {
53 Cow::Borrowed(v) => match &v.0 {
54 Some(inner) => inner.into_bound_py_any(py),
55 None => f(py),
56 },
57 Cow::Owned(v) => match v.0 {
58 Some(inner) => inner.into_bound_py_any(py),
59 None => f(py),
60 },
61 }
62 }
63
64 #[inline]
65 pub fn into_py_with_none(slf: Cow<'_, Self>, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
67 fn none(py: Python<'_>) -> PyResult<Bound<'_, PyAny>> {
68 Ok(py.None().into_bound(py))
69 }
70 Self::into_py_with(none)(slf, py)
71 }
72
73 #[inline]
74 pub fn into_py_with_default(slf: Cow<'_, Self>, py: Python<'py>) -> PyResult<Bound<'py, PyAny>>
76 where
77 T: Default,
78 {
79 fn default<'py, T>(py: Python<'py>) -> PyResult<Bound<'py, PyAny>>
80 where
81 T: Default + IntoPyObject<'py>,
82 {
83 T::default().into_bound_py_any(py)
84 }
85 Self::into_py_with(default::<'py, T>)(slf, py)
86 }
87
88 #[inline]
89 pub fn into_py_with_err(slf: Cow<'_, Self>, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
91 fn not_required_into_pyobject_err(py: Python<'_>) -> PyResult<Bound<'_, PyAny>> {
92 const NOT_REQUIRED_INTO_PYOBJECT_ERR: &str =
93 "`NotRequired` value does not exist, cannot convert to PyObject";
94
95 Err(PyTypeError::new_err(
96 pyo3::intern!(py, NOT_REQUIRED_INTO_PYOBJECT_ERR)
97 .clone()
98 .unbind(),
99 ))
100 }
101
102 Self::into_py_with(not_required_into_pyobject_err)(slf, py)
103 }
104}
105
106pub trait FromPyDict: Sized {
109 fn from_py_dict(dict: &Bound<'_, PyDict>) -> PyResult<Self>;
110}
111
112#[doc(hidden)]
113pub fn __get_item_with_default<T>(
114 dict: &Bound<'_, PyDict>,
115 key: &Bound<'_, PyString>,
116) -> PyResult<T>
117where
118 for<'a, 'py> T: FromPyObjectBound<'a, 'py> + Default,
119{
120 let value = match dict.get_item(key)? {
121 Some(value) => value.extract::<T>()?,
122 None => Default::default(),
123 };
124 Ok(value)
125}
126
127#[doc(hidden)]
128pub fn __get_item<T>(dict: &Bound<'_, PyDict>, key: &Bound<'_, PyString>) -> PyResult<T>
129where
130 for<'a, 'py> T: FromPyObjectBound<'a, 'py>,
131{
132 let value = dict.as_any().get_item(key)?.extract::<T>()?;
133 Ok(value)
134}
135
136#[doc(hidden)]
138pub fn __failed_to_extract_struct_field<T>(
139 py: Python<'_>,
140 result: PyResult<T>,
141 struct_name: &'static str,
142 field_name: &'static str,
143) -> PyResult<T> {
144 result.map_err(|err| {
145 let new_err = PyTypeError::new_err(format!(
146 "failed to extract field {struct_name}.{field_name}"
147 ));
148 new_err.set_cause(py, Some(err));
149 new_err
150 })
151}
152
153#[macro_export]
222macro_rules! __derive_from_py_dict {
223 ($dict:expr, $key:expr, #) => {
224 $crate::from_py_dict::__get_item($dict, $key)
225 };
226 ($dict:expr, $key:expr, #default) => {
227 $crate::from_py_dict::__get_item_with_default($dict, $key)
228 };
229 ($dict:expr, $key:expr, #$attribute:ident) => {
230 compile_error!(concat!(
231 "Invalid attribute: #[pyo3(",
232 stringify!($attribute),
233 ")]. Only the optional `#[pyo3(default)]` attribute is accepted."
234 ))
235 };
236
237 (
238 $name:ty {
239 $(
240 $( #[cfg($cfg_meta:meta)] )?
241 $( #[pyo3($pyo3_meta:ident)] )?
242 $field:ident,
243 )*
244 }
245 ) => {
246 impl $crate::from_py_dict::FromPyDict for $name {
247 fn from_py_dict(dict: &::pyo3::Bound<'_, ::pyo3::types::PyDict>) -> ::pyo3::PyResult<Self> {
248 use $name as __name;
249 Ok(__name {
250 $(
251 $( #[cfg($cfg_meta)] )*
252 $field: $crate::from_py_dict::__failed_to_extract_struct_field(
253 dict.py(),
254 {
255 let key = ::pyo3::intern!(dict.py(), stringify!($field));
256 $crate::from_py_dict::derive_from_py_dict!(dict, key, #$($pyo3_meta)?)
257 },
258 stringify!($name),
259 stringify!($field),
260 )?,
261 )*
262 })
263 }
264 }
265 };
266}
267
268pub use __derive_from_py_dict as derive_from_py_dict;