add new `PyTuple` constructors

This commit is contained in:
David Hewitt 2023-12-26 15:07:32 +00:00
parent 375e3d4eee
commit 53d25f7ff2
12 changed files with 82 additions and 47 deletions

View File

@ -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<T>` where `T` is the Rust type of the argument.

View File

@ -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

View File

@ -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(),

View File

@ -465,7 +465,7 @@ mod implementations {
/// Converts `()` to an empty Python tuple.
impl IntoPy<Py<PyTuple>> for () {
fn into_py(self, py: Python<'_>) -> Py<PyTuple> {
PyTuple::empty(py).into()
PyTuple::empty_bound(py).unbind()
}
}

View File

@ -615,7 +615,7 @@ impl<'py> VarargsHandler<'py> for TupleVarargs {
varargs: &[Option<&PyAny>],
_function_description: &FunctionDescription,
) -> PyResult<Self::Varargs> {
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::<NoVarargs, NoVarkeywords>(
@ -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::<NoVarargs, NoVarkeywords>(
@ -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::<NoVarargs, NoVarkeywords>(

View File

@ -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 \

View File

@ -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);

View File

@ -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());
})
}

View File

@ -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::<PySequence>(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());
});
}

View File

@ -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<T, U>(
py: Python<'_>,
elements: impl IntoIterator<Item = T, IntoIter = U>,
) -> &PyTuple
where
T: ToPyObject,
U: ExactSizeIterator<Item = T>,
{
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<i32> = 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`]`<T>` or `&[T]` will always succeed.
#[track_caller]
pub fn new<T, U>(
pub fn new_bound<T, U>(
py: Python<'_>,
elements: impl IntoIterator<Item = T, IntoIter = U>,
) -> &PyTuple
) -> Bound<'_, PyTuple>
where
T: ToPyObject,
U: ExactSizeIterator<Item = T>,
{
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};

View File

@ -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(),
"\

View File

@ -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<Self>,
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))
}
}