Skip to content

Commit

Permalink
Added as_super and into_super methods for Bound<T: PyClass> (#4351
Browse files Browse the repository at this point in the history
)

* Added `Bound::as_super` and `Bound::into_super` methods.

* Added tests for the `Bound::as_super` and `Bound::into_super` methods.

* Added newsfragment entry.

* Fixed newsfragment PR number.

* Fixed doc links.

* Fixed missing spaces in method docstrings.

Co-authored-by: Lily Foote <[email protected]>

* fixup docs

* fixup docs

---------

Co-authored-by: jrudolph <[email protected]>
Co-authored-by: Lily Foote <[email protected]>
Co-authored-by: Icxolu <[email protected]>
  • Loading branch information
4 people committed Jul 14, 2024
1 parent 17f40ca commit d598d1f
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 0 deletions.
1 change: 1 addition & 0 deletions newsfragments/4351.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added `as_super` and `into_super` methods for `Bound<T: PyClass>`.
142 changes: 142 additions & 0 deletions src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,110 @@ where
self.1.get()
}

/// Upcast this `Bound<PyClass>` to its base type by reference.
///
/// If this type defined an explicit base class in its `pyclass` declaration
/// (e.g. `#[pyclass(extends = BaseType)]`), the returned type will be
/// `&Bound<BaseType>`. If an explicit base class was _not_ declared, the
/// return value will be `&Bound<PyAny>` (making this method equivalent
/// to [`as_any`]).
///
/// This method is particularly useful for calling methods defined in an
/// extension trait that has been implemented for `Bound<BaseType>`.
///
/// See also the [`into_super`] method to upcast by value, and the
/// [`PyRef::as_super`]/[`PyRefMut::as_super`] methods for upcasting a pyclass
/// that has already been [`borrow`]ed.
///
/// # Example: Calling a method defined on the `Bound` base type
///
/// ```rust
/// # fn main() {
/// use pyo3::prelude::*;
///
/// #[pyclass(subclass)]
/// struct BaseClass;
///
/// trait MyClassMethods<'py> {
/// fn pyrepr(&self) -> PyResult<String>;
/// }
/// impl<'py> MyClassMethods<'py> for Bound<'py, BaseClass> {
/// fn pyrepr(&self) -> PyResult<String> {
/// self.call_method0("__repr__")?.extract()
/// }
/// }
///
/// #[pyclass(extends = BaseClass)]
/// struct SubClass;
///
/// Python::with_gil(|py| {
/// let obj = Bound::new(py, (SubClass, BaseClass)).unwrap();
/// assert!(obj.as_super().pyrepr().is_ok());
/// })
/// # }
/// ```
///
/// [`as_any`]: Bound::as_any
/// [`into_super`]: Bound::into_super
/// [`borrow`]: Bound::borrow
#[inline]
pub fn as_super(&self) -> &Bound<'py, T::BaseType> {
// a pyclass can always be safely "downcast" to its base type
unsafe { self.as_any().downcast_unchecked() }
}

/// Upcast this `Bound<PyClass>` to its base type by value.
///
/// If this type defined an explicit base class in its `pyclass` declaration
/// (e.g. `#[pyclass(extends = BaseType)]`), the returned type will be
/// `Bound<BaseType>`. If an explicit base class was _not_ declared, the
/// return value will be `Bound<PyAny>` (making this method equivalent
/// to [`into_any`]).
///
/// This method is particularly useful for calling methods defined in an
/// extension trait that has been implemented for `Bound<BaseType>`.
///
/// See also the [`as_super`] method to upcast by reference, and the
/// [`PyRef::into_super`]/[`PyRefMut::into_super`] methods for upcasting a pyclass
/// that has already been [`borrow`]ed.
///
/// # Example: Calling a method defined on the `Bound` base type
///
/// ```rust
/// # fn main() {
/// use pyo3::prelude::*;
///
/// #[pyclass(subclass)]
/// struct BaseClass;
///
/// trait MyClassMethods<'py> {
/// fn pyrepr(self) -> PyResult<String>;
/// }
/// impl<'py> MyClassMethods<'py> for Bound<'py, BaseClass> {
/// fn pyrepr(self) -> PyResult<String> {
/// self.call_method0("__repr__")?.extract()
/// }
/// }
///
/// #[pyclass(extends = BaseClass)]
/// struct SubClass;
///
/// Python::with_gil(|py| {
/// let obj = Bound::new(py, (SubClass, BaseClass)).unwrap();
/// assert!(obj.into_super().pyrepr().is_ok());
/// })
/// # }
/// ```
///
/// [`into_any`]: Bound::into_any
/// [`as_super`]: Bound::as_super
/// [`borrow`]: Bound::borrow
#[inline]
pub fn into_super(self) -> Bound<'py, T::BaseType> {
// a pyclass can always be safely "downcast" to its base type
unsafe { self.into_any().downcast_into_unchecked() }
}

#[inline]
pub(crate) fn get_class_object(&self) -> &PyClassObject<T> {
self.1.get_class_object()
Expand Down Expand Up @@ -2324,5 +2428,43 @@ a = A()
}
})
}

#[crate::pyclass(crate = "crate", subclass)]
struct BaseClass;

trait MyClassMethods<'py>: Sized {
fn pyrepr_by_ref(&self) -> PyResult<String>;
fn pyrepr_by_val(self) -> PyResult<String> {
self.pyrepr_by_ref()
}
}
impl<'py> MyClassMethods<'py> for Bound<'py, BaseClass> {
fn pyrepr_by_ref(&self) -> PyResult<String> {
self.call_method0("__repr__")?.extract()
}
}

#[crate::pyclass(crate = "crate", extends = BaseClass)]
struct SubClass;

#[test]
fn test_as_super() {
Python::with_gil(|py| {
let obj = Bound::new(py, (SubClass, BaseClass)).unwrap();
let _: &Bound<'_, BaseClass> = obj.as_super();
let _: &Bound<'_, PyAny> = obj.as_super().as_super();
assert!(obj.as_super().pyrepr_by_ref().is_ok());
})
}

#[test]
fn test_into_super() {
Python::with_gil(|py| {
let obj = Bound::new(py, (SubClass, BaseClass)).unwrap();
let _: Bound<'_, BaseClass> = obj.clone().into_super();
let _: Bound<'_, PyAny> = obj.clone().into_super().into_super();
assert!(obj.into_super().pyrepr_by_val().is_ok());
})
}
}
}

0 comments on commit d598d1f

Please sign in to comment.