diff --git a/src/conversion.rs b/src/conversion.rs index 02afb27725e..287c9ff6399 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -193,15 +193,48 @@ pub trait IntoPyObject<'py>: Sized { fn into_pyobject(self, py: Python<'py>) -> Result, Self::Error>; } -impl<'py, T> IntoPyObject<'py> for &'_ T -where - T: Copy + IntoPyObject<'py>, -{ - type Target = T::Target; - type Error = T::Error; +impl<'py, T> IntoPyObject<'py> for Bound<'py, T> { + type Target = T; + type Error = Infallible; + + fn into_pyobject(self, _py: Python<'py>) -> Result, Self::Error> { + Ok(self) + } +} + +impl<'py, T> IntoPyObject<'py> for &Bound<'py, T> { + type Target = T; + type Error = Infallible; + + fn into_pyobject(self, _py: Python<'py>) -> Result, Self::Error> { + Ok(self.clone()) + } +} + +impl<'py, T> IntoPyObject<'py> for Borrowed<'_, 'py, T> { + type Target = T; + type Error = Infallible; + + fn into_pyobject(self, _py: Python<'py>) -> Result, Self::Error> { + Ok(self.to_owned()) + } +} + +impl<'py, T> IntoPyObject<'py> for Py { + type Target = T; + type Error = Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result, Self::Error> { + Ok(self.into_bound(py)) + } +} + +impl<'py, T> IntoPyObject<'py> for &Py { + type Target = T; + type Error = Infallible; fn into_pyobject(self, py: Python<'py>) -> Result, Self::Error> { - (*self).into_pyobject(py) + Ok(self.bind(py).clone()) } } diff --git a/src/conversions/either.rs b/src/conversions/either.rs index 84ec88ea009..060971f5ffa 100644 --- a/src/conversions/either.rs +++ b/src/conversions/either.rs @@ -46,8 +46,8 @@ #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; use crate::{ - exceptions::PyTypeError, types::any::PyAnyMethods, Bound, FromPyObject, IntoPy, PyAny, - PyObject, PyResult, Python, ToPyObject, + conversion::IntoPyObject, exceptions::PyTypeError, types::any::PyAnyMethods, Bound, + FromPyObject, IntoPy, PyAny, PyObject, PyResult, Python, ToPyObject, }; use either::Either; @@ -66,6 +66,23 @@ where } } +#[cfg_attr(docsrs, doc(cfg(feature = "either")))] +impl<'py, L, R, T, E> IntoPyObject<'py> for Either +where + L: IntoPyObject<'py, Target = T, Error = E>, + R: IntoPyObject<'py, Target = T, Error = E>, +{ + type Target = T; + type Error = E; + + fn into_pyobject(self, py: Python<'py>) -> Result, Self::Error> { + match self { + Either::Left(l) => l.into_pyobject(py), + Either::Right(r) => r.into_pyobject(py), + } + } +} + #[cfg_attr(docsrs, doc(cfg(feature = "either")))] impl ToPyObject for Either where diff --git a/src/conversions/hashbrown.rs b/src/conversions/hashbrown.rs index 9eea7734bfc..22bbafb0134 100644 --- a/src/conversions/hashbrown.rs +++ b/src/conversions/hashbrown.rs @@ -17,11 +17,14 @@ //! Note that you must use compatible versions of hashbrown and PyO3. //! The required hashbrown version may vary based on the version of PyO3. use crate::{ - types::any::PyAnyMethods, - types::dict::PyDictMethods, - types::frozenset::PyFrozenSetMethods, - types::set::{new_from_iter, PySetMethods}, - types::{IntoPyDict, PyDict, PyFrozenSet, PySet}, + conversion::IntoPyObject, + types::{ + any::PyAnyMethods, + dict::PyDictMethods, + frozenset::PyFrozenSetMethods, + set::{new_from_iter, try_new_from_iter, PySetMethods}, + IntoPyDict, PyDict, PyFrozenSet, PySet, + }, Bound, FromPyObject, IntoPy, PyAny, PyErr, PyObject, PyResult, Python, ToPyObject, }; use std::{cmp, hash}; @@ -51,6 +54,25 @@ where } } +impl<'py, K, V, H> IntoPyObject<'py> for hashbrown::HashMap +where + K: hash::Hash + cmp::Eq + IntoPyObject<'py>, + V: IntoPyObject<'py>, + H: hash::BuildHasher, + PyErr: From + From, +{ + type Target = PyDict; + type Error = PyErr; + + fn into_pyobject(self, py: Python<'py>) -> Result, Self::Error> { + let dict = PyDict::new_bound(py); + for (k, v) in self { + dict.set_item(k.into_pyobject(py)?, v.into_pyobject(py)?)?; + } + Ok(dict) + } +} + impl<'py, K, V, S> FromPyObject<'py> for hashbrown::HashMap where K: FromPyObject<'py> + cmp::Eq + hash::Hash, @@ -90,6 +112,28 @@ where } } +impl<'py, K, H> IntoPyObject<'py> for hashbrown::HashSet +where + K: hash::Hash + cmp::Eq + IntoPyObject<'py>, + H: hash::BuildHasher, + PyErr: From, +{ + type Target = PySet; + type Error = PyErr; + + fn into_pyobject(self, py: Python<'py>) -> Result, Self::Error> { + try_new_from_iter( + py, + self.into_iter().map(|item| { + item.into_pyobject(py) + .map(Bound::into_any) + .map(Bound::unbind) + .map_err(Into::into) + }), + ) + } +} + impl<'py, K, S> FromPyObject<'py> for hashbrown::HashSet where K: FromPyObject<'py> + cmp::Eq + hash::Hash, diff --git a/src/conversions/indexmap.rs b/src/conversions/indexmap.rs index fdbe057f32d..fe0ba5ec187 100644 --- a/src/conversions/indexmap.rs +++ b/src/conversions/indexmap.rs @@ -87,6 +87,7 @@ //! # if another hash table was used, the order could be random //! ``` +use crate::conversion::IntoPyObject; use crate::types::*; use crate::{Bound, FromPyObject, IntoPy, PyErr, PyObject, Python, ToPyObject}; use std::{cmp, hash}; @@ -116,6 +117,25 @@ where } } +impl<'py, K, V, H> IntoPyObject<'py> for indexmap::IndexMap +where + K: hash::Hash + cmp::Eq + IntoPyObject<'py>, + V: IntoPyObject<'py>, + H: hash::BuildHasher, + PyErr: From + From, +{ + type Target = PyDict; + type Error = PyErr; + + fn into_pyobject(self, py: Python<'py>) -> Result, Self::Error> { + let dict = PyDict::new_bound(py); + for (k, v) in self { + dict.set_item(k.into_pyobject(py)?, v.into_pyobject(py)?)?; + } + Ok(dict) + } +} + impl<'py, K, V, S> FromPyObject<'py> for indexmap::IndexMap where K: FromPyObject<'py> + cmp::Eq + hash::Hash, diff --git a/src/conversions/num_bigint.rs b/src/conversions/num_bigint.rs index 196ae28e788..daf02f3f6fa 100644 --- a/src/conversions/num_bigint.rs +++ b/src/conversions/num_bigint.rs @@ -52,10 +52,11 @@ use crate::ffi_ptr_ext::FfiPtrExt; #[cfg(Py_LIMITED_API)] use crate::types::{bytes::PyBytesMethods, PyBytes}; use crate::{ + conversion::IntoPyObject, ffi, instance::Bound, types::{any::PyAnyMethods, PyLong}, - FromPyObject, IntoPy, Py, PyAny, PyObject, PyResult, Python, ToPyObject, + FromPyObject, IntoPy, Py, PyAny, PyErr, PyObject, PyResult, Python, ToPyObject, }; use num_bigint::{BigInt, BigUint}; @@ -133,6 +134,53 @@ macro_rules! bigint_conversion { self.to_object(py) } } + + #[cfg_attr(docsrs, doc(cfg(feature = "num-bigint")))] + impl<'py> IntoPyObject<'py> for $rust_ty { + type Target = PyLong; + type Error = PyErr; + + #[cfg(not(Py_LIMITED_API))] + fn into_pyobject( + self, + py: Python<'py>, + ) -> Result, Self::Error> { + use crate::ffi_ptr_ext::FfiPtrExt; + let bytes = $to_bytes(&self); + unsafe { + Ok(ffi::_PyLong_FromByteArray( + bytes.as_ptr().cast(), + bytes.len(), + 1, + $is_signed.into(), + ) + .assume_owned(py) + .downcast_into_unchecked()) + } + } + + #[cfg(Py_LIMITED_API)] + fn into_pyobject( + self, + py: Python<'py>, + ) -> Result, Self::Error> { + use $crate::py_result_ext::PyResultExt; + let bytes = $to_bytes(&self); + let bytes_obj = PyBytes::new_bound(py, &bytes); + let kwargs = if $is_signed { + let kwargs = crate::types::PyDict::new_bound(py); + kwargs.set_item(crate::intern!(py, "signed"), true)?; + Some(kwargs) + } else { + None + }; + unsafe { + py.get_type_bound::() + .call_method("from_bytes", (bytes_obj, "little"), kwargs.as_ref()) + .downcast_into_unchecked() + } + } + } }; } diff --git a/src/conversions/num_complex.rs b/src/conversions/num_complex.rs index 12f208aa8d1..8910a9efc9c 100644 --- a/src/conversions/num_complex.rs +++ b/src/conversions/num_complex.rs @@ -147,6 +147,25 @@ macro_rules! complex_conversion { } } + #[cfg_attr(docsrs, doc(cfg(feature = "num-complex")))] + impl<'py> crate::conversion::IntoPyObject<'py> for Complex<$float> { + type Target = PyComplex; + type Error = std::convert::Infallible; + + fn into_pyobject( + self, + py: Python<'py>, + ) -> Result, Self::Error> { + unsafe { + Ok( + ffi::PyComplex_FromDoubles(self.re as c_double, self.im as c_double) + .assume_owned(py) + .downcast_into_unchecked(), + ) + } + } + } + #[cfg_attr(docsrs, doc(cfg(feature = "num-complex")))] impl FromPyObject<'_> for Complex<$float> { fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult> { diff --git a/src/conversions/rust_decimal.rs b/src/conversions/rust_decimal.rs index 782ca2e80f0..326ef541ea5 100644 --- a/src/conversions/rust_decimal.rs +++ b/src/conversions/rust_decimal.rs @@ -49,12 +49,15 @@ //! assert d + 1 == value //! ``` +use crate::conversion::IntoPyObject; use crate::exceptions::PyValueError; use crate::sync::GILOnceCell; use crate::types::any::PyAnyMethods; use crate::types::string::PyStringMethods; use crate::types::PyType; -use crate::{Bound, FromPyObject, IntoPy, Py, PyAny, PyObject, PyResult, Python, ToPyObject}; +use crate::{ + Bound, FromPyObject, IntoPy, Py, PyAny, PyErr, PyObject, PyResult, Python, ToPyObject, +}; use rust_decimal::Decimal; use std::str::FromStr; @@ -99,6 +102,18 @@ impl IntoPy for Decimal { } } +impl<'py> IntoPyObject<'py> for Decimal { + type Target = PyAny; + type Error = PyErr; + + fn into_pyobject(self, py: Python<'py>) -> Result, Self::Error> { + let dec_cls = get_decimal_cls(py)?; + // now call the constructor with the Rust Decimal string-ified + // to not be lossy + dec_cls.call1((self.to_string(),)) + } +} + #[cfg(test)] mod test_rust_decimal { use super::*; diff --git a/src/conversions/smallvec.rs b/src/conversions/smallvec.rs index 96dbfad14b7..0f49c55c461 100644 --- a/src/conversions/smallvec.rs +++ b/src/conversions/smallvec.rs @@ -15,12 +15,14 @@ //! //! Note that you must use compatible versions of smallvec and PyO3. //! The required smallvec version may vary based on the version of PyO3. +use crate::conversion::IntoPyObject; use crate::exceptions::PyTypeError; #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; use crate::types::any::PyAnyMethods; -use crate::types::list::new_from_iter; -use crate::types::{PySequence, PyString}; +use crate::types::list::{new_from_iter, try_new_from_iter}; +use crate::types::{PyList, PySequence, PyString}; +use crate::PyErr; use crate::{ err::DowncastError, ffi, Bound, FromPyObject, IntoPy, PyAny, PyObject, PyResult, Python, ToPyObject, @@ -54,6 +56,26 @@ where } } +impl<'py, A> IntoPyObject<'py> for SmallVec +where + A: Array, + A::Item: IntoPyObject<'py>, + PyErr: From<>::Error>, +{ + type Target = PyList; + type Error = PyErr; + + fn into_pyobject(self, py: Python<'py>) -> Result, Self::Error> { + let mut iter = self.into_iter().map(|e| { + e.into_pyobject(py) + .map(Bound::into_any) + .map(Bound::unbind) + .map_err(Into::into) + }); + try_new_from_iter(py, &mut iter) + } +} + impl<'py, A> FromPyObject<'py> for SmallVec where A: Array, diff --git a/src/conversions/std/osstr.rs b/src/conversions/std/osstr.rs index 8616a11689c..5bc32bc8331 100644 --- a/src/conversions/std/osstr.rs +++ b/src/conversions/std/osstr.rs @@ -1,15 +1,27 @@ +use crate::conversion::IntoPyObject; +use crate::ffi_ptr_ext::FfiPtrExt; use crate::instance::Bound; use crate::types::any::PyAnyMethods; use crate::types::PyString; use crate::{ffi, FromPyObject, IntoPy, PyAny, PyObject, PyResult, Python, ToPyObject}; use std::borrow::Cow; +use std::convert::Infallible; use std::ffi::{OsStr, OsString}; impl ToPyObject for OsStr { fn to_object(&self, py: Python<'_>) -> PyObject { + self.into_pyobject(py).unwrap().into_any().unbind() + } +} + +impl<'py> IntoPyObject<'py> for &OsStr { + type Target = PyString; + type Error = Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result, Self::Error> { // If the string is UTF-8, take the quick and easy shortcut if let Some(valid_utf8_path) = self.to_str() { - return valid_utf8_path.to_object(py); + return valid_utf8_path.into_pyobject(py); } // All targets besides windows support the std::os::unix::ffi::OsStrExt API: @@ -26,8 +38,9 @@ impl ToPyObject for OsStr { unsafe { // DecodeFSDefault automatically chooses an appropriate decoding mechanism to // parse os strings losslessly (i.e. surrogateescape most of the time) - let pystring = ffi::PyUnicode_DecodeFSDefaultAndSize(ptr, len); - PyObject::from_owned_ptr(py, pystring) + Ok(ffi::PyUnicode_DecodeFSDefaultAndSize(ptr, len) + .assume_owned(py) + .downcast_into_unchecked::()) } } @@ -38,9 +51,11 @@ impl ToPyObject for OsStr { unsafe { // This will not panic because the data from encode_wide is well-formed Windows // string data - PyObject::from_owned_ptr( - py, - ffi::PyUnicode_FromWideChar(wstr.as_ptr(), wstr.len() as ffi::Py_ssize_t), + + Ok( + ffi::PyUnicode_FromWideChar(wstr.as_ptr(), wstr.len() as ffi::Py_ssize_t) + .assume_owned(py) + .downcast_into_unchecked::(), ) } } @@ -124,6 +139,15 @@ impl IntoPy for Cow<'_, OsStr> { } } +impl<'py> IntoPyObject<'py> for Cow<'_, OsStr> { + type Target = PyString; + type Error = Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result, Self::Error> { + (*self).into_pyobject(py) + } +} + impl ToPyObject for OsString { #[inline] fn to_object(&self, py: Python<'_>) -> PyObject { @@ -137,12 +161,30 @@ impl IntoPy for OsString { } } +impl<'py> IntoPyObject<'py> for OsString { + type Target = PyString; + type Error = Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result, Self::Error> { + self.as_os_str().into_pyobject(py) + } +} + impl<'a> IntoPy for &'a OsString { fn into_py(self, py: Python<'_>) -> PyObject { self.to_object(py) } } +impl<'py> IntoPyObject<'py> for &OsString { + type Target = PyString; + type Error = Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result, Self::Error> { + self.as_os_str().into_pyobject(py) + } +} + #[cfg(test)] mod tests { use crate::types::{PyAnyMethods, PyStringMethods}; diff --git a/src/conversions/std/path.rs b/src/conversions/std/path.rs index d7f3121ea10..c0e83174275 100644 --- a/src/conversions/std/path.rs +++ b/src/conversions/std/path.rs @@ -1,8 +1,11 @@ +use crate::conversion::IntoPyObject; use crate::ffi_ptr_ext::FfiPtrExt; use crate::instance::Bound; use crate::types::any::PyAnyMethods; +use crate::types::PyString; use crate::{ffi, FromPyObject, IntoPy, PyAny, PyObject, PyResult, Python, ToPyObject}; use std::borrow::Cow; +use std::convert::Infallible; use std::ffi::OsString; use std::path::{Path, PathBuf}; @@ -29,6 +32,15 @@ impl<'a> IntoPy for &'a Path { } } +impl<'py> IntoPyObject<'py> for &Path { + type Target = PyString; + type Error = Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result, Self::Error> { + self.as_os_str().into_pyobject(py) + } +} + impl<'a> ToPyObject for Cow<'a, Path> { #[inline] fn to_object(&self, py: Python<'_>) -> PyObject { @@ -43,6 +55,15 @@ impl<'a> IntoPy for Cow<'a, Path> { } } +impl<'py> IntoPyObject<'py> for Cow<'_, Path> { + type Target = PyString; + type Error = Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result, Self::Error> { + self.as_os_str().into_pyobject(py) + } +} + impl ToPyObject for PathBuf { #[inline] fn to_object(&self, py: Python<'_>) -> PyObject { @@ -56,12 +77,30 @@ impl IntoPy for PathBuf { } } +impl<'py> IntoPyObject<'py> for PathBuf { + type Target = PyString; + type Error = Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result, Self::Error> { + self.as_os_str().into_pyobject(py) + } +} + impl<'a> IntoPy for &'a PathBuf { fn into_py(self, py: Python<'_>) -> PyObject { self.as_os_str().to_object(py) } } +impl<'py> IntoPyObject<'py> for &PathBuf { + type Target = PyString; + type Error = Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result, Self::Error> { + self.as_os_str().into_pyobject(py) + } +} + #[cfg(test)] mod tests { use crate::types::{PyAnyMethods, PyStringMethods}; diff --git a/src/conversions/std/slice.rs b/src/conversions/std/slice.rs index 9c9cde06fc7..b8eb1f8b68b 100644 --- a/src/conversions/std/slice.rs +++ b/src/conversions/std/slice.rs @@ -1,10 +1,11 @@ -use std::borrow::Cow; +use std::{borrow::Cow, convert::Infallible}; #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; use crate::{ + conversion::IntoPyObject, types::{PyByteArray, PyByteArrayMethods, PyBytes}, - IntoPy, Py, PyAny, PyObject, PyResult, Python, ToPyObject, + Bound, IntoPy, Py, PyAny, PyObject, PyResult, Python, ToPyObject, }; impl<'a> IntoPy for &'a [u8] { @@ -18,6 +19,15 @@ impl<'a> IntoPy for &'a [u8] { } } +impl<'py> IntoPyObject<'py> for &[u8] { + type Target = PyBytes; + type Error = Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result, Self::Error> { + Ok(PyBytes::new_bound(py, self)) + } +} + #[cfg(feature = "gil-refs")] impl<'py> crate::FromPyObject<'py> for &'py [u8] { fn extract_bound(obj: &crate::Bound<'py, PyAny>) -> PyResult { @@ -89,6 +99,15 @@ impl IntoPy> for Cow<'_, [u8]> { } } +impl<'py> IntoPyObject<'py> for Cow<'_, [u8]> { + type Target = PyBytes; + type Error = Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result, Self::Error> { + Ok(PyBytes::new_bound(py, &self)) + } +} + #[cfg(test)] mod tests { use std::borrow::Cow; diff --git a/src/conversions/std/string.rs b/src/conversions/std/string.rs index 5bc05c1a091..890a742e5e1 100644 --- a/src/conversions/std/string.rs +++ b/src/conversions/std/string.rs @@ -1,8 +1,9 @@ -use std::borrow::Cow; +use std::{borrow::Cow, convert::Infallible}; #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; use crate::{ + conversion::IntoPyObject, instance::Bound, types::{any::PyAnyMethods, string::PyStringMethods, PyString}, FromPyObject, IntoPy, Py, PyAny, PyObject, PyResult, Python, ToPyObject, @@ -41,6 +42,15 @@ impl<'a> IntoPy> for &'a str { } } +impl<'py> IntoPyObject<'py> for &str { + type Target = PyString; + type Error = Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result, Self::Error> { + Ok(PyString::new_bound(py, self)) + } +} + /// Converts a Rust `Cow<'_, str>` to a Python object. /// See `PyString::new` for details on the conversion. impl ToPyObject for Cow<'_, str> { @@ -62,6 +72,15 @@ impl IntoPy for Cow<'_, str> { } } +impl<'py> IntoPyObject<'py> for Cow<'_, str> { + type Target = PyString; + type Error = Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result, Self::Error> { + (*self).into_pyobject(py) + } +} + /// Converts a Rust `String` to a Python object. /// See `PyString::new` for details on the conversion. impl ToPyObject for String { @@ -89,6 +108,16 @@ impl IntoPy for char { } } +impl<'py> IntoPyObject<'py> for char { + type Target = PyString; + type Error = Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result, Self::Error> { + let mut bytes = [0u8; 4]; + Ok(PyString::new_bound(py, self.encode_utf8(&mut bytes))) + } +} + impl IntoPy for String { fn into_py(self, py: Python<'_>) -> PyObject { PyString::new_bound(py, &self).into() @@ -100,6 +129,15 @@ impl IntoPy for String { } } +impl<'py> IntoPyObject<'py> for String { + type Target = PyString; + type Error = Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result, Self::Error> { + Ok(PyString::new_bound(py, &self)) + } +} + impl<'a> IntoPy for &'a String { #[inline] fn into_py(self, py: Python<'_>) -> PyObject { @@ -112,6 +150,15 @@ impl<'a> IntoPy for &'a String { } } +impl<'py> IntoPyObject<'py> for &String { + type Target = PyString; + type Error = Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result, Self::Error> { + Ok(PyString::new_bound(py, &self)) + } +} + /// Allows extracting strings from Python objects. /// Accepts Python `str` objects. #[cfg(feature = "gil-refs")] diff --git a/src/conversions/std/time.rs b/src/conversions/std/time.rs index 89d61e696a1..4c725fd21fd 100755 --- a/src/conversions/std/time.rs +++ b/src/conversions/std/time.rs @@ -1,3 +1,4 @@ +use crate::conversion::IntoPyObject; use crate::exceptions::{PyOverflowError, PyValueError}; use crate::sync::GILOnceCell; use crate::types::any::PyAnyMethods; @@ -89,6 +90,38 @@ impl IntoPy for Duration { } } +impl<'py> IntoPyObject<'py> for Duration { + #[cfg(not(Py_LIMITED_API))] + type Target = PyDelta; + #[cfg(Py_LIMITED_API)] + type Target = PyAny; + type Error = PyErr; + + fn into_pyobject(self, py: Python<'py>) -> Result, Self::Error> { + let days = self.as_secs() / SECONDS_PER_DAY; + let seconds = self.as_secs() % SECONDS_PER_DAY; + let microseconds = self.subsec_micros(); + + #[cfg(not(Py_LIMITED_API))] + { + PyDelta::new_bound( + py, + days.try_into()?, + seconds.try_into().unwrap(), + microseconds.try_into().unwrap(), + false, + ) + } + #[cfg(Py_LIMITED_API)] + { + static TIMEDELTA: GILOnceCell> = GILOnceCell::new(); + TIMEDELTA + .get_or_try_init_type_ref(py, "datetime", "timedelta")? + .call1((days, seconds, microseconds)) + } + } +} + // Conversions between SystemTime and datetime do not rely on the floating point timestamp of the // timestamp/fromtimestamp APIs to avoid possible precision loss but goes through the // timedelta/std::time::Duration types by taking for reference point the UNIX epoch. @@ -123,6 +156,19 @@ impl IntoPy for SystemTime { } } +impl<'py> IntoPyObject<'py> for SystemTime { + type Target = PyAny; + type Error = PyErr; + + fn into_pyobject(self, py: Python<'py>) -> Result, Self::Error> { + let duration_since_unix_epoch = + self.duration_since(UNIX_EPOCH).unwrap().into_pyobject(py)?; + unix_epoch_py(py) + .bind(py) + .call_method1(intern!(py, "__add__"), (duration_since_unix_epoch,)) + } +} + fn unix_epoch_py(py: Python<'_>) -> &PyObject { static UNIX_EPOCH: GILOnceCell = GILOnceCell::new(); UNIX_EPOCH diff --git a/src/pycell.rs b/src/pycell.rs index 77d174cb9e1..2cd70d2fcdf 100644 --- a/src/pycell.rs +++ b/src/pycell.rs @@ -193,7 +193,7 @@ //! [guide]: https://pyo3.rs/latest/class.html#pycell-and-interior-mutability "PyCell and interior mutability" //! [Interior Mutability]: https://doc.rust-lang.org/book/ch15-05-interior-mutability.html "RefCell and the Interior Mutability Pattern - The Rust Programming Language" -use crate::conversion::AsPyPointer; +use crate::conversion::{AsPyPointer, IntoPyObject}; use crate::exceptions::PyRuntimeError; use crate::ffi_ptr_ext::FfiPtrExt; use crate::internal_tricks::{ptr_from_mut, ptr_from_ref}; @@ -210,6 +210,7 @@ use crate::{ PyNativeType, PyResult, PyTypeCheck, }; use crate::{ffi, Bound, IntoPy, PyErr, PyObject, Python}; +use std::convert::Infallible; use std::fmt; use std::mem::ManuallyDrop; use std::ops::{Deref, DerefMut}; @@ -828,6 +829,24 @@ impl IntoPy for &'_ PyRef<'_, T> { } } +impl<'py, T: PyClass> IntoPyObject<'py> for PyRef<'py, T> { + type Target = T; + type Error = Infallible; + + fn into_pyobject(self, _py: Python<'py>) -> Result, Self::Error> { + Ok(self.inner.clone()) + } +} + +impl<'py, T: PyClass> IntoPyObject<'py> for &PyRef<'py, T> { + type Target = T; + type Error = Infallible; + + fn into_pyobject(self, _py: Python<'py>) -> Result, Self::Error> { + Ok(self.inner.clone()) + } +} + #[cfg(feature = "gil-refs")] #[allow(deprecated)] impl<'a, T: PyClass> std::convert::TryFrom<&'a PyCell> for crate::PyRef<'a, T> { @@ -1006,6 +1025,24 @@ impl> IntoPy for &'_ PyRefMut<'_, T> { } } +impl<'py, T: PyClass> IntoPyObject<'py> for PyRefMut<'py, T> { + type Target = T; + type Error = Infallible; + + fn into_pyobject(self, _py: Python<'py>) -> Result, Self::Error> { + Ok(self.inner.clone()) + } +} + +impl<'py, T: PyClass> IntoPyObject<'py> for &PyRefMut<'py, T> { + type Target = T; + type Error = Infallible; + + fn into_pyobject(self, _py: Python<'py>) -> Result, Self::Error> { + Ok(self.inner.clone()) + } +} + unsafe impl<'a, T: PyClass> AsPyPointer for PyRefMut<'a, T> { fn as_ptr(&self) -> *mut ffi::PyObject { self.inner.as_ptr()