From 53d25f7ff28ed72c197a50a841987f6efe605d0a Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Tue, 26 Dec 2023 15:07:32 +0000 Subject: [PATCH] add new `PyTuple` constructors --- guide/src/conversions/traits.md | 6 ++--- guide/src/python_from_rust.md | 2 +- pytests/src/datetime.rs | 24 +++++++++--------- src/conversion.rs | 2 +- src/impl_/extract_argument.rs | 14 +++++------ src/impl_/pymodule.rs | 2 +- src/types/datetime.rs | 2 +- src/types/list.rs | 2 +- src/types/sequence.rs | 8 ++++-- src/types/tuple.rs | 43 ++++++++++++++++++++++++++++----- tests/test_frompyobject.rs | 16 ++++++------ tests/test_various.rs | 8 +++--- 12 files changed, 82 insertions(+), 47 deletions(-) diff --git a/guide/src/conversions/traits.md b/guide/src/conversions/traits.md index fd136c83..68304753 100644 --- a/guide/src/conversions/traits.md +++ b/guide/src/conversions/traits.md @@ -181,7 +181,7 @@ struct RustyTuple(String, String); # use pyo3::types::PyTuple; # fn main() -> PyResult<()> { # Python::with_gil(|py| -> PyResult<()> { -# let tuple = PyTuple::new(py, vec!["test", "test2"]); +# let tuple = PyTuple::new_bound(py, vec!["test", "test2"]); # # let rustytuple: RustyTuple = tuple.extract()?; # assert_eq!(rustytuple.0, "test"); @@ -204,7 +204,7 @@ struct RustyTuple((String,)); # use pyo3::types::PyTuple; # fn main() -> PyResult<()> { # Python::with_gil(|py| -> PyResult<()> { -# let tuple = PyTuple::new(py, vec!["test"]); +# let tuple = PyTuple::new_bound(py, vec!["test"]); # # let rustytuple: RustyTuple = tuple.extract()?; # assert_eq!((rustytuple.0).0, "test"); @@ -482,7 +482,7 @@ If the input is neither a string nor an integer, the error message will be: - retrieve the field from a mapping, possibly with the custom key specified as an argument. - can be any literal that implements `ToBorrowedObject` - `pyo3(from_py_with = "...")` - - apply a custom function to convert the field from Python the desired Rust type. + - apply a custom function to convert the field from Python the desired Rust type. - the argument must be the name of the function as a string. - the function signature must be `fn(&PyAny) -> PyResult` where `T` is the Rust type of the argument. diff --git a/guide/src/python_from_rust.md b/guide/src/python_from_rust.md index ed51b772..6d403482 100644 --- a/guide/src/python_from_rust.md +++ b/guide/src/python_from_rust.md @@ -51,7 +51,7 @@ fn main() -> PyResult<()> { fun.call0(py)?; // call object with PyTuple - let args = PyTuple::new(py, &[arg1, arg2, arg3]); + let args = PyTuple::new_bound(py, &[arg1, arg2, arg3]); fun.call1(py, args)?; // pass arguments as rust tuple diff --git a/pytests/src/datetime.rs b/pytests/src/datetime.rs index f5a15b4f..1407da3f 100644 --- a/pytests/src/datetime.rs +++ b/pytests/src/datetime.rs @@ -12,8 +12,8 @@ fn make_date(py: Python<'_>, year: i32, month: u8, day: u8) -> PyResult<&PyDate> } #[pyfunction] -fn get_date_tuple<'p>(py: Python<'p>, d: &PyDate) -> &'p PyTuple { - PyTuple::new(py, [d.get_year(), d.get_month() as i32, d.get_day() as i32]) +fn get_date_tuple<'p>(py: Python<'p>, d: &PyDate) -> Bound<'p, PyTuple> { + PyTuple::new_bound(py, [d.get_year(), d.get_month() as i32, d.get_day() as i32]) } #[pyfunction] @@ -48,8 +48,8 @@ fn time_with_fold<'p>( } #[pyfunction] -fn get_time_tuple<'p>(py: Python<'p>, dt: &PyTime) -> &'p PyTuple { - PyTuple::new( +fn get_time_tuple<'p>(py: Python<'p>, dt: &PyTime) -> Bound<'p, PyTuple> { + PyTuple::new_bound( py, [ dt.get_hour() as u32, @@ -61,8 +61,8 @@ fn get_time_tuple<'p>(py: Python<'p>, dt: &PyTime) -> &'p PyTuple { } #[pyfunction] -fn get_time_tuple_fold<'p>(py: Python<'p>, dt: &PyTime) -> &'p PyTuple { - PyTuple::new( +fn get_time_tuple_fold<'p>(py: Python<'p>, dt: &PyTime) -> Bound<'p, PyTuple> { + PyTuple::new_bound( py, [ dt.get_hour() as u32, @@ -80,8 +80,8 @@ fn make_delta(py: Python<'_>, days: i32, seconds: i32, microseconds: i32) -> PyR } #[pyfunction] -fn get_delta_tuple<'p>(py: Python<'p>, delta: &PyDelta) -> &'p PyTuple { - PyTuple::new( +fn get_delta_tuple<'p>(py: Python<'p>, delta: &PyDelta) -> Bound<'p, PyTuple> { + PyTuple::new_bound( py, [ delta.get_days(), @@ -118,8 +118,8 @@ fn make_datetime<'p>( } #[pyfunction] -fn get_datetime_tuple<'p>(py: Python<'p>, dt: &PyDateTime) -> &'p PyTuple { - PyTuple::new( +fn get_datetime_tuple<'p>(py: Python<'p>, dt: &PyDateTime) -> Bound<'p, PyTuple> { + PyTuple::new_bound( py, [ dt.get_year(), @@ -134,8 +134,8 @@ fn get_datetime_tuple<'p>(py: Python<'p>, dt: &PyDateTime) -> &'p PyTuple { } #[pyfunction] -fn get_datetime_tuple_fold<'p>(py: Python<'p>, dt: &PyDateTime) -> &'p PyTuple { - PyTuple::new( +fn get_datetime_tuple_fold<'p>(py: Python<'p>, dt: &PyDateTime) -> Bound<'p, PyTuple> { + PyTuple::new_bound( py, [ dt.get_year(), diff --git a/src/conversion.rs b/src/conversion.rs index 0a842f9a..429ca9e8 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -465,7 +465,7 @@ mod implementations { /// Converts `()` to an empty Python tuple. impl IntoPy> for () { fn into_py(self, py: Python<'_>) -> Py { - PyTuple::empty(py).into() + PyTuple::empty_bound(py).unbind() } } diff --git a/src/impl_/extract_argument.rs b/src/impl_/extract_argument.rs index 437ece48..a18c8d4b 100644 --- a/src/impl_/extract_argument.rs +++ b/src/impl_/extract_argument.rs @@ -615,7 +615,7 @@ impl<'py> VarargsHandler<'py> for TupleVarargs { varargs: &[Option<&PyAny>], _function_description: &FunctionDescription, ) -> PyResult { - Ok(PyTuple::new(py, varargs)) + Ok(PyTuple::new_bound(py, varargs).into_gil_ref()) } #[inline] @@ -697,7 +697,7 @@ fn push_parameter_list(msg: &mut String, parameter_names: &[&str]) { mod tests { use crate::{ types::{IntoPyDict, PyTuple}, - PyAny, Python, ToPyObject, + PyAny, Python, }; use super::{push_parameter_list, FunctionDescription, NoVarargs, NoVarkeywords}; @@ -714,8 +714,8 @@ mod tests { }; Python::with_gil(|py| { - let args = PyTuple::new(py, Vec::<&PyAny>::new()); - let kwargs = [("foo".to_object(py).into_ref(py), 0u8)].into_py_dict(py); + let args = PyTuple::new_bound(py, Vec::<&PyAny>::new()); + let kwargs = [("foo", 0u8)].into_py_dict(py); let err = unsafe { function_description .extract_arguments_tuple_dict::( @@ -745,8 +745,8 @@ mod tests { }; Python::with_gil(|py| { - let args = PyTuple::new(py, Vec::<&PyAny>::new()); - let kwargs = [(1u8.to_object(py).into_ref(py), 1u8)].into_py_dict(py); + let args = PyTuple::new_bound(py, Vec::<&PyAny>::new()); + let kwargs = [(1u8, 1u8)].into_py_dict(py); let err = unsafe { function_description .extract_arguments_tuple_dict::( @@ -776,7 +776,7 @@ mod tests { }; Python::with_gil(|py| { - let args = PyTuple::new(py, Vec::<&PyAny>::new()); + let args = PyTuple::new_bound(py, Vec::<&PyAny>::new()); let mut output = [None, None]; let err = unsafe { function_description.extract_arguments_tuple_dict::( diff --git a/src/impl_/pymodule.rs b/src/impl_/pymodule.rs index 7c5243fc..0fe5c384 100644 --- a/src/impl_/pymodule.rs +++ b/src/impl_/pymodule.rs @@ -72,7 +72,7 @@ impl ModuleDef { .import("sys")? .getattr("implementation")? .getattr("version")?; - if version.lt(crate::types::PyTuple::new(py, PYPY_GOOD_VERSION))? { + if version.lt(crate::types::PyTuple::new_bound(py, PYPY_GOOD_VERSION))? { let warn = py.import("warnings")?.getattr("warn")?; warn.call1(( "PyPy 3.7 versions older than 7.3.8 are known to have binary \ diff --git a/src/types/datetime.rs b/src/types/datetime.rs index 3d66cee4..354414b8 100644 --- a/src/types/datetime.rs +++ b/src/types/datetime.rs @@ -212,7 +212,7 @@ impl PyDate { /// /// This is equivalent to `datetime.date.fromtimestamp` pub fn from_timestamp(py: Python<'_>, timestamp: i64) -> PyResult<&PyDate> { - let time_tuple = PyTuple::new(py, [timestamp]); + let time_tuple = PyTuple::new_bound(py, [timestamp]); // safety ensure that the API is loaded let _api = ensure_datetime_api(py); diff --git a/src/types/list.rs b/src/types/list.rs index 2ba4b10c..f8db0e62 100644 --- a/src/types/list.rs +++ b/src/types/list.rs @@ -1231,7 +1231,7 @@ mod tests { Python::with_gil(|py| { let list = PyList::new(py, vec![1, 2, 3]); let tuple = list.to_tuple(); - let tuple_expected = PyTuple::new(py, vec![1, 2, 3]); + let tuple_expected = PyTuple::new_bound(py, vec![1, 2, 3]); assert!(tuple.eq(tuple_expected).unwrap()); }) } diff --git a/src/types/sequence.rs b/src/types/sequence.rs index f4eae4d0..fa15fe06 100644 --- a/src/types/sequence.rs +++ b/src/types/sequence.rs @@ -1009,7 +1009,7 @@ mod tests { assert!(seq .to_tuple() .unwrap() - .eq(PyTuple::new(py, ["foo", "bar"])) + .eq(PyTuple::new_bound(py, ["foo", "bar"])) .unwrap()); }); } @@ -1020,7 +1020,11 @@ mod tests { let v = vec!["foo", "bar"]; let ob = v.to_object(py); let seq = ob.downcast::(py).unwrap(); - assert!(seq.to_tuple().unwrap().eq(PyTuple::new(py, &v)).unwrap()); + assert!(seq + .to_tuple() + .unwrap() + .eq(PyTuple::new_bound(py, &v)) + .unwrap()); }); } diff --git a/src/types/tuple.rs b/src/types/tuple.rs index 1b87499f..114c36c4 100644 --- a/src/types/tuple.rs +++ b/src/types/tuple.rs @@ -58,6 +58,23 @@ pub struct PyTuple(PyAny); pyobject_native_type_core!(PyTuple, pyobject_native_static_type_object!(ffi::PyTuple_Type), #checkfunction=ffi::PyTuple_Check); impl PyTuple { + /// Deprecated form of `PyTuple::new_bound`. + #[track_caller] + #[deprecated( + since = "0.21.0", + note = "`PyTuple::new` will be replaced by `PyTuple::new_bound` in a future PyO3 version" + )] + pub fn new( + py: Python<'_>, + elements: impl IntoIterator, + ) -> &PyTuple + where + T: ToPyObject, + U: ExactSizeIterator, + { + Self::new_bound(py, elements).into_gil_ref() + } + /// Constructs a new tuple with the given elements. /// /// If you want to create a [`PyTuple`] with elements of different or unknown types, or from an @@ -73,7 +90,7 @@ impl PyTuple { /// # fn main() { /// Python::with_gil(|py| { /// let elements: Vec = vec![0, 1, 2, 3, 4, 5]; - /// let tuple: &PyTuple = PyTuple::new(py, elements); + /// let tuple = PyTuple::new_bound(py, elements); /// assert_eq!(format!("{:?}", tuple), "(0, 1, 2, 3, 4, 5)"); /// }); /// # } @@ -85,21 +102,34 @@ impl PyTuple { /// All standard library structures implement this trait correctly, if they do, so calling this /// function using [`Vec`]`` or `&[T]` will always succeed. #[track_caller] - pub fn new( + pub fn new_bound( py: Python<'_>, elements: impl IntoIterator, - ) -> &PyTuple + ) -> Bound<'_, PyTuple> where T: ToPyObject, U: ExactSizeIterator, { let mut elements = elements.into_iter().map(|e| e.to_object(py)); - new_from_iter(py, &mut elements).into_gil_ref() + new_from_iter(py, &mut elements) + } + + /// Deprecated form of `PyTuple::empty_bound`. + #[deprecated( + since = "0.21.0", + note = "`PyTuple::empty` will be replaced by `PyTuple::empty_bound` in a future PyO3 version" + )] + pub fn empty(py: Python<'_>) -> &PyTuple { + Self::empty_bound(py).into_gil_ref() } /// Constructs an empty tuple (on the Python side, a singleton object). - pub fn empty(py: Python<'_>) -> &PyTuple { - unsafe { py.from_owned_ptr(ffi::PyTuple_New(0)) } + pub fn empty_bound(py: Python<'_>) -> Bound<'_, PyTuple> { + unsafe { + ffi::PyTuple_New(0) + .assume_owned(py) + .downcast_into_unchecked() + } } /// Gets the length of the tuple. @@ -765,6 +795,7 @@ tuple_conversion!( ); #[cfg(test)] +#[allow(deprecated)] // TODO: remove allow when GIL Pool is removed mod tests { use crate::types::{PyAny, PyList, PyTuple}; use crate::{Python, ToPyObject}; diff --git a/tests/test_frompyobject.rs b/tests/test_frompyobject.rs index 47e5ec53..30edf6f7 100644 --- a/tests/test_frompyobject.rs +++ b/tests/test_frompyobject.rs @@ -162,11 +162,11 @@ pub struct Tuple(String, usize); #[test] fn test_tuple_struct() { Python::with_gil(|py| { - let tup = PyTuple::new(py, &[1.into_py(py), "test".into_py(py)]); - let tup = Tuple::extract(tup.as_ref()); + let tup = PyTuple::new_bound(py, &[1.into_py(py), "test".into_py(py)]); + let tup = Tuple::extract(tup.as_gil_ref()); assert!(tup.is_err()); - let tup = PyTuple::new(py, &["test".into_py(py), 1.into_py(py)]); - let tup = Tuple::extract(tup.as_ref()).expect("Failed to extract Tuple from PyTuple"); + let tup = PyTuple::new_bound(py, &["test".into_py(py), 1.into_py(py)]); + let tup = Tuple::extract(tup.as_gil_ref()).expect("Failed to extract Tuple from PyTuple"); assert_eq!(tup.0, "test"); assert_eq!(tup.1, 1); }); @@ -324,8 +324,8 @@ pub struct PyBool { #[test] fn test_enum() { Python::with_gil(|py| { - let tup = PyTuple::new(py, &[1.into_py(py), "test".into_py(py)]); - let f = Foo::extract(tup.as_ref()).expect("Failed to extract Foo from tuple"); + let tup = PyTuple::new_bound(py, &[1.into_py(py), "test".into_py(py)]); + let f = Foo::extract(tup.as_gil_ref()).expect("Failed to extract Foo from tuple"); match f { Foo::TupleVar(test, test2) => { assert_eq!(test, 1); @@ -401,8 +401,8 @@ TypeError: failed to extract enum Foo ('TupleVar | StructVar | TransparentTuple - variant StructWithGetItemArg (StructWithGetItemArg): KeyError: 'foo'" ); - let tup = PyTuple::empty(py); - let err = Foo::extract(tup.as_ref()).unwrap_err(); + let tup = PyTuple::empty_bound(py); + let err = Foo::extract(tup.as_gil_ref()).unwrap_err(); assert_eq!( err.to_string(), "\ diff --git a/tests/test_various.rs b/tests/test_various.rs index 076d2ba2..6560610f 100644 --- a/tests/test_various.rs +++ b/tests/test_various.rs @@ -91,7 +91,7 @@ fn intopytuple_pyclass() { #[test] fn pytuple_primitive_iter() { Python::with_gil(|py| { - let tup = PyTuple::new(py, [1u32, 2, 3].iter()); + let tup = PyTuple::new_bound(py, [1u32, 2, 3].iter()); py_assert!(py, tup, "tup == (1, 2, 3)"); }); } @@ -99,7 +99,7 @@ fn pytuple_primitive_iter() { #[test] fn pytuple_pyclass_iter() { Python::with_gil(|py| { - let tup = PyTuple::new( + let tup = PyTuple::new_bound( py, [ PyCell::new(py, SimplePyClass {}).unwrap(), @@ -126,10 +126,10 @@ impl PickleSupport { pub fn __reduce__<'py>( slf: &'py PyCell, py: Python<'py>, - ) -> PyResult<(PyObject, &'py PyTuple, PyObject)> { + ) -> PyResult<(PyObject, Bound<'py, PyTuple>, PyObject)> { let cls = slf.to_object(py).getattr(py, "__class__")?; let dict = slf.to_object(py).getattr(py, "__dict__")?; - Ok((cls, PyTuple::empty(py), dict)) + Ok((cls, PyTuple::empty_bound(py), dict)) } }