| 1 | use crate::err::{self, PyResult}; |
| 2 | use crate::instance::Borrowed; |
| 3 | #[cfg (not(Py_3_13))] |
| 4 | use crate::pybacked::PyBackedStr; |
| 5 | use crate::types::any::PyAnyMethods; |
| 6 | use crate::types::PyTuple; |
| 7 | use crate::{ffi, Bound, PyAny, PyTypeInfo, Python}; |
| 8 | |
| 9 | use super::PyString; |
| 10 | |
| 11 | /// Represents a reference to a Python `type` object. |
| 12 | /// |
| 13 | /// Values of this type are accessed via PyO3's smart pointers, e.g. as |
| 14 | /// [`Py<PyType>`][crate::Py] or [`Bound<'py, PyType>`][Bound]. |
| 15 | /// |
| 16 | /// For APIs available on `type` objects, see the [`PyTypeMethods`] trait which is implemented for |
| 17 | /// [`Bound<'py, PyType>`][Bound]. |
| 18 | #[repr (transparent)] |
| 19 | pub struct PyType(PyAny); |
| 20 | |
| 21 | pyobject_native_type_core!(PyType, pyobject_native_static_type_object!(ffi::PyType_Type), #checkfunction=ffi::PyType_Check); |
| 22 | |
| 23 | impl PyType { |
| 24 | /// Creates a new type object. |
| 25 | #[inline ] |
| 26 | pub fn new<T: PyTypeInfo>(py: Python<'_>) -> Bound<'_, PyType> { |
| 27 | T::type_object(py) |
| 28 | } |
| 29 | |
| 30 | /// Deprecated name for [`PyType::new`]. |
| 31 | #[deprecated (since = "0.23.0" , note = "renamed to `PyType::new`" )] |
| 32 | #[inline ] |
| 33 | pub fn new_bound<T: PyTypeInfo>(py: Python<'_>) -> Bound<'_, PyType> { |
| 34 | Self::new::<T>(py) |
| 35 | } |
| 36 | |
| 37 | /// Converts the given FFI pointer into `Bound<PyType>`, to use in safe code. |
| 38 | /// |
| 39 | /// The function creates a new reference from the given pointer, and returns |
| 40 | /// it as a `Bound<PyType>`. |
| 41 | /// |
| 42 | /// # Safety |
| 43 | /// - The pointer must be a valid non-null reference to a `PyTypeObject` |
| 44 | #[inline ] |
| 45 | pub unsafe fn from_borrowed_type_ptr( |
| 46 | py: Python<'_>, |
| 47 | p: *mut ffi::PyTypeObject, |
| 48 | ) -> Bound<'_, PyType> { |
| 49 | unsafe { |
| 50 | Borrowed::from_ptr_unchecked(py, p.cast()) |
| 51 | .downcast_unchecked() |
| 52 | .to_owned() |
| 53 | } |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | /// Implementation of functionality for [`PyType`]. |
| 58 | /// |
| 59 | /// These methods are defined for the `Bound<'py, PyType>` smart pointer, so to use method call |
| 60 | /// syntax these methods are separated into a trait, because stable Rust does not yet support |
| 61 | /// `arbitrary_self_types`. |
| 62 | #[doc (alias = "PyType" )] |
| 63 | pub trait PyTypeMethods<'py>: crate::sealed::Sealed { |
| 64 | /// Retrieves the underlying FFI pointer associated with this Python object. |
| 65 | fn as_type_ptr(&self) -> *mut ffi::PyTypeObject; |
| 66 | |
| 67 | /// Gets the name of the `PyType`. Equivalent to `self.__name__` in Python. |
| 68 | fn name(&self) -> PyResult<Bound<'py, PyString>>; |
| 69 | |
| 70 | /// Gets the [qualified name](https://docs.python.org/3/glossary.html#term-qualified-name) of the `PyType`. |
| 71 | /// Equivalent to `self.__qualname__` in Python. |
| 72 | fn qualname(&self) -> PyResult<Bound<'py, PyString>>; |
| 73 | |
| 74 | /// Gets the name of the module defining the `PyType`. |
| 75 | fn module(&self) -> PyResult<Bound<'py, PyString>>; |
| 76 | |
| 77 | /// Gets the [fully qualified name](https://peps.python.org/pep-0737/#add-pytype-getfullyqualifiedname-function) of the `PyType`. |
| 78 | fn fully_qualified_name(&self) -> PyResult<Bound<'py, PyString>>; |
| 79 | |
| 80 | /// Checks whether `self` is a subclass of `other`. |
| 81 | /// |
| 82 | /// Equivalent to the Python expression `issubclass(self, other)`. |
| 83 | fn is_subclass(&self, other: &Bound<'_, PyAny>) -> PyResult<bool>; |
| 84 | |
| 85 | /// Checks whether `self` is a subclass of type `T`. |
| 86 | /// |
| 87 | /// Equivalent to the Python expression `issubclass(self, T)`, if the type |
| 88 | /// `T` is known at compile time. |
| 89 | fn is_subclass_of<T>(&self) -> PyResult<bool> |
| 90 | where |
| 91 | T: PyTypeInfo; |
| 92 | |
| 93 | /// Return the method resolution order for this type. |
| 94 | /// |
| 95 | /// Equivalent to the Python expression `self.__mro__`. |
| 96 | fn mro(&self) -> Bound<'py, PyTuple>; |
| 97 | |
| 98 | /// Return Python bases |
| 99 | /// |
| 100 | /// Equivalent to the Python expression `self.__bases__`. |
| 101 | fn bases(&self) -> Bound<'py, PyTuple>; |
| 102 | } |
| 103 | |
| 104 | impl<'py> PyTypeMethods<'py> for Bound<'py, PyType> { |
| 105 | /// Retrieves the underlying FFI pointer associated with this Python object. |
| 106 | #[inline ] |
| 107 | fn as_type_ptr(&self) -> *mut ffi::PyTypeObject { |
| 108 | self.as_ptr() as *mut ffi::PyTypeObject |
| 109 | } |
| 110 | |
| 111 | /// Gets the name of the `PyType`. |
| 112 | fn name(&self) -> PyResult<Bound<'py, PyString>> { |
| 113 | #[cfg (not(Py_3_11))] |
| 114 | let name = self |
| 115 | .getattr(intern!(self.py(), "__name__" ))? |
| 116 | .downcast_into()?; |
| 117 | |
| 118 | #[cfg (Py_3_11)] |
| 119 | let name = unsafe { |
| 120 | use crate::ffi_ptr_ext::FfiPtrExt; |
| 121 | ffi::PyType_GetName(self.as_type_ptr()) |
| 122 | .assume_owned_or_err(self.py())? |
| 123 | // SAFETY: setting `__name__` from Python is required to be a `str` |
| 124 | .downcast_into_unchecked() |
| 125 | }; |
| 126 | |
| 127 | Ok(name) |
| 128 | } |
| 129 | |
| 130 | /// Gets the [qualified name](https://docs.python.org/3/glossary.html#term-qualified-name) of the `PyType`. |
| 131 | fn qualname(&self) -> PyResult<Bound<'py, PyString>> { |
| 132 | #[cfg (not(Py_3_11))] |
| 133 | let name = self |
| 134 | .getattr(intern!(self.py(), "__qualname__" ))? |
| 135 | .downcast_into()?; |
| 136 | |
| 137 | #[cfg (Py_3_11)] |
| 138 | let name = unsafe { |
| 139 | use crate::ffi_ptr_ext::FfiPtrExt; |
| 140 | ffi::PyType_GetQualName(self.as_type_ptr()) |
| 141 | .assume_owned_or_err(self.py())? |
| 142 | // SAFETY: setting `__qualname__` from Python is required to be a `str` |
| 143 | .downcast_into_unchecked() |
| 144 | }; |
| 145 | |
| 146 | Ok(name) |
| 147 | } |
| 148 | |
| 149 | /// Gets the name of the module defining the `PyType`. |
| 150 | fn module(&self) -> PyResult<Bound<'py, PyString>> { |
| 151 | #[cfg (not(Py_3_13))] |
| 152 | let name = self.getattr(intern!(self.py(), "__module__" ))?; |
| 153 | |
| 154 | #[cfg (Py_3_13)] |
| 155 | let name = unsafe { |
| 156 | use crate::ffi_ptr_ext::FfiPtrExt; |
| 157 | ffi::PyType_GetModuleName(self.as_type_ptr()).assume_owned_or_err(self.py())? |
| 158 | }; |
| 159 | |
| 160 | // `__module__` is never guaranteed to be a `str` |
| 161 | name.downcast_into().map_err(Into::into) |
| 162 | } |
| 163 | |
| 164 | /// Gets the [fully qualified name](https://docs.python.org/3/glossary.html#term-qualified-name) of the `PyType`. |
| 165 | fn fully_qualified_name(&self) -> PyResult<Bound<'py, PyString>> { |
| 166 | #[cfg (not(Py_3_13))] |
| 167 | let name = { |
| 168 | let module = self.getattr(intern!(self.py(), "__module__" ))?; |
| 169 | let qualname = self.getattr(intern!(self.py(), "__qualname__" ))?; |
| 170 | |
| 171 | let module_str = module.extract::<PyBackedStr>()?; |
| 172 | if module_str == "builtins" || module_str == "__main__" { |
| 173 | qualname.downcast_into()? |
| 174 | } else { |
| 175 | PyString::new(self.py(), &format!(" {}. {}" , module, qualname)) |
| 176 | } |
| 177 | }; |
| 178 | |
| 179 | #[cfg (Py_3_13)] |
| 180 | let name = unsafe { |
| 181 | use crate::ffi_ptr_ext::FfiPtrExt; |
| 182 | ffi::PyType_GetFullyQualifiedName(self.as_type_ptr()) |
| 183 | .assume_owned_or_err(self.py())? |
| 184 | .downcast_into_unchecked() |
| 185 | }; |
| 186 | |
| 187 | Ok(name) |
| 188 | } |
| 189 | |
| 190 | /// Checks whether `self` is a subclass of `other`. |
| 191 | /// |
| 192 | /// Equivalent to the Python expression `issubclass(self, other)`. |
| 193 | fn is_subclass(&self, other: &Bound<'_, PyAny>) -> PyResult<bool> { |
| 194 | let result = unsafe { ffi::PyObject_IsSubclass(self.as_ptr(), other.as_ptr()) }; |
| 195 | err::error_on_minusone(self.py(), result)?; |
| 196 | Ok(result == 1) |
| 197 | } |
| 198 | |
| 199 | /// Checks whether `self` is a subclass of type `T`. |
| 200 | /// |
| 201 | /// Equivalent to the Python expression `issubclass(self, T)`, if the type |
| 202 | /// `T` is known at compile time. |
| 203 | fn is_subclass_of<T>(&self) -> PyResult<bool> |
| 204 | where |
| 205 | T: PyTypeInfo, |
| 206 | { |
| 207 | self.is_subclass(&T::type_object(self.py())) |
| 208 | } |
| 209 | |
| 210 | fn mro(&self) -> Bound<'py, PyTuple> { |
| 211 | #[cfg (any(Py_LIMITED_API, PyPy))] |
| 212 | let mro = self |
| 213 | .getattr(intern!(self.py(), "__mro__" )) |
| 214 | .expect("Cannot get `__mro__` from object." ) |
| 215 | .extract() |
| 216 | .expect("Unexpected type in `__mro__` attribute." ); |
| 217 | |
| 218 | #[cfg (not(any(Py_LIMITED_API, PyPy)))] |
| 219 | let mro = unsafe { |
| 220 | use crate::ffi_ptr_ext::FfiPtrExt; |
| 221 | (*self.as_type_ptr()) |
| 222 | .tp_mro |
| 223 | .assume_borrowed(self.py()) |
| 224 | .to_owned() |
| 225 | .downcast_into_unchecked() |
| 226 | }; |
| 227 | |
| 228 | mro |
| 229 | } |
| 230 | |
| 231 | fn bases(&self) -> Bound<'py, PyTuple> { |
| 232 | #[cfg (any(Py_LIMITED_API, PyPy))] |
| 233 | let bases = self |
| 234 | .getattr(intern!(self.py(), "__bases__" )) |
| 235 | .expect("Cannot get `__bases__` from object." ) |
| 236 | .extract() |
| 237 | .expect("Unexpected type in `__bases__` attribute." ); |
| 238 | |
| 239 | #[cfg (not(any(Py_LIMITED_API, PyPy)))] |
| 240 | let bases = unsafe { |
| 241 | use crate::ffi_ptr_ext::FfiPtrExt; |
| 242 | (*self.as_type_ptr()) |
| 243 | .tp_bases |
| 244 | .assume_borrowed(self.py()) |
| 245 | .to_owned() |
| 246 | .downcast_into_unchecked() |
| 247 | }; |
| 248 | |
| 249 | bases |
| 250 | } |
| 251 | } |
| 252 | |
| 253 | #[cfg (test)] |
| 254 | mod tests { |
| 255 | use crate::tests::common::generate_unique_module_name; |
| 256 | use crate::types::{PyAnyMethods, PyBool, PyInt, PyModule, PyTuple, PyType, PyTypeMethods}; |
| 257 | use crate::PyAny; |
| 258 | use crate::Python; |
| 259 | use pyo3_ffi::c_str; |
| 260 | |
| 261 | #[test ] |
| 262 | fn test_type_is_subclass() { |
| 263 | Python::with_gil(|py| { |
| 264 | let bool_type = py.get_type::<PyBool>(); |
| 265 | let long_type = py.get_type::<PyInt>(); |
| 266 | assert!(bool_type.is_subclass(&long_type).unwrap()); |
| 267 | }); |
| 268 | } |
| 269 | |
| 270 | #[test ] |
| 271 | fn test_type_is_subclass_of() { |
| 272 | Python::with_gil(|py| { |
| 273 | assert!(py.get_type::<PyBool>().is_subclass_of::<PyInt>().unwrap()); |
| 274 | }); |
| 275 | } |
| 276 | |
| 277 | #[test ] |
| 278 | fn test_mro() { |
| 279 | Python::with_gil(|py| { |
| 280 | assert!(py |
| 281 | .get_type::<PyBool>() |
| 282 | .mro() |
| 283 | .eq(PyTuple::new( |
| 284 | py, |
| 285 | [ |
| 286 | py.get_type::<PyBool>(), |
| 287 | py.get_type::<PyInt>(), |
| 288 | py.get_type::<PyAny>() |
| 289 | ] |
| 290 | ) |
| 291 | .unwrap()) |
| 292 | .unwrap()); |
| 293 | }); |
| 294 | } |
| 295 | |
| 296 | #[test ] |
| 297 | fn test_bases_bool() { |
| 298 | Python::with_gil(|py| { |
| 299 | assert!(py |
| 300 | .get_type::<PyBool>() |
| 301 | .bases() |
| 302 | .eq(PyTuple::new(py, [py.get_type::<PyInt>()]).unwrap()) |
| 303 | .unwrap()); |
| 304 | }); |
| 305 | } |
| 306 | |
| 307 | #[test ] |
| 308 | fn test_bases_object() { |
| 309 | Python::with_gil(|py| { |
| 310 | assert!(py |
| 311 | .get_type::<PyAny>() |
| 312 | .bases() |
| 313 | .eq(PyTuple::empty(py)) |
| 314 | .unwrap()); |
| 315 | }); |
| 316 | } |
| 317 | |
| 318 | #[test ] |
| 319 | fn test_type_names_standard() { |
| 320 | Python::with_gil(|py| { |
| 321 | let module_name = generate_unique_module_name("test_module" ); |
| 322 | let module = PyModule::from_code( |
| 323 | py, |
| 324 | c_str!( |
| 325 | r#" |
| 326 | class MyClass: |
| 327 | pass |
| 328 | "# |
| 329 | ), |
| 330 | c_str!(file!()), |
| 331 | &module_name, |
| 332 | ) |
| 333 | .expect("module create failed" ); |
| 334 | |
| 335 | let my_class = module.getattr("MyClass" ).unwrap(); |
| 336 | let my_class_type = my_class.downcast_into::<PyType>().unwrap(); |
| 337 | assert_eq!(my_class_type.name().unwrap(), "MyClass" ); |
| 338 | assert_eq!(my_class_type.qualname().unwrap(), "MyClass" ); |
| 339 | let module_name = module_name.to_str().unwrap(); |
| 340 | let qualname = format!("{module_name}.MyClass" ); |
| 341 | assert_eq!(my_class_type.module().unwrap(), module_name); |
| 342 | assert_eq!( |
| 343 | my_class_type.fully_qualified_name().unwrap(), |
| 344 | qualname.as_str() |
| 345 | ); |
| 346 | }); |
| 347 | } |
| 348 | |
| 349 | #[test ] |
| 350 | fn test_type_names_builtin() { |
| 351 | Python::with_gil(|py| { |
| 352 | let bool_type = py.get_type::<PyBool>(); |
| 353 | assert_eq!(bool_type.name().unwrap(), "bool" ); |
| 354 | assert_eq!(bool_type.qualname().unwrap(), "bool" ); |
| 355 | assert_eq!(bool_type.module().unwrap(), "builtins" ); |
| 356 | assert_eq!(bool_type.fully_qualified_name().unwrap(), "bool" ); |
| 357 | }); |
| 358 | } |
| 359 | |
| 360 | #[test ] |
| 361 | fn test_type_names_nested() { |
| 362 | Python::with_gil(|py| { |
| 363 | let module_name = generate_unique_module_name("test_module" ); |
| 364 | let module = PyModule::from_code( |
| 365 | py, |
| 366 | c_str!( |
| 367 | r#" |
| 368 | class OuterClass: |
| 369 | class InnerClass: |
| 370 | pass |
| 371 | "# |
| 372 | ), |
| 373 | c_str!(file!()), |
| 374 | &module_name, |
| 375 | ) |
| 376 | .expect("module create failed" ); |
| 377 | |
| 378 | let outer_class = module.getattr("OuterClass" ).unwrap(); |
| 379 | let inner_class = outer_class.getattr("InnerClass" ).unwrap(); |
| 380 | let inner_class_type = inner_class.downcast_into::<PyType>().unwrap(); |
| 381 | assert_eq!(inner_class_type.name().unwrap(), "InnerClass" ); |
| 382 | assert_eq!( |
| 383 | inner_class_type.qualname().unwrap(), |
| 384 | "OuterClass.InnerClass" |
| 385 | ); |
| 386 | let module_name = module_name.to_str().unwrap(); |
| 387 | let qualname = format!("{module_name}.OuterClass.InnerClass" ); |
| 388 | assert_eq!(inner_class_type.module().unwrap(), module_name); |
| 389 | assert_eq!( |
| 390 | inner_class_type.fully_qualified_name().unwrap(), |
| 391 | qualname.as_str() |
| 392 | ); |
| 393 | }); |
| 394 | } |
| 395 | } |
| 396 | |