diff --git a/guide/src/class/numeric.md b/guide/src/class/numeric.md index a7ad9c2c..10a06e7c 100644 --- a/guide/src/class/numeric.md +++ b/guide/src/class/numeric.md @@ -232,7 +232,7 @@ impl Number { fn __repr__(slf: &PyCell) -> PyResult { // Get the class name dynamically in case `Number` is subclassed - let class_name: &str = slf.get_type().name()?; + let class_name: String = slf.get_type().qualname()?; Ok(format!("{}({})", class_name, slf.borrow().0)) } diff --git a/guide/src/class/object.md b/guide/src/class/object.md index c6bf0483..cfaa8bb1 100644 --- a/guide/src/class/object.md +++ b/guide/src/class/object.md @@ -87,7 +87,7 @@ the subclass name. This is typically done in Python code by accessing impl Number { fn __repr__(slf: &PyCell) -> PyResult { // This is the equivalent of `self.__class__.__name__` in Python. - let class_name: &str = slf.get_type().name()?; + let class_name: String = slf.get_type().qualname()?; // To access fields of the Rust struct, we need to borrow the `PyCell`. Ok(format!("{}({})", class_name, slf.borrow().0)) } @@ -263,7 +263,7 @@ impl Number { } fn __repr__(slf: &PyCell) -> PyResult { - let class_name: &str = slf.get_type().name()?; + let class_name: String = slf.get_type().qualname()?; Ok(format!("{}({})", class_name, slf.borrow().0)) } diff --git a/guide/src/migration.md b/guide/src/migration.md index 4538f799..0c55c15c 100644 --- a/guide/src/migration.md +++ b/guide/src/migration.md @@ -74,6 +74,9 @@ Python::with_gil(|py| { }); ``` +### `PyType::name` is now `PyType::qualname` + +`PyType::name` has been renamed to `PyType::qualname` to indicate that it does indeed return the [qualified name](https://docs.python.org/3/glossary.html#term-qualified-name), matching the `__qualname__` attribute. The newly added `PyType::name` yields the full name including the module name now which corresponds to `__module__.__name__` on the level of attributes. ## from 0.19.* to 0.20 diff --git a/newsfragments/3660.added.md b/newsfragments/3660.added.md deleted file mode 100644 index 7350e4af..00000000 --- a/newsfragments/3660.added.md +++ /dev/null @@ -1 +0,0 @@ -Added `PyType::full_name` which in contrast to `PyType::name` includes the module name. diff --git a/newsfragments/3660.changed.md b/newsfragments/3660.changed.md new file mode 100644 index 00000000..8b4a3f73 --- /dev/null +++ b/newsfragments/3660.changed.md @@ -0,0 +1 @@ +`PyType::name` is now `PyType::qualname` whereas `PyType::name` efficiently accesses the full name which includes the module name. diff --git a/pytests/src/misc.rs b/pytests/src/misc.rs index 74770ef6..69f3b75e 100644 --- a/pytests/src/misc.rs +++ b/pytests/src/misc.rs @@ -9,7 +9,7 @@ fn issue_219() { #[pyfunction] fn get_type_full_name(obj: &PyAny) -> PyResult> { - obj.get_type().full_name() + obj.get_type().name() } #[pymodule] diff --git a/src/err/mod.rs b/src/err/mod.rs index cd2139b6..88f930cf 100644 --- a/src/err/mod.rs +++ b/src/err/mod.rs @@ -706,7 +706,7 @@ impl std::fmt::Display for PyErr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Python::with_gil(|py| { let value = self.value(py); - let type_name = value.get_type().name().map_err(|_| std::fmt::Error)?; + let type_name = value.get_type().qualname().map_err(|_| std::fmt::Error)?; write!(f, "{}", type_name)?; if let Ok(s) = value.str() { write!(f, ": {}", &s.to_string_lossy()) @@ -748,7 +748,8 @@ impl PyErrArguments for PyDowncastErrorArguments { "'{}' object cannot be converted to '{}'", self.from .as_ref(py) - .name() + .qualname() + .as_deref() .unwrap_or(""), self.to ) @@ -775,7 +776,10 @@ impl<'a> std::fmt::Display for PyDowncastError<'a> { write!( f, "'{}' object cannot be converted to '{}'", - self.from.get_type().name().map_err(|_| std::fmt::Error)?, + self.from + .get_type() + .qualname() + .map_err(|_| std::fmt::Error)?, self.to ) } diff --git a/src/types/typeobject.rs b/src/types/typeobject.rs index 74dbbfb7..7e92925a 100644 --- a/src/types/typeobject.rs +++ b/src/types/typeobject.rs @@ -33,13 +33,28 @@ impl PyType { py.from_borrowed_ptr(p as *mut ffi::PyObject) } - /// Gets the name of the `PyType`. - pub fn name(&self) -> PyResult<&str> { - self.getattr(intern!(self.py(), "__qualname__"))?.extract() + /// Gets the [qualified name](https://docs.python.org/3/glossary.html#term-qualified-name) of the `PyType`. + pub fn qualname(&self) -> PyResult { + #[cfg(any(Py_LIMITED_API, PyPy, not(Py_3_11)))] + let name = self.getattr(intern!(self.py(), "__qualname__"))?.extract(); + + #[cfg(not(any(Py_LIMITED_API, PyPy, not(Py_3_11))))] + let name = { + use crate::ffi_ptr_ext::FfiPtrExt; + use crate::types::any::PyAnyMethods; + + let obj = unsafe { + ffi::PyType_GetQualName(self.as_type_ptr()).assume_owned_or_err(self.py())? + }; + + obj.extract() + }; + + name } /// Gets the full name, which includes the module, of the `PyType`. - pub fn full_name(&self) -> PyResult> { + pub fn name(&self) -> PyResult> { #[cfg(not(any(Py_LIMITED_API, PyPy)))] { let name = unsafe { CStr::from_ptr((*self.as_type_ptr()).tp_name) }.to_str()?; @@ -49,13 +64,17 @@ impl PyType { #[cfg(any(Py_LIMITED_API, PyPy))] { - let module = self - .getattr(intern!(self.py(), "__module__"))? - .extract::<&str>()?; + let module = self.getattr(intern!(self.py(), "__module__"))?; - let name = self - .getattr(intern!(self.py(), "__name__"))? - .extract::<&str>()?; + #[cfg(not(Py_3_11))] + let name = self.getattr(intern!(self.py(), "__name__"))?; + + #[cfg(Py_3_11)] + let name = { + use crate::ffi_ptr_ext::FfiPtrExt; + + unsafe { ffi::PyType_GetName(self.as_type_ptr()).assume_owned_or_err(self.py())? } + }; Ok(Cow::Owned(format!("{}.{}", module, name))) } diff --git a/tests/test_coroutine.rs b/tests/test_coroutine.rs index 8acea3ea..206c35da 100644 --- a/tests/test_coroutine.rs +++ b/tests/test_coroutine.rs @@ -136,7 +136,10 @@ fn cancelled_coroutine() { None, ) .unwrap_err(); - assert_eq!(err.value(gil).get_type().name().unwrap(), "CancelledError"); + assert_eq!( + err.value(gil).get_type().qualname().unwrap(), + "CancelledError" + ); }) } diff --git a/tests/test_methods.rs b/tests/test_methods.rs index 7919ac0c..6e42ad66 100644 --- a/tests/test_methods.rs +++ b/tests/test_methods.rs @@ -74,14 +74,14 @@ impl ClassMethod { #[classmethod] /// Test class method. fn method(cls: &PyType) -> PyResult { - Ok(format!("{}.method()!", cls.name()?)) + Ok(format!("{}.method()!", cls.qualname()?)) } #[classmethod] fn method_owned(cls: Py) -> PyResult { Ok(format!( "{}.method_owned()!", - Python::with_gil(|gil| cls.as_ref(gil).name().map(ToString::to_string))? + Python::with_gil(|gil| cls.as_ref(gil).qualname())? )) } } @@ -109,7 +109,7 @@ struct ClassMethodWithArgs {} impl ClassMethodWithArgs { #[classmethod] fn method(cls: &PyType, input: &PyString) -> PyResult { - Ok(format!("{}.method({})", cls.name()?, input)) + Ok(format!("{}.method({})", cls.qualname()?, input)) } } @@ -937,7 +937,7 @@ impl r#RawIdents { fn test_raw_idents() { Python::with_gil(|py| { let raw_idents_type = py.get_type::(); - assert_eq!(raw_idents_type.name().unwrap(), "RawIdents"); + assert_eq!(raw_idents_type.qualname().unwrap(), "RawIdents"); py_run!( py, raw_idents_type,