Skip to content

Commit 3369dfc

Browse files
committed
remove FromPyObject blanket implementation
1 parent 0ef7321 commit 3369dfc

25 files changed

Lines changed: 470 additions & 854 deletions

guide/pyclass-parameters.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
| `sequence` | Inform PyO3 that this class is a [`Sequence`][params-sequence], and so leave its C-API mapping length slot empty. |
2424
| `set_all` | Generates setters for all fields of the pyclass. |
2525
| `new = "from_fields"` | Generates a default `__new__` constructor with all fields as parameters in the `new()` method. |
26-
| `skip_from_py_object` | Prevents this PyClass from participating in the `FromPyObject: PyClass + Clone` blanket implementation. This allows a custom `FromPyObject` impl, even if `self` is `Clone`. |
2726
| `str` | Implements `__str__` using the `Display` implementation of the underlying Rust datatype or by passing an optional format string `str="<format string>"`. *Note: The optional format string is only allowed for structs. `name` and `rename_all` are incompatible with the optional format string. Additional details can be found in the discussion on this [PR](https://github.com/PyO3/pyo3/pull/4233).* |
2827
| `subclass` | Allows other Python classes and `#[pyclass]` to inherit from this class. Enums cannot be subclassed. |
2928
| `unsendable` | Required if your struct is not [`Send`][params-3]. Rather than using `unsendable`, consider implementing your struct in a thread-safe way by e.g. substituting [`Rc`][params-4] with [`Arc`][params-5]. By using `unsendable`, your class will panic when accessed by another thread. Also note the Python's GC is multi-threaded and while unsendable classes will not be traversed on foreign threads to avoid UB, this can lead to memory leaks. |

guide/src/conversions/traits.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,7 @@ Over the next few releases the blanket implementation is gradually phased out, a
525525
As a first step of this migration a new `skip_from_py_object` option for `#[pyclass]` was introduced, to opt-out of the blanket implementation and allow downstream users to provide their own implementation:
526526

527527
```rust
528-
# #![allow(dead_code)]
528+
# #![allow(dead_code, deprecated)]
529529
# use pyo3::prelude::*;
530530

531531
#[pyclass(skip_from_py_object)] // opt-out of the PyO3 FromPyObject blanket
@@ -548,6 +548,10 @@ impl<'py> FromPyObject<'_, 'py> for Number {
548548
As a second step the `from_py_object` option was introduced.
549549
This option also opts-out of the blanket implementation and instead generates a custom `FromPyObject` implementation for the pyclass which is functionally equivalent to the blanket.
550550

551+
As of PyO3 0.30.0 `skip_from_py_object` is the new default behavior.
552+
Setting the option is now deprecated and can be safely removed without changes in behavior.
553+
PyO3 will remove this option entirely in a future release.
554+
551555
## `IntoPyObject`
552556

553557
The [`IntoPyObject`] trait defines the to-python conversion for a Rust type.

guide/src/migration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ struct PyClass {}
173173

174174
After:
175175

176-
```rust
176+
```rust,ignore
177177
# use pyo3::prelude::*;
178178
// If the automatic implementation of `FromPyObject` is desired, opt in:
179179
#[pyclass(from_py_object)]

pyo3-macros-backend/src/pyclass.rs

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2944,15 +2944,9 @@ impl<'a> PyClassImplsBuilder<'a> {
29442944
});
29452945
}
29462946

2947-
let deprecation = if self.attr.options.skip_from_py_object.is_none()
2948-
&& self.attr.options.from_py_object.is_none()
2949-
{
2950-
quote! {
2951-
const _: () = {
2952-
#[allow(unused_import)]
2953-
use #pyo3_path::impl_::pyclass::Probe as _;
2954-
#pyo3_path::impl_::deprecated::HasAutomaticFromPyObject::<{ #pyo3_path::impl_::pyclass::IsClone::<#cls>::VALUE }>::MSG
2955-
};
2947+
let deprecation = if self.attr.options.skip_from_py_object.is_some() {
2948+
quote_spanned! { self.attr.options.skip_from_py_object.span() =>
2949+
const _: () = #pyo3_path::impl_::deprecated::SKIP_FROM_PY_OBJECT_DEPRECATED;
29562950
}
29572951
} else {
29582952
TokenStream::new()
@@ -2976,8 +2970,6 @@ impl<'a> PyClassImplsBuilder<'a> {
29762970
}
29772971
}
29782972
}
2979-
} else if self.attr.options.skip_from_py_object.is_none() {
2980-
quote!( impl #pyo3_path::impl_::pyclass::ExtractPyClassWithClone for #cls {} )
29812973
} else {
29822974
TokenStream::new()
29832975
};

pytests/src/pyclasses.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ impl PyClassThreadIter {
7474
}
7575

7676
/// Demonstrates a base class which can operate on the relevant subclass in its constructor.
77-
#[pyclass(subclass, skip_from_py_object)]
77+
#[pyclass(subclass)]
7878
#[derive(Clone, Debug)]
7979
struct AssertingBaseClass;
8080

@@ -136,7 +136,7 @@ impl SubClassWithInit {
136136
}
137137
}
138138

139-
#[pyclass(skip_from_py_object)]
139+
#[pyclass]
140140
#[derive(Clone)]
141141
struct ClassWithDecorators {
142142
attr: Option<usize>,

src/conversion.rs

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
//! Defines conversions between Rust and Python types.
22
use crate::err::PyResult;
3-
use crate::impl_::pyclass::ExtractPyClassWithClone;
43
#[cfg(feature = "experimental-inspect")]
54
use crate::inspect::{type_hint_identifier, type_hint_subscript, PyStaticExpr};
65
use crate::platform::prelude::*;
@@ -9,8 +8,7 @@ use crate::pyclass::{PyClassGuardError, PyClassGuardMutError};
98
use crate::types::PyList;
109
use crate::types::PyTuple;
1110
use crate::{
12-
Borrowed, Bound, BoundObject, Py, PyAny, PyClass, PyClassGuard, PyErr, PyRef, PyRefMut,
13-
PyTypeCheck, Python,
11+
Borrowed, Bound, BoundObject, Py, PyAny, PyClass, PyErr, PyRef, PyRefMut, PyTypeCheck, Python,
1412
};
1513
use core::convert::Infallible;
1614
use core::marker::PhantomData;
@@ -512,20 +510,6 @@ pub(crate) use from_py_object_sequence::FromPyObjectSequence;
512510
pub trait FromPyObjectOwned<'py>: for<'a> FromPyObject<'a, 'py> {}
513511
impl<'py, T> FromPyObjectOwned<'py> for T where T: for<'a> FromPyObject<'a, 'py> {}
514512

515-
impl<'a, 'py, T> FromPyObject<'a, 'py> for T
516-
where
517-
T: PyClass + Clone + ExtractPyClassWithClone,
518-
{
519-
type Error = PyClassGuardError<'a, 'py>;
520-
521-
#[cfg(feature = "experimental-inspect")]
522-
const INPUT_TYPE: PyStaticExpr = <T as crate::PyTypeInfo>::TYPE_HINT;
523-
524-
fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
525-
Ok(obj.extract::<PyClassGuard<'_, T>>()?.clone())
526-
}
527-
}
528-
529513
impl<'a, 'py, T> FromPyObject<'a, 'py> for PyRef<'py, T>
530514
where
531515
T: PyClass,
@@ -595,6 +579,7 @@ mod test_no_clone {}
595579
mod tests {
596580
#[test]
597581
#[cfg(feature = "macros")]
582+
#[expect(deprecated)]
598583
fn test_pyclass_skip_from_py_object() {
599584
use crate::{types::PyAnyMethods, FromPyObject, IntoPyObject, PyErr, Python};
600585

src/impl_/deprecated.rs

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
1-
pub struct HasAutomaticFromPyObject<const IS_CLONE: bool> {}
2-
3-
impl HasAutomaticFromPyObject<true> {
4-
#[deprecated(
5-
since = "0.28.0",
6-
note = "The `FromPyObject` implementation for `#[pyclass]` types which implement `Clone` is changing to an opt-in option. Use `#[pyclass(from_py_object)]` to opt-in to the `FromPyObject` derive now, or `#[pyclass(skip_from_py_object)]` to skip the `FromPyObject` implementation."
7-
)]
8-
pub const MSG: () = ();
9-
}
10-
11-
impl HasAutomaticFromPyObject<false> {
12-
pub const MSG: () = ();
13-
}
1+
#[deprecated(
2+
since = "0.30.0",
3+
note = "`skip_from_py_object` enabled by default. The option will be removed in the future"
4+
)]
5+
pub const SKIP_FROM_PY_OBJECT_DEPRECATED: () = ();

src/impl_/pyclass.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1472,8 +1472,6 @@ impl<const IMPLEMENTS_INTOPYOBJECT: bool> ConvertField<false, IMPLEMENTS_INTOPYO
14721472
}
14731473
}
14741474

1475-
pub trait ExtractPyClassWithClone: generic_pyclass::Sealed {}
1476-
14771475
#[cfg(test)]
14781476
#[cfg(feature = "macros")]
14791477
mod tests {

src/marker.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -970,7 +970,7 @@ mod tests {
970970
fn test_py_run_inserts_globals_2() {
971971
use alloc::ffi::CString;
972972

973-
#[crate::pyclass(crate = "crate", skip_from_py_object)]
973+
#[crate::pyclass(crate = "crate")]
974974
#[derive(Clone)]
975975
struct CodeRunner {
976976
code: CString,

src/pycell.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -775,7 +775,7 @@ mod tests {
775775

776776
use super::*;
777777

778-
#[crate::pyclass(skip_from_py_object)]
778+
#[crate::pyclass]
779779
#[pyo3(crate = "crate")]
780780
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
781781
struct SomeClass(i32);

0 commit comments

Comments
 (0)