Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion guide/pyclass-parameters.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
| `sequence` | Inform PyO3 that this class is a [`Sequence`][params-sequence], and so leave its C-API mapping length slot empty. |
| `set_all` | Generates setters for all fields of the pyclass. |
| `new = "from_fields"` | Generates a default `__new__` constructor with all fields as parameters in the `new()` method. |
| `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`. |
| `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).* |
| `subclass` | Allows other Python classes and `#[pyclass]` to inherit from this class. Enums cannot be subclassed. |
| `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. |
Expand Down
6 changes: 5 additions & 1 deletion guide/src/conversions/traits.md
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ Over the next few releases the blanket implementation is gradually phased out, a
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:

```rust
# #![allow(dead_code)]
# #![allow(dead_code, deprecated)]
# use pyo3::prelude::*;

#[pyclass(skip_from_py_object)] // opt-out of the PyO3 FromPyObject blanket
Expand All @@ -548,6 +548,10 @@ impl<'py> FromPyObject<'_, 'py> for Number {
As a second step the `from_py_object` option was introduced.
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.

As of PyO3 0.30.0 `skip_from_py_object` is the new default behavior.
Setting the option is now deprecated and can be safely removed without changes in behavior.
PyO3 will remove this option entirely in a future release.

## `IntoPyObject`

The [`IntoPyObject`] trait defines the to-python conversion for a Rust type.
Expand Down
2 changes: 1 addition & 1 deletion guide/src/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ struct PyClass {}

After:

```rust
```rust,ignore
# use pyo3::prelude::*;
// If the automatic implementation of `FromPyObject` is desired, opt in:
#[pyclass(from_py_object)]
Expand Down
2 changes: 2 additions & 0 deletions newsfragments/6188.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
switched the default pyclass behavior to not emit `FromPyObject` implementation (previously deprecated)
deprecated `skip_from_py_object` pyclass option (as is is now the default)
1 change: 1 addition & 0 deletions newsfragments/6188.removed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
removed deprecated `FromPyObject` blanket implementation
14 changes: 3 additions & 11 deletions pyo3-macros-backend/src/pyclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2944,15 +2944,9 @@ impl<'a> PyClassImplsBuilder<'a> {
});
}

let deprecation = if self.attr.options.skip_from_py_object.is_none()
&& self.attr.options.from_py_object.is_none()
{
quote! {
const _: () = {
#[allow(unused_import)]
use #pyo3_path::impl_::pyclass::Probe as _;
#pyo3_path::impl_::deprecated::HasAutomaticFromPyObject::<{ #pyo3_path::impl_::pyclass::IsClone::<#cls>::VALUE }>::MSG
};
let deprecation = if self.attr.options.skip_from_py_object.is_some() {
quote_spanned! { self.attr.options.skip_from_py_object.span() =>
const _: () = #pyo3_path::impl_::deprecated::SKIP_FROM_PY_OBJECT_DEPRECATED;
}
} else {
TokenStream::new()
Expand All @@ -2976,8 +2970,6 @@ impl<'a> PyClassImplsBuilder<'a> {
}
}
}
} else if self.attr.options.skip_from_py_object.is_none() {
quote!( impl #pyo3_path::impl_::pyclass::ExtractPyClassWithClone for #cls {} )
} else {
TokenStream::new()
};
Expand Down
4 changes: 2 additions & 2 deletions pytests/src/pyclasses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ impl PyClassThreadIter {
}

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

Expand Down Expand Up @@ -136,7 +136,7 @@ impl SubClassWithInit {
}
}

#[pyclass(skip_from_py_object)]
#[pyclass]
#[derive(Clone)]
struct ClassWithDecorators {
attr: Option<usize>,
Expand Down
19 changes: 2 additions & 17 deletions src/conversion.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
//! Defines conversions between Rust and Python types.
use crate::err::PyResult;
use crate::impl_::pyclass::ExtractPyClassWithClone;
#[cfg(feature = "experimental-inspect")]
use crate::inspect::{type_hint_identifier, type_hint_subscript, PyStaticExpr};
use crate::platform::prelude::*;
Expand All @@ -9,8 +8,7 @@ use crate::pyclass::{PyClassGuardError, PyClassGuardMutError};
use crate::types::PyList;
use crate::types::PyTuple;
use crate::{
Borrowed, Bound, BoundObject, Py, PyAny, PyClass, PyClassGuard, PyErr, PyRef, PyRefMut,
PyTypeCheck, Python,
Borrowed, Bound, BoundObject, Py, PyAny, PyClass, PyErr, PyRef, PyRefMut, PyTypeCheck, Python,
};
use core::convert::Infallible;
use core::marker::PhantomData;
Expand Down Expand Up @@ -512,20 +510,6 @@ pub(crate) use from_py_object_sequence::FromPyObjectSequence;
pub trait FromPyObjectOwned<'py>: for<'a> FromPyObject<'a, 'py> {}
impl<'py, T> FromPyObjectOwned<'py> for T where T: for<'a> FromPyObject<'a, 'py> {}

impl<'a, 'py, T> FromPyObject<'a, 'py> for T
where
T: PyClass + Clone + ExtractPyClassWithClone,
{
type Error = PyClassGuardError<'a, 'py>;

#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = <T as crate::PyTypeInfo>::TYPE_HINT;

fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
Ok(obj.extract::<PyClassGuard<'_, T>>()?.clone())
}
}

impl<'a, 'py, T> FromPyObject<'a, 'py> for PyRef<'py, T>
where
T: PyClass,
Expand Down Expand Up @@ -595,6 +579,7 @@ mod test_no_clone {}
mod tests {
#[test]
#[cfg(feature = "macros")]
#[expect(deprecated)]
fn test_pyclass_skip_from_py_object() {
use crate::{types::PyAnyMethods, FromPyObject, IntoPyObject, PyErr, Python};

Expand Down
18 changes: 5 additions & 13 deletions src/impl_/deprecated.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
pub struct HasAutomaticFromPyObject<const IS_CLONE: bool> {}

impl HasAutomaticFromPyObject<true> {
#[deprecated(
since = "0.28.0",
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."
)]
pub const MSG: () = ();
}

impl HasAutomaticFromPyObject<false> {
pub const MSG: () = ();
}
#[deprecated(
since = "0.30.0",
note = "`skip_from_py_object` enabled by default. The option will be removed in the future"
)]
pub const SKIP_FROM_PY_OBJECT_DEPRECATED: () = ();
2 changes: 0 additions & 2 deletions src/impl_/pyclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1472,8 +1472,6 @@ impl<const IMPLEMENTS_INTOPYOBJECT: bool> ConvertField<false, IMPLEMENTS_INTOPYO
}
}

pub trait ExtractPyClassWithClone: generic_pyclass::Sealed {}

#[cfg(test)]
#[cfg(feature = "macros")]
mod tests {
Expand Down
2 changes: 1 addition & 1 deletion src/marker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -970,7 +970,7 @@ mod tests {
fn test_py_run_inserts_globals_2() {
use alloc::ffi::CString;

#[crate::pyclass(crate = "crate", skip_from_py_object)]
#[crate::pyclass(crate = "crate")]
#[derive(Clone)]
struct CodeRunner {
code: CString,
Expand Down
2 changes: 1 addition & 1 deletion src/pycell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -775,7 +775,7 @@ mod tests {

use super::*;

#[crate::pyclass(skip_from_py_object)]
#[crate::pyclass]
#[pyo3(crate = "crate")]
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
struct SomeClass(i32);
Expand Down
14 changes: 7 additions & 7 deletions tests/test_class_comparisons.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ use pyo3::prelude::*;

mod test_utils;

#[pyclass(eq, skip_from_py_object)]
#[pyclass(eq)]
#[derive(Debug, Clone, PartialEq)]
pub enum MyEnum {
Variant,
OtherVariant,
}

#[pyclass(eq, ord, skip_from_py_object)]
#[pyclass(eq, ord)]
#[derive(Debug, PartialEq, Eq, Clone, PartialOrd)]
pub enum MyEnumOrd {
Variant,
Expand Down Expand Up @@ -63,14 +63,14 @@ fn test_simple_enum_ord_comparable() {
})
}

#[pyclass(eq, ord, skip_from_py_object)]
#[pyclass(eq, ord)]
#[derive(Debug, PartialEq, Eq, Clone, PartialOrd)]
pub enum MyComplexEnumOrd {
Variant(i32),
OtherVariant(String),
}

#[pyclass(eq, ord, skip_from_py_object)]
#[pyclass(eq, ord)]
#[derive(Debug, PartialEq, Eq, Clone, PartialOrd)]
pub enum MyComplexEnumOrd2 {
Variant { msg: String, idx: u32 },
Expand Down Expand Up @@ -145,7 +145,7 @@ fn test_complex_enum_ord_comparable() {
})
}

#[pyclass(eq, ord, skip_from_py_object)]
#[pyclass(eq, ord)]
#[derive(Debug, PartialEq, Eq, Clone, PartialOrd)]
pub struct Point {
x: i32,
Expand All @@ -170,7 +170,7 @@ fn test_struct_numeric_ord_comparable() {
})
}

#[pyclass(eq, ord, skip_from_py_object)]
#[pyclass(eq, ord)]
#[derive(Debug, PartialEq, Eq, Clone, PartialOrd)]
pub struct Person {
surname: String,
Expand Down Expand Up @@ -222,7 +222,7 @@ fn test_struct_string_ord_comparable() {
})
}

#[pyclass(eq, ord, skip_from_py_object)]
#[pyclass(eq, ord)]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Record {
name: String,
Expand Down
4 changes: 2 additions & 2 deletions tests/test_class_formatting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ fn test_enum_class_fmt() {
})
}

#[pyclass(str = "X: {x}, Y: {y}, Z: {z}", skip_from_py_object)]
#[pyclass(str = "X: {x}, Y: {y}, Z: {z}")]
#[derive(PartialEq, Eq, Clone, PartialOrd)]
pub struct Point {
x: i32,
Expand All @@ -65,7 +65,7 @@ fn test_custom_struct_custom_str() {
})
}

#[pyclass(str, skip_from_py_object)]
#[pyclass(str)]
#[derive(PartialEq, Eq, Clone, PartialOrd)]
pub struct Point2 {
x: i32,
Expand Down
22 changes: 11 additions & 11 deletions tests/test_enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ fn test_enum_arg() {
})
}

#[pyclass(eq, eq_int, skip_from_py_object)]
#[pyclass(eq, eq_int)]
#[derive(Debug, PartialEq, Eq, Clone)]
enum CustomDiscriminant {
One = 1,
Expand Down Expand Up @@ -147,7 +147,7 @@ fn test_enum_compare_int() {
})
}

#[pyclass(eq, eq_int, skip_from_py_object)]
#[pyclass(eq, eq_int)]
#[derive(Debug, PartialEq, Eq, Clone)]
#[repr(u8)]
enum SmallEnum {
Expand All @@ -162,7 +162,7 @@ fn test_enum_compare_int_no_throw_when_overflow() {
})
}

#[pyclass(eq, eq_int, skip_from_py_object)]
#[pyclass(eq, eq_int)]
#[derive(Debug, PartialEq, Eq, Clone)]
#[repr(usize)]
#[allow(clippy::enum_clike_unportable_variant)]
Expand All @@ -181,7 +181,7 @@ fn test_big_enum_no_overflow() {
})
}

#[pyclass(eq, eq_int, skip_from_py_object)]
#[pyclass(eq, eq_int)]
#[derive(Debug, PartialEq, Eq, Clone)]
#[repr(u16, align(8))]
enum TestReprParse {
Expand All @@ -193,7 +193,7 @@ fn test_repr_parse() {
assert_eq!(std::mem::align_of::<TestReprParse>(), 8);
}

#[pyclass(eq, eq_int, name = "MyEnum", skip_from_py_object)]
#[pyclass(eq, eq_int, name = "MyEnum")]
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum RenameEnum {
Variant,
Expand All @@ -207,7 +207,7 @@ fn test_rename_enum_repr_correct() {
})
}

#[pyclass(eq, eq_int, skip_from_py_object)]
#[pyclass(eq, eq_int)]
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum RenameVariantEnum {
#[pyo3(name = "VARIANT")]
Expand All @@ -222,7 +222,7 @@ fn test_rename_variant_repr_correct() {
})
}

#[pyclass(eq, eq_int, rename_all = "SCREAMING_SNAKE_CASE", skip_from_py_object)]
#[pyclass(eq, eq_int, rename_all = "SCREAMING_SNAKE_CASE")]
#[derive(Debug, PartialEq, Eq, Clone)]
#[expect(clippy::enum_variant_names)]
enum RenameAllVariantsEnum {
Expand Down Expand Up @@ -265,7 +265,7 @@ fn test_custom_module() {
});
}

#[pyclass(eq, skip_from_py_object)]
#[pyclass(eq)]
#[derive(Debug, Clone, PartialEq)]
pub enum EqOnly {
VariantA,
Expand Down Expand Up @@ -390,7 +390,7 @@ fn custom_eq() {
})
}

#[pyclass(skip_from_py_object)]
#[pyclass]
#[derive(Clone, Copy)]
pub enum ComplexEnumWithRaw {
Raw { r#type: i32 },
Expand Down Expand Up @@ -427,7 +427,7 @@ fn complex_enum_with_raw_pattern_match() {

#[test]
fn complex_enum_variant_qualname() {
#[pyclass(skip_from_py_object)]
#[pyclass]
pub enum ComplexEnum {
A(i32),
B { msg: String },
Expand All @@ -442,7 +442,7 @@ fn complex_enum_variant_qualname() {

#[test]
fn complex_enum_renamed_variant_qualname() {
#[pyclass(name = "ComplexEnum", skip_from_py_object)]
#[pyclass(name = "ComplexEnum")]
pub enum PyComplexEnum {
#[pyo3(name = "A")]
PyA(i32),
Expand Down
2 changes: 1 addition & 1 deletion tests/test_frompyobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ pub struct E<T, T2> {
test2: T2,
}

#[pyclass(skip_from_py_object)]
#[pyclass]
#[derive(Clone)]
pub struct PyE {
#[pyo3(get)]
Expand Down
2 changes: 1 addition & 1 deletion tests/test_pyself.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ mod test_utils;

/// Assumes it's a file reader or so.
/// Inspired by https://github.com/jothan/cordoba, thanks.
#[pyclass(skip_from_py_object)]
#[pyclass]
#[derive(Clone, Debug)]
struct Reader {
inner: HashMap<u8, String>,
Expand Down
11 changes: 1 addition & 10 deletions tests/ui/abi3_inheritance.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,6 @@ note: required by a bound in `pyo3::impl_::pyclass::PyClassImpl::BaseType`
| type BaseType: PyTypeInfo + PyClassBaseType;
| ^^^^^^^^^^^^^^^ required by this bound in `PyClassImpl::BaseType`

warning: use of deprecated associated constant `pyo3::impl_::deprecated::HasAutomaticFromPyObject::<true>::MSG`: 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.
--> tests/ui/abi3_inheritance.rs:4:1
|
4 | #[pyclass(extends=PyException)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `#[warn(deprecated)]` on by default
= note: this warning originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info)

error: aborting due to 2 previous errors; 1 warning emitted
error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0277`.
Loading
Loading