--- a/src/direct_product.rs Sun May 18 19:56:28 2025 -0500 +++ b/src/direct_product.rs Sun May 18 23:15:50 2025 -0500 @@ -566,3 +566,63 @@ Pair(self.0.dual_origin(), self.1.dual_origin()) } } + +#[cfg(feature = "pyo3")] +mod python { + use super::Pair; + use pyo3::conversion::FromPyObject; + use pyo3::types::{PyAny, PyTuple}; + use pyo3::{Bound, IntoPyObject, PyErr, PyResult, Python}; + + impl<'py, A, B> IntoPyObject<'py> for Pair<A, B> + where + A: IntoPyObject<'py>, + B: IntoPyObject<'py>, + { + type Target = PyTuple; + type Error = PyErr; + type Output = Bound<'py, Self::Target>; + + fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> { + (self.0, self.1).into_pyobject(py) + } + } + + impl<'a, 'py, A, B> IntoPyObject<'py> for &'a mut Pair<A, B> + where + &'a mut A: IntoPyObject<'py>, + &'a mut B: IntoPyObject<'py>, + { + type Target = PyTuple; + type Error = PyErr; + type Output = Bound<'py, Self::Target>; + + fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> { + (&mut self.0, &mut self.1).into_pyobject(py) + } + } + + impl<'a, 'py, A, B> IntoPyObject<'py> for &'a Pair<A, B> + where + &'a A: IntoPyObject<'py>, + &'a B: IntoPyObject<'py>, + { + type Target = PyTuple; + type Error = PyErr; + type Output = Bound<'py, Self::Target>; + + fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> { + (&self.0, &self.1).into_pyobject(py) + } + } + + impl<'py, A, B> FromPyObject<'py> for Pair<A, B> + where + A: Clone + FromPyObject<'py>, + B: Clone + FromPyObject<'py>, + { + fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> { + FromPyObject::extract_bound(ob).map(|(a, b)| Pair(a, b)) + } + } +}