pyo3/types/
typeobject.rs

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