diff --git a/newsfragments/3660.added.md b/newsfragments/3660.added.md new file mode 100644 index 00000000000..7350e4af57d --- /dev/null +++ b/newsfragments/3660.added.md @@ -0,0 +1 @@ +Added `PyType::full_name` which in contrast to `PyType::name` includes the module name. diff --git a/pytests/src/misc.rs b/pytests/src/misc.rs index b79af4b9e51..74770ef6a6d 100644 --- a/pytests/src/misc.rs +++ b/pytests/src/misc.rs @@ -1,4 +1,5 @@ use pyo3::prelude::*; +use std::borrow::Cow; #[pyfunction] fn issue_219() { @@ -6,8 +7,14 @@ fn issue_219() { Python::with_gil(|_| {}); } +#[pyfunction] +fn get_type_full_name(obj: &PyAny) -> PyResult> { + obj.get_type().full_name() +} + #[pymodule] pub fn misc(_py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_function(wrap_pyfunction!(issue_219, m)?)?; + m.add_function(wrap_pyfunction!(get_type_full_name, m)?)?; Ok(()) } diff --git a/pytests/tests/test_misc.py b/pytests/tests/test_misc.py index 9cc0cebc771..537ee1190ed 100644 --- a/pytests/tests/test_misc.py +++ b/pytests/tests/test_misc.py @@ -48,3 +48,9 @@ def test_import_in_subinterpreter_forbidden(): ) _xxsubinterpreters.destroy(sub_interpreter) + + +def test_type_full_name_includes_module(): + numpy = pytest.importorskip("numpy") + + assert pyo3_pytests.misc.get_type_full_name(numpy.bool_(True)) == "numpy.bool_" diff --git a/src/types/typeobject.rs b/src/types/typeobject.rs index 67eeb566da5..1763d2d9df8 100644 --- a/src/types/typeobject.rs +++ b/src/types/typeobject.rs @@ -1,5 +1,8 @@ use crate::err::{self, PyResult}; use crate::{ffi, PyAny, PyTypeInfo, Python}; +use std::borrow::Cow; +#[cfg(not(any(Py_LIMITED_API, PyPy)))] +use std::ffi::CStr; /// Represents a reference to a Python `type object`. #[repr(transparent)] @@ -35,6 +38,29 @@ impl PyType { self.getattr(intern!(self.py(), "__qualname__"))?.extract() } + /// Gets the full name including the module of the `PyType`. + pub fn full_name(&self) -> PyResult> { + #[cfg(not(any(Py_LIMITED_API, PyPy)))] + { + let name = unsafe { CStr::from_ptr((*self.as_type_ptr()).tp_name) }.to_str()?; + + Ok(Cow::Borrowed(name)) + } + + #[cfg(any(Py_LIMITED_API, PyPy))] + { + let module = self + .getattr(intern!(self.py(), "__module__"))? + .extract::<&str>()?; + + let name = self + .getattr(intern!(self.py(), "__name__"))? + .extract::<&str>()?; + + Ok(Cow::Owned(format!("{module}.{name}"))) + } + } + /// Checks whether `self` is a subclass of `other`. /// /// Equivalent to the Python expression `issubclass(self, other)`.