Skip to content

Commit 613e542

Browse files
authored
deprecate Py<T> constructors (#5585)
* deprecate `Py<T>` constructors * sentence per line * newsfragment * change some additional uses of `Py` constructors * fix pre-3.12 implementation * coverage * fixup cfg for pre-3.10
1 parent 9b9adca commit 613e542

File tree

12 files changed

+212
-70
lines changed

12 files changed

+212
-70
lines changed

guide/src/migration.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,43 @@ To migrate use either
1515
- `from_py_object` to keep the automatic derive, or
1616
- `skip_from_py_object` to accept the new behaviour
1717

18+
### Deprecation of `Py<T>` constructors from raw pointer
19+
20+
The constructors `Py::from_owned_ptr`, `Py::from_owned_ptr_or_opt`, and `Py::from_owned_ptr_or_err` (and similar "borrowed" variants) perform an unchecked cast to the `Py<T>` target type `T`.
21+
This unchecked cast is a footgun on APIs where the primary concern is about constructing PyO3's safe smart pointer types correctly from the raw pointer value.
22+
23+
The equivalent constructors on `Bound` always produce a `Bound<PyAny>`, which encourages any subsequent cast to be done explicitly as either checked or unchecked.
24+
These should be used instead.
25+
26+
Before:
27+
28+
```rust
29+
#![allow(deprecated)]
30+
# use pyo3::prelude::*;
31+
# use pyo3::types::PyNone;
32+
# Python::attach(|py| {
33+
let raw_ptr = py.None().into_ptr();
34+
35+
let _: Py<PyNone> = unsafe { Py::from_borrowed_ptr(py, raw_ptr) };
36+
let _: Py<PyNone> = unsafe { Py::from_owned_ptr(py, raw_ptr) };
37+
# })
38+
```
39+
40+
Before:
41+
42+
```rust
43+
# use pyo3::prelude::*;
44+
# use pyo3::types::PyNone;
45+
# Python::attach(|py| {
46+
let raw_ptr = py.None().into_ptr();
47+
48+
// Bound APIs require choice of doing unchecked or checked cast. Optionally `.unbind()` to
49+
// produce `Py<T>` values.
50+
let _: Bound<'_, PyNone> = unsafe { Bound::from_borrowed_ptr(py, raw_ptr).cast_into_unchecked() };
51+
let _: Bound<'_, PyNone> = unsafe { Bound::from_owned_ptr(py, raw_ptr).cast_into_unchecked() };
52+
# })
53+
```
54+
1855
## from 0.26.* to 0.27
1956

2057
### `FromPyObject` reworked for flexibility and efficiency

newsfragments/5585.changed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Deprecate `Py<T>::from_{owned,borrowed}[or_{err,opt}]` constructors from raw pointer.

src/conversions/num_bigint.rs

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@
5050
#[cfg(Py_LIMITED_API)]
5151
use crate::types::{bytes::PyBytesMethods, PyBytes};
5252
use crate::{
53-
conversion::IntoPyObject, ffi, types::PyInt, Borrowed, Bound, FromPyObject, Py, PyAny, PyErr,
54-
PyResult, Python,
53+
conversion::IntoPyObject, std::num::nb_index, types::PyInt, Borrowed, Bound, FromPyObject,
54+
PyAny, PyErr, PyResult, Python,
5555
};
5656

5757
use num_bigint::{BigInt, BigUint};
@@ -129,14 +129,13 @@ impl<'py> FromPyObject<'_, 'py> for BigInt {
129129
type Error = PyErr;
130130

131131
fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result<BigInt, Self::Error> {
132-
let py = ob.py();
133132
// fast path - checking for subclass of `int` just checks a bit in the type object
134-
let num_owned: Py<PyInt>;
133+
let num_owned: Bound<'_, PyInt>;
135134
let num = if let Ok(long) = ob.cast::<PyInt>() {
136135
long
137136
} else {
138-
num_owned = unsafe { Py::from_owned_ptr_or_err(py, ffi::PyNumber_Index(ob.as_ptr()))? };
139-
num_owned.bind_borrowed(py)
137+
num_owned = nb_index(&ob)?;
138+
num_owned.as_borrowed()
140139
};
141140
#[cfg(not(Py_LIMITED_API))]
142141
{
@@ -179,14 +178,13 @@ impl<'py> FromPyObject<'_, 'py> for BigUint {
179178
type Error = PyErr;
180179

181180
fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result<BigUint, Self::Error> {
182-
let py = ob.py();
183181
// fast path - checking for subclass of `int` just checks a bit in the type object
184-
let num_owned: Py<PyInt>;
182+
let num_owned: Bound<'_, PyInt>;
185183
let num = if let Ok(long) = ob.cast::<PyInt>() {
186184
long
187185
} else {
188-
num_owned = unsafe { Py::from_owned_ptr_or_err(py, ffi::PyNumber_Index(ob.as_ptr()))? };
189-
num_owned.bind_borrowed(py)
186+
num_owned = nb_index(&ob)?;
187+
num_owned.as_borrowed()
190188
};
191189
#[cfg(not(Py_LIMITED_API))]
192190
{
@@ -208,6 +206,8 @@ impl<'py> FromPyObject<'_, 'py> for BigUint {
208206
#[cfg(not(any(Py_LIMITED_API, Py_3_13)))]
209207
#[inline]
210208
fn int_to_u32_vec<const SIGNED: bool>(long: &Bound<'_, PyInt>) -> PyResult<Vec<u32>> {
209+
use crate::ffi;
210+
211211
let mut buffer = Vec::new();
212212
let n_bits = int_n_bits(long)?;
213213
if n_bits == 0 {
@@ -242,6 +242,8 @@ fn int_to_u32_vec<const SIGNED: bool>(long: &Bound<'_, PyInt>) -> PyResult<Vec<u
242242
#[cfg(all(not(Py_LIMITED_API), Py_3_13))]
243243
#[inline]
244244
fn int_to_u32_vec<const SIGNED: bool>(long: &Bound<'_, PyInt>) -> PyResult<Vec<u32>> {
245+
use crate::ffi;
246+
245247
let mut buffer = Vec::new();
246248
let mut flags = ffi::Py_ASNATIVEBYTES_LITTLE_ENDIAN;
247249
if !SIGNED {
@@ -304,7 +306,7 @@ fn int_n_bits(long: &Bound<'_, PyInt>) -> PyResult<usize> {
304306
#[cfg(not(Py_LIMITED_API))]
305307
{
306308
// fast path
307-
let n_bits = unsafe { ffi::_PyLong_NumBits(long.as_ptr()) };
309+
let n_bits = unsafe { crate::ffi::_PyLong_NumBits(long.as_ptr()) };
308310
if n_bits == (-1isize as usize) {
309311
return Err(crate::PyErr::fetch(py));
310312
}
@@ -323,6 +325,7 @@ fn int_n_bits(long: &Bound<'_, PyInt>) -> PyResult<usize> {
323325
#[cfg(test)]
324326
mod tests {
325327
use super::*;
328+
use crate::exceptions::PyTypeError;
326329
use crate::test_utils::generate_unique_module_name;
327330
use crate::types::{PyAnyMethods as _, PyDict, PyModule};
328331
use indoc::indoc;
@@ -413,6 +416,7 @@ mod tests {
413416
locals.set_item("index", index).unwrap();
414417
let ob = py.eval(c"index.C(10)", None, Some(&locals)).unwrap();
415418
let _: BigInt = ob.extract().unwrap();
419+
let _: BigUint = ob.extract().unwrap();
416420
});
417421
}
418422

@@ -455,4 +459,16 @@ mod tests {
455459
}
456460
});
457461
}
462+
463+
#[test]
464+
fn from_py_float_type_error() {
465+
Python::attach(|py| {
466+
let obj = (12.3f64).into_pyobject(py).unwrap();
467+
let err = obj.extract::<BigInt>().unwrap_err();
468+
assert!(err.is_instance_of::<PyTypeError>(py));
469+
470+
let err = obj.extract::<BigUint>().unwrap_err();
471+
assert!(err.is_instance_of::<PyTypeError>(py));
472+
});
473+
}
458474
}

src/conversions/std/num.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::ffi_ptr_ext::FfiPtrExt;
55
use crate::inspect::types::TypeInfo;
66
#[cfg(feature = "experimental-inspect")]
77
use crate::inspect::TypeHint;
8+
use crate::py_result_ext::PyResultExt;
89
use crate::types::{PyByteArray, PyByteArrayMethods, PyBytes, PyInt};
910
use crate::{exceptions, ffi, Borrowed, Bound, FromPyObject, PyAny, PyErr, PyResult, Python};
1011
use std::convert::Infallible;
@@ -93,7 +94,7 @@ macro_rules! extract_int {
9394
err_if_invalid_value($obj.py(), $error_val, unsafe { $pylong_as(long.as_ptr()) })
9495
} else {
9596
unsafe {
96-
let num = ffi::PyNumber_Index($obj.as_ptr()).assume_owned_or_err($obj.py())?;
97+
let num = nb_index(&$obj)?;
9798
err_if_invalid_value($obj.py(), $error_val, $pylong_as(num.as_ptr()))
9899
}
99100
}
@@ -459,8 +460,7 @@ mod fast_128bit_int_conversion {
459460
const INPUT_TYPE: TypeHint = TypeHint::builtin("int");
460461

461462
fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result<$rust_type, Self::Error> {
462-
let num =
463-
unsafe { ffi::PyNumber_Index(ob.as_ptr()).assume_owned_or_err(ob.py())? };
463+
let num = nb_index(&ob)?;
464464
let mut buffer = [0u8; std::mem::size_of::<$rust_type>()];
465465
#[cfg(not(Py_3_13))]
466466
{
@@ -545,6 +545,11 @@ pub(crate) fn int_from_ne_bytes<'py, const IS_SIGNED: bool>(
545545
}
546546
}
547547

548+
pub(crate) fn nb_index<'py>(obj: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyInt>> {
549+
// SAFETY: PyNumber_Index returns a new reference or NULL on error
550+
unsafe { ffi::PyNumber_Index(obj.as_ptr()).assume_owned_or_err(obj.py()) }.cast_into()
551+
}
552+
548553
// For ABI3 we implement the conversion manually.
549554
#[cfg(Py_LIMITED_API)]
550555
mod slow_128bit_int_conversion {

src/conversions/std/osstr.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,23 +75,25 @@ impl FromPyObject<'_, '_> for OsString {
7575

7676
#[cfg(not(windows))]
7777
{
78+
use crate::types::{PyBytes, PyBytesMethods};
79+
7880
// Decode from Python's lossless bytes string representation back into raw bytes
81+
// SAFETY: PyUnicode_EncodeFSDefault returns a new reference or null on error, known to
82+
// be a `bytes` object, thread is attached to the interpreter
7983
let fs_encoded_bytes = unsafe {
80-
crate::Py::<crate::types::PyBytes>::from_owned_ptr(
81-
ob.py(),
82-
ffi::PyUnicode_EncodeFSDefault(pystring.as_ptr()),
83-
)
84+
ffi::PyUnicode_EncodeFSDefault(pystring.as_ptr())
85+
.assume_owned_or_err(ob.py())?
86+
.cast_into_unchecked::<PyBytes>()
8487
};
8588

8689
// Create an OsStr view into the raw bytes from Python
8790
//
8891
// For WASI: OS strings are UTF-8 by definition.
8992
#[cfg(target_os = "wasi")]
90-
let os_str: &OsStr =
91-
OsStr::new(std::str::from_utf8(fs_encoded_bytes.as_bytes(ob.py()))?);
93+
let os_str: &OsStr = OsStr::new(std::str::from_utf8(fs_encoded_bytes.as_bytes())?);
9294
#[cfg(not(target_os = "wasi"))]
9395
let os_str: &OsStr =
94-
std::os::unix::ffi::OsStrExt::from_bytes(fs_encoded_bytes.as_bytes(ob.py()));
96+
std::os::unix::ffi::OsStrExt::from_bytes(fs_encoded_bytes.as_bytes());
9597

9698
Ok(os_str.to_os_string())
9799
}

src/err/err_state.rs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,9 @@ impl PyErrStateNormalized {
147147
ptype: pvalue.get_type().into(),
148148
#[cfg(not(Py_3_12))]
149149
ptraceback: unsafe {
150-
Py::from_owned_ptr_or_opt(
151-
pvalue.py(),
152-
ffi::PyException_GetTraceback(pvalue.as_ptr()),
153-
)
150+
ffi::PyException_GetTraceback(pvalue.as_ptr())
151+
.assume_owned_or_opt(pvalue.py())
152+
.map(|b| b.cast_into_unchecked().unbind())
154153
},
155154
pvalue: pvalue.into(),
156155
}
@@ -240,11 +239,22 @@ impl PyErrStateNormalized {
240239
ptraceback: *mut ffi::PyObject,
241240
) -> Self {
242241
PyErrStateNormalized {
243-
ptype: unsafe { Py::from_owned_ptr_or_opt(py, ptype).expect("Exception type missing") },
242+
ptype: unsafe {
243+
ptype
244+
.assume_owned_or_opt(py)
245+
.expect("Exception type missing")
246+
.cast_into_unchecked()
247+
}
248+
.unbind(),
244249
pvalue: unsafe {
245-
Py::from_owned_ptr_or_opt(py, pvalue).expect("Exception value missing")
246-
},
247-
ptraceback: unsafe { Py::from_owned_ptr_or_opt(py, ptraceback) },
250+
pvalue
251+
.assume_owned_or_opt(py)
252+
.expect("Exception value missing")
253+
.cast_into_unchecked()
254+
}
255+
.unbind(),
256+
ptraceback: unsafe { ptraceback.assume_owned_or_opt(py) }
257+
.map(|b| unsafe { b.cast_into_unchecked() }.unbind()),
248258
}
249259
}
250260

src/err/mod.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
use crate::ffi_ptr_ext::FfiPtrExt;
12
use crate::instance::Bound;
23
#[cfg(Py_3_11)]
34
use crate::intern;
45
use crate::panic::PanicException;
6+
use crate::py_result_ext::PyResultExt;
57
use crate::type_object::PyTypeInfo;
68
use crate::types::any::PyAnyMethods;
79
#[cfg(Py_3_11)]
@@ -361,9 +363,14 @@ impl PyErr {
361363
None => std::ptr::null(),
362364
};
363365

364-
let ptr = unsafe { ffi::PyErr_NewExceptionWithDoc(name.as_ptr(), doc_ptr, base, dict) };
365-
366-
unsafe { Py::from_owned_ptr_or_err(py, ptr) }
366+
// SAFETY: correct call to FFI function, return value is known to be a new
367+
// exception type or null on error
368+
unsafe {
369+
ffi::PyErr_NewExceptionWithDoc(name.as_ptr(), doc_ptr, base, dict)
370+
.assume_owned_or_err(py)
371+
.cast_into_unchecked()
372+
}
373+
.map(Bound::unbind)
367374
}
368375

369376
/// Prints a standard traceback to `sys.stderr`.

src/impl_/pyclass.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::{
22
exceptions::{PyAttributeError, PyNotImplementedError, PyRuntimeError},
33
ffi,
4+
ffi_ptr_ext::FfiPtrExt,
45
impl_::{
56
freelist::PyObjectFreeList,
67
pycell::{GetBorrowChecker, PyClassMutability, PyClassObjectLayout},
@@ -332,7 +333,8 @@ slot_fragment_trait! {
332333
attr: *mut ffi::PyObject,
333334
) -> PyResult<*mut ffi::PyObject> {
334335
Err(PyErr::new::<PyAttributeError, _>(
335-
(unsafe {Py::<PyAny>::from_borrowed_ptr(py, attr)},)
336+
// SAFETY: caller has upheld the safety contract
337+
(unsafe { attr.assume_borrowed_unchecked(py) }.to_owned().unbind(),)
336338
))
337339
}
338340
}

0 commit comments

Comments
 (0)