1use crate::err::{self, PyResult};
2use crate::instance::Borrowed;
3#[cfg(not(Py_3_13))]
4use crate::pybacked::PyBackedStr;
5use crate::types::any::PyAnyMethods;
6use crate::types::PyTuple;
7use crate::{ffi, Bound, PyAny, PyTypeInfo, Python};
8
9use 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)]
19pub struct PyType(PyAny);
20
21pyobject_native_type_core!(PyType, pyobject_native_static_type_object!(ffi::PyType_Type), #checkfunction=ffi::PyType_Check);
22
23impl 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")]
63pub 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
104impl<'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)]
254mod 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#"
326class 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#"
368class 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