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#[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 #[inline]
26 pub fn new<T: PyTypeInfo>(py: Python<'_>) -> Bound<'_, PyType> {
27 T::type_object(py)
28 }
29
30 #[inline]
38 pub unsafe fn from_borrowed_type_ptr(
39 py: Python<'_>,
40 p: *mut ffi::PyTypeObject,
41 ) -> Bound<'_, PyType> {
42 unsafe {
43 Borrowed::from_ptr_unchecked(py, p.cast())
44 .downcast_unchecked()
45 .to_owned()
46 }
47 }
48}
49
50#[doc(alias = "PyType")]
56pub trait PyTypeMethods<'py>: crate::sealed::Sealed {
57 fn as_type_ptr(&self) -> *mut ffi::PyTypeObject;
59
60 fn name(&self) -> PyResult<Bound<'py, PyString>>;
62
63 fn qualname(&self) -> PyResult<Bound<'py, PyString>>;
66
67 fn module(&self) -> PyResult<Bound<'py, PyString>>;
69
70 fn fully_qualified_name(&self) -> PyResult<Bound<'py, PyString>>;
72
73 fn is_subclass(&self, other: &Bound<'_, PyAny>) -> PyResult<bool>;
77
78 fn is_subclass_of<T>(&self) -> PyResult<bool>
83 where
84 T: PyTypeInfo;
85
86 fn mro(&self) -> Bound<'py, PyTuple>;
90
91 fn bases(&self) -> Bound<'py, PyTuple>;
95}
96
97impl<'py> PyTypeMethods<'py> for Bound<'py, PyType> {
98 #[inline]
100 fn as_type_ptr(&self) -> *mut ffi::PyTypeObject {
101 self.as_ptr() as *mut ffi::PyTypeObject
102 }
103
104 fn name(&self) -> PyResult<Bound<'py, PyString>> {
106 #[cfg(not(Py_3_11))]
107 let name = self
108 .getattr(intern!(self.py(), "__name__"))?
109 .downcast_into()?;
110
111 #[cfg(Py_3_11)]
112 let name = unsafe {
113 use crate::ffi_ptr_ext::FfiPtrExt;
114 ffi::PyType_GetName(self.as_type_ptr())
115 .assume_owned_or_err(self.py())?
116 .downcast_into_unchecked()
118 };
119
120 Ok(name)
121 }
122
123 fn qualname(&self) -> PyResult<Bound<'py, PyString>> {
125 #[cfg(not(Py_3_11))]
126 let name = self
127 .getattr(intern!(self.py(), "__qualname__"))?
128 .downcast_into()?;
129
130 #[cfg(Py_3_11)]
131 let name = unsafe {
132 use crate::ffi_ptr_ext::FfiPtrExt;
133 ffi::PyType_GetQualName(self.as_type_ptr())
134 .assume_owned_or_err(self.py())?
135 .downcast_into_unchecked()
137 };
138
139 Ok(name)
140 }
141
142 fn module(&self) -> PyResult<Bound<'py, PyString>> {
144 #[cfg(not(Py_3_13))]
145 let name = self.getattr(intern!(self.py(), "__module__"))?;
146
147 #[cfg(Py_3_13)]
148 let name = unsafe {
149 use crate::ffi_ptr_ext::FfiPtrExt;
150 ffi::PyType_GetModuleName(self.as_type_ptr()).assume_owned_or_err(self.py())?
151 };
152
153 name.downcast_into().map_err(Into::into)
155 }
156
157 fn fully_qualified_name(&self) -> PyResult<Bound<'py, PyString>> {
159 #[cfg(not(Py_3_13))]
160 let name = {
161 let module = self.getattr(intern!(self.py(), "__module__"))?;
162 let qualname = self.getattr(intern!(self.py(), "__qualname__"))?;
163
164 let module_str = module.extract::<PyBackedStr>()?;
165 if module_str == "builtins" || module_str == "__main__" {
166 qualname.downcast_into()?
167 } else {
168 PyString::new(self.py(), &format!("{}.{}", module, qualname))
169 }
170 };
171
172 #[cfg(Py_3_13)]
173 let name = unsafe {
174 use crate::ffi_ptr_ext::FfiPtrExt;
175 ffi::PyType_GetFullyQualifiedName(self.as_type_ptr())
176 .assume_owned_or_err(self.py())?
177 .downcast_into_unchecked()
178 };
179
180 Ok(name)
181 }
182
183 fn is_subclass(&self, other: &Bound<'_, PyAny>) -> PyResult<bool> {
187 let result = unsafe { ffi::PyObject_IsSubclass(self.as_ptr(), other.as_ptr()) };
188 err::error_on_minusone(self.py(), result)?;
189 Ok(result == 1)
190 }
191
192 fn is_subclass_of<T>(&self) -> PyResult<bool>
197 where
198 T: PyTypeInfo,
199 {
200 self.is_subclass(&T::type_object(self.py()))
201 }
202
203 fn mro(&self) -> Bound<'py, PyTuple> {
204 #[cfg(any(Py_LIMITED_API, PyPy))]
205 let mro = self
206 .getattr(intern!(self.py(), "__mro__"))
207 .expect("Cannot get `__mro__` from object.")
208 .extract()
209 .expect("Unexpected type in `__mro__` attribute.");
210
211 #[cfg(not(any(Py_LIMITED_API, PyPy)))]
212 let mro = unsafe {
213 use crate::ffi_ptr_ext::FfiPtrExt;
214 (*self.as_type_ptr())
215 .tp_mro
216 .assume_borrowed(self.py())
217 .to_owned()
218 .downcast_into_unchecked()
219 };
220
221 mro
222 }
223
224 fn bases(&self) -> Bound<'py, PyTuple> {
225 #[cfg(any(Py_LIMITED_API, PyPy))]
226 let bases = self
227 .getattr(intern!(self.py(), "__bases__"))
228 .expect("Cannot get `__bases__` from object.")
229 .extract()
230 .expect("Unexpected type in `__bases__` attribute.");
231
232 #[cfg(not(any(Py_LIMITED_API, PyPy)))]
233 let bases = unsafe {
234 use crate::ffi_ptr_ext::FfiPtrExt;
235 (*self.as_type_ptr())
236 .tp_bases
237 .assume_borrowed(self.py())
238 .to_owned()
239 .downcast_into_unchecked()
240 };
241
242 bases
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use crate::tests::common::generate_unique_module_name;
249 use crate::types::{PyAnyMethods, PyBool, PyInt, PyModule, PyTuple, PyType, PyTypeMethods};
250 use crate::PyAny;
251 use crate::Python;
252 use pyo3_ffi::c_str;
253
254 #[test]
255 fn test_type_is_subclass() {
256 Python::with_gil(|py| {
257 let bool_type = py.get_type::<PyBool>();
258 let long_type = py.get_type::<PyInt>();
259 assert!(bool_type.is_subclass(&long_type).unwrap());
260 });
261 }
262
263 #[test]
264 fn test_type_is_subclass_of() {
265 Python::with_gil(|py| {
266 assert!(py.get_type::<PyBool>().is_subclass_of::<PyInt>().unwrap());
267 });
268 }
269
270 #[test]
271 fn test_mro() {
272 Python::with_gil(|py| {
273 assert!(py
274 .get_type::<PyBool>()
275 .mro()
276 .eq(PyTuple::new(
277 py,
278 [
279 py.get_type::<PyBool>(),
280 py.get_type::<PyInt>(),
281 py.get_type::<PyAny>()
282 ]
283 )
284 .unwrap())
285 .unwrap());
286 });
287 }
288
289 #[test]
290 fn test_bases_bool() {
291 Python::with_gil(|py| {
292 assert!(py
293 .get_type::<PyBool>()
294 .bases()
295 .eq(PyTuple::new(py, [py.get_type::<PyInt>()]).unwrap())
296 .unwrap());
297 });
298 }
299
300 #[test]
301 fn test_bases_object() {
302 Python::with_gil(|py| {
303 assert!(py
304 .get_type::<PyAny>()
305 .bases()
306 .eq(PyTuple::empty(py))
307 .unwrap());
308 });
309 }
310
311 #[test]
312 fn test_type_names_standard() {
313 Python::with_gil(|py| {
314 let module_name = generate_unique_module_name("test_module");
315 let module = PyModule::from_code(
316 py,
317 c_str!(
318 r#"
319class MyClass:
320 pass
321"#
322 ),
323 c_str!(file!()),
324 &module_name,
325 )
326 .expect("module create failed");
327
328 let my_class = module.getattr("MyClass").unwrap();
329 let my_class_type = my_class.downcast_into::<PyType>().unwrap();
330 assert_eq!(my_class_type.name().unwrap(), "MyClass");
331 assert_eq!(my_class_type.qualname().unwrap(), "MyClass");
332 let module_name = module_name.to_str().unwrap();
333 let qualname = format!("{module_name}.MyClass");
334 assert_eq!(my_class_type.module().unwrap(), module_name);
335 assert_eq!(
336 my_class_type.fully_qualified_name().unwrap(),
337 qualname.as_str()
338 );
339 });
340 }
341
342 #[test]
343 fn test_type_names_builtin() {
344 Python::with_gil(|py| {
345 let bool_type = py.get_type::<PyBool>();
346 assert_eq!(bool_type.name().unwrap(), "bool");
347 assert_eq!(bool_type.qualname().unwrap(), "bool");
348 assert_eq!(bool_type.module().unwrap(), "builtins");
349 assert_eq!(bool_type.fully_qualified_name().unwrap(), "bool");
350 });
351 }
352
353 #[test]
354 fn test_type_names_nested() {
355 Python::with_gil(|py| {
356 let module_name = generate_unique_module_name("test_module");
357 let module = PyModule::from_code(
358 py,
359 c_str!(
360 r#"
361class OuterClass:
362 class InnerClass:
363 pass
364"#
365 ),
366 c_str!(file!()),
367 &module_name,
368 )
369 .expect("module create failed");
370
371 let outer_class = module.getattr("OuterClass").unwrap();
372 let inner_class = outer_class.getattr("InnerClass").unwrap();
373 let inner_class_type = inner_class.downcast_into::<PyType>().unwrap();
374 assert_eq!(inner_class_type.name().unwrap(), "InnerClass");
375 assert_eq!(
376 inner_class_type.qualname().unwrap(),
377 "OuterClass.InnerClass"
378 );
379 let module_name = module_name.to_str().unwrap();
380 let qualname = format!("{module_name}.OuterClass.InnerClass");
381 assert_eq!(inner_class_type.module().unwrap(), module_name);
382 assert_eq!(
383 inner_class_type.fully_qualified_name().unwrap(),
384 qualname.as_str()
385 );
386 });
387 }
388}