Skip to content

Commit a69e6ba

Browse files
committed
add new PyTuple constructors
1 parent f7da91a commit a69e6ba

13 files changed

+83
-48
lines changed

guide/src/conversions/traits.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ struct RustyTuple(String, String);
181181
# use pyo3::types::PyTuple;
182182
# fn main() -> PyResult<()> {
183183
# Python::with_gil(|py| -> PyResult<()> {
184-
# let tuple = PyTuple::new(py, vec!["test", "test2"]);
184+
# let tuple = PyTuple::new_bound(py, vec!["test", "test2"]);
185185
#
186186
# let rustytuple: RustyTuple = tuple.extract()?;
187187
# assert_eq!(rustytuple.0, "test");
@@ -204,7 +204,7 @@ struct RustyTuple((String,));
204204
# use pyo3::types::PyTuple;
205205
# fn main() -> PyResult<()> {
206206
# Python::with_gil(|py| -> PyResult<()> {
207-
# let tuple = PyTuple::new(py, vec!["test"]);
207+
# let tuple = PyTuple::new_bound(py, vec!["test"]);
208208
#
209209
# let rustytuple: RustyTuple = tuple.extract()?;
210210
# 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:
482482
- retrieve the field from a mapping, possibly with the custom key specified as an argument.
483483
- can be any literal that implements `ToBorrowedObject`
484484
- `pyo3(from_py_with = "...")`
485-
- apply a custom function to convert the field from Python the desired Rust type.
485+
- apply a custom function to convert the field from Python the desired Rust type.
486486
- the argument must be the name of the function as a string.
487487
- the function signature must be `fn(&PyAny) -> PyResult<T>` where `T` is the Rust type of the argument.
488488

guide/src/python_from_rust.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ fn main() -> PyResult<()> {
5151
fun.call0(py)?;
5252

5353
// call object with PyTuple
54-
let args = PyTuple::new(py, &[arg1, arg2, arg3]);
54+
let args = PyTuple::new_bound(py, &[arg1, arg2, arg3]);
5555
fun.call1(py, args)?;
5656

5757
// pass arguments as rust tuple

pytests/src/datetime.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ fn make_date(py: Python<'_>, year: i32, month: u8, day: u8) -> PyResult<&PyDate>
1212
}
1313

1414
#[pyfunction]
15-
fn get_date_tuple<'p>(py: Python<'p>, d: &PyDate) -> &'p PyTuple {
16-
PyTuple::new(py, [d.get_year(), d.get_month() as i32, d.get_day() as i32])
15+
fn get_date_tuple<'p>(py: Python<'p>, d: &PyDate) -> Bound<'p, PyTuple> {
16+
PyTuple::new_bound(py, [d.get_year(), d.get_month() as i32, d.get_day() as i32])
1717
}
1818

1919
#[pyfunction]
@@ -48,8 +48,8 @@ fn time_with_fold<'p>(
4848
}
4949

5050
#[pyfunction]
51-
fn get_time_tuple<'p>(py: Python<'p>, dt: &PyTime) -> &'p PyTuple {
52-
PyTuple::new(
51+
fn get_time_tuple<'p>(py: Python<'p>, dt: &PyTime) -> Bound<'p, PyTuple> {
52+
PyTuple::new_bound(
5353
py,
5454
[
5555
dt.get_hour() as u32,
@@ -61,8 +61,8 @@ fn get_time_tuple<'p>(py: Python<'p>, dt: &PyTime) -> &'p PyTuple {
6161
}
6262

6363
#[pyfunction]
64-
fn get_time_tuple_fold<'p>(py: Python<'p>, dt: &PyTime) -> &'p PyTuple {
65-
PyTuple::new(
64+
fn get_time_tuple_fold<'p>(py: Python<'p>, dt: &PyTime) -> Bound<'p, PyTuple> {
65+
PyTuple::new_bound(
6666
py,
6767
[
6868
dt.get_hour() as u32,
@@ -80,8 +80,8 @@ fn make_delta(py: Python<'_>, days: i32, seconds: i32, microseconds: i32) -> PyR
8080
}
8181

8282
#[pyfunction]
83-
fn get_delta_tuple<'p>(py: Python<'p>, delta: &PyDelta) -> &'p PyTuple {
84-
PyTuple::new(
83+
fn get_delta_tuple<'p>(py: Python<'p>, delta: &PyDelta) -> Bound<'p, PyTuple> {
84+
PyTuple::new_bound(
8585
py,
8686
[
8787
delta.get_days(),
@@ -118,8 +118,8 @@ fn make_datetime<'p>(
118118
}
119119

120120
#[pyfunction]
121-
fn get_datetime_tuple<'p>(py: Python<'p>, dt: &PyDateTime) -> &'p PyTuple {
122-
PyTuple::new(
121+
fn get_datetime_tuple<'p>(py: Python<'p>, dt: &PyDateTime) -> Bound<'p, PyTuple> {
122+
PyTuple::new_bound(
123123
py,
124124
[
125125
dt.get_year(),
@@ -134,8 +134,8 @@ fn get_datetime_tuple<'p>(py: Python<'p>, dt: &PyDateTime) -> &'p PyTuple {
134134
}
135135

136136
#[pyfunction]
137-
fn get_datetime_tuple_fold<'p>(py: Python<'p>, dt: &PyDateTime) -> &'p PyTuple {
138-
PyTuple::new(
137+
fn get_datetime_tuple_fold<'p>(py: Python<'p>, dt: &PyDateTime) -> Bound<'p, PyTuple> {
138+
PyTuple::new_bound(
139139
py,
140140
[
141141
dt.get_year(),

src/conversion.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ mod implementations {
465465
/// Converts `()` to an empty Python tuple.
466466
impl IntoPy<Py<PyTuple>> for () {
467467
fn into_py(self, py: Python<'_>) -> Py<PyTuple> {
468-
PyTuple::empty(py).into()
468+
PyTuple::empty_bound(py).unbind()
469469
}
470470
}
471471

src/impl_/extract_argument.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -615,7 +615,7 @@ impl<'py> VarargsHandler<'py> for TupleVarargs {
615615
varargs: &[Option<&PyAny>],
616616
_function_description: &FunctionDescription,
617617
) -> PyResult<Self::Varargs> {
618-
Ok(PyTuple::new(py, varargs))
618+
Ok(PyTuple::new_bound(py, varargs).into_gil_ref())
619619
}
620620

621621
#[inline]
@@ -697,7 +697,7 @@ fn push_parameter_list(msg: &mut String, parameter_names: &[&str]) {
697697
mod tests {
698698
use crate::{
699699
types::{IntoPyDict, PyTuple},
700-
PyAny, Python, ToPyObject,
700+
PyAny, Python,
701701
};
702702

703703
use super::{push_parameter_list, FunctionDescription, NoVarargs, NoVarkeywords};
@@ -714,8 +714,8 @@ mod tests {
714714
};
715715

716716
Python::with_gil(|py| {
717-
let args = PyTuple::new(py, Vec::<&PyAny>::new());
718-
let kwargs = [("foo".to_object(py).into_ref(py), 0u8)].into_py_dict(py);
717+
let args = PyTuple::new_bound(py, Vec::<&PyAny>::new());
718+
let kwargs = [("foo", 0u8)].into_py_dict(py);
719719
let err = unsafe {
720720
function_description
721721
.extract_arguments_tuple_dict::<NoVarargs, NoVarkeywords>(
@@ -745,8 +745,8 @@ mod tests {
745745
};
746746

747747
Python::with_gil(|py| {
748-
let args = PyTuple::new(py, Vec::<&PyAny>::new());
749-
let kwargs = [(1u8.to_object(py).into_ref(py), 1u8)].into_py_dict(py);
748+
let args = PyTuple::new_bound(py, Vec::<&PyAny>::new());
749+
let kwargs = [(1u8, 1u8)].into_py_dict(py);
750750
let err = unsafe {
751751
function_description
752752
.extract_arguments_tuple_dict::<NoVarargs, NoVarkeywords>(
@@ -776,7 +776,7 @@ mod tests {
776776
};
777777

778778
Python::with_gil(|py| {
779-
let args = PyTuple::new(py, Vec::<&PyAny>::new());
779+
let args = PyTuple::new_bound(py, Vec::<&PyAny>::new());
780780
let mut output = [None, None];
781781
let err = unsafe {
782782
function_description.extract_arguments_tuple_dict::<NoVarargs, NoVarkeywords>(

src/impl_/pymodule.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ impl ModuleDef {
7272
.import("sys")?
7373
.getattr("implementation")?
7474
.getattr("version")?;
75-
if version.lt(crate::types::PyTuple::new(py, PYPY_GOOD_VERSION))? {
75+
if version.lt(crate::types::PyTuple::new_bound(py, PYPY_GOOD_VERSION))? {
7676
let warn = py.import("warnings")?.getattr("warn")?;
7777
warn.call1((
7878
"PyPy 3.7 versions older than 7.3.8 are known to have binary \

src/types/datetime.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ impl PyDate {
209209
///
210210
/// This is equivalent to `datetime.date.fromtimestamp`
211211
pub fn from_timestamp(py: Python<'_>, timestamp: i64) -> PyResult<&PyDate> {
212-
let time_tuple = PyTuple::new(py, [timestamp]);
212+
let time_tuple = PyTuple::new_bound(py, [timestamp]);
213213

214214
// safety ensure that the API is loaded
215215
let _api = ensure_datetime_api(py);

src/types/list.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1231,7 +1231,7 @@ mod tests {
12311231
Python::with_gil(|py| {
12321232
let list = PyList::new(py, vec![1, 2, 3]);
12331233
let tuple = list.to_tuple();
1234-
let tuple_expected = PyTuple::new(py, vec![1, 2, 3]);
1234+
let tuple_expected = PyTuple::new_bound(py, vec![1, 2, 3]);
12351235
assert!(tuple.eq(tuple_expected).unwrap());
12361236
})
12371237
}

src/types/sequence.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1009,7 +1009,7 @@ mod tests {
10091009
assert!(seq
10101010
.to_tuple()
10111011
.unwrap()
1012-
.eq(PyTuple::new(py, ["foo", "bar"]))
1012+
.eq(PyTuple::new_bound(py, ["foo", "bar"]))
10131013
.unwrap());
10141014
});
10151015
}
@@ -1020,7 +1020,11 @@ mod tests {
10201020
let v = vec!["foo", "bar"];
10211021
let ob = v.to_object(py);
10221022
let seq = ob.downcast::<PySequence>(py).unwrap();
1023-
assert!(seq.to_tuple().unwrap().eq(PyTuple::new(py, &v)).unwrap());
1023+
assert!(seq
1024+
.to_tuple()
1025+
.unwrap()
1026+
.eq(PyTuple::new_bound(py, &v))
1027+
.unwrap());
10241028
});
10251029
}
10261030

src/types/tuple.rs

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,23 @@ pub struct PyTuple(PyAny);
5858
pyobject_native_type_core!(PyTuple, pyobject_native_static_type_object!(ffi::PyTuple_Type), #checkfunction=ffi::PyTuple_Check);
5959

6060
impl PyTuple {
61+
/// Deprecated form of `PyTuple::new_bound`.
62+
#[track_caller]
63+
#[deprecated(
64+
since = "0.21.0",
65+
note = "`PyTuple::new` will be replaced by `PyTuple::new_bound` in a future PyO3 version"
66+
)]
67+
pub fn new<T, U>(
68+
py: Python<'_>,
69+
elements: impl IntoIterator<Item = T, IntoIter = U>,
70+
) -> &PyTuple
71+
where
72+
T: ToPyObject,
73+
U: ExactSizeIterator<Item = T>,
74+
{
75+
Self::new_bound(py, elements).into_gil_ref()
76+
}
77+
6178
/// Constructs a new tuple with the given elements.
6279
///
6380
/// If you want to create a [`PyTuple`] with elements of different or unknown types, or from an
@@ -73,7 +90,7 @@ impl PyTuple {
7390
/// # fn main() {
7491
/// Python::with_gil(|py| {
7592
/// let elements: Vec<i32> = vec![0, 1, 2, 3, 4, 5];
76-
/// let tuple: &PyTuple = PyTuple::new(py, elements);
93+
/// let tuple = PyTuple::new_bound(py, elements);
7794
/// assert_eq!(format!("{:?}", tuple), "(0, 1, 2, 3, 4, 5)");
7895
/// });
7996
/// # }
@@ -85,21 +102,34 @@ impl PyTuple {
85102
/// All standard library structures implement this trait correctly, if they do, so calling this
86103
/// function using [`Vec`]`<T>` or `&[T]` will always succeed.
87104
#[track_caller]
88-
pub fn new<T, U>(
105+
pub fn new_bound<T, U>(
89106
py: Python<'_>,
90107
elements: impl IntoIterator<Item = T, IntoIter = U>,
91-
) -> &PyTuple
108+
) -> Bound<'_, PyTuple>
92109
where
93110
T: ToPyObject,
94111
U: ExactSizeIterator<Item = T>,
95112
{
96113
let mut elements = elements.into_iter().map(|e| e.to_object(py));
97-
new_from_iter(py, &mut elements).into_gil_ref()
114+
new_from_iter(py, &mut elements)
98115
}
99116

100-
/// Constructs an empty tuple (on the Python side, a singleton object).
117+
/// Deprecated form of `PyTuple::empty_bound`.
118+
#[deprecated(
119+
since = "0.21.0",
120+
note = "`PyTuple::empty` will be replaced by `PyTuple::empty_bound` in a future PyO3 version"
121+
)]
101122
pub fn empty(py: Python<'_>) -> &PyTuple {
102-
unsafe { py.from_owned_ptr(ffi::PyTuple_New(0)) }
123+
Self::empty_bound(py).into_gil_ref()
124+
}
125+
126+
/// Constructs an empty tuple (on the Python side, a singleton object).
127+
pub fn empty_bound(py: Python<'_>) -> Bound<'_, PyTuple> {
128+
unsafe {
129+
ffi::PyTuple_New(0)
130+
.assume_owned(py)
131+
.downcast_into_unchecked()
132+
}
103133
}
104134

105135
/// Gets the length of the tuple.
@@ -767,6 +797,7 @@ tuple_conversion!(
767797
);
768798

769799
#[cfg(test)]
800+
#[allow(deprecated)] // TODO: remove allow when GIL Pool is removed
770801
mod tests {
771802
use crate::types::{PyAny, PyList, PyTuple};
772803
use crate::{Python, ToPyObject};

tests/test_compile_error.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ fn test_compile_errors() {
3333
t.compile_fail("tests/ui/invalid_pymethod_receiver.rs");
3434
t.compile_fail("tests/ui/missing_intopy.rs");
3535
// adding extra error conversion impls changes the output
36-
#[cfg(not(any(windows, feature = "eyre", feature = "anyhow")))]
36+
#[cfg(not(any(windows, feature = "eyre", feature = "anyhow", Py_LIMITED_API)))]
3737
t.compile_fail("tests/ui/invalid_result_conversion.rs");
3838
t.compile_fail("tests/ui/not_send.rs");
3939
t.compile_fail("tests/ui/not_send2.rs");

tests/test_frompyobject.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -162,11 +162,11 @@ pub struct Tuple(String, usize);
162162
#[test]
163163
fn test_tuple_struct() {
164164
Python::with_gil(|py| {
165-
let tup = PyTuple::new(py, &[1.into_py(py), "test".into_py(py)]);
166-
let tup = Tuple::extract(tup.as_ref());
165+
let tup = PyTuple::new_bound(py, &[1.into_py(py), "test".into_py(py)]);
166+
let tup = Tuple::extract(tup.as_gil_ref());
167167
assert!(tup.is_err());
168-
let tup = PyTuple::new(py, &["test".into_py(py), 1.into_py(py)]);
169-
let tup = Tuple::extract(tup.as_ref()).expect("Failed to extract Tuple from PyTuple");
168+
let tup = PyTuple::new_bound(py, &["test".into_py(py), 1.into_py(py)]);
169+
let tup = Tuple::extract(tup.as_gil_ref()).expect("Failed to extract Tuple from PyTuple");
170170
assert_eq!(tup.0, "test");
171171
assert_eq!(tup.1, 1);
172172
});
@@ -324,8 +324,8 @@ pub struct PyBool {
324324
#[test]
325325
fn test_enum() {
326326
Python::with_gil(|py| {
327-
let tup = PyTuple::new(py, &[1.into_py(py), "test".into_py(py)]);
328-
let f = Foo::extract(tup.as_ref()).expect("Failed to extract Foo from tuple");
327+
let tup = PyTuple::new_bound(py, &[1.into_py(py), "test".into_py(py)]);
328+
let f = Foo::extract(tup.as_gil_ref()).expect("Failed to extract Foo from tuple");
329329
match f {
330330
Foo::TupleVar(test, test2) => {
331331
assert_eq!(test, 1);
@@ -401,8 +401,8 @@ TypeError: failed to extract enum Foo ('TupleVar | StructVar | TransparentTuple
401401
- variant StructWithGetItemArg (StructWithGetItemArg): KeyError: 'foo'"
402402
);
403403

404-
let tup = PyTuple::empty(py);
405-
let err = Foo::extract(tup.as_ref()).unwrap_err();
404+
let tup = PyTuple::empty_bound(py);
405+
let err = Foo::extract(tup.as_gil_ref()).unwrap_err();
406406
assert_eq!(
407407
err.to_string(),
408408
"\

tests/test_various.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,15 +91,15 @@ fn intopytuple_pyclass() {
9191
#[test]
9292
fn pytuple_primitive_iter() {
9393
Python::with_gil(|py| {
94-
let tup = PyTuple::new(py, [1u32, 2, 3].iter());
94+
let tup = PyTuple::new_bound(py, [1u32, 2, 3].iter());
9595
py_assert!(py, tup, "tup == (1, 2, 3)");
9696
});
9797
}
9898

9999
#[test]
100100
fn pytuple_pyclass_iter() {
101101
Python::with_gil(|py| {
102-
let tup = PyTuple::new(
102+
let tup = PyTuple::new_bound(
103103
py,
104104
[
105105
PyCell::new(py, SimplePyClass {}).unwrap(),
@@ -126,10 +126,10 @@ impl PickleSupport {
126126
pub fn __reduce__<'py>(
127127
slf: &'py PyCell<Self>,
128128
py: Python<'py>,
129-
) -> PyResult<(PyObject, &'py PyTuple, PyObject)> {
129+
) -> PyResult<(PyObject, Bound<'py, PyTuple>, PyObject)> {
130130
let cls = slf.to_object(py).getattr(py, "__class__")?;
131131
let dict = slf.to_object(py).getattr(py, "__dict__")?;
132-
Ok((cls, PyTuple::empty(py), dict))
132+
Ok((cls, PyTuple::empty_bound(py), dict))
133133
}
134134
}
135135

0 commit comments

Comments
 (0)