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